Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions auth0_flutter/lib/auth0_flutter_web.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:auth0_flutter_platform_interface/auth0_flutter_platform_interface.dart';

import 'src/version.dart';

export 'package:auth0_flutter_platform_interface/auth0_flutter_platform_interface.dart'
Expand Down Expand Up @@ -115,6 +116,8 @@ class Auth0Web {
/// * [scopes] defaults to `openid profile email`. You can override these
/// scopes, but `openid` is always requested regardless of this setting.
/// * If you want to log into a specific organization, provide the
/// * Use [openUrl] to control the redirect and not rely on the SDK to do the
/// actual redirect. Required *auth0-spa-js* `2.0.1` or later.
/// [organizationId]. Provide [invitationUrl] if a user has been invited
/// to join an organization.
/// * Arbitrary [parameters] can be specified and then picked up in a custom
Expand All @@ -133,6 +136,7 @@ class Auth0Web {
final String? invitationUrl,
final int? maxAge,
final Set<String>? scopes,
final Future<void> Function(String url)? openUrl,
final Map<String, String> parameters = const {},
}) =>
Auth0FlutterWebPlatform.instance.loginWithRedirect(
Expand All @@ -143,6 +147,7 @@ class Auth0Web {
organizationId: organizationId,
invitationUrl: invitationUrl,
scopes: scopes ?? {},
openUrl: openUrl,
idTokenValidationConfig: IdTokenValidationConfig(maxAge: maxAge),
parameters: parameters,
),
Expand Down Expand Up @@ -220,9 +225,20 @@ class Auth0Web {
/// * Use [federated] to log the user out of their identity provider
/// (such as Google) as well as Auth0. Only applicable if the user
/// authenticated using an identity provider. [Read more about how federated logout works at Auth0](https://auth0.com/docs/logout/guides/logout-idps).
Future<void> logout({final bool? federated, final String? returnToUrl}) =>
Auth0FlutterWebPlatform.instance
.logout(LogoutOptions(federated: federated, returnTo: returnToUrl));
/// * Use [openUrl] to control the redirect and not rely on the SDK to do the
/// actual redirect. Required *auth0-spa-js* `2.0.1` or later.
Future<void> logout({
final bool? federated,
final String? returnToUrl,
final Future<void> Function(String url)? openUrl,
}) =>
Auth0FlutterWebPlatform.instance.logout(
LogoutOptions(
federated: federated,
returnTo: returnToUrl,
openUrl: openUrl,
),
);

/// Retrieves a set of credentials for the user.
///
Expand Down
7 changes: 5 additions & 2 deletions auth0_flutter/lib/src/web/auth0_flutter_plugin_real.dart
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,13 @@ class Auth0FlutterPlugin extends Auth0FlutterWebPlatform {
: null),
options?.parameters ?? {}));

final loginOptions = interop.RedirectLoginOptions(
final openUrl = options?.openUrl;

final loginOptions = JsInteropUtils.stripNulls(interop.RedirectLoginOptions(
appState: options?.appState.jsify(),
authorizationParams: authParams,
);
openUrl: openUrl,
));

return client.loginWithRedirect(loginOptions);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import 'dart:js';

import 'package:auth0_flutter_platform_interface/auth0_flutter_platform_interface.dart';

import '../js_interop.dart' as interop;
import '../js_interop_utils.dart';

extension LogoutOptionsExtension on LogoutOptions {
interop.LogoutOptions toClientLogoutOptions() => interop.LogoutOptions(
logoutParams: JsInteropUtils.stripNulls(
interop.LogoutParams(federated: federated, returnTo: returnTo)));
interop.LogoutOptions toClientLogoutOptions() =>
JsInteropUtils.stripNulls(interop.LogoutOptions(
openUrl: openUrl != null ? allowInterop(openUrl!) : null,
logoutParams: JsInteropUtils.stripNulls(
interop.LogoutParams(federated: federated, returnTo: returnTo)),
));
}
10 changes: 8 additions & 2 deletions auth0_flutter/lib/src/web/js_interop.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ extension type RedirectLoginOptions._(JSObject _) implements JSObject {
external JSAny? get appState;
external AuthorizationParams? get authorizationParams;
external String? get fragment;
external Future<void> Function(String url)? openUrl;

external factory RedirectLoginOptions({
final JSAny? appState,
final AuthorizationParams authorizationParams,
final String fragment,
final Future<void> Function(String url)? openUrl,
});
}

Expand Down Expand Up @@ -169,8 +171,12 @@ extension type LogoutParams._(JSObject _) implements JSObject {
@anonymous
extension type LogoutOptions._(JSObject _) implements JSObject {
external LogoutParams? get logoutParams;
external Future<void> Function(String url)? openUrl;

external factory LogoutOptions({final LogoutParams? logoutParams});
external factory LogoutOptions({
final LogoutParams? logoutParams,
final Future<void> Function(String url)? openUrl,
});
}

@JS()
Expand Down Expand Up @@ -213,7 +219,7 @@ extension type Auth0Client._(JSObject _) implements JSObject {
final GetTokenSilentlyOptions? options,
]);
external JSPromise<JSBoolean> isAuthenticated();
external JSPromise<JSAny?> logout([final LogoutOptions? logoutParams]);
external JSPromise<JSAny?> logout([final LogoutOptions? logoutOptions]);
}

// TODO: remove this extension when updating to Dart 3.6.0
Expand Down
41 changes: 40 additions & 1 deletion auth0_flutter/test/web/auth0_flutter_web_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import 'package:mockito/mockito.dart';

import 'auth0_flutter_web_test.mocks.dart';

@GenerateMocks([Auth0FlutterWebClientProxy])
abstract class OpenUrl {
Future<void> call(final String url) async {}
}

@GenerateMocks([Auth0FlutterWebClientProxy, OpenUrl])
void main() {
final auth0 = Auth0Web('test-domain', 'test-client-id');
final mockClientProxy = MockAuth0FlutterWebClientProxy();
Expand Down Expand Up @@ -231,6 +235,25 @@ void main() {
expect(params.screen_hint, 'signup');
});

test('loginWithRedirect supports openUrl', () async {
when(mockClientProxy.isAuthenticated())
.thenAnswer((final _) => Future.value(false));

final openUrlMock = MockOpenUrl();
await auth0.loginWithRedirect(openUrl: openUrlMock);

final openUrl = verify(mockClientProxy.loginWithRedirect(captureAny))
.captured
.first
.openUrl;

expect(openUrl, isNotNull);

await openUrl('http://open.url');

verify(openUrlMock('http://open.url')).called(1);
});

test('loginWithRedirect strips options that are null', () async {
when(mockClientProxy.isAuthenticated())
.thenAnswer((final _) => Future.value(false));
Expand Down Expand Up @@ -327,6 +350,22 @@ void main() {
expect(params.returnTo, 'http://returnto.url');
});

test('logout support openUrl', () async {
when(mockClientProxy.logout(any)).thenAnswer((final _) => Future.value());
final openUrlMock = MockOpenUrl();

await auth0.logout(openUrl: openUrlMock);

final openUrl =
verify(mockClientProxy.logout(captureAny)).captured.first.openUrl;

expect(openUrl, isNotNull);

await openUrl('http://open.url');

verify(openUrlMock('http://open.url')).called(1);
});

test('loginWithPopup is called and succeeds', () async {
when(mockClientProxy.loginWithPopup(any, any))
.thenAnswer((final _) => Future.value());
Expand Down
18 changes: 18 additions & 0 deletions auth0_flutter/test/web/auth0_flutter_web_test.mocks.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import 'package:auth0_flutter/src/web/js_interop.dart' as _i2;
import 'package:mockito/mockito.dart' as _i1;
import 'auth0_extension_type_mocks.dart';

import 'auth0_flutter_web_test.dart' as _i5;

// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
Expand Down Expand Up @@ -172,3 +174,19 @@ class MockAuth0FlutterWebClientProxy extends _i1.Mock
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
}

/// A class which mocks [OpenUrl].
///
/// See the documentation for Mockito's code generation for more information.
class MockOpenUrl extends _i1.Mock implements _i5.OpenUrl {
MockOpenUrl() {
_i1.throwOnMissingStub(this);
}

@override
_i4.Future<void> call(String? url) => (super.noSuchMethod(
Invocation.method(#call, [url]),
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:plugin_platform_interface/plugin_platform_interface.dart';

import '../auth0_flutter_platform_interface.dart';

class StubAuth0FlutterWeb extends Auth0FlutterWebPlatform {
Expand Down
3 changes: 3 additions & 0 deletions auth0_flutter_platform_interface/lib/src/login_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class LoginOptions implements RequestOptions {
final String? redirectUrl;
final String? organizationId;
final String? invitationUrl;
final Future<void> Function(String url)? openUrl;
final Map<String, String> parameters;

LoginOptions({
Expand All @@ -19,6 +20,7 @@ class LoginOptions implements RequestOptions {
this.redirectUrl,
this.organizationId,
this.invitationUrl,
this.openUrl,
this.parameters = const {},
});

Expand All @@ -32,6 +34,7 @@ class LoginOptions implements RequestOptions {
'redirectUrl': redirectUrl,
'organizationId': organizationId,
'invitationUrl': invitationUrl,
'openUrl': openUrl,
'parameters': parameters,
};
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class LogoutOptions {
final String? returnTo;
final bool? federated;
final Future<void> Function(String url)? openUrl;

LogoutOptions({this.returnTo, this.federated});
LogoutOptions({this.returnTo, this.federated, this.openUrl});
}