Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add openUrl support #393

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
20 changes: 17 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 @@ -101,20 +102,24 @@ class Auth0Web {
/// * Arbitrary [parameters] can be specified and then picked up in a custom
/// Auth0 [Action](https://auth0.com/docs/customize/actions) or
/// [Rule](https://auth0.com/docs/customize/rules).
/// * 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> loginWithRedirect(
{final String? audience,
final String? redirectUrl,
final String? organizationId,
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(LoginOptions(
audience: audience,
redirectUrl: redirectUrl,
organizationId: organizationId,
invitationUrl: invitationUrl,
scopes: scopes ?? {},
openUrl: openUrl,
idTokenValidationConfig: IdTokenValidationConfig(maxAge: maxAge),
parameters: parameters));

Expand Down Expand Up @@ -190,9 +195,18 @@ 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
9 changes: 7 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 @@ -3,6 +3,7 @@ import 'dart:html';

import 'package:auth0_flutter_platform_interface/auth0_flutter_platform_interface.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'package:js/js.dart';

import 'auth0_flutter_web_platform_proxy.dart';
import 'extensions/client_options_extensions.dart';
Expand Down Expand Up @@ -61,8 +62,12 @@ class Auth0FlutterPlugin extends Auth0FlutterWebPlatform {
: null),
options?.parameters ?? {}));

final loginOptions =
interop.RedirectLoginOptions(authorizationParams: authParams);
final openUrl = options?.openUrl;

final loginOptions = JsInteropUtils.stripNulls(interop.RedirectLoginOptions(
authorizationParams: authParams,
openUrl: openUrl != null ? allowInterop(openUrl) : null,
));

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)),
));
}
16 changes: 12 additions & 4 deletions auth0_flutter/lib/src/web/js_interop.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,14 @@ class AuthorizationParams {
@anonymous
class RedirectLoginOptions {
external AuthorizationParams? get authorizationParams;
external Future<void> Function(String url)? openUrl;
external String? get fragment;

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

@JS()
Expand Down Expand Up @@ -150,8 +154,12 @@ class LogoutParams {
@anonymous
class LogoutOptions {
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 @@ -184,5 +192,5 @@ class Auth0Client {
external Future<WebCredentials> getTokenSilently(
[final GetTokenSilentlyOptions? options]);
external Future<bool> isAuthenticated();
external Future<void> logout([final LogoutOptions? logoutParams]);
external Future<void> logout([final LogoutOptions? logoutOptions]);
}
42 changes: 41 additions & 1 deletion auth0_flutter/test/web/auth0_flutter_web_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@ import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
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 @@ -140,6 +145,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 @@ -236,6 +260,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
32 changes: 31 additions & 1 deletion auth0_flutter/test/web/auth0_flutter_web_test.mocks.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Mocks generated by Mockito 5.4.0 from annotations
// Mocks generated by Mockito 5.4.4 from annotations
// in auth0_flutter/test/web/auth0_flutter_web_test.dart.
// Do not manually edit this file.

Expand All @@ -10,10 +10,14 @@ import 'package:auth0_flutter/src/web/auth0_flutter_web_platform_proxy.dart'
import 'package:auth0_flutter/src/web/js_interop.dart' as _i2;
import 'package:mockito/mockito.dart' as _i1;

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
// ignore_for_file: comment_references
// ignore_for_file: deprecated_member_use
// ignore_for_file: deprecated_member_use_from_same_package
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
Expand Down Expand Up @@ -59,6 +63,7 @@ class MockAuth0FlutterWebClientProxy extends _i1.Mock
Invocation.getter(#client),
),
) as _i2.Auth0Client);

@override
_i4.Future<void> loginWithRedirect(_i2.RedirectLoginOptions? options) =>
(super.noSuchMethod(
Expand All @@ -69,6 +74,7 @@ class MockAuth0FlutterWebClientProxy extends _i1.Mock
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);

@override
_i4.Future<void> loginWithPopup([
_i2.PopupLoginOptions? options,
Expand All @@ -85,6 +91,7 @@ class MockAuth0FlutterWebClientProxy extends _i1.Mock
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);

@override
_i4.Future<void> checkSession() => (super.noSuchMethod(
Invocation.method(
Expand All @@ -94,6 +101,7 @@ class MockAuth0FlutterWebClientProxy extends _i1.Mock
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);

@override
_i4.Future<_i2.WebCredentials> getTokenSilently(
[_i2.GetTokenSilentlyOptions? options]) =>
Expand All @@ -110,6 +118,7 @@ class MockAuth0FlutterWebClientProxy extends _i1.Mock
),
)),
) as _i4.Future<_i2.WebCredentials>);

@override
_i4.Future<void> handleRedirectCallback() => (super.noSuchMethod(
Invocation.method(
Expand All @@ -119,6 +128,7 @@ class MockAuth0FlutterWebClientProxy extends _i1.Mock
returnValue: _i4.Future<void>.value(),
returnValueForMissingStub: _i4.Future<void>.value(),
) as _i4.Future<void>);

@override
_i4.Future<bool> isAuthenticated() => (super.noSuchMethod(
Invocation.method(
Expand All @@ -127,6 +137,7 @@ class MockAuth0FlutterWebClientProxy extends _i1.Mock
),
returnValue: _i4.Future<bool>.value(false),
) as _i4.Future<bool>);

@override
_i4.Future<void> logout(_i2.LogoutOptions? options) => (super.noSuchMethod(
Invocation.method(
Expand All @@ -137,3 +148,22 @@ 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 @@ -8,6 +8,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 @@ -17,6 +18,7 @@ class LoginOptions implements RequestOptions {
this.redirectUrl,
this.organizationId,
this.invitationUrl,
this.openUrl,
this.parameters = const {}});

@override
Expand All @@ -29,6 +31,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});
}
Loading