diff --git a/CHANGELOG.md b/CHANGELOG.md index bb7bc81..83f441d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 1.1.0 + +- [BREAKING] Updated the authentication service and interface to be more versatile + +### Migration + +| Class | Migration path | +| ------------------- | -------------------------------------------------------------------------------------- | +| ArcaneAuthInterface | `loginWtihEmailAndPassword({String email, String password})` -> `login({T? input})` | +| ArcaneAuthInterface | `signup({String email, String password})` -> `register({T? input})` | + ## 1.0.8 - Added the `extra` parameter to the `Arcane.log` shortcut method diff --git a/example/lib/interfaces/debug_auth_interface.dart b/example/lib/interfaces/debug_auth_interface.dart index 5d52c5a..363af27 100644 --- a/example/lib/interfaces/debug_auth_interface.dart +++ b/example/lib/interfaces/debug_auth_interface.dart @@ -1,6 +1,6 @@ import "package:arcane_framework/arcane_framework.dart"; -typedef LoginInput = ({String email, String password}); +typedef Credentials = ({String email, String password}); class DebugAuthInterface implements ArcaneAuthInterface { DebugAuthInterface._internal(); @@ -32,15 +32,8 @@ class DebugAuthInterface implements ArcaneAuthInterface { } @override - Future> loginWithEmailAndPassword({ - required String email, - required String password, - }) async => - throw UnimplementedError(); - - @override - Future> login({ - LoginInput? input, + Future> login({ + Credentials? input, Future Function()? onLoggedIn, }) async { final bool alreadyLoggedIn = await isSignedIn; @@ -66,11 +59,18 @@ class DebugAuthInterface implements ArcaneAuthInterface { } @override - Future> signup({ - required String password, - required String email, + Future> register({ + Credentials? input, }) async { - Arcane.log("Creating account for $email with password $password"); + if (input != null) { + final credentials = input as ({String email, String password}); + + final String email = credentials.email; + final String password = credentials.password; + + Arcane.log("Creating account for $email with password $password"); + } + return Result.ok(SignUpStep.confirmSignUp); } diff --git a/lib/src/services/authentication/authentication_interface.dart b/lib/src/services/authentication/authentication_interface.dart index a8d22c0..62d6973 100644 --- a/lib/src/services/authentication/authentication_interface.dart +++ b/lib/src/services/authentication/authentication_interface.dart @@ -14,13 +14,13 @@ abstract class ArcaneAuthInterface { /// /// This is used to retrieve the current session's access token for authenticated /// API requests. Returns `null` if the user is not signed in or the token is unavailable. - Future get accessToken; + Future? get accessToken; /// Returns the refresh token if available. /// /// The refresh token is used to renew access tokens when they expire. Returns `null` if /// the user is not signed in or the token is unavailable. - Future get refreshToken; + Future? get refreshToken; /// Initializes the authentication interface. /// @@ -34,24 +34,6 @@ abstract class ArcaneAuthInterface { /// Returns a `Result` that either contains a `void` on success or an error message. Future> logout(); - /// Logs the user in using an email address and password. - /// - /// This method authenticates the user with their email and password credentials. - /// Returns a `Result` that either contains a `void` on success or an error message. - /// - /// Example: - /// ```dart - /// await authInterface.loginWithEmailAndPassword( - /// email: "user@example.com", - /// password: "password123", - /// ); - /// ``` - @Deprecated("Use `login` instead. Deprecated as of version 1.0.5") - Future> loginWithEmailAndPassword({ - required String email, - required String password, - }); - /// Logs the user in using an optional, generic `T` type of input. /// This login method is a generic method that can be used to login with any /// type of input. It is useful for login methods that do not require an email @@ -78,32 +60,29 @@ abstract class ArcaneAuthInterface { /// This method is typically used when the user hasn't received or has lost their initial /// verification code. Returns a `Result` that contains the verification code on success /// or an error message. - Future> resendVerificationCode( + Future>? resendVerificationCode( String email, ); - /// Signs a user up with a username, password, and email. + /// Registers a new account using user-supplied input. /// /// This method registers a new user in the system. Returns a `Result` that contains /// the next [SignUpStep] in the process on success or an error message. /// /// Example: /// ```dart - /// await authInterface.signup( + /// await authInterface.register( /// email: "user@example.com", /// password: "password123", /// ); /// ``` - Future> signup({ - required String password, - required String email, - }); + Future>? register({T? input}); /// Confirms a user's signup using a username and a confirmation code. /// /// This method completes the sign-up process by verifying the user's confirmation code. /// Returns a `Result` that contains `true` on success or an error message. - Future> confirmSignup({ + Future>? confirmSignup({ required String username, required String confirmationCode, }); @@ -113,7 +92,12 @@ abstract class ArcaneAuthInterface { /// This method is used when a user requests to reset their password. The reset code /// they receive via email is used to verify the request. Optionally, a new password can /// be provided. Returns a `Result` that contains `true` on success or an error message. - Future> resetPassword({ + /// + /// This method may be called twice. In the first instance, the `email` is provided, which + /// triggers a password reset confirmation email to be sent, containing a password reset + /// code or pin. The second call should include the `email`, the user's new password + /// (`newPassword`), and the `code` they received in their email. + Future>? resetPassword({ required String email, String? newPassword, String? code, diff --git a/lib/src/services/authentication/authentication_service.dart b/lib/src/services/authentication/authentication_service.dart index 1ca166a..4d383bd 100644 --- a/lib/src/services/authentication/authentication_service.dart +++ b/lib/src/services/authentication/authentication_service.dart @@ -52,11 +52,13 @@ class ArcaneAuthenticationService extends ArcaneService { /// Returns a JWT access token if the registered `ArcaneAuthInterface` /// provides one. This token is often used in the headers of HTTP requests /// to the backend API. - Future get accessToken => authInterface.accessToken; + Future get accessToken => + authInterface.accessToken ?? Future.value(""); /// Returns a JWT refresh token if the registered `ArcaneAuthInterface` /// provides one. - Future get refreshToken => authInterface.refreshToken; + Future get refreshToken => + authInterface.refreshToken ?? Future.value(""); /// Removes any registered `ArcaneAuthInterface` and resets all values to /// default. @@ -174,36 +176,6 @@ class ArcaneAuthenticationService extends ArcaneService { ); } - /// Attempts to log in the user using their `email` and `password`. - /// Upon successful login, `status` will be set to - /// `AuthenticationStatus.authenticated]` If `onLoggedIn` is specified, the - /// method will run after the authentication status has been updated. - /// When logging in with email and password, the user's email address will be - /// cached in `ArcaneSecureStorage`. - @Deprecated("Use `login` instead. Deprecated as of version 1.0.5") - Future> loginWithEmailAndPassword({ - required String email, - required String password, - Future Function()? onLoggedIn, - }) async { - if (status != AuthenticationStatus.unauthenticated) { - return Result.error("Cannot sign in. Status is already ${status.name}."); - } - - final Result result = - await authInterface.loginWithEmailAndPassword( - email: email, - password: password, - ); - - if (result.isSuccess) { - setAuthenticated(); - if (onLoggedIn != null) await onLoggedIn(); - } - - return result; - } - /// Logs the user in using an optional, generic `T` type of input. Future> login({ T? input, @@ -225,22 +197,26 @@ class ArcaneAuthenticationService extends ArcaneService { return result; } - /// Attempts to register a new account using the provided `email` and - /// `password`. Upon success, returns a `SignUpStep` indicating the next step + /// Attempts to register a new account using user-defined input. + /// Upon success, returns a `SignUpStep` indicating the next step /// in the signup process as a `SignUpStep`. - Future> signup({ - required String email, - required String password, + Future> register({ + T? input, }) async { if (_authInterface == null) { return Result.error("No ArcaneAuthInterface has been registered"); } - final Result result = await authInterface.signup( - email: email, - password: password, + final Result? result = await authInterface.register( + input: input, ); + if (result == null) { + return Result.error( + "Registered ArcaneAuthInterface returned a null value.", + ); + } + return result; } @@ -254,11 +230,17 @@ class ArcaneAuthenticationService extends ArcaneService { return Result.error("No ArcaneAuthInterface has been registered"); } - final Result result = await authInterface.confirmSignup( + final Result? result = await authInterface.confirmSignup( username: email, confirmationCode: confirmationCode, ); + if (result == null) { + return Result.error( + "Registered ArcaneAuthInterface returned a null value.", + ); + } + return result; } @@ -269,7 +251,16 @@ class ArcaneAuthenticationService extends ArcaneService { return Result.error("No ArcaneAuthInterface has been registered"); } - return authInterface.resendVerificationCode(email); + final Future>? result = + authInterface.resendVerificationCode(email); + + if (result == null) { + return Result.error( + "Registered ArcaneAuthInterface returned a null value.", + ); + } + + return result; } /// Attempts to reset the user's password using their `email`. This method @@ -287,12 +278,18 @@ class ArcaneAuthenticationService extends ArcaneService { return Result.error("No ArcaneAuthInterface has been registered"); } - final Result result = await authInterface.resetPassword( + final Result? result = await authInterface.resetPassword( email: email, newPassword: newPassword, code: confirmationCode, ); + if (result == null) { + return Result.error( + "Registered ArcaneAuthInterface returned a null value.", + ); + } + return result; } } diff --git a/pubspec.yaml b/pubspec.yaml index 799e4e9..71c35b5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: arcane_framework description: "Agnostic Reusable Component Architecture for New Ecosystems: a modern framework for bootstrapping new applications" -version: 1.0.8 +version: 1.1.0 repository: https://github.com/hanskokx/arcane_framework issue_tracker: https://github.com/hanskokx/arcane_framework/issues