From 51b85c8b7939d70c32fd9baa4c09eb567755d33d Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 13 Oct 2025 14:40:25 +0700 Subject: [PATCH] TF-4049 Pre-fill OIDC login form --- .../authentication_oidc_datasource.dart | 8 ++++++- .../authentication_oidc_datasource_impl.dart | 16 ++++++++++++-- .../authentication_client_base.dart | 10 +++++---- ...thentication_client_interaction_mixin.dart | 6 ++++-- .../authentication_client_mobile.dart | 6 ++++-- .../authentication_client_web.dart | 6 ++++-- .../authentication_oidc_repository_impl.dart | 16 ++++++++++++-- .../authentication_oidc_repository.dart | 8 ++++++- .../get_oidc_configuration_interactor.dart | 15 ++++++++++--- .../usecases/get_token_oidc_interactor.dart | 10 +++++---- .../login/presentation/login_controller.dart | 21 ++++++++++++------- model/lib/oidc/oidc_configuration.dart | 20 ++++++++++++++++++ 12 files changed, 112 insertions(+), 30 deletions(-) diff --git a/lib/features/login/data/datasource/authentication_oidc_datasource.dart b/lib/features/login/data/datasource/authentication_oidc_datasource.dart index 5cfc0cf59..434a8a43a 100644 --- a/lib/features/login/data/datasource/authentication_oidc_datasource.dart +++ b/lib/features/login/data/datasource/authentication_oidc_datasource.dart @@ -8,7 +8,13 @@ abstract class AuthenticationOIDCDataSource { Future discoverOIDC(OIDCConfiguration oidcConfiguration); - Future getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List scopes); + Future getTokenOIDC( + String clientId, + String redirectUrl, + String discoveryUrl, + List scopes, { + String? loginHint, + }); Future persistTokenOIDC(TokenOIDC tokenOidc); diff --git a/lib/features/login/data/datasource_impl/authentication_oidc_datasource_impl.dart b/lib/features/login/data/datasource_impl/authentication_oidc_datasource_impl.dart index c0ab80194..5f951c54b 100644 --- a/lib/features/login/data/datasource_impl/authentication_oidc_datasource_impl.dart +++ b/lib/features/login/data/datasource_impl/authentication_oidc_datasource_impl.dart @@ -56,9 +56,21 @@ class AuthenticationOIDCDataSourceImpl extends AuthenticationOIDCDataSource { } @override - Future getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List scopes) { + Future getTokenOIDC( + String clientId, + String redirectUrl, + String discoveryUrl, + List scopes, { + String? loginHint, + }) { return Future.sync(() async { - return await _authenticationClient.getTokenOIDC(clientId, redirectUrl, discoveryUrl, scopes); + return await _authenticationClient.getTokenOIDC( + clientId, + redirectUrl, + discoveryUrl, + scopes, + loginHint: loginHint, + ); }).catchError(_exceptionThrower.throwException); } diff --git a/lib/features/login/data/network/authentication_client/authentication_client_base.dart b/lib/features/login/data/network/authentication_client/authentication_client_base.dart index 75ef41ac2..34165b897 100644 --- a/lib/features/login/data/network/authentication_client/authentication_client_base.dart +++ b/lib/features/login/data/network/authentication_client/authentication_client_base.dart @@ -13,10 +13,12 @@ abstract class AuthenticationClientBase { List scopes); Future getTokenOIDC( - String clientId, - String redirectUrl, - String discoveryUrl, - List scopes); + String clientId, + String redirectUrl, + String discoveryUrl, + List scopes, { + String? loginHint, + }); Future refreshingTokensOIDC( String clientId, diff --git a/lib/features/login/data/network/authentication_client/authentication_client_interaction_mixin.dart b/lib/features/login/data/network/authentication_client/authentication_client_interaction_mixin.dart index a70eb3aa9..33c7d6b45 100644 --- a/lib/features/login/data/network/authentication_client/authentication_client_interaction_mixin.dart +++ b/lib/features/login/data/network/authentication_client/authentication_client_interaction_mixin.dart @@ -59,14 +59,16 @@ mixin AuthenticationClientInteractionMixin { String clientId, String redirectUrl, String discoveryUrl, - List scopes, - ) { + List scopes, { + String? loginHint, + }) { return AuthorizationTokenRequest( clientId, redirectUrl, discoveryUrl: discoveryUrl, scopes: scopes, externalUserAgent: getExternalUserAgent(), + loginHint: loginHint, ); } diff --git a/lib/features/login/data/network/authentication_client/authentication_client_mobile.dart b/lib/features/login/data/network/authentication_client/authentication_client_mobile.dart index 4671d7af9..fb56fc941 100644 --- a/lib/features/login/data/network/authentication_client/authentication_client_mobile.dart +++ b/lib/features/login/data/network/authentication_client/authentication_client_mobile.dart @@ -27,13 +27,15 @@ class AuthenticationClientMobile with AuthenticationClientInteractionMixin String clientId, String redirectUrl, String discoveryUrl, - List scopes, - ) async { + List scopes, { + String? loginHint, + }) async { final authorizationTokenRequest = getAuthorizationTokenRequest( clientId, redirectUrl, discoveryUrl, scopes, + loginHint: loginHint, ); final authorizationTokenResponse = await _appAuth.authorizeAndExchangeCode( authorizationTokenRequest, diff --git a/lib/features/login/data/network/authentication_client/authentication_client_web.dart b/lib/features/login/data/network/authentication_client/authentication_client_web.dart index e0a66d5e4..d9a2e4f78 100644 --- a/lib/features/login/data/network/authentication_client/authentication_client_web.dart +++ b/lib/features/login/data/network/authentication_client/authentication_client_web.dart @@ -25,13 +25,15 @@ class AuthenticationClientWeb with AuthenticationClientInteractionMixin String clientId, String redirectUrl, String discoveryUrl, - List scopes, - ) async { + List scopes, { + String? loginHint, + }) async { final authorizationTokenRequest = getAuthorizationTokenRequest( clientId, redirectUrl, discoveryUrl, scopes, + loginHint: loginHint, ); final authorizationTokenResponse = await _appAuthWeb.authorizeAndExchangeCode( authorizationTokenRequest, diff --git a/lib/features/login/data/repository/authentication_oidc_repository_impl.dart b/lib/features/login/data/repository/authentication_oidc_repository_impl.dart index 3821fea0e..0f83295bd 100644 --- a/lib/features/login/data/repository/authentication_oidc_repository_impl.dart +++ b/lib/features/login/data/repository/authentication_oidc_repository_impl.dart @@ -28,8 +28,20 @@ class AuthenticationOIDCRepositoryImpl extends AuthenticationOIDCRepository { } @override - Future getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List scopes) { - return _oidcDataSource.getTokenOIDC(clientId, redirectUrl, discoveryUrl, scopes); + Future getTokenOIDC( + String clientId, + String redirectUrl, + String discoveryUrl, + List scopes, { + String? loginHint, + }) { + return _oidcDataSource.getTokenOIDC( + clientId, + redirectUrl, + discoveryUrl, + scopes, + loginHint: loginHint, + ); } @override diff --git a/lib/features/login/domain/repository/authentication_oidc_repository.dart b/lib/features/login/domain/repository/authentication_oidc_repository.dart index 2574bba4f..5f5893134 100644 --- a/lib/features/login/domain/repository/authentication_oidc_repository.dart +++ b/lib/features/login/domain/repository/authentication_oidc_repository.dart @@ -8,7 +8,13 @@ abstract class AuthenticationOIDCRepository { Future discoverOIDC(OIDCConfiguration oidcConfiguration); - Future getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List scopes); + Future getTokenOIDC( + String clientId, + String redirectUrl, + String discoveryUrl, + List scopes, { + String? loginHint, + }); Future persistTokenOIDC(TokenOIDC tokenOidc); diff --git a/lib/features/login/domain/usecases/get_oidc_configuration_interactor.dart b/lib/features/login/domain/usecases/get_oidc_configuration_interactor.dart index 92698b767..9c4274c1e 100644 --- a/lib/features/login/domain/usecases/get_oidc_configuration_interactor.dart +++ b/lib/features/login/domain/usecases/get_oidc_configuration_interactor.dart @@ -2,6 +2,7 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; import 'package:dartz/dartz.dart'; +import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/response/oidc_response.dart'; import 'package:tmail_ui_user/features/login/domain/model/base_url_oidc_response.dart'; import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart'; @@ -12,12 +13,20 @@ class GetOIDCConfigurationInteractor { GetOIDCConfigurationInteractor(this._oidcRepository); - Stream> execute(OIDCResponse oidcResponse) async* { + Stream> execute( + OIDCResponse oidcResponse, { + String? loginHint, + }) async* { try { yield Right(GetOIDCConfigurationLoading()); final oidcConfiguration = await _oidcRepository.getOIDCConfiguration(oidcResponse); - await _oidcRepository.persistOidcConfiguration(oidcConfiguration); - yield Right(GetOIDCConfigurationSuccess(oidcConfiguration)); + final configWithLoginHint = oidcConfiguration.copyWidth( + loginHint: loginHint, + ); + await _oidcRepository.persistOidcConfiguration(configWithLoginHint); + yield Right( + GetOIDCConfigurationSuccess(configWithLoginHint), + ); } catch (e) { logError('$runtimeType::execute():oidcResponse = ${oidcResponse.runtimeType} | Exception = $e'); if (oidcResponse is BaseUrlOidcResponse) { diff --git a/lib/features/login/domain/usecases/get_token_oidc_interactor.dart b/lib/features/login/domain/usecases/get_token_oidc_interactor.dart index 09ce13418..e309075a8 100644 --- a/lib/features/login/domain/usecases/get_token_oidc_interactor.dart +++ b/lib/features/login/domain/usecases/get_token_oidc_interactor.dart @@ -27,10 +27,12 @@ class GetTokenOIDCInteractor { try { yield Right(GetTokenOIDCLoading()); final tokenOIDC = await authenticationOIDCRepository.getTokenOIDC( - config.clientId, - config.redirectUrl, - config.discoveryUrl, - config.scopes); + config.clientId, + config.redirectUrl, + config.discoveryUrl, + config.scopes, + loginHint: config.loginHint, + ); await Future.wait([ _credentialRepository.saveBaseUrl(baseUri), diff --git a/lib/features/login/presentation/login_controller.dart b/lib/features/login/presentation/login_controller.dart index ec41a9e7a..7de167224 100644 --- a/lib/features/login/presentation/login_controller.dart +++ b/lib/features/login/presentation/login_controller.dart @@ -188,7 +188,7 @@ class LoginController extends ReloadableController { } else if (success is CheckOIDCIsAvailableSuccess) { getOIDCConfiguration(success.oidcResponse); } else if (success is GetOIDCConfigurationSuccess) { - _getOIDCConfigurationSuccess(success); + _getOIDCConfigurationSuccess(success.oidcConfiguration); } else if (success is GetTokenOIDCSuccess) { _getTokenOIDCSuccess(success); } else if (success is AuthenticationUserSuccess) { @@ -413,16 +413,23 @@ class LoginController extends ReloadableController { } void getOIDCConfiguration(OIDCResponse oidcResponse) { - consumeState(_getOIDCConfigurationInteractor.execute(oidcResponse)); + final loginHint = PlatformInfo.isMobile ? _username?.value : null; + log('$runtimeType::getOIDCConfiguration:loginHint = $loginHint'); + consumeState( + _getOIDCConfigurationInteractor.execute( + oidcResponse, + loginHint: loginHint, + ), + ); } - void _getOIDCConfigurationSuccess(GetOIDCConfigurationSuccess success) { + void _getOIDCConfigurationSuccess(OIDCConfiguration config) { if (PlatformInfo.isWeb) { - _authenticateOidcOnBrowserAction(success.oidcConfiguration); - } else if (success.oidcConfiguration.authority == AppConfig.saasRegistrationUrl) { - _getTokenOIDCOnSaaSPlatform(success.oidcConfiguration); + _authenticateOidcOnBrowserAction(config); + } else if (config.authority == AppConfig.saasRegistrationUrl) { + _getTokenOIDCOnSaaSPlatform(config); } else { - _getTokenOIDCAction(success.oidcConfiguration); + _getTokenOIDCAction(config); } } diff --git a/model/lib/oidc/oidc_configuration.dart b/model/lib/oidc/oidc_configuration.dart index 80cc6302f..455e61f7d 100644 --- a/model/lib/oidc/oidc_configuration.dart +++ b/model/lib/oidc/oidc_configuration.dart @@ -8,12 +8,14 @@ class OIDCConfiguration with EquatableMixin { final String clientId; final List scopes; final bool isTWP; + final String? loginHint; OIDCConfiguration({ required this.authority, required this.clientId, required this.scopes, this.isTWP = false, + this.loginHint, }); String get discoveryUrl { @@ -30,5 +32,23 @@ class OIDCConfiguration with EquatableMixin { clientId, scopes, isTWP, + loginHint, ]; } + +extension OIDCConfigurationExtension on OIDCConfiguration { + OIDCConfiguration copyWidth({ + String? authority, + String? clientId, + List? scopes, + bool? isTWP, + String? loginHint, + }) => + OIDCConfiguration( + authority: authority ?? this.authority, + clientId: clientId ?? this.clientId, + scopes: scopes ?? this.scopes, + isTWP: isTWP ?? this.isTWP, + loginHint: loginHint ?? this.loginHint, + ); +}