TF-3278 Handle open app via deep link at MailboxDashboard screen

This commit is contained in:
dab246
2024-11-22 23:30:37 +07:00
committed by Dat H. Pham
parent 8022559bc2
commit 223fdbf1fb
59 changed files with 1058 additions and 429 deletions
@@ -16,9 +16,9 @@ abstract class AuthenticationOIDCDataSource {
Future<TokenOIDC> getStoredTokenOIDC(String tokenIdHash);
Future<void> persistAuthorityOidc(String authority);
Future<void> persistOidcConfiguration(OIDCConfiguration oidcConfiguration);
Future<void> deleteAuthorityOidc();
Future<void> deleteOidcConfiguration();
Future<OIDCConfiguration> getStoredOidcConfiguration();
@@ -74,9 +74,9 @@ class AuthenticationOIDCDataSourceImpl extends AuthenticationOIDCDataSource {
}
@override
Future<void> persistAuthorityOidc(String authority) {
Future<void> persistOidcConfiguration(OIDCConfiguration oidcConfiguration) {
return Future.sync(() async {
return await _oidcConfigurationCacheManager.persistAuthorityOidc(authority);
return await _oidcConfigurationCacheManager.persistOidcConfiguration(oidcConfiguration);
}).catchError(_cacheExceptionThrower.throwException);
}
@@ -106,9 +106,9 @@ class AuthenticationOIDCDataSourceImpl extends AuthenticationOIDCDataSource {
}
@override
Future<void> deleteAuthorityOidc() {
Future<void> deleteOidcConfiguration() {
return Future.sync(() async {
return await _oidcConfigurationCacheManager.deleteAuthorityOidc();
return await _oidcConfigurationCacheManager.deleteOidcConfiguration();
}).catchError(_exceptionThrower.throwException);
}
@@ -0,0 +1,16 @@
import 'package:model/oidc/oidc_configuration.dart';
import 'package:tmail_ui_user/features/login/data/model/oidc_configuration_cache.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 OidcConfigutationCacheExtension on OidcConfigurationCache {
OIDCConfiguration toOIDCConfiguration() {
return OIDCConfiguration(
authority: authority,
isTWP: isTWP,
clientId: OIDCConstant.clientId,
scopes: AppConfig.oidcScopes,
);
}
}
@@ -1,34 +1,54 @@
import 'package:core/utils/app_logger.dart';
import 'package:model/oidc/oidc_configuration.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tmail_ui_user/features/caching/clients/oidc_configuration_cache_client.dart';
import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart';
import 'package:tmail_ui_user/features/login/data/extensions/oidc_configutation_cache_extension.dart';
import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.dart';
import 'package:tmail_ui_user/features/login/data/network/oidc_error.dart';
import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuration_extensions.dart';
import 'package:tmail_ui_user/main/utils/app_config.dart';
class OidcConfigurationCacheManager {
final SharedPreferences _sharedPreferences;
final OidcConfigurationCacheClient _oidcConfigurationCacheClient;
OidcConfigurationCacheManager(this._sharedPreferences);
OidcConfigurationCacheManager(this._sharedPreferences, this._oidcConfigurationCacheClient);
Future<OIDCConfiguration> getOidcConfiguration() async {
final authority = _sharedPreferences.getString(OIDCConstant.keyAuthorityOidc);
if (authority == null || authority.isEmpty) {
throw CanNotFoundOIDCAuthority();
} else {
final oidcConfigurationCache = await _oidcConfigurationCacheClient.getItem(CachingConstants.oidcConfigurationCacheKeyName);
if (oidcConfigurationCache == null) {
final authority = _sharedPreferences.getString(OIDCConstant.keyAuthorityOidc);
if (authority == null || authority.isEmpty) {
throw CanNotFoundOIDCAuthority();
}
return OIDCConfiguration(
authority: authority,
clientId: OIDCConstant.clientId,
scopes: AppConfig.oidcScopes);
authority: authority,
clientId: OIDCConstant.clientId,
scopes: AppConfig.oidcScopes,
);
} else {
return oidcConfigurationCache.toOIDCConfiguration();
}
}
Future<void> persistAuthorityOidc(String authority) async {
log('OidcConfigurationCacheManager::persistAuthorityOidc(): $authority');
await _sharedPreferences.setString(OIDCConstant.keyAuthorityOidc, authority);
Future<void> persistOidcConfiguration(OIDCConfiguration oidcConfiguration) async {
log('OidcConfigurationCacheManager::persistOidcConfiguration(): $oidcConfiguration');
await _oidcConfigurationCacheClient.insertItem(
CachingConstants.oidcConfigurationCacheKeyName,
oidcConfiguration.toOidcConfigurationCache(),
);
}
Future<void> deleteAuthorityOidc() async {
log('OidcConfigurationCacheManager::deleteAuthorityOidc()');
await _sharedPreferences.remove(OIDCConstant.keyAuthorityOidc);
Future<void> deleteOidcConfiguration() async {
log('OidcConfigurationCacheManager::deleteOidcConfiguration()');
await Future.wait([
_oidcConfigurationCacheClient.deleteItem(
CachingConstants.oidcConfigurationCacheKeyName,
),
_sharedPreferences.remove(OIDCConstant.keyAuthorityOidc),
]);
}
}
@@ -0,0 +1,20 @@
import 'package:equatable/equatable.dart';
import 'package:hive/hive.dart';
import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart';
part 'oidc_configuration_cache.g.dart';
@HiveType(typeId: CachingConstants.OIDC_CONFIGURATION_CACHE_ID)
class OidcConfigurationCache extends HiveObject with EquatableMixin {
@HiveField(0)
final String authority;
@HiveField(1)
final bool isTWP;
OidcConfigurationCache(this.authority, this.isTWP);
@override
List<Object?> get props => [authority, isTWP];
}
@@ -4,13 +4,17 @@ import 'package:tmail_ui_user/main/utils/app_config.dart';
class OIDCConstant {
static String get mobileOidcClientId => 'teammail-mobile';
static List<String> get oidcScope => ['openid', 'profile', 'email', 'offline_access'];
static const keyAuthorityOidc = 'KEY_AUTHORITY_OIDC';
static const authResponseKey = "auth_info";
static const String keyAuthorityOidc = 'KEY_AUTHORITY_OIDC';
static const String 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 const String endSessionFailedCode = 'end_session_failed';
static const String redirectOidcMobile = 'teammail.mobile://oauthredirect';
static const String loginRedirectOidcWeb = 'login-callback.html';
static const String logoutRedirectOidcWeb = 'logout-callback.html';
static String get clientId => PlatformInfo.isWeb ? AppConfig.webOidcClientId : mobileOidcClientId;
}
@@ -52,11 +52,14 @@ class OIDCHttpClient {
if (oidcResponse.links.isEmpty) {
throw CanNotFoundOIDCAuthority();
}
log('OIDCHttpClient::getOIDCConfiguration(): href: ${oidcResponse.links[0].href}');
final authority = oidcResponse.links[0].href.toString();
final isTWP = authority == AppConfig.saasRegistrationUrl;
log('OIDCHttpClient::getOIDCConfiguration():authority: $authority');
return OIDCConfiguration(
authority: oidcResponse.links[0].href.toString(),
authority: authority,
clientId: OIDCConstant.clientId,
scopes: AppConfig.oidcScopes
scopes: AppConfig.oidcScopes,
isTWP: isTWP,
);
}
@@ -22,7 +22,6 @@ class AuthenticationOIDCRepositoryImpl extends AuthenticationOIDCRepository {
return _oidcDataSource.getOIDCConfiguration(oidcResponse);
}
@override
Future<OIDCDiscoveryResponse> discoverOIDC(OIDCConfiguration oidcConfiguration) {
return _oidcDataSource.discoverOIDC(oidcConfiguration);
@@ -49,8 +48,8 @@ class AuthenticationOIDCRepositoryImpl extends AuthenticationOIDCRepository {
}
@override
Future<void> persistAuthorityOidc(String authority) {
return _oidcDataSource.persistAuthorityOidc(authority);
Future<void> persistOidcConfiguration(OIDCConfiguration oidcConfiguration) {
return _oidcDataSource.persistOidcConfiguration(oidcConfiguration);
}
@override
@@ -75,8 +74,8 @@ class AuthenticationOIDCRepositoryImpl extends AuthenticationOIDCRepository {
}
@override
Future<void> deleteAuthorityOidc() {
return _oidcDataSource.deleteAuthorityOidc();
Future<void> deleteOidcConfiguration() {
return _oidcDataSource.deleteOidcConfiguration();
}
@override
@@ -0,0 +1 @@
class UserCancelledLogoutOIDCFlowException implements Exception {}
@@ -4,6 +4,7 @@ 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/model/oidc_configuration_cache.dart';
import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.dart';
import 'package:tmail_ui_user/main/utils/app_config.dart';
@@ -12,32 +13,29 @@ extension OidcConfigurationExtensions on OIDCConfiguration {
String get redirectUrl {
if (PlatformInfo.isWeb) {
return AppConfig.domainRedirectUrl.endsWith('/')
? AppConfig.domainRedirectUrl + loginRedirectOidcWeb
: '${AppConfig.domainRedirectUrl}/$loginRedirectOidcWeb';
? AppConfig.domainRedirectUrl + OIDCConstant.loginRedirectOidcWeb
: '${AppConfig.domainRedirectUrl}/${OIDCConstant.loginRedirectOidcWeb}';
} else {
return _isSaasAuthority(authority)
return isTWP
? OIDCConstant.twakeWorkplaceRedirectUrl
: redirectOidcMobile;
: OIDCConstant.redirectOidcMobile;
}
}
String get logoutRedirectUrl {
if (PlatformInfo.isWeb) {
if (AppConfig.domainRedirectUrl.endsWith('/')) {
return AppConfig.domainRedirectUrl + logoutRedirectOidcWeb;
return AppConfig.domainRedirectUrl + OIDCConstant.logoutRedirectOidcWeb;
} else {
return '${AppConfig.domainRedirectUrl}/$logoutRedirectOidcWeb';
return '${AppConfig.domainRedirectUrl}/${OIDCConstant.logoutRedirectOidcWeb}';
}
} else {
return _isSaasAuthority(authority)
return isTWP
? OIDCConstant.twakeWorkplaceRedirectUrl
: redirectOidcMobile;
: OIDCConstant.redirectOidcMobile;
}
}
bool _isSaasAuthority(String authority) =>
authority == AppConfig.saasRegistrationUrl;
String get signInTWPUrl => ServicePath(authority)
.withQueryParameters([
StringQueryParameter(
@@ -57,4 +55,8 @@ extension OidcConfigurationExtensions on OIDCConfiguration {
StringQueryParameter('app', OIDCConstant.appParameter),
])
.generateEndpointPath();
OidcConfigurationCache toOidcConfigurationCache() {
return OidcConfigurationCache(authority, isTWP);
}
}
@@ -16,9 +16,9 @@ abstract class AuthenticationOIDCRepository {
Future<TokenOIDC> getStoredTokenOIDC(String tokenIdHash);
Future<void> persistAuthorityOidc(String authority);
Future<void> persistOidcConfiguration(OIDCConfiguration oidcConfiguration);
Future<void> deleteAuthorityOidc();
Future<void> deleteOidcConfiguration();
Future<OIDCConfiguration> getStoredOidcConfiguration();
@@ -17,7 +17,7 @@ class DeleteAuthorityOidcInteractor {
try {
await Future.wait([
_credentialRepository.removeBaseUrl(),
_authenticationOIDCRepository.deleteAuthorityOidc(),
_authenticationOIDCRepository.deleteOidcConfiguration(),
_authenticationOIDCRepository.deleteTokenOIDC(),
]);
return Right(DeleteAuthorityOidcSuccess());
@@ -15,7 +15,7 @@ class GetOIDCConfigurationInteractor {
try {
yield Right<Failure, Success>(GetOIDCConfigurationLoading());
final oidcConfiguration = await _oidcRepository.getOIDCConfiguration(oidcResponse);
await _oidcRepository.persistAuthorityOidc(oidcConfiguration.authority);
await _oidcRepository.persistOidcConfiguration(oidcConfiguration);
yield Right<Failure, Success>(GetOIDCConfigurationSuccess(oidcConfiguration));
} catch (e) {
log('GetOIDCConfigurationInteractor::execute(): ERROR: $e');
@@ -35,7 +35,7 @@ class GetTokenOIDCInteractor {
await Future.wait([
_credentialRepository.saveBaseUrl(baseUrl),
authenticationOIDCRepository.persistTokenOIDC(tokenOIDC),
authenticationOIDCRepository.persistAuthorityOidc(config.authority),
authenticationOIDCRepository.persistOidcConfiguration(config),
]);
await _accountRepository.setCurrentAccount(
@@ -58,6 +58,7 @@ import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_twake_
import 'package:tmail_ui_user/features/starting_page/domain/usecase/sign_in_twake_workplace_interactor.dart';
import 'package:tmail_ui_user/main/deep_links/deep_link_data.dart';
import 'package:tmail_ui_user/main/deep_links/deep_links_manager.dart';
import 'package:tmail_ui_user/main/deep_links/open_app_deep_link_data.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';
@@ -163,6 +164,7 @@ class LoginController extends ReloadableController {
} else if (failure is GetAuthenticatedAccountFailure) {
_checkOIDCIsAvailable();
} else if (failure is GetSessionFailure) {
SmartDialog.dismiss();
clearAllData();
} else if (failure is DNSLookupToGetJmapUrlFailure) {
_username = null;
@@ -218,6 +220,7 @@ class LoginController extends ReloadableController {
_handleNoSuitableBrowserOIDC(failure)
.map((stillFailed) => _handleCommonOIDCFailure());
} else if (failure is GetSessionFailure) {
SmartDialog.dismiss();
clearAllData();
} else {
super.handleUrgentException(failure: failure, exception: exception);
@@ -234,12 +237,6 @@ class LoginController extends ReloadableController {
);
}
@override
void handleGetSessionFailure(GetSessionFailure failure) {
SmartDialog.dismiss();
super.handleGetSessionFailure(failure);
}
void _registerDeepLinks() {
_deepLinksManager = getBinding<DeepLinksManager>();
_deepLinksManager?.clearPendingDeepLinkData();
@@ -250,16 +247,21 @@ class LoginController extends ReloadableController {
void _handlePendingDeepLinkDataStream(DeepLinkData? deepLinkData) {
log('LoginController::_handlePendingDeepLinkDataStream:DeepLinkData = $deepLinkData');
if (deepLinkData == null) return;
if (currentContext != null) {
SmartDialog.showLoading(msg: AppLocalizations.of(currentContext!).loadingPleaseWait);
}
_deepLinksManager?.handleDeepLinksWhenAppOnForegroundNotSignedIn(
_deepLinksManager?.handleDeepLinksWhenAppRunning(
deepLinkData: deepLinkData,
onSuccessCallback: _handleAutoSignInViaDeepLinkSuccess,
onFailureCallback: SmartDialog.dismiss,
onSuccessCallback: (deepLinkData) {
if (deepLinkData is! OpenAppDeepLinkData) return;
if (currentContext != null) {
SmartDialog.showLoading(msg: AppLocalizations.of(currentContext!).loadingPleaseWait);
}
_deepLinksManager?.autoSignInViaDeepLink(
openAppDeepLinkData: deepLinkData,
onAutoSignInSuccessCallback: _handleAutoSignInViaDeepLinkSuccess,
onFailureCallback: SmartDialog.dismiss,
);
},
);
}
@@ -0,0 +1,29 @@
import 'package:tmail_ui_user/features/home/domain/state/auto_sign_in_via_deep_link_state.dart';
import 'package:tmail_ui_user/features/login/presentation/model/login_navigate_type.dart';
import 'package:tmail_ui_user/main/routes/router_arguments.dart';
class LoginNavigateArguments extends RouterArguments {
final LoginNavigateType navigateType;
final AutoSignInViaDeepLinkSuccess? autoSignInViaDeepLinkSuccess;
LoginNavigateArguments({
required this.navigateType,
this.autoSignInViaDeepLinkSuccess,
});
factory LoginNavigateArguments.autoSignIn({
required AutoSignInViaDeepLinkSuccess autoSignInViaDeepLinkSuccess,
}) {
return LoginNavigateArguments(
navigateType: LoginNavigateType.autoSignIn,
autoSignInViaDeepLinkSuccess: autoSignInViaDeepLinkSuccess,
);
}
@override
List<Object?> get props => [
navigateType,
autoSignInViaDeepLinkSuccess,
];
}
@@ -0,0 +1,3 @@
enum LoginNavigateType {
autoSignIn
}