diff --git a/README.md b/README.md index 46d355d..0b238c3 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,14 @@ authentication methods. For example, let's use OTP via email: ```dart // sends an OTP code to the given email address -await Descope.otp.signUp(method: DeliveryMethod.Email, loginId: "andy@example.com"); +await Descope.otp.signUp(method: DeliveryMethod.Email, loginId: 'andy@example.com'); ``` Finish the authentication by verifying the OTP code the user entered: ```dart // if the user entered the right code the authentication is successful -final authResponse = await Descope.otp.verify(method: DeliveryMethod.Email, loginId: "andy@example.com", code: code); +final authResponse = await Descope.otp.verify(method: DeliveryMethod.Email, loginId: 'andy@example.com', code: code); // we create a DescopeSession object that represents an authenticated user session final session = DescopeSession.fromAuthenticationResponse(authResponse); @@ -84,7 +84,7 @@ in flow successfully you should set the `DescopeSession` object as the active session of the session manager. ```dart -final authResponse = await Descope.otp.verify(method: DeliverMethod.Email, loginId: "andy@example.com", code: "123456"); +final authResponse = await Descope.otp.verify(method: DeliverMethod.Email, loginId: 'andy@example.com', code: '123456'); final session = DescopeSession.fromAuthenticationResponse(authResponse); Descope.sessionManager.manageSession(session); ``` @@ -106,7 +106,7 @@ can do the following. await Descope.sessionManager.refreshSessionIfNeeded(); final sessionJwt = Descope.sessionManager.session?.sessionJwt; if (sessionJwt != null) { - request.headers["X-Auth-Token"] = sessionJwt; + request.headers['X-Auth-Token'] = sessionJwt; } else { // unauthorized } @@ -132,7 +132,7 @@ void main() async { final session = Descope.sessionManager.session; if (session != null) { - print("User is logged in: ${session.user}"); + print('User is logged in: ${session.user}'); } runApp( @@ -198,7 +198,7 @@ final _router = GoRouter( } catch (e) { // Handle errors here } - return "/"; // This route doesn't display anything but returns the root path where the user will be signed in + return '/'; // This route doesn't display anything but returns the root path where the user will be signed in }, ), ], @@ -241,7 +241,7 @@ or via [ASWebAuthenticationSession](https://developer.apple.com/documentation/au Run the flow by calling the flow start function: ```dart -final authResponse = await Descope.flow.start("", deepLinkUrl: ""); +final authResponse = await Descope.flow.start('', deepLinkUrl: ''); final session = DescopeSession.fromAuthenticationResponse(authResponse); Descope.sessionManager.manageSession(session); ``` @@ -271,7 +271,7 @@ final maskedEmail = await Descope.otp.signUp(method: DeliveryMethod.email, login The user will receive a code using the selected delivery method. Verify that code using: ```dart -final authResponse = await Descope.otp.verify(method: DeliveryMethod.email, loginId: "desmond_c@mail.com", code: "123456"); +final authResponse = await Descope.otp.verify(method: DeliveryMethod.email, loginId: 'desmond_c@mail.com', code: '123456'); ``` ### Magic Link diff --git a/lib/descope.dart b/lib/descope.dart index 2e9a395..707ea8c 100644 --- a/lib/descope.dart +++ b/lib/descope.dart @@ -31,7 +31,7 @@ class Descope { /// and in most cases you only need to set this to work with the `Descope` singleton. /// /// **Note:** This is a shortcut for setting the [Descope.config] property. - static String _projectId = ""; + static String _projectId = ''; static String get projectId => _projectId; @@ -53,7 +53,7 @@ class Descope { static DescopeConfig get config => _config; static set config(DescopeConfig config) { - assert(config.projectId != ""); + assert(config.projectId != ''); _config = config; } @@ -62,7 +62,7 @@ class Descope { /// You can use this [DescopeSessionManager] object as a shared instance to manage /// authenticated sessions in your application. /// - /// final authResponse = Descope.otp.verify(DeliveryMethod.Email, "andy@example.com", "123456") + /// final authResponse = Descope.otp.verify(DeliveryMethod.Email, 'andy@example.com', '123456') /// val session = DescopeSession(authResponse) /// Descope.sessionManager.manageSession(session) /// diff --git a/lib/src/extensions/request.dart b/lib/src/extensions/request.dart index 54bf63c..ab55619 100644 --- a/lib/src/extensions/request.dart +++ b/lib/src/extensions/request.dart @@ -25,6 +25,6 @@ extension AddAuthorization on http.Request { /// Sets the JWT from a [DescopeToken] as the Bearer Token value of /// the Authorization header field in the [http.Request]. void setAuthorizationFromToken(DescopeToken token) { - headers['Authorization'] = "Bearer ${token.jwt}"; + headers['Authorization'] = 'Bearer ${token.jwt}'; } } diff --git a/lib/src/internal/http/descope_client.dart b/lib/src/internal/http/descope_client.dart index e0b59cd..b0c24ad 100644 --- a/lib/src/internal/http/descope_client.dart +++ b/lib/src/internal/http/descope_client.dart @@ -17,14 +17,18 @@ class DescopeClient extends HttpClient { }); } - Future otpSignIn(DeliveryMethod method, String loginId) { - return post('auth/otp/signin/${method.name}', MaskedAddressServerResponse.decoder, body: { + Future otpSignIn(DeliveryMethod method, String loginId, SignInOptions? options) { + return post('auth/otp/signin/${method.name}', MaskedAddressServerResponse.decoder, headers: authorization(options?.refreshJwt), body: { 'loginId': loginId, + 'loginOptions': options?.toMap(), }); } - Future otpSignUpIn(DeliveryMethod method, String loginId) { - return post('auth/otp/signup-in/${method.name}', MaskedAddressServerResponse.decoder, body: {'loginId': loginId}); + Future otpSignUpIn(DeliveryMethod method, String loginId, SignInOptions? options) { + return post('auth/otp/signup-in/${method.name}', MaskedAddressServerResponse.decoder, headers: authorization(options?.refreshJwt), body: { + 'loginId': loginId, + 'loginOptions': options?.toMap(), + }); } Future otpVerify(DeliveryMethod method, String loginId, String code) { @@ -58,10 +62,11 @@ class DescopeClient extends HttpClient { }); } - Future totpVerify(String loginId, String code) { - return post('auth/totp/verify', JWTServerResponse.decoder, body: { + Future totpVerify(String loginId, String code, SignInOptions? options) { + return post('auth/totp/verify', JWTServerResponse.decoder, headers: authorization(options?.refreshJwt), body: { 'loginId': loginId, 'code': code, + 'loginOptions': options?.toMap(), }); } @@ -124,17 +129,19 @@ class DescopeClient extends HttpClient { }); } - Future magicLinkSignIn(DeliveryMethod method, String loginId, String? uri) { - return post('auth/magiclink/signin/${method.name}', MaskedAddressServerResponse.decoder, body: { + Future magicLinkSignIn(DeliveryMethod method, String loginId, String? uri, SignInOptions? options) { + return post('auth/magiclink/signin/${method.name}', MaskedAddressServerResponse.decoder, headers: authorization(options?.refreshJwt), body: { 'loginId': loginId, 'uri': uri, + 'loginOptions': options?.toMap(), }); } - Future magicLinkSignUpOrIn(DeliveryMethod method, String loginId, String? uri) { - return post('auth/magiclink/signup-in/${method.name}', MaskedAddressServerResponse.decoder, body: { + Future magicLinkSignUpOrIn(DeliveryMethod method, String loginId, String? uri, SignInOptions? options) { + return post('auth/magiclink/signup-in/${method.name}', MaskedAddressServerResponse.decoder, headers: authorization(options?.refreshJwt), body: { 'loginId': loginId, 'uri': uri, + 'loginOptions': options?.toMap(), }); } @@ -171,17 +178,19 @@ class DescopeClient extends HttpClient { }); } - Future enchantedLinkSignIn(String loginId, String? uri) { - return post('auth/enchantedlink/signin/email', EnchantedLinkServerResponse.decoder, body: { + Future enchantedLinkSignIn(String loginId, String? uri, SignInOptions? options) { + return post('auth/enchantedlink/signin/email', EnchantedLinkServerResponse.decoder, headers: authorization(options?.refreshJwt), body: { 'loginId': loginId, 'uri': uri, + 'loginOptions': options?.toMap(), }); } - Future enchantedLinkSignUpOrIn(String loginId, String? uri) { - return post('auth/enchantedlink/signup-in/email', EnchantedLinkServerResponse.decoder, body: { + Future enchantedLinkSignUpOrIn(String loginId, String? uri, SignInOptions? options) { + return post('auth/enchantedlink/signup-in/email', EnchantedLinkServerResponse.decoder, headers: authorization(options?.refreshJwt), body: { 'loginId': loginId, 'uri': uri, + 'loginOptions': options?.toMap(), }); } @@ -201,10 +210,12 @@ class DescopeClient extends HttpClient { // OAuth - Future oauthStart(OAuthProvider provider, String? redirectUrl) { - return post('auth/oauth/authorize', OAuthServerResponse.decoder, params: { + Future oauthStart(OAuthProvider provider, String? redirectUrl, SignInOptions? options) { + return post('auth/oauth/authorize', OAuthServerResponse.decoder, headers: authorization(options?.refreshJwt), params: { 'provider': provider.name, 'redirectURL': redirectUrl, + }, body: { + 'loginOptions': options?.toMap(), }); } @@ -216,10 +227,12 @@ class DescopeClient extends HttpClient { // SSO - Future ssoStart(String emailOrTenantId, String? redirectUrl) { - return post('auth/saml/authorize', SsoServerResponse.decoder, params: { + Future ssoStart(String emailOrTenantId, String? redirectUrl, SignInOptions? options) { + return post('auth/saml/authorize', SsoServerResponse.decoder, headers: authorization(options?.refreshJwt), params: { 'tenant': emailOrTenantId, 'redirectURL': redirectUrl, + }, body: { + 'loginOptions': options?.toMap(), }); } @@ -264,12 +277,22 @@ class DescopeClient extends HttpClient { 'x-descope-sdk-version': '0.1.0', }; - Map authorization(String value) { - return {'Authorization': 'Bearer ${config.projectId}:$value'}; + Map authorization(String? value) { + return value != null ? {'Authorization': 'Bearer ${config.projectId}:$value'} : {}; } } -// Extensions +extension on SignInOptions { + String? get refreshJwt => stepupRefreshJwt ?? mfaRefreshJwt; + + Map toMap() { + return { + 'stepup': stepupRefreshJwt != null ? true : null, + 'mfa': mfaRefreshJwt != null ? true : null, + 'customClaims': customClaims.isNotEmpty ? customClaims : null, + }; + } +} extension on SignUpDetails { Map toMap() { diff --git a/lib/src/internal/http/responses.dart b/lib/src/internal/http/responses.dart index e504266..5eedc0f 100644 --- a/lib/src/internal/http/responses.dart +++ b/lib/src/internal/http/responses.dart @@ -4,7 +4,7 @@ import 'http_client.dart'; part 'responses.g.dart'; -const refreshCookieName = "DSR"; +const refreshCookieName = 'DSR'; @JsonSerializable(createToJson: false) class JWTServerResponse { diff --git a/lib/src/internal/routes/enchanted_link.dart b/lib/src/internal/routes/enchanted_link.dart index 6024f3f..0d9fb5a 100644 --- a/lib/src/internal/routes/enchanted_link.dart +++ b/lib/src/internal/routes/enchanted_link.dart @@ -18,13 +18,13 @@ class EnchantedLink implements DescopeEnchantedLink { } @override - Future signIn({required String loginId, String? uri}) async { - return (await client.enchantedLinkSignIn(loginId, uri)).convert(); + Future signIn({required String loginId, String? uri, SignInOptions? options}) async { + return (await client.enchantedLinkSignIn(loginId, uri, options)).convert(); } @override - Future signUpOrIn({required String loginId, String? uri}) async { - return (await client.enchantedLinkSignUpOrIn(loginId, uri)).convert(); + Future signUpOrIn({required String loginId, String? uri, SignInOptions? options}) async { + return (await client.enchantedLinkSignUpOrIn(loginId, uri, options)).convert(); } @override diff --git a/lib/src/internal/routes/flow.dart b/lib/src/internal/routes/flow.dart index 1316f43..5ec5aa1 100644 --- a/lib/src/internal/routes/flow.dart +++ b/lib/src/internal/routes/flow.dart @@ -12,7 +12,7 @@ import 'package:flutter/services.dart'; import '/src/internal/http/descope_client.dart'; import '/src/sdk/routes.dart'; -const _defaultRedirectURL = "descopeauth://flow"; +const _defaultRedirectURL = 'descopeauth://flow'; class Flow extends DescopeFlow { static const _mChannel = MethodChannel('descope_flutter/methods'); @@ -28,7 +28,7 @@ class Flow extends DescopeFlow { @override Future start(String flowUrl, {String? deepLinkUrl}) async { // cancel any previous still running flows - _current?.completer?.completeError(Exception("Canceled")); + _current?.completer?.completeError(Exception('Canceled')); // prepare a new flow runner final runner = _FlowRunner(flowUrl, deepLinkUrl); @@ -53,7 +53,7 @@ class Flow extends DescopeFlow { final codeVerifier = runner?.codeVerifier; final completer = runner?.completer; if (runner == null || codeVerifier == null || completer == null) { - throw Exception("No flow pending exchange"); + throw Exception('No flow pending exchange'); } _current = null; @@ -77,23 +77,23 @@ class Flow extends DescopeFlow { subscription = _eChannel.receiveBroadcastStream().listen((event) { final str = event as String; switch(str) { - case "canceled": - _completeWithError("Flow canceled by user"); + case 'canceled': + _completeWithError('Flow canceled by user'); break; - case "": - _completeWithError("Unexpected error running flow"); + case '': + _completeWithError('Unexpected error running flow'); break; default: try { final uri = Uri.parse(str); exchange(uri); } on Exception { - _completeWithError("Unexpected URI received from flow"); + _completeWithError('Unexpected URI received from flow'); } } subscription?.cancel(); }, onError: (_) { - _completeWithError("Authentication failed"); + _completeWithError('Authentication failed'); subscription?.cancel(); }); diff --git a/lib/src/internal/routes/magic_link.dart b/lib/src/internal/routes/magic_link.dart index e2c5835..40a2d44 100644 --- a/lib/src/internal/routes/magic_link.dart +++ b/lib/src/internal/routes/magic_link.dart @@ -15,13 +15,13 @@ class MagicLink implements DescopeMagicLink { } @override - Future signIn({required DeliveryMethod method, required String loginId, String? uri}) async { - return (await client.magicLinkSignIn(method, loginId, uri)).convert(method); + Future signIn({required DeliveryMethod method, required String loginId, String? uri, SignInOptions? options}) async { + return (await client.magicLinkSignIn(method, loginId, uri, options)).convert(method); } @override - Future signUpOrIn({required DeliveryMethod method, required String loginId, String? uri}) async { - return (await client.magicLinkSignUpOrIn(method, loginId, uri)).convert(method); + Future signUpOrIn({required DeliveryMethod method, required String loginId, String? uri, SignInOptions? options}) async { + return (await client.magicLinkSignUpOrIn(method, loginId, uri, options)).convert(method); } @override diff --git a/lib/src/internal/routes/oauth.dart b/lib/src/internal/routes/oauth.dart index f13a5da..a1cc8f0 100644 --- a/lib/src/internal/routes/oauth.dart +++ b/lib/src/internal/routes/oauth.dart @@ -10,8 +10,8 @@ class OAuth implements DescopeOAuth { OAuth(this.client); @override - Future start({required OAuthProvider provider, String? redirectUrl}) async { - return (await client.oauthStart(provider, redirectUrl)).url; + Future start({required OAuthProvider provider, String? redirectUrl, SignInOptions? options}) async { + return (await client.oauthStart(provider, redirectUrl, options)).url; } @override diff --git a/lib/src/internal/routes/otp.dart b/lib/src/internal/routes/otp.dart index f1cb131..2b90ac7 100644 --- a/lib/src/internal/routes/otp.dart +++ b/lib/src/internal/routes/otp.dart @@ -15,13 +15,13 @@ class Otp implements DescopeOtp { } @override - Future signIn({required DeliveryMethod method, required String loginId}) async { - return (await client.otpSignIn(method, loginId)).convert(method); + Future signIn({required DeliveryMethod method, required String loginId, SignInOptions? options}) async { + return (await client.otpSignIn(method, loginId, options)).convert(method); } @override - Future signUpOrIn({required DeliveryMethod method, required String loginId}) async { - return (await client.otpSignUpIn(method, loginId)).convert(method); + Future signUpOrIn({required DeliveryMethod method, required String loginId, SignInOptions? options}) async { + return (await client.otpSignUpIn(method, loginId, options)).convert(method); } @override diff --git a/lib/src/internal/routes/sso.dart b/lib/src/internal/routes/sso.dart index 12414f0..bd5f05c 100644 --- a/lib/src/internal/routes/sso.dart +++ b/lib/src/internal/routes/sso.dart @@ -1,5 +1,6 @@ import '/src/sdk/routes.dart'; import '/src/types/responses.dart'; +import '../../types/others.dart'; import '../http/descope_client.dart'; import 'shared.dart'; @@ -9,8 +10,8 @@ class Sso implements DescopeSso { Sso(this.client); @override - Future start({required String emailOrTenantId, String? redirectUrl}) async { - return (await client.ssoStart(emailOrTenantId, redirectUrl)).url; + Future start({required String emailOrTenantId, String? redirectUrl, SignInOptions? options}) async { + return (await client.ssoStart(emailOrTenantId, redirectUrl, options)).url; } @override diff --git a/lib/src/internal/routes/totp.dart b/lib/src/internal/routes/totp.dart index ae085e5..880f28c 100644 --- a/lib/src/internal/routes/totp.dart +++ b/lib/src/internal/routes/totp.dart @@ -21,8 +21,8 @@ class Totp implements DescopeTotp { } @override - Future verify({required String loginId, required String code}) async { - return (await client.totpVerify(loginId, code)).convert(); + Future verify({required String loginId, required String code, SignInOptions? options}) async { + return (await client.totpVerify(loginId, code, options)).convert(); } } diff --git a/lib/src/sdk/config.dart b/lib/src/sdk/config.dart index 5c32246..9618762 100644 --- a/lib/src/sdk/config.dart +++ b/lib/src/sdk/config.dart @@ -12,5 +12,5 @@ class DescopeConfig { /// Creates a new `DescopeConfig` object. DescopeConfig({required this.projectId, this.baseUrl = defaultBaseUrl}); - static DescopeConfig initial = DescopeConfig(projectId: ""); + static DescopeConfig initial = DescopeConfig(projectId: ''); } diff --git a/lib/src/sdk/routes.dart b/lib/src/sdk/routes.dart index 79322bd..97f6e91 100644 --- a/lib/src/sdk/routes.dart +++ b/lib/src/sdk/routes.dart @@ -74,7 +74,7 @@ abstract class DescopeOtp { /// /// The OTP code will be sent to the user identified by [loginId] /// via a delivery [method] of choice. - Future signIn({required DeliveryMethod method, required String loginId}); + Future signIn({required DeliveryMethod method, required String loginId, SignInOptions? options}); /// Authenticates an existing user if one exists, or creates a new user /// using an OTP @@ -85,7 +85,7 @@ abstract class DescopeOtp { /// **Important**: Make sure the delivery information corresponding with /// the delivery [method] is given either in the optional [user] parameter or as /// the [loginId] itself, i.e., the email address, phone number, etc. - Future signUpOrIn({required DeliveryMethod method, required String loginId}); + Future signUpOrIn({required DeliveryMethod method, required String loginId, SignInOptions? options}); /// Verifies an OTP [code] sent to the user. /// @@ -140,7 +140,7 @@ abstract class DescopeTotp { /// /// Returns an [AuthenticationResponse] if the provided [loginId] and the [code] /// generated by an authenticator app match. - Future verify({required String loginId, required String code}); + Future verify({required String loginId, required String code, SignInOptions? options}); } /// Authenticate users using a password. @@ -229,7 +229,7 @@ abstract class DescopeMagicLink { /// /// **Important:** Make sure a default magic link URI is configured /// in the Descope console, or provided by this call via [uri]. - Future signIn({required DeliveryMethod method, required String loginId, String? uri}); + Future signIn({required DeliveryMethod method, required String loginId, String? uri, SignInOptions? options}); /// Authenticates an existing user if one exists, or creates a new user /// using a magic link. @@ -243,7 +243,7 @@ abstract class DescopeMagicLink { /// /// **Important:** Make sure a default magic link URI is configured /// in the Descope console, or provided by this call via [uri]. - Future signUpOrIn({required DeliveryMethod method, required String loginId, String? uri}); + Future signUpOrIn({required DeliveryMethod method, required String loginId, String? uri, SignInOptions? options}); /// Updates an existing user by adding an [email] address. /// @@ -312,7 +312,7 @@ abstract class DescopeEnchantedLink { /// /// **Important:** Make sure a default Enchanted link URI is configured /// in the Descope console, or provided via [uri] by this call. - Future signIn({required String loginId, String? uri}); + Future signIn({required String loginId, String? uri, SignInOptions? options}); /// Authenticates an existing user if one exists, or create a new user using an /// enchanted link, sent via email. @@ -323,7 +323,7 @@ abstract class DescopeEnchantedLink { /// /// **Important:** Make sure a default Enchanted link URI is configured /// in the Descope console, or provided via [uri] by this call. - Future signUpOrIn({required String loginId, String? uri}); + Future signUpOrIn({required String loginId, String? uri, SignInOptions? options}); /// Updates an existing user by adding an email address. /// @@ -381,7 +381,7 @@ abstract class DescopeOAuth { /// /// **Important:** Make sure a default OAuth redirect URL is configured /// in the Descope console, or provided by this call via [redirectUrl]. - Future start({required OAuthProvider provider, String? redirectUrl}); + Future start({required OAuthProvider provider, String? redirectUrl, SignInOptions? options}); /// Completes an OAuth redirect chain. /// @@ -406,7 +406,7 @@ abstract class DescopeSso { /// /// **Important:** Make sure a SSO is set up correctly and a redirect URL is configured /// in the Descope console, or provided by this call via [redirectUrl]. - Future start({required String emailOrTenantId, String? redirectUrl}); + Future start({required String emailOrTenantId, String? redirectUrl, SignInOptions? options}); /// Completes an SSO redirect chain. /// diff --git a/lib/src/session/manager.dart b/lib/src/session/manager.dart index 3bf7869..7fee4b2 100644 --- a/lib/src/session/manager.dart +++ b/lib/src/session/manager.dart @@ -13,7 +13,7 @@ import 'storage.dart'; /// Once the user completes a sign in flow successfully you should set the /// [DescopeSession] object as the active session of the session manager. /// -/// final authResponse = await Descope.otp.verify(method: DeliverMethod.Email, loginId: "andy@example.com", code: "123456"); +/// final authResponse = await Descope.otp.verify(method: DeliverMethod.Email, loginId: 'andy@example.com', code: '123456'); /// final session = DescopeSession.fromAuthenticationResponse(authResponse); /// Descope.sessionManager.manageSession(session); /// @@ -45,7 +45,7 @@ import 'storage.dart'; /// await Descope.sessionManager.loadSession(); /// final session = Descope.sessionManager.session; /// if (session != null) { -/// print("User is logged in: ${session.user}"); +/// print('User is logged in: ${session.user}'); /// } /// ... /// diff --git a/lib/src/session/session.dart b/lib/src/session/session.dart index adfc5a1..2470094 100644 --- a/lib/src/session/session.dart +++ b/lib/src/session/session.dart @@ -8,7 +8,7 @@ import 'token.dart'; /// a `DescopeSession` object from the [AuthenticationResponse] value returned /// by all the authentication APIs. /// -/// final authResponse = await Descope.otp.verify(method: DeliveryMethod.Email, loginId: "andy@example.com", code: "123456"); +/// final authResponse = await Descope.otp.verify(method: DeliveryMethod.Email, loginId: 'andy@example.com', code: '123456'); /// final session = DescopeSession.fromAuthenticationResponse(authResponse); /// /// The session can then be used to authenticate outgoing requests to your backend diff --git a/lib/src/session/token.dart b/lib/src/session/token.dart index e0827ac..55c901e 100644 --- a/lib/src/session/token.dart +++ b/lib/src/session/token.dart @@ -179,7 +179,7 @@ Map getTenants(Map claims) { // JWT Decoding Uint8List decodeEncodedFragment(String value) { - final length = value.length + 4 - value.length % 4; + final length = 4 * ((value.length + 3) / 4).floor(); final data = const Base64Decoder().convert(value.padRight(length, '=')); return data; } diff --git a/lib/src/types/others.dart b/lib/src/types/others.dart index 7a496c1..57a6181 100644 --- a/lib/src/types/others.dart +++ b/lib/src/types/others.dart @@ -16,8 +16,6 @@ enum OAuthProvider { apple, } -// Classes - /// Used to provide additional details about a user in sign up calls. class SignUpDetails { final String? name; @@ -26,3 +24,48 @@ class SignUpDetails { SignUpDetails({this.name, this.email, this.phone}); } + +/// Used to require additional behaviors when authenticating a user. +class SignInOptions { + /// Used to add layered security to your app by implementing Step-up authentication. + /// + /// final session = Descope.sessionManager.session; + /// if (session == null) { + /// throw Exception('User is not logged in'); + /// } + /// final options = SignInOptions(stepupRefreshJwt: session.refreshJwt); + /// final future = Descope.otp.signIn(method: DeliveryMethod.email, loginId: email, options: options); + /// + /// After the Step-up authentication completes successfully the returned session JWT will + /// have an `su` claim with a value of `true`. + /// + /// **Note:** The `su` claim is not set on the refresh JWT. + final String? stepupRefreshJwt; + + /// Used to add layered security to your app by implementing Multi-factor authentication. + /// + /// Assuming the user has already signed in successfully with one authentication method, + /// we can take the `refreshJwt` from the [AuthenticationResponse] and pass it as the + /// [mfaRefreshJwt] value to another authentication method. + /// + /// final options = SignInOptions(mfaRefreshJwt: authResponse.refreshJwt); + /// final future = Descope.otp.signIn(method: DeliveryMethod.email, loginId: email, options: options); + /// + /// After the MFA authentication completes successfully the `amr` claim in both the session + /// and refresh JWTs will be an array with an entry for each authentication method used. + final String? mfaRefreshJwt; + + /// Adds additional custom claims to the user's JWT during authentication. + /// + /// For example, the following code starts an OTP sign in and requests a custom claim + /// with the authenticated user's full name: + /// + /// const options = SignInOptions(customClaims: {'name': '{{user.name}}'}); + /// await Descope.otp.signIn(method: DeliveryMethod.email, loginId: email, options: options); + /// + /// **Important:** Any custom claims added via this method are considered insecure and will + /// be nested under the `nsec` custom claim. + final Map customClaims; + + const SignInOptions({this.stepupRefreshJwt, this.mfaRefreshJwt, this.customClaims = const {}}); +} diff --git a/lib/src/types/user.dart b/lib/src/types/user.dart index eedb6e5..81043cd 100644 --- a/lib/src/types/user.dart +++ b/lib/src/types/user.dart @@ -15,11 +15,11 @@ part 'user.g.dart'; /// code. The authentication response has a [DescopeUser] property which can be used /// directly or later on when it's kept in the [DescopeSession]. /// -/// final authResponse = await Descope.otp.verify(method: DeliveryMethod.Email, loginId: "andy@example.com", code: "123456"); -/// print("Finished OTP login for user: ${authResponse.user}"); +/// final authResponse = await Descope.otp.verify(method: DeliveryMethod.Email, loginId: 'andy@example.com', code: '123456'); +/// print('Finished OTP login for user: ${authResponse.user}'); /// /// Descope.sessionManager.session = DescopeSession(authResponse); -/// print("Created session for user ${descopeSession.user.userId}"); +/// print('Created session for user ${descopeSession.user.userId}'); /// /// The details for a signed in user can be updated manually by calling the `auth.me` API with /// the `refreshJwt` from the active [DescopeSession]. If the operation is successful the call