diff --git a/flutter_reach_five/CHANGELOG.md b/flutter_reach_five/CHANGELOG.md index 1abcf6a..f3313cb 100644 --- a/flutter_reach_five/CHANGELOG.md +++ b/flutter_reach_five/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.0 + +- **FEAT**: Add `passwordPolicy` method returning the `PasswordPolicy` configured for the ReachFive client (minimum length, minimum strength, required character classes and whether the password can be updated with only an access token) + # 2.0.5 - **FEAT**: Add `PasswordTooWeakException` thrown when trying to set a password that is too weak or that has leaked diff --git a/flutter_reach_five/lib/flutter_reach_five.dart b/flutter_reach_five/lib/flutter_reach_five.dart index c1a77b0..e66bc8c 100644 --- a/flutter_reach_five/lib/flutter_reach_five.dart +++ b/flutter_reach_five/lib/flutter_reach_five.dart @@ -7,6 +7,7 @@ export 'models/emails.dart'; export 'models/errors.dart'; export 'models/login_summary.dart'; export 'models/open_id_user.dart'; +export 'models/password_policy.dart'; export 'models/profile.dart'; export 'models/profile_address.dart'; export 'models/profile_signup_request.dart'; diff --git a/flutter_reach_five/lib/helpers/address_converter.dart b/flutter_reach_five/lib/helpers/address_converter.dart index d4e8f02..68269db 100644 --- a/flutter_reach_five/lib/helpers/address_converter.dart +++ b/flutter_reach_five/lib/helpers/address_converter.dart @@ -2,7 +2,7 @@ import 'package:flutter_reach_five_platform_interface/flutter_reach_five_platfor import '../models/address.dart'; -/// Used to be the link between the [AddressInterface] +/// Link between the [AddressInterface] /// and the [Address] exported from this package class AddressConverter { /// convert a [Address] to a [AddressInterface] diff --git a/flutter_reach_five/lib/helpers/auth_token.dart b/flutter_reach_five/lib/helpers/auth_token.dart index b833fb9..087d4d8 100644 --- a/flutter_reach_five/lib/helpers/auth_token.dart +++ b/flutter_reach_five/lib/helpers/auth_token.dart @@ -3,7 +3,7 @@ import 'package:flutter_reach_five_platform_interface/flutter_reach_five_platfor import '../models/auth_token.dart'; import 'open_id_user.dart'; -/// Used to be the link between the [AuthTokenInterface] +/// Link between the [AuthTokenInterface] /// and the [AuthToken] exported from this package class AuthTokenConverter { /// convert a [AuthToken] to a [AuthTokenInterface] diff --git a/flutter_reach_five/lib/helpers/consent_converter.dart b/flutter_reach_five/lib/helpers/consent_converter.dart index 96e9ffe..fdaf47a 100644 --- a/flutter_reach_five/lib/helpers/consent_converter.dart +++ b/flutter_reach_five/lib/helpers/consent_converter.dart @@ -2,7 +2,7 @@ import 'package:flutter_reach_five_platform_interface/flutter_reach_five_platfor import '../models/consent.dart'; -/// Used to be the link between the [ConsentInterface] +/// Link between the [ConsentInterface] /// and the [Consent] exported from this package class ConsentConverter { /// convert a [Consent] to a [ConsentInterface] diff --git a/flutter_reach_five/lib/helpers/emails_converter.dart b/flutter_reach_five/lib/helpers/emails_converter.dart index bd319fc..4928046 100644 --- a/flutter_reach_five/lib/helpers/emails_converter.dart +++ b/flutter_reach_five/lib/helpers/emails_converter.dart @@ -2,7 +2,7 @@ import 'package:flutter_reach_five_platform_interface/flutter_reach_five_platfor import '../models/emails.dart'; -/// Used to be the link between the [EmailsInterface] +/// Link between the [EmailsInterface] /// and the [Emails] exported from this package class EmailsConverter { /// convert a [Emails] to a [EmailsInterface] diff --git a/flutter_reach_five/lib/helpers/login_summary_converter.dart b/flutter_reach_five/lib/helpers/login_summary_converter.dart index 170d086..e12b121 100644 --- a/flutter_reach_five/lib/helpers/login_summary_converter.dart +++ b/flutter_reach_five/lib/helpers/login_summary_converter.dart @@ -2,7 +2,7 @@ import 'package:flutter_reach_five_platform_interface/flutter_reach_five_platfor import '../models/login_summary.dart'; -/// Used to be the link between the [LoginSummaryInterface] +/// Link between the [LoginSummaryInterface] /// and the [LoginSummary] exported from this package class LoginSummaryConverter { /// convert a [LoginSummary] to a [LoginSummaryInterface] diff --git a/flutter_reach_five/lib/helpers/open_id_user.dart b/flutter_reach_five/lib/helpers/open_id_user.dart index 25395b4..4a1222d 100644 --- a/flutter_reach_five/lib/helpers/open_id_user.dart +++ b/flutter_reach_five/lib/helpers/open_id_user.dart @@ -3,7 +3,7 @@ import 'package:flutter_reach_five_platform_interface/flutter_reach_five_platfor import '../models/open_id_user.dart'; import 'address_converter.dart'; -/// Used to be the link between the [OpenIdUserInterface] +/// Link between the [OpenIdUserInterface] /// and the [OpenIdUser] exported from this package class OpenIdUserConverter { /// convert a [OpenIdUser] to a [OpenIdUserInterface] diff --git a/flutter_reach_five/lib/helpers/password_policy_converter.dart b/flutter_reach_five/lib/helpers/password_policy_converter.dart new file mode 100644 index 0000000..723cd10 --- /dev/null +++ b/flutter_reach_five/lib/helpers/password_policy_converter.dart @@ -0,0 +1,20 @@ +import 'package:reach_five_identity_repo/reach_five_identity_repo.dart'; + +import '../models/password_policy.dart'; + +/// Used as the link between the [PasswordPolicyResponse] from the +/// identity repo and the [PasswordPolicy] exported from this package +class PasswordPolicyConverter { + /// convert a [PasswordPolicyResponse] to a [PasswordPolicy] + static PasswordPolicy fromInterface(PasswordPolicyResponse passwordPolicy) => + PasswordPolicy( + minLength: passwordPolicy.minLength, + minStrength: passwordPolicy.minStrength, + specialCharacters: passwordPolicy.specialCharacters, + uppercaseCharacters: passwordPolicy.uppercaseCharacters, + lowercaseCharacters: passwordPolicy.lowercaseCharacters, + digitCharacters: passwordPolicy.digitCharacters, + allowUpdateWithAccessTokenOnly: + passwordPolicy.allowUpdateWithAccessTokenOnly, + ); +} diff --git a/flutter_reach_five/lib/helpers/profile_address_converter.dart b/flutter_reach_five/lib/helpers/profile_address_converter.dart index 7e462ee..55e12ae 100644 --- a/flutter_reach_five/lib/helpers/profile_address_converter.dart +++ b/flutter_reach_five/lib/helpers/profile_address_converter.dart @@ -2,7 +2,7 @@ import 'package:flutter_reach_five_platform_interface/flutter_reach_five_platfor import '../models/profile_address.dart'; -/// Used to be the link between the [ProfileAddressTypeInterface] +/// Link between the [ProfileAddressTypeInterface] /// and the [ProfileAddressType] exported from this package class ProfileAddressTypeConverter { /// convert a [ProfileAddressType] to a [ProfileAddressTypeInterface] @@ -30,7 +30,7 @@ class ProfileAddressTypeConverter { } } -/// Used to be the link between the [ProfileAddressInterface] +/// Link between the [ProfileAddressInterface] /// and the [ProfileAddress] exported from this package class ProfileAddressConverter { /// convert a [ProfileAddress] to a [ProfileAddressInterface] diff --git a/flutter_reach_five/lib/helpers/profile_converter.dart b/flutter_reach_five/lib/helpers/profile_converter.dart index 6950201..eaf8c23 100644 --- a/flutter_reach_five/lib/helpers/profile_converter.dart +++ b/flutter_reach_five/lib/helpers/profile_converter.dart @@ -6,7 +6,7 @@ import 'emails_converter.dart'; import 'login_summary_converter.dart'; import 'profile_address_converter.dart'; -/// Used to be the link between the [ProfileInterface] +/// Link between the [ProfileInterface] /// and the [Profile] exported from this package class ProfileConverter { /// convert a [Profile] to a [ProfileInterface] diff --git a/flutter_reach_five/lib/helpers/profile_signup_request_converter.dart b/flutter_reach_five/lib/helpers/profile_signup_request_converter.dart index 6cbcbf4..bc3645a 100644 --- a/flutter_reach_five/lib/helpers/profile_signup_request_converter.dart +++ b/flutter_reach_five/lib/helpers/profile_signup_request_converter.dart @@ -4,7 +4,7 @@ import '../models/profile_signup_request.dart'; import 'consent_converter.dart'; import 'profile_address_converter.dart'; -/// Used to be the link between the [ProfileSignupRequestInterface] +/// Link between the [ProfileSignupRequestInterface] /// and the [ProfileSignupRequest] exported from this package class ProfileSignupRequestConverter { /// convert a [ProfileSignupRequest] to a [ProfileSignupRequestInterface] diff --git a/flutter_reach_five/lib/helpers/provider_converter.dart b/flutter_reach_five/lib/helpers/provider_converter.dart index 0dbea00..bfb392a 100644 --- a/flutter_reach_five/lib/helpers/provider_converter.dart +++ b/flutter_reach_five/lib/helpers/provider_converter.dart @@ -1,6 +1,6 @@ import '../models/provider.dart'; -/// Used to be the link between a [String] +/// Link between a [String] /// and the [Provider] exported from this package class ProviderConverter { /// convert a [Provider] to a [String] diff --git a/flutter_reach_five/lib/helpers/provider_creator_converter.dart b/flutter_reach_five/lib/helpers/provider_creator_converter.dart index 0c54586..4f16699 100644 --- a/flutter_reach_five/lib/helpers/provider_creator_converter.dart +++ b/flutter_reach_five/lib/helpers/provider_creator_converter.dart @@ -2,7 +2,7 @@ import 'package:flutter_reach_five_platform_interface/flutter_reach_five_platfor import '../models/provider_creator.dart'; -/// Used to be the link between a [ProviderCreatorInterface] +/// Link between a [ProviderCreatorInterface] /// and the [ProviderCreator] exported from this package class ProviderCreatorConverter { /// convert a [ProviderCreator] to a [ProviderCreatorInterface] diff --git a/flutter_reach_five/lib/helpers/reach_five_key_converter.dart b/flutter_reach_five/lib/helpers/reach_five_key_converter.dart index 26827bd..c5bca93 100644 --- a/flutter_reach_five/lib/helpers/reach_five_key_converter.dart +++ b/flutter_reach_five/lib/helpers/reach_five_key_converter.dart @@ -4,7 +4,7 @@ import '../models/reach_five_key.dart'; import 'provider_creator_converter.dart'; import 'sdk_config_converter.dart'; -/// Used to be the link between the [ReachFiveKeyInterface] +/// Link between the [ReachFiveKeyInterface] /// and the [ReachFiveKey] exported from this package class ReachFiveKeyConverter { /// convert a [ReachFiveKey] to a [ReachFiveKeyInterface] diff --git a/flutter_reach_five/lib/helpers/scope_value_converter.dart b/flutter_reach_five/lib/helpers/scope_value_converter.dart index ab0b1f8..6fd3401 100644 --- a/flutter_reach_five/lib/helpers/scope_value_converter.dart +++ b/flutter_reach_five/lib/helpers/scope_value_converter.dart @@ -1,6 +1,6 @@ import '../models/scope_value.dart'; -/// Used to be the link between a [String] +/// Link between a [String] /// and the [ScopeValue] exported from this package class ScopeValueConverter { /// convert a [ScopeValue] to a [String] diff --git a/flutter_reach_five/lib/helpers/sdk_config_converter.dart b/flutter_reach_five/lib/helpers/sdk_config_converter.dart index 0cfc0a5..ed2f6c0 100644 --- a/flutter_reach_five/lib/helpers/sdk_config_converter.dart +++ b/flutter_reach_five/lib/helpers/sdk_config_converter.dart @@ -2,7 +2,7 @@ import 'package:flutter_reach_five_platform_interface/flutter_reach_five_platfor import '../models/sdk_config.dart'; -/// Used to be the link between the [SdkConfigInterface] +/// Link between the [SdkConfigInterface] /// and the [SdkConfig] exported from this package class SdkConfigConverter { /// convert a [SdkConfig] to a [SdkConfigInterface] diff --git a/flutter_reach_five/lib/models/password_policy.dart b/flutter_reach_five/lib/models/password_policy.dart new file mode 100644 index 0000000..c4222ea --- /dev/null +++ b/flutter_reach_five/lib/models/password_policy.dart @@ -0,0 +1,53 @@ +import 'package:equatable/equatable.dart'; + +/// [PasswordPolicy] class, contains the password policy configured for the +/// ReachFive client (as defined on the Password policy page of the console) +class PasswordPolicy extends Equatable { + /// [PasswordPolicy] default constructor + const PasswordPolicy({ + required this.minLength, + required this.minStrength, + required this.allowUpdateWithAccessTokenOnly, + this.specialCharacters, + this.uppercaseCharacters, + this.lowercaseCharacters, + this.digitCharacters, + }); + + /// The minimum number of characters a password must contain + final int minLength; + + /// The minimum strength a password must reach (from 0 to 4) + final int minStrength; + + /// The minimum number of special characters a password must contain, + /// or `null` if not enforced + final int? specialCharacters; + + /// The minimum number of uppercase characters a password must contain, + /// or `null` if not enforced + final int? uppercaseCharacters; + + /// The minimum number of lowercase characters a password must contain, + /// or `null` if not enforced + final int? lowercaseCharacters; + + /// The minimum number of digit characters a password must contain, + /// or `null` if not enforced + final int? digitCharacters; + + /// Whether the password can be updated with only an access token + /// (without providing the old password) + final bool allowUpdateWithAccessTokenOnly; + + @override + List get props => [ + minLength, + minStrength, + specialCharacters, + uppercaseCharacters, + lowercaseCharacters, + digitCharacters, + allowUpdateWithAccessTokenOnly, + ]; +} diff --git a/flutter_reach_five/lib/reach_five.dart b/flutter_reach_five/lib/reach_five.dart index 6bc4f25..f003f07 100644 --- a/flutter_reach_five/lib/reach_five.dart +++ b/flutter_reach_five/lib/reach_five.dart @@ -6,6 +6,7 @@ import 'package:reach_five_identity_repo/reach_five_identity_repo.dart'; import 'flutter_reach_five.dart'; import 'helpers/adapt_errors.dart'; import 'helpers/auth_token.dart'; +import 'helpers/password_policy_converter.dart'; import 'helpers/profile_converter.dart'; import 'helpers/profile_signup_request_converter.dart'; import 'helpers/provider_converter.dart'; @@ -57,6 +58,12 @@ class ReachFive { /// {@endtemplate} EmailApi get emailApi => identityRepo.getEmailApi(); + /// {@template flutter_reach_five.reachFive.configApi} + /// [ConfigApi] instance from [identityRepo] to be given in every + /// reachFive api methods that needs it + /// {@endtemplate} + ConfigApi get configApi => identityRepo.getConfigApi(); + /// {@template flutter_reach_five.reachFive.signup} /// Create and authenticate a new user with the specified data. /// {@endtemplate} @@ -345,6 +352,29 @@ class ReachFive { } } + /// {@template flutter_reach_five.reachFive.passwordPolicy} + /// Get the password policy configured for the ReachFive client. + /// + /// These are the values configured on the `Password policy` page of the + /// ReachFive console (minimum length, minimum strength, required character + /// classes, ...). + /// {@endtemplate} + Future passwordPolicy() async { + final response = await configApi.getConfig( + clientId: reachFiveKey.sdkConfig.clientId, + ); + + final passwordPolicy = response.data?.passwordPolicy; + + if (passwordPolicy == null) { + throw const FormatException( + 'The ReachFive config response did not contain a password policy.', + ); + } + + return PasswordPolicyConverter.fromInterface(passwordPolicy); + } + /// {@template flutter_reach_five.reachFive.updatePassword} /// Make request for password reset /// diff --git a/flutter_reach_five/pubspec.yaml b/flutter_reach_five/pubspec.yaml index e390208..9d3b9fa 100644 --- a/flutter_reach_five/pubspec.yaml +++ b/flutter_reach_five/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_reach_five description: This package allows you to use the methods from the reachFive android and ios native sdks in Flutter -version: 2.0.5 +version: 2.1.0 homepage: https://github.com/bamlab/Flutter-ReachFive repository: https://github.com/bamlab/Flutter-ReachFive @@ -27,7 +27,7 @@ dependencies: flutter_reach_five_platform_interface: ^2.0.5 freezed: ^3.2.3 freezed_annotation: ^3.1.0 - reach_five_identity_repo: ^2.0.0 + reach_five_identity_repo: ^2.1.0 dev_dependencies: build_runner: ^2.4.8 diff --git a/flutter_reach_five/test/reach_five_test.dart b/flutter_reach_five/test/reach_five_test.dart index 351c68f..ce323b6 100644 --- a/flutter_reach_five/test/reach_five_test.dart +++ b/flutter_reach_five/test/reach_five_test.dart @@ -22,6 +22,8 @@ class MockOAuthApi extends Mock implements OAuthApi {} class MockEmailApi extends Mock implements EmailApi {} +class MockConfigApi extends Mock implements ConfigApi {} + class MockFlutterReachFivePlatform extends Mock with PlatformInterfaceMockMixin implements FlutterReachFivePlatform {} @@ -34,6 +36,7 @@ void main() { late MockReackFiveIdentityRepo mockReachFiveIdentityRepo; late MockOAuthApi mockOAuthApi; late MockEmailApi mockEmailApi; + late MockConfigApi mockConfigApi; late ReachFive reachFive; @@ -44,9 +47,11 @@ void main() { mockReachFiveIdentityRepo = MockReackFiveIdentityRepo(); mockOAuthApi = MockOAuthApi(); mockEmailApi = MockEmailApi(); + mockConfigApi = MockConfigApi(); when(mockReachFiveIdentityRepo.getOAuthApi).thenReturn(mockOAuthApi); when(mockReachFiveIdentityRepo.getEmailApi).thenReturn(mockEmailApi); + when(mockReachFiveIdentityRepo.getConfigApi).thenReturn(mockConfigApi); const sdkConfig = SdkConfig( domain: 'domain', @@ -618,6 +623,91 @@ void main() { }); }); + group('passwordPolicy', () { + test('returns the password policy from the config api', () async { + when( + () => mockConfigApi.getConfig(clientId: any(named: 'clientId')), + ).thenAnswer((_) async { + return Response( + requestOptions: RequestOptions(path: 'path'), + data: ClientConfigResponse.fromJson(const { + 'password_policy': { + 'minLength': 8, + 'minStrength': 1, + 'specialCharacters': null, + 'uppercaseCharacters': null, + 'lowercaseCharacters': null, + 'digitCharacters': null, + 'allowUpdateWithAccessTokenOnly': true, + }, + }), + ); + }); + + final passwordPolicy = await reachFive.passwordPolicy(); + + expect( + passwordPolicy, + const PasswordPolicy( + minLength: 8, + minStrength: 1, + allowUpdateWithAccessTokenOnly: true, + ), + ); + + verify(() => mockConfigApi.getConfig(clientId: 'clientId')).called(1); + }); + + test('maps enforced character counts', () async { + when( + () => mockConfigApi.getConfig(clientId: any(named: 'clientId')), + ).thenAnswer((_) async { + return Response( + requestOptions: RequestOptions(path: 'path'), + data: ClientConfigResponse.fromJson(const { + 'password_policy': { + 'minLength': 10, + 'minStrength': 2, + 'specialCharacters': 1, + 'uppercaseCharacters': 1, + 'lowercaseCharacters': 1, + 'digitCharacters': 1, + 'allowUpdateWithAccessTokenOnly': false, + }, + }), + ); + }); + + final passwordPolicy = await reachFive.passwordPolicy(); + + expect( + passwordPolicy, + const PasswordPolicy( + minLength: 10, + minStrength: 2, + specialCharacters: 1, + uppercaseCharacters: 1, + lowercaseCharacters: 1, + digitCharacters: 1, + allowUpdateWithAccessTokenOnly: false, + ), + ); + }); + + test('throws when the config has no password policy', () async { + when( + () => mockConfigApi.getConfig(clientId: any(named: 'clientId')), + ).thenAnswer((_) async { + return Response( + requestOptions: RequestOptions(path: 'path'), + data: ClientConfigResponse.fromJson(const {}), + ); + }); + + expect(reachFive.passwordPolicy(), throwsA(isA())); + }); + }); + group('requestPasswordReset', () { test('call requestPasswordReset method', () async { const email = 'email'; diff --git a/reach_five_identity_api.yaml b/reach_five_identity_api.yaml index 229653a..0d2e333 100644 --- a/reach_five_identity_api.yaml +++ b/reach_five_identity_api.yaml @@ -336,7 +336,88 @@ paths: "redirect_url": "https://example-email-verification.com" }' + /identity/v1/config: + get: + security: + - OAuth2: + - "none" + summary: Get the client SDK configuration + tags: + - Config + description: | + Returns the public SDK configuration of a ReachFive client, including its password policy. + + This endpoint is public and only requires the `client_id` as a query parameter. + operationId: getConfig + parameters: + - name: client_id + in: query + required: true + description: Your client ID. + schema: + type: string + example: slNIt...yKzQM + responses: + "200": + description: The client SDK configuration. + content: + application/json: + schema: + $ref: "#/components/schemas/ClientConfigResponse" + x-codeSamples: + - lang: "cURL" + source: | + curl --request GET \ + --url 'https://YOUR_DOMAIN/identity/v1/config?client_id=slNIt...yKzQM' + components: + schemas: + PasswordPolicyResponse: + type: object + description: The password policy configured for the client. + properties: + minLength: + type: integer + description: The minimum number of characters a password must contain. + example: 8 + minStrength: + type: integer + description: The minimum strength a password must reach (0 to 4). + example: 1 + specialCharacters: + type: integer + nullable: true + description: The minimum number of special characters a password must contain, or `null` if not enforced. + example: 1 + uppercaseCharacters: + type: integer + nullable: true + description: The minimum number of uppercase characters a password must contain, or `null` if not enforced. + example: 1 + lowercaseCharacters: + type: integer + nullable: true + description: The minimum number of lowercase characters a password must contain, or `null` if not enforced. + example: 1 + digitCharacters: + type: integer + nullable: true + description: The minimum number of digit characters a password must contain, or `null` if not enforced. + example: 1 + allowUpdateWithAccessTokenOnly: + type: boolean + description: Whether the password can be updated with only an access token (without the old password). + example: true + required: + - minLength + - minStrength + - allowUpdateWithAccessTokenOnly + ClientConfigResponse: + type: object + description: The public SDK configuration of a ReachFive client. + properties: + password_policy: + $ref: "#/components/schemas/PasswordPolicyResponse" responses: 303redirect: description: | diff --git a/reach_five_identity_repo/CHANGELOG.md b/reach_five_identity_repo/CHANGELOG.md index f5a1bbb..df9ad62 100644 --- a/reach_five_identity_repo/CHANGELOG.md +++ b/reach_five_identity_repo/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.1.0 + +- **FEAT**: Add `ConfigApi` with the `getConfig` endpoint (`GET /identity/v1/config`) returning the client `ClientConfigResponse` and its `PasswordPolicy` + # 2.0.0 - **BREAKING**: Update Flutter to >=3.41.0 diff --git a/reach_five_identity_repo/lib/reach_five_identity_repo.dart b/reach_five_identity_repo/lib/reach_five_identity_repo.dart index c9e4a93..a03ff2f 100644 --- a/reach_five_identity_repo/lib/reach_five_identity_repo.dart +++ b/reach_five_identity_repo/lib/reach_five_identity_repo.dart @@ -7,9 +7,12 @@ export 'package:reach_five_identity_repo/src/auth/api_key_auth.dart'; export 'package:reach_five_identity_repo/src/auth/basic_auth.dart'; export 'package:reach_five_identity_repo/src/auth/oauth.dart'; +export 'package:reach_five_identity_repo/src/api/config_api.dart'; export 'package:reach_five_identity_repo/src/api/email_api.dart'; export 'package:reach_five_identity_repo/src/api/o_auth_api.dart'; +export 'package:reach_five_identity_repo/src/model/client_config_response.dart'; +export 'package:reach_five_identity_repo/src/model/password_policy_response.dart'; export 'package:reach_five_identity_repo/src/model/revoke_token_request.dart'; export 'package:reach_five_identity_repo/src/model/send_email_verification200_response.dart'; export 'package:reach_five_identity_repo/src/model/send_email_verification_request.dart'; diff --git a/reach_five_identity_repo/lib/src/api.dart b/reach_five_identity_repo/lib/src/api.dart index a0317eb..6f5b6b9 100644 --- a/reach_five_identity_repo/lib/src/api.dart +++ b/reach_five_identity_repo/lib/src/api.dart @@ -7,6 +7,7 @@ import 'package:reach_five_identity_repo/src/auth/api_key_auth.dart'; import 'package:reach_five_identity_repo/src/auth/basic_auth.dart'; import 'package:reach_five_identity_repo/src/auth/bearer_auth.dart'; import 'package:reach_five_identity_repo/src/auth/oauth.dart'; +import 'package:reach_five_identity_repo/src/api/config_api.dart'; import 'package:reach_five_identity_repo/src/api/email_api.dart'; import 'package:reach_five_identity_repo/src/api/o_auth_api.dart'; @@ -79,6 +80,12 @@ class ReachFiveIdentityRepo { } } + /// Get ConfigApi instance, base route and serializer can be overridden by a given but be careful, + /// by doing that all interceptors will not be executed + ConfigApi getConfigApi() { + return ConfigApi(dio); + } + /// Get EmailApi instance, base route and serializer can be overridden by a given but be careful, /// by doing that all interceptors will not be executed EmailApi getEmailApi() { diff --git a/reach_five_identity_repo/lib/src/api/config_api.dart b/reach_five_identity_repo/lib/src/api/config_api.dart new file mode 100644 index 0000000..fe8b8b8 --- /dev/null +++ b/reach_five_identity_repo/lib/src/api/config_api.dart @@ -0,0 +1,93 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// + +import 'dart:async'; + +// ignore: unused_import +import 'dart:convert'; +import 'package:reach_five_identity_repo/src/deserialize.dart'; +import 'package:dio/dio.dart'; + +import 'package:reach_five_identity_repo/src/model/client_config_response.dart'; + +class ConfigApi { + final Dio _dio; + + const ConfigApi(this._dio); + + /// Get the client SDK configuration + /// Returns the public SDK configuration of a ReachFive client, including its password policy. This endpoint is public and only requires the `client_id` as a query parameter. + /// + /// Parameters: + /// * [clientId] - Your client ID. + /// * [cancelToken] - A [CancelToken] that can be used to cancel the operation + /// * [headers] - Can be used to add additional headers to the request + /// * [extras] - Can be used to add flags to the request + /// * [validateStatus] - A [ValidateStatus] callback that can be used to determine request success based on the HTTP status of the response + /// * [onSendProgress] - A [ProgressCallback] that can be used to get the send progress + /// * [onReceiveProgress] - A [ProgressCallback] that can be used to get the receive progress + /// + /// Returns a [Future] containing a [Response] with a [ClientConfigResponse] as data + /// Throws [DioException] if API call or serialization fails + Future> getConfig({ + required String clientId, + CancelToken? cancelToken, + Map? headers, + Map? extra, + ValidateStatus? validateStatus, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) async { + final _path = r'/identity/v1/config'; + final _options = Options( + method: r'GET', + headers: {...?headers}, + extra: {'secure': >[], ...?extra}, + validateStatus: validateStatus, + ); + + final _queryParameters = {r'client_id': clientId}; + + final _response = await _dio.request( + _path, + options: _options, + queryParameters: _queryParameters, + cancelToken: cancelToken, + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + ); + + ClientConfigResponse? _responseData; + + try { + final rawData = _response.data; + _responseData = rawData == null + ? null + : deserialize( + rawData, + 'ClientConfigResponse', + growable: true, + ); + } catch (error, stackTrace) { + throw DioException( + requestOptions: _response.requestOptions, + response: _response, + type: DioExceptionType.unknown, + error: error, + stackTrace: stackTrace, + ); + } + + return Response( + data: _responseData, + headers: _response.headers, + isRedirect: _response.isRedirect, + requestOptions: _response.requestOptions, + redirects: _response.redirects, + statusCode: _response.statusCode, + statusMessage: _response.statusMessage, + extra: _response.extra, + ); + } +} diff --git a/reach_five_identity_repo/lib/src/deserialize.dart b/reach_five_identity_repo/lib/src/deserialize.dart index e4f697e..32d0935 100644 --- a/reach_five_identity_repo/lib/src/deserialize.dart +++ b/reach_five_identity_repo/lib/src/deserialize.dart @@ -1,3 +1,5 @@ +import 'package:reach_five_identity_repo/src/model/client_config_response.dart'; +import 'package:reach_five_identity_repo/src/model/password_policy_response.dart'; import 'package:reach_five_identity_repo/src/model/revoke_token_request.dart'; import 'package:reach_five_identity_repo/src/model/send_email_verification200_response.dart'; import 'package:reach_five_identity_repo/src/model/send_email_verification_request.dart'; @@ -24,6 +26,12 @@ ReturnType deserialize( return (valueString == 'true' || valueString == '1') as ReturnType; case 'double': return (value is double ? value : double.parse('$value')) as ReturnType; + case 'ClientConfigResponse': + return ClientConfigResponse.fromJson(value as Map) + as ReturnType; + case 'PasswordPolicyResponse': + return PasswordPolicyResponse.fromJson(value as Map) + as ReturnType; case 'RevokeTokenRequest': return RevokeTokenRequest.fromJson(value as Map) as ReturnType; diff --git a/reach_five_identity_repo/lib/src/model/client_config_response.dart b/reach_five_identity_repo/lib/src/model/client_config_response.dart new file mode 100644 index 0000000..0e2ba24 --- /dev/null +++ b/reach_five_identity_repo/lib/src/model/client_config_response.dart @@ -0,0 +1,42 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// + +// ignore_for_file: unused_element +import 'package:reach_five_identity_repo/src/model/password_policy_response.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'client_config_response.g.dart'; + +@JsonSerializable( + checked: true, + createToJson: true, + disallowUnrecognizedKeys: false, + explicitToJson: true, +) +class ClientConfigResponse { + /// Returns a new [ClientConfigResponse] instance. + ClientConfigResponse({this.passwordPolicy}); + + /// The password policy configured for the client. + @JsonKey(name: r'password_policy', required: false, includeIfNull: false) + final PasswordPolicyResponse? passwordPolicy; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ClientConfigResponse && other.passwordPolicy == passwordPolicy; + + @override + int get hashCode => passwordPolicy.hashCode; + + factory ClientConfigResponse.fromJson(Map json) => + _$ClientConfigResponseFromJson(json); + + Map toJson() => _$ClientConfigResponseToJson(this); + + @override + String toString() { + return toJson().toString(); + } +} diff --git a/reach_five_identity_repo/lib/src/model/client_config_response.g.dart b/reach_five_identity_repo/lib/src/model/client_config_response.g.dart new file mode 100644 index 0000000..ac14dcc --- /dev/null +++ b/reach_five_identity_repo/lib/src/model/client_config_response.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'client_config_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ClientConfigResponse _$ClientConfigResponseFromJson( + Map json, +) => $checkedCreate( + 'ClientConfigResponse', + json, + ($checkedConvert) { + final val = ClientConfigResponse( + passwordPolicy: $checkedConvert( + 'password_policy', + (v) => v == null + ? null + : PasswordPolicyResponse.fromJson(v as Map), + ), + ); + return val; + }, + fieldKeyMap: const {'passwordPolicy': 'password_policy'}, +); + +Map _$ClientConfigResponseToJson( + ClientConfigResponse instance, +) => {'password_policy': ?instance.passwordPolicy?.toJson()}; diff --git a/reach_five_identity_repo/lib/src/model/password_policy_response.dart b/reach_five_identity_repo/lib/src/model/password_policy_response.dart new file mode 100644 index 0000000..a2a7d6a --- /dev/null +++ b/reach_five_identity_repo/lib/src/model/password_policy_response.dart @@ -0,0 +1,92 @@ +// +// AUTO-GENERATED FILE, DO NOT MODIFY! +// + +// ignore_for_file: unused_element +import 'package:json_annotation/json_annotation.dart'; + +part 'password_policy_response.g.dart'; + +@JsonSerializable( + checked: true, + createToJson: true, + disallowUnrecognizedKeys: false, + explicitToJson: true, +) +class PasswordPolicyResponse { + /// Returns a new [PasswordPolicyResponse] instance. + PasswordPolicyResponse({ + required this.minLength, + required this.minStrength, + this.specialCharacters, + this.uppercaseCharacters, + this.lowercaseCharacters, + this.digitCharacters, + required this.allowUpdateWithAccessTokenOnly, + }); + + /// The minimum number of characters a password must contain. + @JsonKey(name: r'minLength', required: true, includeIfNull: false) + final int minLength; + + /// The minimum strength a password must reach (0 to 4). + @JsonKey(name: r'minStrength', required: true, includeIfNull: false) + final int minStrength; + + /// The minimum number of special characters a password must contain, or `null` if not enforced. + @JsonKey(name: r'specialCharacters', required: false, includeIfNull: false) + final int? specialCharacters; + + /// The minimum number of uppercase characters a password must contain, or `null` if not enforced. + @JsonKey(name: r'uppercaseCharacters', required: false, includeIfNull: false) + final int? uppercaseCharacters; + + /// The minimum number of lowercase characters a password must contain, or `null` if not enforced. + @JsonKey(name: r'lowercaseCharacters', required: false, includeIfNull: false) + final int? lowercaseCharacters; + + /// The minimum number of digit characters a password must contain, or `null` if not enforced. + @JsonKey(name: r'digitCharacters', required: false, includeIfNull: false) + final int? digitCharacters; + + /// Whether the password can be updated with only an access token (without the old password). + @JsonKey( + name: r'allowUpdateWithAccessTokenOnly', + required: true, + includeIfNull: false, + ) + final bool allowUpdateWithAccessTokenOnly; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PasswordPolicyResponse && + other.minLength == minLength && + other.minStrength == minStrength && + other.specialCharacters == specialCharacters && + other.uppercaseCharacters == uppercaseCharacters && + other.lowercaseCharacters == lowercaseCharacters && + other.digitCharacters == digitCharacters && + other.allowUpdateWithAccessTokenOnly == + allowUpdateWithAccessTokenOnly; + + @override + int get hashCode => + minLength.hashCode + + minStrength.hashCode + + specialCharacters.hashCode + + uppercaseCharacters.hashCode + + lowercaseCharacters.hashCode + + digitCharacters.hashCode + + allowUpdateWithAccessTokenOnly.hashCode; + + factory PasswordPolicyResponse.fromJson(Map json) => + _$PasswordPolicyResponseFromJson(json); + + Map toJson() => _$PasswordPolicyResponseToJson(this); + + @override + String toString() { + return toJson().toString(); + } +} diff --git a/reach_five_identity_repo/lib/src/model/password_policy_response.g.dart b/reach_five_identity_repo/lib/src/model/password_policy_response.g.dart new file mode 100644 index 0000000..cc04106 --- /dev/null +++ b/reach_five_identity_repo/lib/src/model/password_policy_response.g.dart @@ -0,0 +1,57 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'password_policy_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PasswordPolicyResponse _$PasswordPolicyResponseFromJson( + Map json, +) => $checkedCreate('PasswordPolicyResponse', json, ($checkedConvert) { + $checkKeys( + json, + requiredKeys: const [ + 'minLength', + 'minStrength', + 'allowUpdateWithAccessTokenOnly', + ], + ); + final val = PasswordPolicyResponse( + minLength: $checkedConvert('minLength', (v) => (v as num).toInt()), + minStrength: $checkedConvert('minStrength', (v) => (v as num).toInt()), + specialCharacters: $checkedConvert( + 'specialCharacters', + (v) => (v as num?)?.toInt(), + ), + uppercaseCharacters: $checkedConvert( + 'uppercaseCharacters', + (v) => (v as num?)?.toInt(), + ), + lowercaseCharacters: $checkedConvert( + 'lowercaseCharacters', + (v) => (v as num?)?.toInt(), + ), + digitCharacters: $checkedConvert( + 'digitCharacters', + (v) => (v as num?)?.toInt(), + ), + allowUpdateWithAccessTokenOnly: $checkedConvert( + 'allowUpdateWithAccessTokenOnly', + (v) => v as bool, + ), + ); + return val; +}); + +Map _$PasswordPolicyResponseToJson( + PasswordPolicyResponse instance, +) => { + 'minLength': instance.minLength, + 'minStrength': instance.minStrength, + 'specialCharacters': ?instance.specialCharacters, + 'uppercaseCharacters': ?instance.uppercaseCharacters, + 'lowercaseCharacters': ?instance.lowercaseCharacters, + 'digitCharacters': ?instance.digitCharacters, + 'allowUpdateWithAccessTokenOnly': instance.allowUpdateWithAccessTokenOnly, +}; diff --git a/reach_five_identity_repo/pubspec.yaml b/reach_five_identity_repo/pubspec.yaml index b261c59..aa8c103 100644 --- a/reach_five_identity_repo/pubspec.yaml +++ b/reach_five_identity_repo/pubspec.yaml @@ -1,5 +1,5 @@ name: reach_five_identity_repo -version: 2.0.0 +version: 2.1.0 description: OpenAPI API client homepage: https://github.com/bamlab/Flutter-ReachFive