diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 07027d3fc..94c635f1e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -98,6 +98,18 @@ + + + + + + + + + logoutOidc(TokenId tokenId, OIDCConfiguration config, OIDCDiscoveryResponse oidcRescovery); + Future signInTwakeWorkplace(OIDCConfiguration oidcConfiguration); + + Future signUpTwakeWorkplace(OIDCConfiguration oidcConfiguration); + factory AuthenticationClientBase({String? tag}) => getAuthenticationClientImplementation(tag: tag); } \ No newline at end of file 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 f22968b8a..d0c25b1e4 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 @@ -1,6 +1,7 @@ import 'package:core/utils/app_logger.dart'; import 'package:flutter_appauth/flutter_appauth.dart'; +import 'package:flutter_web_auth_2/flutter_web_auth_2.dart'; import 'package:get/get.dart'; import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/response/oidc_discovery_response.dart'; @@ -9,6 +10,7 @@ import 'package:model/oidc/token_oidc.dart'; import 'package:tmail_ui_user/features/login/data/extensions/authentication_token_extension.dart'; import 'package:tmail_ui_user/features/login/data/extensions/token_response_extension.dart'; import 'package:tmail_ui_user/features/login/data/network/authentication_client/authentication_client_base.dart'; +import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.dart'; import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart'; import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuration_extensions.dart'; @@ -96,6 +98,32 @@ class AuthenticationClientMobile implements AuthenticationClientBase { Future getAuthenticationInfo() { return Future.value(''); } + + @override + Future signInTwakeWorkplace(OIDCConfiguration oidcConfiguration) async { + final uri = await FlutterWebAuth2.authenticate( + url: oidcConfiguration.singInUrl, + callbackUrlScheme: OIDCConstant.twakeWorkplaceUrlScheme, + options: const FlutterWebAuth2Options( + intentFlags: ephemeralIntentFlags, + ), + ); + log('AuthenticationClientMobile::signInTwakeWorkplace():Uri = $uri'); + return TokenOIDC.fromUri(uri); + } + + @override + Future signUpTwakeWorkplace(OIDCConfiguration oidcConfiguration) async { + final uri = await FlutterWebAuth2.authenticate( + url: oidcConfiguration.signUpUrl, + callbackUrlScheme: OIDCConstant.twakeWorkplaceUrlScheme, + options: const FlutterWebAuth2Options( + intentFlags: ephemeralIntentFlags, + ), + ); + log('AuthenticationClientMobile::signUpTwakeWorkplace():Uri = $uri'); + return TokenOIDC.fromUri(uri); + } } AuthenticationClientBase getAuthenticationClientImplementation({String? tag}) => 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 463f79a11..1e3146a00 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 @@ -106,6 +106,16 @@ class AuthenticationClientWeb implements AuthenticationClientBase { throw CanNotAuthenticationInfoOnWeb(); } } + + @override + Future signUpTwakeWorkplace(OIDCConfiguration oidcConfiguration) { + throw UnimplementedError(); + } + + @override + Future signInTwakeWorkplace(OIDCConfiguration oidcConfiguration) { + throw UnimplementedError(); + } } AuthenticationClientBase getAuthenticationClientImplementation({String? tag}) => diff --git a/lib/features/login/data/network/config/oidc_constant.dart b/lib/features/login/data/network/config/oidc_constant.dart index 27793be72..2e4790c58 100644 --- a/lib/features/login/data/network/config/oidc_constant.dart +++ b/lib/features/login/data/network/config/oidc_constant.dart @@ -1,4 +1,4 @@ -import 'package:core/core.dart'; +import 'package:core/utils/platform_info.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; class OIDCConstant { @@ -6,10 +6,11 @@ class OIDCConstant { static List get oidcScope => ['openid', 'profile', 'email', 'offline_access']; static const keyAuthorityOidc = 'KEY_AUTHORITY_OIDC'; static const authResponseKey = "auth_info"; + static const String twakeWorkplaceUrlScheme = 'twakemail.mobile'; + static const String twakeWorkplaceRedirectUrl = '$twakeWorkplaceUrlScheme://redirect'; + static const String appParameter = 'tmail'; + static const String postRegisteredRedirectUrlPathParams = 'post_registered_redirect_url'; + static const String postLoginRedirectUrlPathParams = 'post_login_redirect_url'; static String get clientId => PlatformInfo.isWeb ? AppConfig.webOidcClientId : mobileOidcClientId; - - static String get saasClientId => PlatformInfo.isMobile - ? mobileOidcClientId - : ''; } \ No newline at end of file diff --git a/lib/features/login/domain/extensions/oidc_configuration_extensions.dart b/lib/features/login/domain/extensions/oidc_configuration_extensions.dart index fef03e30f..3a0312f7c 100644 --- a/lib/features/login/domain/extensions/oidc_configuration_extensions.dart +++ b/lib/features/login/domain/extensions/oidc_configuration_extensions.dart @@ -1,6 +1,10 @@ +import 'package:core/data/model/query/query_parameter.dart'; +import 'package:core/data/network/config/service_path.dart'; import 'package:core/utils/platform_info.dart'; import 'package:model/oidc/oidc_configuration.dart'; +import 'package:tmail_ui_user/features/login/data/extensions/service_path_extension.dart'; +import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; extension OidcConfigurationExtensions on OIDCConfiguration { @@ -28,4 +32,24 @@ extension OidcConfigurationExtensions on OIDCConfiguration { return redirectOidcMobile; } } + + String get singInUrl => ServicePath(authority) + .withQueryParameters([ + StringQueryParameter( + OIDCConstant.postLoginRedirectUrlPathParams, + OIDCConstant.twakeWorkplaceRedirectUrl, + ), + StringQueryParameter('app', OIDCConstant.appParameter), + ]) + .generateEndpointPath(); + + String get signUpUrl => ServicePath(authority) + .withQueryParameters([ + StringQueryParameter( + OIDCConstant.postRegisteredRedirectUrlPathParams, + OIDCConstant.twakeWorkplaceRedirectUrl, + ), + StringQueryParameter('app', OIDCConstant.appParameter), + ]) + .generateEndpointPath(); } \ No newline at end of file diff --git a/lib/features/starting_page/data/datasource/saas_authentication_datasource.dart b/lib/features/starting_page/data/datasource/saas_authentication_datasource.dart index 2ac9a755f..93b31654b 100644 --- a/lib/features/starting_page/data/datasource/saas_authentication_datasource.dart +++ b/lib/features/starting_page/data/datasource/saas_authentication_datasource.dart @@ -3,5 +3,7 @@ import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/token_oidc.dart'; abstract class SaasAuthenticationDataSource { - Future signIn(OIDCConfiguration oidcConfiguration); + Future signInTwakeWorkplace(OIDCConfiguration oidcConfiguration); + + Future signUpTwakeWorkplace(OIDCConfiguration oidcConfiguration); } \ No newline at end of file diff --git a/lib/features/starting_page/data/datasource_impl/saas_authentication_datasource_impl.dart b/lib/features/starting_page/data/datasource_impl/saas_authentication_datasource_impl.dart index db533c33f..1d59ec59e 100644 --- a/lib/features/starting_page/data/datasource_impl/saas_authentication_datasource_impl.dart +++ b/lib/features/starting_page/data/datasource_impl/saas_authentication_datasource_impl.dart @@ -1,7 +1,6 @@ import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/token_oidc.dart'; import 'package:tmail_ui_user/features/login/data/network/authentication_client/authentication_client_base.dart'; -import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuration_extensions.dart'; import 'package:tmail_ui_user/features/starting_page/data/datasource/saas_authentication_datasource.dart'; import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; @@ -16,14 +15,16 @@ class SaasAuthenticationDataSourceImpl extends SaasAuthenticationDataSource { ); @override - Future signIn(OIDCConfiguration oidcConfiguration) { + Future signInTwakeWorkplace(OIDCConfiguration oidcConfiguration) { return Future.sync(() async { - return await _authenticationClient.getTokenOIDC( - oidcConfiguration.clientId, - oidcConfiguration.redirectUrl, - oidcConfiguration.discoveryUrl, - oidcConfiguration.scopes, - ); + return await _authenticationClient.signInTwakeWorkplace(oidcConfiguration); + }).catchError(_exceptionThrower.throwException); + } + + @override + Future signUpTwakeWorkplace(OIDCConfiguration oidcConfiguration) { + return Future.sync(() async { + return await _authenticationClient.signUpTwakeWorkplace(oidcConfiguration); }).catchError(_exceptionThrower.throwException); } } \ No newline at end of file diff --git a/lib/features/starting_page/data/repository/saas_authentication_repository_impl.dart b/lib/features/starting_page/data/repository/saas_authentication_repository_impl.dart index d334e8979..6ce779c8a 100644 --- a/lib/features/starting_page/data/repository/saas_authentication_repository_impl.dart +++ b/lib/features/starting_page/data/repository/saas_authentication_repository_impl.dart @@ -10,7 +10,12 @@ class SaasAuthenticationRepositoryImpl extends SaasAuthenticationRepository { SaasAuthenticationRepositoryImpl(this._saasAuthenticationDataSource); @override - Future signIn(OIDCConfiguration oidcConfiguration) { - return _saasAuthenticationDataSource.signIn(oidcConfiguration); + Future signInTwakeWorkplace(OIDCConfiguration oidcConfiguration) { + return _saasAuthenticationDataSource.signInTwakeWorkplace(oidcConfiguration); + } + + @override + Future signUpTwakeWorkplace(OIDCConfiguration oidcConfiguration) { + return _saasAuthenticationDataSource.signUpTwakeWorkplace(oidcConfiguration); } } \ No newline at end of file diff --git a/lib/features/starting_page/domain/repository/saas_authentication_repository.dart b/lib/features/starting_page/domain/repository/saas_authentication_repository.dart index d5ef10623..a59a1b0ff 100644 --- a/lib/features/starting_page/domain/repository/saas_authentication_repository.dart +++ b/lib/features/starting_page/domain/repository/saas_authentication_repository.dart @@ -2,5 +2,7 @@ import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/token_oidc.dart'; abstract class SaasAuthenticationRepository { - Future signIn(OIDCConfiguration oidcConfiguration); + Future signInTwakeWorkplace(OIDCConfiguration oidcConfiguration); + + Future signUpTwakeWorkplace(OIDCConfiguration oidcConfiguration); } \ No newline at end of file diff --git a/lib/features/starting_page/domain/state/sign_in_saas_state.dart b/lib/features/starting_page/domain/state/sign_in_twake_workplace_state.dart similarity index 53% rename from lib/features/starting_page/domain/state/sign_in_saas_state.dart rename to lib/features/starting_page/domain/state/sign_in_twake_workplace_state.dart index 67aa56f71..eb8397575 100644 --- a/lib/features/starting_page/domain/state/sign_in_saas_state.dart +++ b/lib/features/starting_page/domain/state/sign_in_twake_workplace_state.dart @@ -3,19 +3,19 @@ import 'package:core/presentation/state/success.dart'; import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/token_oidc.dart'; -class SignInSaasLoading extends LoadingState {} +class SignInTwakeWorkplaceLoading extends LoadingState {} -class SignInSaasSuccess extends Success { +class SignInTwakeWorkplaceSuccess extends Success { final TokenOIDC tokenOIDC; final Uri baseUri; final OIDCConfiguration oidcConfiguration; - SignInSaasSuccess(this.tokenOIDC, this.baseUri, this.oidcConfiguration); + SignInTwakeWorkplaceSuccess(this.tokenOIDC, this.baseUri, this.oidcConfiguration); @override List get props => [tokenOIDC, baseUri, oidcConfiguration]; } -class SignInSaasFailure extends FeatureFailure { - SignInSaasFailure(dynamic exception) : super(exception: exception); +class SignInTwakeWorkplaceFailure extends FeatureFailure { + SignInTwakeWorkplaceFailure(dynamic exception) : super(exception: exception); } \ No newline at end of file diff --git a/lib/features/starting_page/domain/state/sign_up_twake_workplace_state.dart b/lib/features/starting_page/domain/state/sign_up_twake_workplace_state.dart new file mode 100644 index 000000000..d1cce187a --- /dev/null +++ b/lib/features/starting_page/domain/state/sign_up_twake_workplace_state.dart @@ -0,0 +1,21 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:model/oidc/oidc_configuration.dart'; +import 'package:model/oidc/token_oidc.dart'; + +class SignUpTwakeWorkplaceLoading extends LoadingState {} + +class SignUpTwakeWorkplaceSuccess extends Success { + final TokenOIDC tokenOIDC; + final Uri baseUri; + final OIDCConfiguration oidcConfiguration; + + SignUpTwakeWorkplaceSuccess(this.tokenOIDC, this.baseUri, this.oidcConfiguration); + + @override + List get props => [tokenOIDC, baseUri, oidcConfiguration]; +} + +class SignUpTwakeWorkplaceFailure extends FeatureFailure { + SignUpTwakeWorkplaceFailure(dynamic exception) : super(exception: exception); +} \ No newline at end of file diff --git a/lib/features/starting_page/domain/usecase/sign_in_saas_interactor.dart b/lib/features/starting_page/domain/usecase/sign_in_twake_workplace_interactor.dart similarity index 78% rename from lib/features/starting_page/domain/usecase/sign_in_saas_interactor.dart rename to lib/features/starting_page/domain/usecase/sign_in_twake_workplace_interactor.dart index 4e5379b3c..5c47df90d 100644 --- a/lib/features/starting_page/domain/usecase/sign_in_saas_interactor.dart +++ b/lib/features/starting_page/domain/usecase/sign_in_twake_workplace_interactor.dart @@ -5,20 +5,19 @@ import 'package:model/account/authentication_type.dart'; import 'package:model/account/personal_account.dart'; import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/token_oidc.dart'; -import 'package:rich_text_composer/views/commons/logger.dart'; import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart'; import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart'; import 'package:tmail_ui_user/features/starting_page/domain/repository/saas_authentication_repository.dart'; -import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_saas_state.dart'; +import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_twake_workplace_state.dart'; -class SignInSaasInteractor { +class SignInTwakeWorkplaceInteractor { final SaasAuthenticationRepository _saasRepository; final AuthenticationOIDCRepository _authenticationOIDCRepository; final AccountRepository _accountRepository; final CredentialRepository _credentialRepository; - SignInSaasInteractor( + const SignInTwakeWorkplaceInteractor( this._saasRepository, this._authenticationOIDCRepository, this._accountRepository, @@ -30,9 +29,9 @@ class SignInSaasInteractor { required OIDCConfiguration oidcConfiguration }) async* { try { - yield Right(SignInSaasLoading()); + yield Right(SignInTwakeWorkplaceLoading()); - final tokenOIDC = await _saasRepository.signIn(oidcConfiguration); + final tokenOIDC = await _saasRepository.signInTwakeWorkplace(oidcConfiguration); await Future.wait([ _credentialRepository.saveBaseUrl(baseUri), @@ -48,10 +47,9 @@ class SignInSaasInteractor { ) ); - yield Right(SignInSaasSuccess(tokenOIDC, baseUri, oidcConfiguration)); + yield Right(SignInTwakeWorkplaceSuccess(tokenOIDC, baseUri, oidcConfiguration)); } catch (e) { - logError('SignInSaasInteractor::execute():Exception = $e'); - yield Left(SignInSaasFailure(e)); + yield Left(SignInTwakeWorkplaceFailure(e)); } } } \ No newline at end of file diff --git a/lib/features/starting_page/domain/usecase/sign_up_twake_workplace_interactor.dart b/lib/features/starting_page/domain/usecase/sign_up_twake_workplace_interactor.dart new file mode 100644 index 000000000..1460fdf59 --- /dev/null +++ b/lib/features/starting_page/domain/usecase/sign_up_twake_workplace_interactor.dart @@ -0,0 +1,55 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:model/account/authentication_type.dart'; +import 'package:model/account/personal_account.dart'; +import 'package:model/oidc/oidc_configuration.dart'; +import 'package:model/oidc/token_oidc.dart'; +import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart'; +import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart'; +import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart'; +import 'package:tmail_ui_user/features/starting_page/domain/repository/saas_authentication_repository.dart'; +import 'package:tmail_ui_user/features/starting_page/domain/state/sign_up_twake_workplace_state.dart'; + +class SignUpTwakeWorkplaceInteractor { + final SaasAuthenticationRepository _saasRepository; + final AuthenticationOIDCRepository _authenticationOIDCRepository; + final AccountRepository _accountRepository; + final CredentialRepository _credentialRepository; + + const SignUpTwakeWorkplaceInteractor( + this._saasRepository, + this._authenticationOIDCRepository, + this._accountRepository, + this._credentialRepository + ); + + Stream> execute({ + required Uri baseUri, + required OIDCConfiguration oidcConfiguration + }) async* { + try { + yield Right(SignUpTwakeWorkplaceLoading()); + + final tokenOIDC = await _saasRepository.signUpTwakeWorkplace(oidcConfiguration); + + await Future.wait([ + _credentialRepository.saveBaseUrl(baseUri), + _authenticationOIDCRepository.persistTokenOIDC(tokenOIDC), + _authenticationOIDCRepository.persistAuthorityOidc(oidcConfiguration.authority), + ]); + + await _accountRepository.setCurrentAccount( + PersonalAccount( + tokenOIDC.tokenIdHash, + AuthenticationType.oidc, + isSelected: true + ) + ); + + yield Right(SignUpTwakeWorkplaceSuccess(tokenOIDC, baseUri, oidcConfiguration)); + } catch (e) { + yield Left(SignUpTwakeWorkplaceFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/starting_page/presentation/twake_welcome/twake_welcome_bindings.dart b/lib/features/starting_page/presentation/twake_welcome/twake_welcome_bindings.dart index a33b32292..e8f4955f4 100644 --- a/lib/features/starting_page/presentation/twake_welcome/twake_welcome_bindings.dart +++ b/lib/features/starting_page/presentation/twake_welcome/twake_welcome_bindings.dart @@ -8,7 +8,8 @@ import 'package:tmail_ui_user/features/starting_page/data/datasource/saas_authen import 'package:tmail_ui_user/features/starting_page/data/datasource_impl/saas_authentication_datasource_impl.dart'; import 'package:tmail_ui_user/features/starting_page/data/repository/saas_authentication_repository_impl.dart'; import 'package:tmail_ui_user/features/starting_page/domain/repository/saas_authentication_repository.dart'; -import 'package:tmail_ui_user/features/starting_page/domain/usecase/sign_in_saas_interactor.dart'; +import 'package:tmail_ui_user/features/starting_page/domain/usecase/sign_in_twake_workplace_interactor.dart'; +import 'package:tmail_ui_user/features/starting_page/domain/usecase/sign_up_twake_workplace_interactor.dart'; import 'package:tmail_ui_user/features/starting_page/presentation/twake_welcome/twake_welcome_controller.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart'; @@ -17,13 +18,20 @@ class TwakeWelcomeBindings extends BaseBindings { @override void bindingsController() { Get.lazyPut(() => TwakeWelcomeController( - Get.find(), + Get.find(), + Get.find(), )); } @override void bindingsInteractor() { - Get.lazyPut(() => SignInSaasInteractor( + Get.lazyPut(() => SignInTwakeWorkplaceInteractor( + Get.find(), + Get.find(), + Get.find(), + Get.find(), + )); + Get.lazyPut(() => SignUpTwakeWorkplaceInteractor( Get.find(), Get.find(), Get.find(), diff --git a/lib/features/starting_page/presentation/twake_welcome/twake_welcome_controller.dart b/lib/features/starting_page/presentation/twake_welcome/twake_welcome_controller.dart index 0a0d54add..83cc4bfbc 100644 --- a/lib/features/starting_page/presentation/twake_welcome/twake_welcome_controller.dart +++ b/lib/features/starting_page/presentation/twake_welcome/twake_welcome_controller.dart @@ -4,6 +4,7 @@ import 'package:dartz/dartz.dart'; import 'package:flutter/cupertino.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:model/oidc/oidc_configuration.dart'; +import 'package:model/oidc/token_oidc.dart'; import 'package:tip_dialog/tip_dialog.dart'; import 'package:tmail_ui_user/features/base/reloadable/reloadable_controller.dart'; import 'package:tmail_ui_user/features/home/domain/state/get_session_state.dart'; @@ -11,8 +12,10 @@ import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.d import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart'; import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart'; import 'package:tmail_ui_user/features/login/presentation/model/login_arguments.dart'; -import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_saas_state.dart'; -import 'package:tmail_ui_user/features/starting_page/domain/usecase/sign_in_saas_interactor.dart'; +import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_twake_workplace_state.dart'; +import 'package:tmail_ui_user/features/starting_page/domain/state/sign_up_twake_workplace_state.dart'; +import 'package:tmail_ui_user/features/starting_page/domain/usecase/sign_in_twake_workplace_interactor.dart'; +import 'package:tmail_ui_user/features/starting_page/domain/usecase/sign_up_twake_workplace_interactor.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; @@ -22,9 +25,13 @@ import 'package:tmail_ui_user/main/utils/app_utils.dart'; class TwakeWelcomeController extends ReloadableController { - final SignInSaasInteractor _signInSaasInteractor; + final SignInTwakeWorkplaceInteractor _signInTwakeWorkplaceInteractor; + final SignUpTwakeWorkplaceInteractor _signUpTwakeWorkplaceInteractor; - TwakeWelcomeController(this._signInSaasInteractor); + TwakeWelcomeController( + this._signInTwakeWorkplaceInteractor, + this._signUpTwakeWorkplaceInteractor, + ); void handleUseCompanyServer() { popAndPush( @@ -40,22 +47,47 @@ class TwakeWelcomeController extends ReloadableController { TipDialogHelper.loading(AppLocalizations.of(context).loadingPleaseWait); if (AppConfig.saasServerUrl.isEmpty) { - consumeState(Stream.value(Left(SignInSaasFailure(CanNotFoundSaasServerUrl())))); + consumeState(Stream.value(Left(SignInTwakeWorkplaceFailure(CanNotFoundSaasServerUrl())))); return; } final baseUri = Uri.tryParse(AppConfig.saasServerUrl); if (baseUri == null) { - consumeState(Stream.value(Left(SignInSaasFailure(SaasServerUriIsNull())))); + consumeState(Stream.value(Left(SignInTwakeWorkplaceFailure(SaasServerUriIsNull())))); return; } - consumeState(_signInSaasInteractor.execute( + consumeState(_signInTwakeWorkplaceInteractor.execute( baseUri: baseUri, oidcConfiguration: OIDCConfiguration( authority: AppConfig.registrationUrl, - clientId: OIDCConstant.saasClientId, + clientId: OIDCConstant.clientId, + scopes: AppConfig.oidcScopes + ) + )); + } + + void onSignUpTwakeWorkplace(BuildContext context) { + TipDialogHelper.loading(AppLocalizations.of(context).loadingPleaseWait); + + if (AppConfig.saasServerUrl.isEmpty) { + consumeState(Stream.value(Left(SignUpTwakeWorkplaceFailure(CanNotFoundSaasServerUrl())))); + return; + } + + final baseUri = Uri.tryParse(AppConfig.saasServerUrl); + + if (baseUri == null) { + consumeState(Stream.value(Left(SignUpTwakeWorkplaceFailure(SaasServerUriIsNull())))); + return; + } + + consumeState(_signUpTwakeWorkplaceInteractor.execute( + baseUri: baseUri, + oidcConfiguration: OIDCConfiguration( + authority: AppConfig.registrationUrl, + clientId: OIDCConstant.clientId, scopes: AppConfig.oidcScopes ) )); @@ -63,8 +95,18 @@ class TwakeWelcomeController extends ReloadableController { @override void handleSuccessViewState(Success success) { - if (success is SignInSaasSuccess) { - _handleSignInSaasSuccess(success); + if (success is SignInTwakeWorkplaceSuccess) { + _synchronizeTokenAndGetSession( + baseUri: success.baseUri, + tokenOIDC: success.tokenOIDC, + oidcConfiguration: success.oidcConfiguration, + ); + } else if (success is SignUpTwakeWorkplaceSuccess) { + _synchronizeTokenAndGetSession( + baseUri: success.baseUri, + tokenOIDC: success.tokenOIDC, + oidcConfiguration: success.oidcConfiguration, + ); } else { super.handleSuccessViewState(success); } @@ -72,8 +114,10 @@ class TwakeWelcomeController extends ReloadableController { @override void handleFailureViewState(Failure failure) { - if (failure is SignInSaasFailure) { - _handleSignInSaasFailure(failure); + if (failure is SignInTwakeWorkplaceFailure) { + _handleSignInTwakeWorkplaceFailure(failure); + } else if (failure is SignUpTwakeWorkplaceFailure) { + _handleSignUpTwakeWorkplaceFailure(failure); } else { super.handleFailureViewState(failure); } @@ -95,19 +139,30 @@ class TwakeWelcomeController extends ReloadableController { toastManager.showMessageFailure(failure); } - void _handleSignInSaasSuccess(SignInSaasSuccess success) { - dynamicUrlInterceptors.setJmapUrl(success.baseUri.toString()); - dynamicUrlInterceptors.changeBaseUrl(success.baseUri.toString()); + void _synchronizeTokenAndGetSession({ + required Uri baseUri, + required TokenOIDC tokenOIDC, + required OIDCConfiguration oidcConfiguration, + }) { + dynamicUrlInterceptors.setJmapUrl(baseUri.toString()); + dynamicUrlInterceptors.changeBaseUrl(baseUri.toString()); authorizationInterceptors.setTokenAndAuthorityOidc( - newToken: success.tokenOIDC, - newConfig: success.oidcConfiguration); + newToken: tokenOIDC, + newConfig: oidcConfiguration); authorizationIsolateInterceptors.setTokenAndAuthorityOidc( - newToken: success.tokenOIDC, - newConfig: success.oidcConfiguration); + newToken: tokenOIDC, + newConfig: oidcConfiguration); + getSessionAction(); } - void _handleSignInSaasFailure(SignInSaasFailure failure) { + void _handleSignInTwakeWorkplaceFailure(SignInTwakeWorkplaceFailure failure) { + TipDialogHelper.dismiss(); + + toastManager.showMessageFailure(failure); + } + + void _handleSignUpTwakeWorkplaceFailure(SignUpTwakeWorkplaceFailure failure) { TipDialogHelper.dismiss(); toastManager.showMessageFailure(failure); diff --git a/lib/features/starting_page/presentation/twake_welcome/twake_welcome_view.dart b/lib/features/starting_page/presentation/twake_welcome/twake_welcome_view.dart index f7c1f0297..33266720f 100644 --- a/lib/features/starting_page/presentation/twake_welcome/twake_welcome_view.dart +++ b/lib/features/starting_page/presentation/twake_welcome/twake_welcome_view.dart @@ -38,6 +38,7 @@ class TwakeWelcomeView extends GetWidget { ? AppLocalizations.of(context).signIn.capitalizeFirst ?? '' : null, onSignInOnTap: () => controller.onClickSignIn(context), + onCreateTwakeIdOnTap: () => controller.onSignUpTwakeWorkplace(context), createTwakeIdTitle: AppConfig.isSaasPlatForm ? AppLocalizations.of(context).createTwakeId : null, diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index d170139a1..d4d794eb1 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-10-29T16:13:26.917296", + "@@last_modified": "2024-10-31T13:18:32.336494", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -4042,7 +4042,7 @@ "placeholders_order": [], "placeholders": {} }, - "descriptionWelcomeTo": "The new Open Source standard for secure professional e-mail", + "descriptionWelcomeTo": "The new Open Source standard for\n secure professional e-mail", "@descriptionWelcomeTo": { "type": "text", "placeholders_order": [], @@ -4065,5 +4065,11 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "createTwakeIdFailed": "Create Twake Id failed. Please check again.", + "@createTwakeIdFailed": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 4ba963de5..9dac836f6 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4266,4 +4266,11 @@ class AppLocalizations { name: 'sigInSaasFailed', ); } + + String get createTwakeIdFailed { + return Intl.message( + 'Create Twake Id failed. Please check again.', + name: 'createTwakeIdFailed', + ); + } } diff --git a/lib/main/utils/toast_manager.dart b/lib/main/utils/toast_manager.dart index d6e497005..595b20f6a 100644 --- a/lib/main/utils/toast_manager.dart +++ b/lib/main/utils/toast_manager.dart @@ -2,13 +2,15 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/utils/app_toast.dart'; import 'package:core/utils/app_logger.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:model/email/email_action_type.dart'; import 'package:tmail_ui_user/features/email/domain/model/move_action.dart'; import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; import 'package:tmail_ui_user/features/home/domain/state/get_session_state.dart'; import 'package:tmail_ui_user/features/login/data/network/oidc_error.dart'; import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart'; -import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_saas_state.dart'; +import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_twake_workplace_state.dart'; +import 'package:tmail_ui_user/features/starting_page/domain/state/sign_up_twake_workplace_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception.dart'; @@ -65,8 +67,20 @@ class ToastManager { && failure.emailActionType == EmailActionType.moveToSpam && failure.moveAction == MoveAction.moving) { message = AppLocalizations.of(currentContext!).markAsSpamFailed; - } else if (failure is SignInSaasFailure) { - message = AppLocalizations.of(currentContext!).sigInSaasFailed; + } else if (failure is SignInTwakeWorkplaceFailure) { + final exception = failure.exception; + if (exception is PlatformException && exception.message?.isNotEmpty == true) { + message = exception.message; + } else { + message = AppLocalizations.of(currentContext!).sigInSaasFailed; + } + } else if (failure is SignUpTwakeWorkplaceFailure) { + final exception = failure.exception; + if (exception is PlatformException && exception.message?.isNotEmpty == true) { + message = exception.message; + } else { + message = AppLocalizations.of(currentContext!).createTwakeIdFailed; + } } if (message?.isNotEmpty == true) { diff --git a/model/lib/exceptions/token_oidc_exceptions.dart b/model/lib/exceptions/token_oidc_exceptions.dart new file mode 100644 index 000000000..cbef3761e --- /dev/null +++ b/model/lib/exceptions/token_oidc_exceptions.dart @@ -0,0 +1,7 @@ +class AccessTokenIsNullException implements Exception {} + +class RefreshTokenIsNullException implements Exception {} + +class TokenIdIsNullException implements Exception {} + +class ExpiresTimeIsNullException implements Exception {} diff --git a/model/lib/oidc/token_oidc.dart b/model/lib/oidc/token_oidc.dart index 05afa3448..a490a562b 100644 --- a/model/lib/oidc/token_oidc.dart +++ b/model/lib/oidc/token_oidc.dart @@ -2,6 +2,7 @@ import 'package:core/utils/app_logger.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:model/exceptions/token_oidc_exceptions.dart'; import 'package:model/oidc/converter/token_id_converter.dart'; import 'package:model/oidc/token_id.dart'; @@ -27,6 +28,38 @@ class TokenOIDC with EquatableMixin { Map toJson() => _$TokenOIDCToJson(this); + factory TokenOIDC.fromUri(String uriString) { + final uri = Uri.parse(uriString); + final queryParams = uri.queryParameters; + + final accessToken = queryParams['access_token']; + if (accessToken == null || accessToken.isEmpty) { + throw AccessTokenIsNullException(); + } + + final refreshToken = queryParams['refresh_token']; + if (refreshToken == null || refreshToken.isEmpty) { + throw RefreshTokenIsNullException(); + } + + final idToken = queryParams['id_token']; + if (idToken == null || idToken.isEmpty) { + throw TokenIdIsNullException(); + } + + final expiresIn = queryParams['expires_in']; + if (expiresIn == null || expiresIn.isEmpty) { + throw ExpiresTimeIsNullException(); + } + + return TokenOIDC( + accessToken, + TokenId(idToken), + refreshToken, + expiredTime: DateTime.now().add(Duration(seconds: int.parse(expiresIn))), + ); + } + @override List get props => [token, tokenId, expiredTime, refreshToken]; } diff --git a/model/test/oidc/token_oidc_test.dart b/model/test/oidc/token_oidc_test.dart new file mode 100644 index 000000000..8deaf6b5e --- /dev/null +++ b/model/test/oidc/token_oidc_test.dart @@ -0,0 +1,48 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:model/exceptions/token_oidc_exceptions.dart'; +import 'package:model/oidc/token_oidc.dart'; + +void main() { + group('TokenOIDC.fromUrl', () { + test('returns TokenOIDC instance on successful parsing', () { + const uriString = 'https://example.com/callback?access_token=valid_access&refresh_token=valid_refresh&id_token=valid_id&expires_in=3600'; + final token = TokenOIDC.fromUri(uriString); + + expect(token.token, 'valid_access'); + expect(token.refreshToken, 'valid_refresh'); + expect(token.tokenId.uuid, 'valid_id'); + expect(token.expiredTime?.isAfter(DateTime.now()), isTrue); + }); + + test('throws AccessTokenIsNullException if access_token is missing', () { + const uriString = 'https://example.com/callback?refresh_token=valid_refresh&id_token=valid_id&expires_in=3600'; + + expect(() => TokenOIDC.fromUri(uriString), throwsA(isA())); + }); + + test('throws RefreshTokenIsNullException if refresh_token is missing', () { + const uriString = 'https://example.com/callback?access_token=valid_access&id_token=valid_id&expires_in=3600'; + + expect(() => TokenOIDC.fromUri(uriString), throwsA(isA())); + }); + + test('throws TokenIdIsNullException if id_token is missing', () { + const uriString = 'https://example.com/callback?access_token=valid_access&refresh_token=valid_refresh&expires_in=3600'; + + expect(() => TokenOIDC.fromUri(uriString), throwsA(isA())); + }); + + test('throws ExpiresTimeIsNullException if expires_in is missing', () { + const uriString = 'https://example.com/callback?access_token=valid_access&refresh_token=valid_refresh&id_token=valid_id'; + + expect(() => TokenOIDC.fromUri(uriString), throwsA(isA())); + }); + + test('throws FormatException on invalid URL format', () { + const uriString = '::Not valid URI::'; + + expect(() => TokenOIDC.fromUri(uriString), throwsA(isA())); + }); + }); +} + diff --git a/pubspec.lock b/pubspec.lock index a6d0fc0af..3d79362eb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1050,6 +1050,22 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.2" + flutter_web_auth_2: + dependency: "direct main" + description: + name: flutter_web_auth_2 + sha256: "3ea3a0cc539ca74319f4f2f7484f62742fe5b2ff9a0fca37575426d6e6f07901" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + flutter_web_auth_2_platform_interface: + dependency: transitive + description: + name: flutter_web_auth_2_platform_interface + sha256: e8669e262005a8354389ba2971f0fc1c36188481234ff50d013aaf993f30f739 + url: "https://pub.dev" + source: hosted + version: "3.1.0" flutter_web_plugins: dependency: transitive description: flutter @@ -2224,6 +2240,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.4" + window_to_front: + dependency: transitive + description: + name: window_to_front + sha256: "7aef379752b7190c10479e12b5fd7c0b9d92adc96817d9e96c59937929512aee" + url: "https://pub.dev" + source: hosted + version: "0.0.3" worker_manager: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index f85093add..2a552b792 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -257,6 +257,8 @@ dependencies: tip_dialog: 4.0.0 + flutter_web_auth_2: 3.1.1 + dev_dependencies: flutter_test: sdk: flutter