diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e00cc3..dd265b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,47 @@ +## 1.2.0 + +- Removed flutter_secure_storage dependency as it was unused + +### Breaking Changes + +The following methods have been moved outside of the ArcaneAuthInterface base +class: + +- resendVerificationCode +- register +- confirmSignup +- resetPassword + +These methods have been moved to mixin classes. To continue using them, please +update your ArcaneAuthInterface implementations. + +- To use `resendVerificationCode`, `register` and `confirmSignup`, use the new +`ArcaneAuthAccountRegistration` mixin. +- To use `resetPassword`, use the new `ArcaneAuthPasswordManagement` mixin. + +### Migration + +In order to migrate your existing interfaces, update them from: + +```dart +class MyAuthInterface implements ArcaneAuthInterface {} +``` + +to: + +```dart +class MyAuthInterface + with ArcaneAuthAccountRegistration, ArcaneAuthPasswordManagement + implements ArcaneAuthInterface {} +``` + +If the methods that these mixins provide are not being used, the mixins can +safely be omitted. If only one of these mixins is required, the other can be +safely omitted. + +This change should result in fewer lines of code for interface implementations +that do not require these additional features. + ## 1.1.7 - Fixed an issue with the `ArcaneAuthenticationService` where an exception would diff --git a/README.md b/README.md index 25bf23a..82b2d88 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# Arcane Framework: Agnostic Reusable Component Architecture for New Ecosystems + The Arcane Framework is a powerful Dart package designed to provide a robust architecture for managing key application services such as logging, authentication, secure storage, feature flags, theming, and more. This framework is ideal for building scalable applications that require dynamic configuration and service management. [![style: arcane analysis](https://img.shields.io/badge/style-arcane_analysis-6E35AE)](https://pub.dev/packages/arcane_analysis) @@ -246,7 +248,9 @@ import "package:arcane_framework/arcane_framework.dart"; typedef Credentials = ({String email, String password}); -class DebugAuthInterface implements ArcaneAuthInterface { +class DebugAuthInterface + with ArcaneAuthAccountRegistration, ArcaneAuthPasswordManagement + implements ArcaneAuthInterface { DebugAuthInterface._internal(); static final ArcaneAuthInterface _instance = DebugAuthInterface._internal(); @@ -296,6 +300,7 @@ class DebugAuthInterface implements ArcaneAuthInterface { return Result.ok(null); } + // Provided by the ArcaneAuthAccountRegistration mixin @override Future> resendVerificationCode({ T? input, @@ -304,6 +309,7 @@ class DebugAuthInterface implements ArcaneAuthInterface { return Result.ok("Code sent"); } + // Provided by the ArcaneAuthAccountRegistration mixin @override Future> register({ Credentials? input, @@ -320,6 +326,7 @@ class DebugAuthInterface implements ArcaneAuthInterface { return Result.ok(SignUpStep.confirmSignUp); } + // Provided by the ArcaneAuthAccountRegistration mixin @override Future> confirmSignup({ String? username, @@ -331,6 +338,7 @@ class DebugAuthInterface implements ArcaneAuthInterface { return Result.ok(true); } + // Provided by the ArcaneAuthPasswordManagement mixin @override Future> resetPassword({ String? email, @@ -357,27 +365,27 @@ await Arcane.auth.registerInterface(AuthProviderInterface.I); Once your interface has been created and registered, you can use it to perform a number of common authentication tasks: ```dart -// Register an account +// Register an account using the ArcaneAuthAccountRegistration mixin final nextStep = await Arcane.auth.register( input: ("email": "user@example.com", "password": "password123"), ); -// Confirm a newly registered account +// Confirm a newly registered account using the ArcaneAuthAccountRegistration mixin final accountConfirmed = await Arcane.auth.confirmSignup( email: "user@example.com", confirmationCode: "123456", ); -// Re-send a verification code +// Re-send a verification code using the ArcaneAuthAccountRegistration mixin final response = await Arcane.auth.resendVerificationCode("user@example.com"); -// Initiate a password reset flow +// Initiate a password reset flow using the ArcaneAuthPasswordManagement mixin final passwordResetStarted = await Arcane.auth.resetPassword( email: "user@example.com", newPassword: "password456", ); -// Confirm password reset +// Confirm password reset using the ArcaneAuthPasswordManagement mixin final passwordResetFinished = await Arcane.auth.resetPassword( email: "user@example.com", newPassword: "password456", @@ -392,9 +400,6 @@ final result = await Arcane.auth.login( // Sign out await Arcane.auth.logout(); - -// Set the system to debug mode -await Arcane.auth.setDebug(); ``` ### Dynamic Theming diff --git a/example/lib/interfaces/debug_auth_interface.dart b/example/lib/interfaces/debug_auth_interface.dart index abebe61..2c2f54e 100644 --- a/example/lib/interfaces/debug_auth_interface.dart +++ b/example/lib/interfaces/debug_auth_interface.dart @@ -2,7 +2,9 @@ import "package:arcane_framework/arcane_framework.dart"; typedef Credentials = ({String email, String password}); -class DebugAuthInterface implements ArcaneAuthInterface { +class DebugAuthInterface + with ArcaneAuthAccountRegistration, ArcaneAuthPasswordManagement + implements ArcaneAuthInterface { DebugAuthInterface._internal(); static final ArcaneAuthInterface _instance = DebugAuthInterface._internal(); diff --git a/lib/src/services/authentication/authentication_interface.dart b/lib/src/services/authentication/authentication_interface.dart index d84d658..06f0205 100644 --- a/lib/src/services/authentication/authentication_interface.dart +++ b/lib/src/services/authentication/authentication_interface.dart @@ -2,36 +2,40 @@ part of "authentication_service.dart"; /// An abstract class that defines the authentication interface. /// -/// This interface provides methods for various authentication operations, including -/// signing in, signing up, resetting passwords, and managing tokens. +/// This interface provides methods for various authentication operations, +/// including signing in, signing up, resetting passwords, and managing tokens. abstract class ArcaneAuthInterface { /// Returns `true` if the user is currently signed in. /// - /// This is a getter that asynchronously checks if the user has an active session. + /// This is a getter that asynchronously checks if the user has an active + /// session. Future get isSignedIn; /// Returns the access token if available. /// - /// 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. + /// 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; /// 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. + /// 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; /// Initializes the authentication interface. /// - /// This method sets up any necessary configurations or initializations required for - /// the authentication process. It must be called before any other methods in the interface. - Future init(); + /// This method sets up any necessary configurations or initializations + /// required for the authentication process. It must be called before any + /// other methods in the interface. + Future init() => Future.value(null); /// Logs the user out of the session. /// /// This method terminates the current session and removes any stored tokens. - /// Returns a `Result` that either contains a `void` on success or an error message. + /// Returns a `Result` that either contains a `void` on success or an error + /// message. Future> logout(); /// Logs the user in using an optional, generic `T` type of input. @@ -54,47 +58,107 @@ abstract class ArcaneAuthInterface { T? input, Future Function()? onLoggedIn, }); +} +/// Provides methods related to account registration and verification. +/// +/// This mixin is intended to be used as part of an authentication system +/// where account registration and email verification are required. +mixin ArcaneAuthAccountRegistration { /// Re-sends a verification code to the user's email address. /// - /// 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. + /// This method is typically used when the user hasn't received or has lost + /// their initial verification code. Returns a [Result] containing the + /// verification code on success or an error message. + /// + /// - [T]: The type of input passed to the method. This can be used for custom + /// input structures. + /// + /// Example: + /// ```dart + /// final result = await auth.resendVerificationCode(input: {"email": "user@example.com"}); + /// ``` Future>? resendVerificationCode({T? input}); /// 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. + /// This method adds a new user to the system. Returns a [Result] containing + /// the next [SignUpStep] in the registration process on success or an error + /// message. + /// + /// - [T]: The type of input passed to the method. This can be used for custom + /// input structures. /// /// Example: /// ```dart - /// await authInterface.register( - /// email: "user@example.com", - /// password: "password123", - /// ); + /// final result = await auth.register(input: { + /// "email": "user@example.com", + /// "password": "password123" + /// }); /// ``` 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. + /// This method finalizes the account registration process by validating the + /// confirmation code sent to the user. Returns a [Result] containing `true` + /// on success or an error message. + /// + /// Parameters: + /// - [username]: The username of the account being confirmed. + /// - [confirmationCode]: The code sent to the user's email address for + /// verification. + /// + /// Example: + /// ```dart + /// final result = await auth.confirmSignup( + /// username: "user@example.com", + /// confirmationCode: "123456" + /// ); + /// ``` Future>? confirmSignup({ String? username, String? confirmationCode, }); +} +/// Provides methods for managing user passwords. +/// +/// This mixin includes functionality for resetting passwords and handling +/// password reset codes or pins as part of an authentication system. +mixin ArcaneAuthPasswordManagement { /// Resets a user's password using an email address and a code. /// - /// 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. + /// 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] containing + /// `true` on success or an error message. /// - /// 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. + /// This method may be called in two steps: + /// 1. Provide the `email` to trigger a password reset email. This email will + /// contain a reset code or pin for verification. + /// 2. Provide the `email`, the `newPassword`, and the `code` received in the + /// email to finalize the password reset process. + /// + /// Parameters: + /// - [email]: The email address associated with the user account. + /// - [newPassword]: The new password to set for the user. Required in the + /// second step. + /// - [code]: The password reset code sent to the user's email. Required in + /// the second step. + /// + /// Example: + /// ```dart + /// // Step 1: Trigger reset email + /// final step1 = await auth.resetPassword(email: "user@example.com"); + /// + /// // Step 2: Complete reset + /// final step2 = await auth.resetPassword( + /// email: "user@example.com", + /// newPassword: "newPassword123", + /// code: "123456" + /// ); + /// ``` Future>? resetPassword({ String? email, String? newPassword, diff --git a/lib/src/services/authentication/authentication_service.dart b/lib/src/services/authentication/authentication_service.dart index 1f30959..554f1d7 100644 --- a/lib/src/services/authentication/authentication_service.dart +++ b/lib/src/services/authentication/authentication_service.dart @@ -199,7 +199,15 @@ class ArcaneAuthenticationService extends ArcaneService { return Result.error("No ArcaneAuthInterface has been registered"); } - final Result? result = await authInterface!.register( + if (authInterface is! ArcaneAuthAccountRegistration) { + return Result.error( + "The provided ArcaneAuthInterface does not support account registration.", + ); + } + + final auth = authInterface as ArcaneAuthAccountRegistration; + + final Result? result = await auth.register( input: input, ); @@ -222,7 +230,15 @@ class ArcaneAuthenticationService extends ArcaneService { return Result.error("No ArcaneAuthInterface has been registered"); } - final Result? result = await authInterface!.confirmSignup( + if (authInterface is! ArcaneAuthAccountRegistration) { + return Result.error( + "The provided ArcaneAuthInterface does not support account registration.", + ); + } + + final auth = authInterface as ArcaneAuthAccountRegistration; + + final Result? result = await auth.confirmSignup( username: email, confirmationCode: confirmationCode, ); @@ -243,8 +259,16 @@ class ArcaneAuthenticationService extends ArcaneService { return Result.error("No ArcaneAuthInterface has been registered"); } + if (authInterface is! ArcaneAuthAccountRegistration) { + return Result.error( + "The provided ArcaneAuthInterface does not support account registration.", + ); + } + + final auth = authInterface as ArcaneAuthAccountRegistration; + final Future>? result = - authInterface!.resendVerificationCode(input: email); + auth.resendVerificationCode(input: email); if (result == null) { return Result.error( @@ -270,7 +294,15 @@ class ArcaneAuthenticationService extends ArcaneService { return Result.error("No ArcaneAuthInterface has been registered"); } - final Result? result = await authInterface!.resetPassword( + if (authInterface is! ArcaneAuthPasswordManagement) { + return Result.error( + "The provided ArcaneAuthInterface does not support password management.", + ); + } + + final auth = authInterface as ArcaneAuthPasswordManagement; + + final Result? result = await auth.resetPassword( email: email, newPassword: newPassword, code: confirmationCode, diff --git a/lib/src/services/logging/logging_interface.dart b/lib/src/services/logging/logging_interface.dart index 1237d9b..269061f 100644 --- a/lib/src/services/logging/logging_interface.dart +++ b/lib/src/services/logging/logging_interface.dart @@ -12,7 +12,7 @@ abstract class LoggingInterface { /// ensures that the logging interface, once configured, remains so. static LoggingInterface get I => _instance; - final bool _initialized = false; + bool _initialized = false; /// Whether the logging interface has been initialized. bool get initialized => I._initialized; @@ -22,7 +22,10 @@ abstract class LoggingInterface { /// If any configuration needs to be performed on the logging interface prior /// to use, this is where it should be done. /// This method should, at a minimum, set `I._initialized = true`. - Future init(); + Future init() async { + I._initialized = true; + return I; + } /// This method is called by the `ArcaneLogger` when a log message is /// received. See `ArcaneLogger.log` for further details on how logging diff --git a/pubspec.yaml b/pubspec.yaml index 4d33af8..17d9450 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.1.7 +version: 1.2.0 repository: https://github.com/hanskokx/arcane_framework issue_tracker: https://github.com/hanskokx/arcane_framework/issues @@ -8,19 +8,18 @@ topics: - arcane-framework environment: - sdk: ^3.5.2 + sdk: ^3.6.0 flutter: ">=1.17.0" dependencies: arcane_helper_utils: ^1.2.5 - collection: ^1.18.0 + collection: ^1.19.0 flutter: sdk: flutter flutter_bloc: ^8.1.6 - flutter_secure_storage: ^9.2.2 result_monad: ^2.3.2 dev_dependencies: - arcane_analysis: ^1.0.2 + arcane_analysis: ^1.0.3 flutter_test: sdk: flutter