TF-2387 Handle sign up twake workplace

This commit is contained in:
dab246
2024-10-31 14:11:15 +07:00
committed by Dat H. Pham
parent 2de503d990
commit e13f86a037
25 changed files with 427 additions and 59 deletions
+12
View File
@@ -98,6 +98,18 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name="com.linusu.flutter_web_auth_2.CallbackActivity"
android:exported="true">
<intent-filter android:label="flutter_web_auth_2">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="twakemail.mobile"
android:host="redirect" />
</intent-filter>
</activity>
<provider android:authorities="${applicationId}.firebaseinitprovider" <provider android:authorities="${applicationId}.firebaseinitprovider"
android:name="com.google.firebase.provider.FirebaseInitProvider" android:name="com.google.firebase.provider.FirebaseInitProvider"
android:exported="false" android:exported="false"
@@ -30,5 +30,9 @@ abstract class AuthenticationClientBase {
Future<bool> logoutOidc(TokenId tokenId, OIDCConfiguration config, OIDCDiscoveryResponse oidcRescovery); Future<bool> logoutOidc(TokenId tokenId, OIDCConfiguration config, OIDCDiscoveryResponse oidcRescovery);
Future<TokenOIDC> signInTwakeWorkplace(OIDCConfiguration oidcConfiguration);
Future<TokenOIDC> signUpTwakeWorkplace(OIDCConfiguration oidcConfiguration);
factory AuthenticationClientBase({String? tag}) => getAuthenticationClientImplementation(tag: tag); factory AuthenticationClientBase({String? tag}) => getAuthenticationClientImplementation(tag: tag);
} }
@@ -1,6 +1,7 @@
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:flutter_appauth/flutter_appauth.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:get/get.dart';
import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/response/oidc_discovery_response.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/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/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/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/exceptions/authentication_exception.dart';
import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuration_extensions.dart'; import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuration_extensions.dart';
@@ -96,6 +98,32 @@ class AuthenticationClientMobile implements AuthenticationClientBase {
Future<String> getAuthenticationInfo() { Future<String> getAuthenticationInfo() {
return Future.value(''); return Future.value('');
} }
@override
Future<TokenOIDC> 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<TokenOIDC> 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}) => AuthenticationClientBase getAuthenticationClientImplementation({String? tag}) =>
@@ -106,6 +106,16 @@ class AuthenticationClientWeb implements AuthenticationClientBase {
throw CanNotAuthenticationInfoOnWeb(); throw CanNotAuthenticationInfoOnWeb();
} }
} }
@override
Future<TokenOIDC> signUpTwakeWorkplace(OIDCConfiguration oidcConfiguration) {
throw UnimplementedError();
}
@override
Future<TokenOIDC> signInTwakeWorkplace(OIDCConfiguration oidcConfiguration) {
throw UnimplementedError();
}
} }
AuthenticationClientBase getAuthenticationClientImplementation({String? tag}) => AuthenticationClientBase getAuthenticationClientImplementation({String? tag}) =>
@@ -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'; import 'package:tmail_ui_user/main/utils/app_config.dart';
class OIDCConstant { class OIDCConstant {
@@ -6,10 +6,11 @@ class OIDCConstant {
static List<String> get oidcScope => ['openid', 'profile', 'email', 'offline_access']; static List<String> get oidcScope => ['openid', 'profile', 'email', 'offline_access'];
static const keyAuthorityOidc = 'KEY_AUTHORITY_OIDC'; static const keyAuthorityOidc = 'KEY_AUTHORITY_OIDC';
static const authResponseKey = "auth_info"; 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 clientId => PlatformInfo.isWeb ? AppConfig.webOidcClientId : mobileOidcClientId;
static String get saasClientId => PlatformInfo.isMobile
? mobileOidcClientId
: '';
} }
@@ -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:core/utils/platform_info.dart';
import 'package:model/oidc/oidc_configuration.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'; import 'package:tmail_ui_user/main/utils/app_config.dart';
extension OidcConfigurationExtensions on OIDCConfiguration { extension OidcConfigurationExtensions on OIDCConfiguration {
@@ -28,4 +32,24 @@ extension OidcConfigurationExtensions on OIDCConfiguration {
return redirectOidcMobile; 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();
} }
@@ -3,5 +3,7 @@ import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/token_oidc.dart'; import 'package:model/oidc/token_oidc.dart';
abstract class SaasAuthenticationDataSource { abstract class SaasAuthenticationDataSource {
Future<TokenOIDC> signIn(OIDCConfiguration oidcConfiguration); Future<TokenOIDC> signInTwakeWorkplace(OIDCConfiguration oidcConfiguration);
Future<TokenOIDC> signUpTwakeWorkplace(OIDCConfiguration oidcConfiguration);
} }
@@ -1,7 +1,6 @@
import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/token_oidc.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/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/features/starting_page/data/datasource/saas_authentication_datasource.dart';
import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart';
@@ -16,14 +15,16 @@ class SaasAuthenticationDataSourceImpl extends SaasAuthenticationDataSource {
); );
@override @override
Future<TokenOIDC> signIn(OIDCConfiguration oidcConfiguration) { Future<TokenOIDC> signInTwakeWorkplace(OIDCConfiguration oidcConfiguration) {
return Future.sync(() async { return Future.sync(() async {
return await _authenticationClient.getTokenOIDC( return await _authenticationClient.signInTwakeWorkplace(oidcConfiguration);
oidcConfiguration.clientId, }).catchError(_exceptionThrower.throwException);
oidcConfiguration.redirectUrl, }
oidcConfiguration.discoveryUrl,
oidcConfiguration.scopes, @override
); Future<TokenOIDC> signUpTwakeWorkplace(OIDCConfiguration oidcConfiguration) {
return Future.sync(() async {
return await _authenticationClient.signUpTwakeWorkplace(oidcConfiguration);
}).catchError(_exceptionThrower.throwException); }).catchError(_exceptionThrower.throwException);
} }
} }
@@ -10,7 +10,12 @@ class SaasAuthenticationRepositoryImpl extends SaasAuthenticationRepository {
SaasAuthenticationRepositoryImpl(this._saasAuthenticationDataSource); SaasAuthenticationRepositoryImpl(this._saasAuthenticationDataSource);
@override @override
Future<TokenOIDC> signIn(OIDCConfiguration oidcConfiguration) { Future<TokenOIDC> signInTwakeWorkplace(OIDCConfiguration oidcConfiguration) {
return _saasAuthenticationDataSource.signIn(oidcConfiguration); return _saasAuthenticationDataSource.signInTwakeWorkplace(oidcConfiguration);
}
@override
Future<TokenOIDC> signUpTwakeWorkplace(OIDCConfiguration oidcConfiguration) {
return _saasAuthenticationDataSource.signUpTwakeWorkplace(oidcConfiguration);
} }
} }
@@ -2,5 +2,7 @@ import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/token_oidc.dart'; import 'package:model/oidc/token_oidc.dart';
abstract class SaasAuthenticationRepository { abstract class SaasAuthenticationRepository {
Future<TokenOIDC> signIn(OIDCConfiguration oidcConfiguration); Future<TokenOIDC> signInTwakeWorkplace(OIDCConfiguration oidcConfiguration);
Future<TokenOIDC> signUpTwakeWorkplace(OIDCConfiguration oidcConfiguration);
} }
@@ -3,19 +3,19 @@ import 'package:core/presentation/state/success.dart';
import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/token_oidc.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 TokenOIDC tokenOIDC;
final Uri baseUri; final Uri baseUri;
final OIDCConfiguration oidcConfiguration; final OIDCConfiguration oidcConfiguration;
SignInSaasSuccess(this.tokenOIDC, this.baseUri, this.oidcConfiguration); SignInTwakeWorkplaceSuccess(this.tokenOIDC, this.baseUri, this.oidcConfiguration);
@override @override
List<Object> get props => [tokenOIDC, baseUri, oidcConfiguration]; List<Object> get props => [tokenOIDC, baseUri, oidcConfiguration];
} }
class SignInSaasFailure extends FeatureFailure { class SignInTwakeWorkplaceFailure extends FeatureFailure {
SignInSaasFailure(dynamic exception) : super(exception: exception); SignInTwakeWorkplaceFailure(dynamic exception) : super(exception: exception);
} }
@@ -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<Object> get props => [tokenOIDC, baseUri, oidcConfiguration];
}
class SignUpTwakeWorkplaceFailure extends FeatureFailure {
SignUpTwakeWorkplaceFailure(dynamic exception) : super(exception: exception);
}
@@ -5,20 +5,19 @@ import 'package:model/account/authentication_type.dart';
import 'package:model/account/personal_account.dart'; import 'package:model/account/personal_account.dart';
import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/token_oidc.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/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/authentication_oidc_repository.dart';
import 'package:tmail_ui_user/features/login/domain/repository/credential_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/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 SaasAuthenticationRepository _saasRepository;
final AuthenticationOIDCRepository _authenticationOIDCRepository; final AuthenticationOIDCRepository _authenticationOIDCRepository;
final AccountRepository _accountRepository; final AccountRepository _accountRepository;
final CredentialRepository _credentialRepository; final CredentialRepository _credentialRepository;
SignInSaasInteractor( const SignInTwakeWorkplaceInteractor(
this._saasRepository, this._saasRepository,
this._authenticationOIDCRepository, this._authenticationOIDCRepository,
this._accountRepository, this._accountRepository,
@@ -30,9 +29,9 @@ class SignInSaasInteractor {
required OIDCConfiguration oidcConfiguration required OIDCConfiguration oidcConfiguration
}) async* { }) async* {
try { try {
yield Right<Failure, Success>(SignInSaasLoading()); yield Right<Failure, Success>(SignInTwakeWorkplaceLoading());
final tokenOIDC = await _saasRepository.signIn(oidcConfiguration); final tokenOIDC = await _saasRepository.signInTwakeWorkplace(oidcConfiguration);
await Future.wait([ await Future.wait([
_credentialRepository.saveBaseUrl(baseUri), _credentialRepository.saveBaseUrl(baseUri),
@@ -48,10 +47,9 @@ class SignInSaasInteractor {
) )
); );
yield Right<Failure, Success>(SignInSaasSuccess(tokenOIDC, baseUri, oidcConfiguration)); yield Right<Failure, Success>(SignInTwakeWorkplaceSuccess(tokenOIDC, baseUri, oidcConfiguration));
} catch (e) { } catch (e) {
logError('SignInSaasInteractor::execute():Exception = $e'); yield Left<Failure, Success>(SignInTwakeWorkplaceFailure(e));
yield Left<Failure, Success>(SignInSaasFailure(e));
} }
} }
} }
@@ -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<Either<Failure, Success>> execute({
required Uri baseUri,
required OIDCConfiguration oidcConfiguration
}) async* {
try {
yield Right<Failure, Success>(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<Failure, Success>(SignUpTwakeWorkplaceSuccess(tokenOIDC, baseUri, oidcConfiguration));
} catch (e) {
yield Left<Failure, Success>(SignUpTwakeWorkplaceFailure(e));
}
}
}
@@ -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/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/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/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/features/starting_page/presentation/twake_welcome/twake_welcome_controller.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart';
@@ -17,13 +18,20 @@ class TwakeWelcomeBindings extends BaseBindings {
@override @override
void bindingsController() { void bindingsController() {
Get.lazyPut(() => TwakeWelcomeController( Get.lazyPut(() => TwakeWelcomeController(
Get.find<SignInSaasInteractor>(), Get.find<SignInTwakeWorkplaceInteractor>(),
Get.find<SignUpTwakeWorkplaceInteractor>(),
)); ));
} }
@override @override
void bindingsInteractor() { void bindingsInteractor() {
Get.lazyPut(() => SignInSaasInteractor( Get.lazyPut(() => SignInTwakeWorkplaceInteractor(
Get.find<SaasAuthenticationRepository>(),
Get.find<AuthenticationOIDCRepository>(),
Get.find<AccountRepository>(),
Get.find<CredentialRepository>(),
));
Get.lazyPut(() => SignUpTwakeWorkplaceInteractor(
Get.find<SaasAuthenticationRepository>(), Get.find<SaasAuthenticationRepository>(),
Get.find<AuthenticationOIDCRepository>(), Get.find<AuthenticationOIDCRepository>(),
Get.find<AccountRepository>(), Get.find<AccountRepository>(),
@@ -4,6 +4,7 @@ import 'package:dartz/dartz.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:model/oidc/oidc_configuration.dart'; import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/token_oidc.dart';
import 'package:tip_dialog/tip_dialog.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/base/reloadable/reloadable_controller.dart';
import 'package:tmail_ui_user/features/home/domain/state/get_session_state.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/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/login_form_type.dart';
import 'package:tmail_ui_user/features/login/presentation/model/login_arguments.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/state/sign_in_twake_workplace_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_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/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.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 { class TwakeWelcomeController extends ReloadableController {
final SignInSaasInteractor _signInSaasInteractor; final SignInTwakeWorkplaceInteractor _signInTwakeWorkplaceInteractor;
final SignUpTwakeWorkplaceInteractor _signUpTwakeWorkplaceInteractor;
TwakeWelcomeController(this._signInSaasInteractor); TwakeWelcomeController(
this._signInTwakeWorkplaceInteractor,
this._signUpTwakeWorkplaceInteractor,
);
void handleUseCompanyServer() { void handleUseCompanyServer() {
popAndPush( popAndPush(
@@ -40,22 +47,47 @@ class TwakeWelcomeController extends ReloadableController {
TipDialogHelper.loading(AppLocalizations.of(context).loadingPleaseWait); TipDialogHelper.loading(AppLocalizations.of(context).loadingPleaseWait);
if (AppConfig.saasServerUrl.isEmpty) { if (AppConfig.saasServerUrl.isEmpty) {
consumeState(Stream.value(Left(SignInSaasFailure(CanNotFoundSaasServerUrl())))); consumeState(Stream.value(Left(SignInTwakeWorkplaceFailure(CanNotFoundSaasServerUrl()))));
return; return;
} }
final baseUri = Uri.tryParse(AppConfig.saasServerUrl); final baseUri = Uri.tryParse(AppConfig.saasServerUrl);
if (baseUri == null) { if (baseUri == null) {
consumeState(Stream.value(Left(SignInSaasFailure(SaasServerUriIsNull())))); consumeState(Stream.value(Left(SignInTwakeWorkplaceFailure(SaasServerUriIsNull()))));
return; return;
} }
consumeState(_signInSaasInteractor.execute( consumeState(_signInTwakeWorkplaceInteractor.execute(
baseUri: baseUri, baseUri: baseUri,
oidcConfiguration: OIDCConfiguration( oidcConfiguration: OIDCConfiguration(
authority: AppConfig.registrationUrl, 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 scopes: AppConfig.oidcScopes
) )
)); ));
@@ -63,8 +95,18 @@ class TwakeWelcomeController extends ReloadableController {
@override @override
void handleSuccessViewState(Success success) { void handleSuccessViewState(Success success) {
if (success is SignInSaasSuccess) { if (success is SignInTwakeWorkplaceSuccess) {
_handleSignInSaasSuccess(success); _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 { } else {
super.handleSuccessViewState(success); super.handleSuccessViewState(success);
} }
@@ -72,8 +114,10 @@ class TwakeWelcomeController extends ReloadableController {
@override @override
void handleFailureViewState(Failure failure) { void handleFailureViewState(Failure failure) {
if (failure is SignInSaasFailure) { if (failure is SignInTwakeWorkplaceFailure) {
_handleSignInSaasFailure(failure); _handleSignInTwakeWorkplaceFailure(failure);
} else if (failure is SignUpTwakeWorkplaceFailure) {
_handleSignUpTwakeWorkplaceFailure(failure);
} else { } else {
super.handleFailureViewState(failure); super.handleFailureViewState(failure);
} }
@@ -95,19 +139,30 @@ class TwakeWelcomeController extends ReloadableController {
toastManager.showMessageFailure(failure); toastManager.showMessageFailure(failure);
} }
void _handleSignInSaasSuccess(SignInSaasSuccess success) { void _synchronizeTokenAndGetSession({
dynamicUrlInterceptors.setJmapUrl(success.baseUri.toString()); required Uri baseUri,
dynamicUrlInterceptors.changeBaseUrl(success.baseUri.toString()); required TokenOIDC tokenOIDC,
required OIDCConfiguration oidcConfiguration,
}) {
dynamicUrlInterceptors.setJmapUrl(baseUri.toString());
dynamicUrlInterceptors.changeBaseUrl(baseUri.toString());
authorizationInterceptors.setTokenAndAuthorityOidc( authorizationInterceptors.setTokenAndAuthorityOidc(
newToken: success.tokenOIDC, newToken: tokenOIDC,
newConfig: success.oidcConfiguration); newConfig: oidcConfiguration);
authorizationIsolateInterceptors.setTokenAndAuthorityOidc( authorizationIsolateInterceptors.setTokenAndAuthorityOidc(
newToken: success.tokenOIDC, newToken: tokenOIDC,
newConfig: success.oidcConfiguration); newConfig: oidcConfiguration);
getSessionAction(); getSessionAction();
} }
void _handleSignInSaasFailure(SignInSaasFailure failure) { void _handleSignInTwakeWorkplaceFailure(SignInTwakeWorkplaceFailure failure) {
TipDialogHelper.dismiss();
toastManager.showMessageFailure(failure);
}
void _handleSignUpTwakeWorkplaceFailure(SignUpTwakeWorkplaceFailure failure) {
TipDialogHelper.dismiss(); TipDialogHelper.dismiss();
toastManager.showMessageFailure(failure); toastManager.showMessageFailure(failure);
@@ -38,6 +38,7 @@ class TwakeWelcomeView extends GetWidget<TwakeWelcomeController> {
? AppLocalizations.of(context).signIn.capitalizeFirst ?? '' ? AppLocalizations.of(context).signIn.capitalizeFirst ?? ''
: null, : null,
onSignInOnTap: () => controller.onClickSignIn(context), onSignInOnTap: () => controller.onClickSignIn(context),
onCreateTwakeIdOnTap: () => controller.onSignUpTwakeWorkplace(context),
createTwakeIdTitle: AppConfig.isSaasPlatForm createTwakeIdTitle: AppConfig.isSaasPlatForm
? AppLocalizations.of(context).createTwakeId ? AppLocalizations.of(context).createTwakeId
: null, : null,
+8 -2
View File
@@ -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": "Initializing data...",
"@initializing_data": { "@initializing_data": {
"type": "text", "type": "text",
@@ -4042,7 +4042,7 @@
"placeholders_order": [], "placeholders_order": [],
"placeholders": {} "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": { "@descriptionWelcomeTo": {
"type": "text", "type": "text",
"placeholders_order": [], "placeholders_order": [],
@@ -4065,5 +4065,11 @@
"type": "text", "type": "text",
"placeholders_order": [], "placeholders_order": [],
"placeholders": {} "placeholders": {}
},
"createTwakeIdFailed": "Create Twake Id failed. Please check again.",
"@createTwakeIdFailed": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
} }
} }
@@ -4266,4 +4266,11 @@ class AppLocalizations {
name: 'sigInSaasFailed', name: 'sigInSaasFailed',
); );
} }
String get createTwakeIdFailed {
return Intl.message(
'Create Twake Id failed. Please check again.',
name: 'createTwakeIdFailed',
);
}
} }
+17 -3
View File
@@ -2,13 +2,15 @@ import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/utils/app_toast.dart'; import 'package:core/presentation/utils/app_toast.dart';
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:model/email/email_action_type.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/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/data/exceptions/session_exceptions.dart';
import 'package:tmail_ui_user/features/home/domain/state/get_session_state.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/data/network/oidc_error.dart';
import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.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/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/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception.dart';
@@ -65,8 +67,20 @@ class ToastManager {
&& failure.emailActionType == EmailActionType.moveToSpam && failure.emailActionType == EmailActionType.moveToSpam
&& failure.moveAction == MoveAction.moving) { && failure.moveAction == MoveAction.moving) {
message = AppLocalizations.of(currentContext!).markAsSpamFailed; message = AppLocalizations.of(currentContext!).markAsSpamFailed;
} else if (failure is SignInSaasFailure) { } else if (failure is SignInTwakeWorkplaceFailure) {
message = AppLocalizations.of(currentContext!).sigInSaasFailed; 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) { if (message?.isNotEmpty == true) {
@@ -0,0 +1,7 @@
class AccessTokenIsNullException implements Exception {}
class RefreshTokenIsNullException implements Exception {}
class TokenIdIsNullException implements Exception {}
class ExpiresTimeIsNullException implements Exception {}
+33
View File
@@ -2,6 +2,7 @@
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.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/converter/token_id_converter.dart';
import 'package:model/oidc/token_id.dart'; import 'package:model/oidc/token_id.dart';
@@ -27,6 +28,38 @@ class TokenOIDC with EquatableMixin {
Map<String, dynamic> toJson() => _$TokenOIDCToJson(this); Map<String, dynamic> 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 @override
List<Object?> get props => [token, tokenId, expiredTime, refreshToken]; List<Object?> get props => [token, tokenId, expiredTime, refreshToken];
} }
+48
View File
@@ -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<AccessTokenIsNullException>()));
});
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<RefreshTokenIsNullException>()));
});
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<TokenIdIsNullException>()));
});
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<ExpiresTimeIsNullException>()));
});
test('throws FormatException on invalid URL format', () {
const uriString = '::Not valid URI::';
expect(() => TokenOIDC.fromUri(uriString), throwsA(isA<FormatException>()));
});
});
}
+24
View File
@@ -1050,6 +1050,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.2" 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: flutter_web_plugins:
dependency: transitive dependency: transitive
description: flutter description: flutter
@@ -2224,6 +2240,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.4" 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: worker_manager:
dependency: "direct main" dependency: "direct main"
description: description:
+2
View File
@@ -257,6 +257,8 @@ dependencies:
tip_dialog: 4.0.0 tip_dialog: 4.0.0
flutter_web_auth_2: 3.1.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter