From e8a168084b3813a96dba245a3d96559cb3d0d5c8 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 5cfc0cf59c..434a8a43ae 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 c0ab801941..5f951c54b7 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 75ef41ac29..34165b8972 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 a70eb3aa9d..33c7d6b45f 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 4671d7af9f..fb56fc9419 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 e0a66d5e4e..d9a2e4f789 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 3821fea0ed..0f83295bdd 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 2574bba4f9..5f58931345 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 92698b7675..9c4274c1e8 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 09ce13418f..e309075a80 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 ec41a9e7a4..7de1672245 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 80cc6302f0..455e61f7db 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, + ); +}