Skip to content

Commit

Permalink
Add support for LoginOptions (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
shilgapira committed Aug 8, 2023
1 parent 6f7c302 commit fa0ca59
Show file tree
Hide file tree
Showing 19 changed files with 147 additions and 80 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
```
Expand All @@ -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
}
Expand All @@ -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(
Expand Down Expand Up @@ -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
},
),
],
Expand Down Expand Up @@ -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("<URL_FOR_FLOW_IN_SETUP_#1>", deepLinkUrl: "<URL_FOR_APP_LINK_IN_SETUP_#2>");
final authResponse = await Descope.flow.start('<URL_FOR_FLOW_IN_SETUP_#1>', deepLinkUrl: '<URL_FOR_APP_LINK_IN_SETUP_#2>');
final session = DescopeSession.fromAuthenticationResponse(authResponse);
Descope.sessionManager.manageSession(session);
```
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions lib/descope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -53,7 +53,7 @@ class Descope {
static DescopeConfig get config => _config;

static set config(DescopeConfig config) {
assert(config.projectId != "");
assert(config.projectId != '');
_config = config;
}

Expand All @@ -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)
///
Expand Down
2 changes: 1 addition & 1 deletion lib/src/extensions/request.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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}';
}
}
65 changes: 44 additions & 21 deletions lib/src/internal/http/descope_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ class DescopeClient extends HttpClient {
});
}

Future<MaskedAddressServerResponse> otpSignIn(DeliveryMethod method, String loginId) {
return post('auth/otp/signin/${method.name}', MaskedAddressServerResponse.decoder, body: {
Future<MaskedAddressServerResponse> 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<MaskedAddressServerResponse> otpSignUpIn(DeliveryMethod method, String loginId) {
return post('auth/otp/signup-in/${method.name}', MaskedAddressServerResponse.decoder, body: {'loginId': loginId});
Future<MaskedAddressServerResponse> 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<JWTServerResponse> otpVerify(DeliveryMethod method, String loginId, String code) {
Expand Down Expand Up @@ -58,10 +62,11 @@ class DescopeClient extends HttpClient {
});
}

Future<JWTServerResponse> totpVerify(String loginId, String code) {
return post('auth/totp/verify', JWTServerResponse.decoder, body: {
Future<JWTServerResponse> 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(),
});
}

Expand Down Expand Up @@ -124,17 +129,19 @@ class DescopeClient extends HttpClient {
});
}

Future<MaskedAddressServerResponse> magicLinkSignIn(DeliveryMethod method, String loginId, String? uri) {
return post('auth/magiclink/signin/${method.name}', MaskedAddressServerResponse.decoder, body: {
Future<MaskedAddressServerResponse> 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<MaskedAddressServerResponse> magicLinkSignUpOrIn(DeliveryMethod method, String loginId, String? uri) {
return post('auth/magiclink/signup-in/${method.name}', MaskedAddressServerResponse.decoder, body: {
Future<MaskedAddressServerResponse> 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(),
});
}

Expand Down Expand Up @@ -171,17 +178,19 @@ class DescopeClient extends HttpClient {
});
}

Future<EnchantedLinkServerResponse> enchantedLinkSignIn(String loginId, String? uri) {
return post('auth/enchantedlink/signin/email', EnchantedLinkServerResponse.decoder, body: {
Future<EnchantedLinkServerResponse> 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<EnchantedLinkServerResponse> enchantedLinkSignUpOrIn(String loginId, String? uri) {
return post('auth/enchantedlink/signup-in/email', EnchantedLinkServerResponse.decoder, body: {
Future<EnchantedLinkServerResponse> 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(),
});
}

Expand All @@ -201,10 +210,12 @@ class DescopeClient extends HttpClient {

// OAuth

Future<OAuthServerResponse> oauthStart(OAuthProvider provider, String? redirectUrl) {
return post('auth/oauth/authorize', OAuthServerResponse.decoder, params: {
Future<OAuthServerResponse> 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(),
});
}

Expand All @@ -216,10 +227,12 @@ class DescopeClient extends HttpClient {

// SSO

Future<SsoServerResponse> ssoStart(String emailOrTenantId, String? redirectUrl) {
return post('auth/saml/authorize', SsoServerResponse.decoder, params: {
Future<SsoServerResponse> 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(),
});
}

Expand Down Expand Up @@ -264,12 +277,22 @@ class DescopeClient extends HttpClient {
'x-descope-sdk-version': '0.1.0',
};

Map<String, String> authorization(String value) {
return {'Authorization': 'Bearer ${config.projectId}:$value'};
Map<String, String> authorization(String? value) {
return value != null ? {'Authorization': 'Bearer ${config.projectId}:$value'} : {};
}
}

// Extensions
extension on SignInOptions {
String? get refreshJwt => stepupRefreshJwt ?? mfaRefreshJwt;

Map<String, dynamic> toMap() {
return {
'stepup': stepupRefreshJwt != null ? true : null,
'mfa': mfaRefreshJwt != null ? true : null,
'customClaims': customClaims.isNotEmpty ? customClaims : null,
};
}
}

extension on SignUpDetails {
Map<String, dynamic> toMap() {
Expand Down
2 changes: 1 addition & 1 deletion lib/src/internal/http/responses.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import 'http_client.dart';

part 'responses.g.dart';

const refreshCookieName = "DSR";
const refreshCookieName = 'DSR';

@JsonSerializable(createToJson: false)
class JWTServerResponse {
Expand Down
8 changes: 4 additions & 4 deletions lib/src/internal/routes/enchanted_link.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ class EnchantedLink implements DescopeEnchantedLink {
}

@override
Future<EnchantedLinkResponse> signIn({required String loginId, String? uri}) async {
return (await client.enchantedLinkSignIn(loginId, uri)).convert();
Future<EnchantedLinkResponse> signIn({required String loginId, String? uri, SignInOptions? options}) async {
return (await client.enchantedLinkSignIn(loginId, uri, options)).convert();
}

@override
Future<EnchantedLinkResponse> signUpOrIn({required String loginId, String? uri}) async {
return (await client.enchantedLinkSignUpOrIn(loginId, uri)).convert();
Future<EnchantedLinkResponse> signUpOrIn({required String loginId, String? uri, SignInOptions? options}) async {
return (await client.enchantedLinkSignUpOrIn(loginId, uri, options)).convert();
}

@override
Expand Down
18 changes: 9 additions & 9 deletions lib/src/internal/routes/flow.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -28,7 +28,7 @@ class Flow extends DescopeFlow {
@override
Future<AuthenticationResponse> 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);
Expand All @@ -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;
Expand All @@ -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();
});

Expand Down
8 changes: 4 additions & 4 deletions lib/src/internal/routes/magic_link.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ class MagicLink implements DescopeMagicLink {
}

@override
Future<String> signIn({required DeliveryMethod method, required String loginId, String? uri}) async {
return (await client.magicLinkSignIn(method, loginId, uri)).convert(method);
Future<String> signIn({required DeliveryMethod method, required String loginId, String? uri, SignInOptions? options}) async {
return (await client.magicLinkSignIn(method, loginId, uri, options)).convert(method);
}

@override
Future<String> signUpOrIn({required DeliveryMethod method, required String loginId, String? uri}) async {
return (await client.magicLinkSignUpOrIn(method, loginId, uri)).convert(method);
Future<String> signUpOrIn({required DeliveryMethod method, required String loginId, String? uri, SignInOptions? options}) async {
return (await client.magicLinkSignUpOrIn(method, loginId, uri, options)).convert(method);
}

@override
Expand Down
4 changes: 2 additions & 2 deletions lib/src/internal/routes/oauth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ class OAuth implements DescopeOAuth {
OAuth(this.client);

@override
Future<String> start({required OAuthProvider provider, String? redirectUrl}) async {
return (await client.oauthStart(provider, redirectUrl)).url;
Future<String> start({required OAuthProvider provider, String? redirectUrl, SignInOptions? options}) async {
return (await client.oauthStart(provider, redirectUrl, options)).url;
}

@override
Expand Down
8 changes: 4 additions & 4 deletions lib/src/internal/routes/otp.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ class Otp implements DescopeOtp {
}

@override
Future<String> signIn({required DeliveryMethod method, required String loginId}) async {
return (await client.otpSignIn(method, loginId)).convert(method);
Future<String> signIn({required DeliveryMethod method, required String loginId, SignInOptions? options}) async {
return (await client.otpSignIn(method, loginId, options)).convert(method);
}

@override
Future<String> signUpOrIn({required DeliveryMethod method, required String loginId}) async {
return (await client.otpSignUpIn(method, loginId)).convert(method);
Future<String> signUpOrIn({required DeliveryMethod method, required String loginId, SignInOptions? options}) async {
return (await client.otpSignUpIn(method, loginId, options)).convert(method);
}

@override
Expand Down
5 changes: 3 additions & 2 deletions lib/src/internal/routes/sso.dart
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -9,8 +10,8 @@ class Sso implements DescopeSso {
Sso(this.client);

@override
Future<String> start({required String emailOrTenantId, String? redirectUrl}) async {
return (await client.ssoStart(emailOrTenantId, redirectUrl)).url;
Future<String> start({required String emailOrTenantId, String? redirectUrl, SignInOptions? options}) async {
return (await client.ssoStart(emailOrTenantId, redirectUrl, options)).url;
}

@override
Expand Down
Loading

0 comments on commit fa0ca59

Please sign in to comment.