TF-3833 Fix failed to keep mobile app connect after network issue
Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
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';
|
||||||
import 'package:model/oidc/token_id.dart';
|
import 'package:model/oidc/token_id.dart';
|
||||||
|
|||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
import 'package:core/utils/app_logger.dart';
|
||||||
|
import 'package:core/utils/platform_info.dart';
|
||||||
|
import 'package:flutter_appauth_platform_interface/flutter_appauth_platform_interface.dart';
|
||||||
|
import 'package:model/oidc/oidc_configuration.dart';
|
||||||
|
import 'package:model/oidc/response/oidc_discovery_response.dart';
|
||||||
|
import 'package:model/oidc/token_id.dart';
|
||||||
|
import 'package:tmail_ui_user/features/login/domain/exceptions/oauth_authorization_error.dart';
|
||||||
|
import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuration_extensions.dart';
|
||||||
|
|
||||||
|
mixin AuthenticationClientInteractionMixin {
|
||||||
|
EndSessionRequest getEndSessionRequest(
|
||||||
|
TokenId tokenId,
|
||||||
|
OIDCConfiguration config,
|
||||||
|
OIDCDiscoveryResponse discoveryResponse,
|
||||||
|
) {
|
||||||
|
final authorizationEndpoint = discoveryResponse.authorizationEndpoint;
|
||||||
|
final tokenEndpoint = discoveryResponse.tokenEndpoint;
|
||||||
|
AuthorizationServiceConfiguration? serviceConfiguration;
|
||||||
|
|
||||||
|
if (authorizationEndpoint != null && tokenEndpoint != null) {
|
||||||
|
serviceConfiguration = AuthorizationServiceConfiguration(
|
||||||
|
authorizationEndpoint: authorizationEndpoint,
|
||||||
|
tokenEndpoint: tokenEndpoint,
|
||||||
|
endSessionEndpoint: discoveryResponse.endSessionEndpoint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EndSessionRequest(
|
||||||
|
idTokenHint: tokenId.uuid,
|
||||||
|
postLogoutRedirectUrl: config.logoutRedirectUrl,
|
||||||
|
discoveryUrl: config.discoveryUrl,
|
||||||
|
serviceConfiguration: serviceConfiguration,
|
||||||
|
externalUserAgent: getExternalUserAgent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalUserAgent getExternalUserAgent() => PlatformInfo.isIOS
|
||||||
|
? ExternalUserAgent.ephemeralAsWebAuthenticationSession
|
||||||
|
: ExternalUserAgent.asWebAuthenticationSession;
|
||||||
|
|
||||||
|
TokenRequest getRefreshTokenRequest(
|
||||||
|
String clientId,
|
||||||
|
String redirectUrl,
|
||||||
|
String discoveryUrl,
|
||||||
|
String refreshToken,
|
||||||
|
List<String> scopes,
|
||||||
|
) {
|
||||||
|
return TokenRequest(
|
||||||
|
clientId,
|
||||||
|
redirectUrl,
|
||||||
|
discoveryUrl: discoveryUrl,
|
||||||
|
refreshToken: refreshToken,
|
||||||
|
grantType: GrantType.refreshToken,
|
||||||
|
scopes: scopes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthorizationTokenRequest getAuthorizationTokenRequest(
|
||||||
|
String clientId,
|
||||||
|
String redirectUrl,
|
||||||
|
String discoveryUrl,
|
||||||
|
List<String> scopes,
|
||||||
|
) {
|
||||||
|
return AuthorizationTokenRequest(
|
||||||
|
clientId,
|
||||||
|
redirectUrl,
|
||||||
|
discoveryUrl: discoveryUrl,
|
||||||
|
scopes: scopes,
|
||||||
|
externalUserAgent: getExternalUserAgent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic handleException(dynamic exception) {
|
||||||
|
if (exception is FlutterAppAuthPlatformException) {
|
||||||
|
logError('$runtimeType::handleException: ErrorDetails = ${exception.platformErrorDetails.toString()}');
|
||||||
|
final errorCode = exception.platformErrorDetails.error;
|
||||||
|
if (errorCode != null) {
|
||||||
|
final oauthErrorCode = OAuthAuthorizationError.fromErrorCode(
|
||||||
|
errorCode,
|
||||||
|
errorDescription: exception.platformErrorDetails.errorDescription,
|
||||||
|
);
|
||||||
|
return oauthErrorCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
+58
-52
@@ -10,82 +10,88 @@ 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/authentication_client/authentication_client_interaction_mixin.dart';
|
||||||
import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.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';
|
||||||
|
|
||||||
class AuthenticationClientMobile implements AuthenticationClientBase {
|
class AuthenticationClientMobile with AuthenticationClientInteractionMixin
|
||||||
|
implements AuthenticationClientBase {
|
||||||
|
|
||||||
final FlutterAppAuth _appAuth;
|
final FlutterAppAuth _appAuth;
|
||||||
|
|
||||||
AuthenticationClientMobile(this._appAuth);
|
AuthenticationClientMobile(this._appAuth);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl,
|
Future<TokenOIDC> getTokenOIDC(
|
||||||
String discoveryUrl, List<String> scopes) async {
|
String clientId,
|
||||||
|
String redirectUrl,
|
||||||
|
String discoveryUrl,
|
||||||
|
List<String> scopes,
|
||||||
|
) async {
|
||||||
|
final authorizationTokenRequest = getAuthorizationTokenRequest(
|
||||||
|
clientId,
|
||||||
|
redirectUrl,
|
||||||
|
discoveryUrl,
|
||||||
|
scopes,
|
||||||
|
);
|
||||||
final authorizationTokenResponse = await _appAuth.authorizeAndExchangeCode(
|
final authorizationTokenResponse = await _appAuth.authorizeAndExchangeCode(
|
||||||
AuthorizationTokenRequest(
|
authorizationTokenRequest,
|
||||||
clientId,
|
);
|
||||||
redirectUrl,
|
log('$runtimeType::getTokenOIDC(): token: ${authorizationTokenResponse.accessToken}');
|
||||||
discoveryUrl: discoveryUrl,
|
final tokenOIDC = authorizationTokenResponse.toTokenOIDC();
|
||||||
scopes: scopes,
|
if (tokenOIDC.isTokenValid()) {
|
||||||
preferEphemeralSession: true));
|
return tokenOIDC;
|
||||||
|
|
||||||
log('AuthenticationClientMobile::getTokenOIDC(): token: ${authorizationTokenResponse?.accessToken}');
|
|
||||||
|
|
||||||
if (authorizationTokenResponse != null) {
|
|
||||||
final tokenOIDC = authorizationTokenResponse.toTokenOIDC();
|
|
||||||
if (tokenOIDC.isTokenValid()) {
|
|
||||||
return tokenOIDC;
|
|
||||||
} else {
|
|
||||||
throw AccessTokenInvalidException();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
throw NotFoundAccessTokenException();
|
throw AccessTokenInvalidException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> logoutOidc(TokenId tokenId, OIDCConfiguration config, OIDCDiscoveryResponse oidcRescovery) async {
|
Future<bool> logoutOidc(
|
||||||
final authorizationServiceConfiguration = oidcRescovery.authorizationEndpoint == null || oidcRescovery.tokenEndpoint == null
|
TokenId tokenId,
|
||||||
? null
|
OIDCConfiguration config,
|
||||||
: AuthorizationServiceConfiguration(
|
OIDCDiscoveryResponse discoveryResponse,
|
||||||
authorizationEndpoint: oidcRescovery.authorizationEndpoint!,
|
) async {
|
||||||
tokenEndpoint: oidcRescovery.tokenEndpoint!,
|
final endSessionRequest = getEndSessionRequest(
|
||||||
endSessionEndpoint: oidcRescovery.endSessionEndpoint);
|
tokenId,
|
||||||
|
config,
|
||||||
final endSession = await _appAuth.endSession(EndSessionRequest(
|
discoveryResponse,
|
||||||
idTokenHint: tokenId.uuid,
|
);
|
||||||
postLogoutRedirectUrl: config.logoutRedirectUrl,
|
final endSession = await _appAuth.endSession(endSessionRequest);
|
||||||
discoveryUrl: config.discoveryUrl,
|
log('$runtimeType::logoutOidc(): ${endSession.state}');
|
||||||
serviceConfiguration: authorizationServiceConfiguration,
|
return endSession.state?.isNotEmpty == true;
|
||||||
preferEphemeralSession: true,
|
|
||||||
));
|
|
||||||
log('AuthenticationClientMobile::logoutOidc(): ${endSession?.state}');
|
|
||||||
return endSession?.state?.isNotEmpty == true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<TokenOIDC> refreshingTokensOIDC(String clientId, String redirectUrl,
|
Future<TokenOIDC> refreshingTokensOIDC(
|
||||||
String discoveryUrl, List<String> scopes, String refreshToken) async {
|
String clientId,
|
||||||
final tokenResponse = await _appAuth.token(TokenRequest(
|
String redirectUrl,
|
||||||
|
String discoveryUrl,
|
||||||
|
List<String> scopes,
|
||||||
|
String refreshToken,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final tokenRequest = getRefreshTokenRequest(
|
||||||
clientId,
|
clientId,
|
||||||
redirectUrl,
|
redirectUrl,
|
||||||
discoveryUrl: discoveryUrl,
|
discoveryUrl,
|
||||||
refreshToken: refreshToken,
|
refreshToken,
|
||||||
scopes: scopes));
|
scopes,
|
||||||
|
);
|
||||||
log('AuthenticationClientMobile::refreshingTokensOIDC(): refreshToken: ${tokenResponse?.accessToken}');
|
final tokenResponse = await _appAuth.token(tokenRequest);
|
||||||
|
log('$runtimeType::refreshingTokensOIDC():Token: ${tokenResponse.accessToken}');
|
||||||
if (tokenResponse != null) {
|
final tokenOIDC = tokenResponse.toTokenOIDC(
|
||||||
final tokenOIDC = tokenResponse.toTokenOIDC(maybeAvailableRefreshToken: refreshToken);
|
maybeAvailableRefreshToken: refreshToken,
|
||||||
|
);
|
||||||
if (tokenOIDC.isTokenValid()) {
|
if (tokenOIDC.isTokenValid()) {
|
||||||
return tokenOIDC;
|
return tokenOIDC;
|
||||||
} else {
|
} else {
|
||||||
throw AccessTokenInvalidException();
|
throw AccessTokenInvalidException();
|
||||||
}
|
}
|
||||||
} else {
|
} catch (e) {
|
||||||
throw NotFoundAccessTokenException();
|
logError('$runtimeType::refreshingTokensOIDC(): $e');
|
||||||
|
throw handleException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +110,7 @@ class AuthenticationClientMobile implements AuthenticationClientBase {
|
|||||||
intentFlags: ephemeralIntentFlags,
|
intentFlags: ephemeralIntentFlags,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
log('AuthenticationClientMobile::signInTwakeWorkplace():Uri = $uri');
|
log('$runtimeType::signInTwakeWorkplace():Uri = $uri');
|
||||||
return TokenOIDC.fromUri(uri);
|
return TokenOIDC.fromUri(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +123,7 @@ class AuthenticationClientMobile implements AuthenticationClientBase {
|
|||||||
intentFlags: ephemeralIntentFlags,
|
intentFlags: ephemeralIntentFlags,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
log('AuthenticationClientMobile::signUpTwakeWorkplace():Uri = $uri');
|
log('$runtimeType::signUpTwakeWorkplace():Uri = $uri');
|
||||||
return TokenOIDC.fromUri(uri);
|
return TokenOIDC.fromUri(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+85
-70
@@ -9,89 +9,104 @@ 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/authentication_client/authentication_client_interaction_mixin.dart';
|
||||||
import 'package:tmail_ui_user/features/login/data/utils/library_platform/app_auth_plugin/app_auth_plugin.dart';
|
import 'package:tmail_ui_user/features/login/data/utils/library_platform/app_auth_plugin/app_auth_plugin.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';
|
|
||||||
|
|
||||||
class AuthenticationClientWeb implements AuthenticationClientBase {
|
class AuthenticationClientWeb with AuthenticationClientInteractionMixin
|
||||||
|
implements AuthenticationClientBase {
|
||||||
|
|
||||||
final AppAuthWebPlugin _appAuthWeb;
|
final AppAuthWebPlugin _appAuthWeb;
|
||||||
|
|
||||||
AuthenticationClientWeb(this._appAuthWeb);
|
AuthenticationClientWeb(this._appAuthWeb);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl,
|
Future<TokenOIDC> getTokenOIDC(
|
||||||
String discoveryUrl, List<String> scopes) async {
|
String clientId,
|
||||||
final authorizationTokenResponse = await _appAuthWeb.authorizeAndExchangeCode(AuthorizationTokenRequest(
|
String redirectUrl,
|
||||||
|
String discoveryUrl,
|
||||||
|
List<String> scopes,
|
||||||
|
) async {
|
||||||
|
final authorizationTokenRequest = getAuthorizationTokenRequest(
|
||||||
|
clientId,
|
||||||
|
redirectUrl,
|
||||||
|
discoveryUrl,
|
||||||
|
scopes,
|
||||||
|
);
|
||||||
|
final authorizationTokenResponse = await _appAuthWeb.authorizeAndExchangeCode(
|
||||||
|
authorizationTokenRequest,
|
||||||
|
);
|
||||||
|
log('$runtimeType::getTokenOIDC():Token: ${authorizationTokenResponse.accessToken}');
|
||||||
|
final tokenOIDC = authorizationTokenResponse.toTokenOIDC();
|
||||||
|
if (tokenOIDC.isTokenValid()) {
|
||||||
|
return tokenOIDC;
|
||||||
|
} else {
|
||||||
|
throw AccessTokenInvalidException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> logoutOidc(
|
||||||
|
TokenId tokenId,
|
||||||
|
OIDCConfiguration config,
|
||||||
|
OIDCDiscoveryResponse discoveryResponse,
|
||||||
|
) async {
|
||||||
|
final endSessionRequest = getEndSessionRequest(
|
||||||
|
tokenId,
|
||||||
|
config,
|
||||||
|
discoveryResponse,
|
||||||
|
);
|
||||||
|
final endSession = await _appAuthWeb.endSession(endSessionRequest);
|
||||||
|
log('$runtimeType::logoutOidc(): ${endSession.state}');
|
||||||
|
return endSession.state?.isNotEmpty == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TokenOIDC> refreshingTokensOIDC(
|
||||||
|
String clientId,
|
||||||
|
String redirectUrl,
|
||||||
|
String discoveryUrl,
|
||||||
|
List<String> scopes,
|
||||||
|
String refreshToken,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final tokenRequest = getRefreshTokenRequest(
|
||||||
|
clientId,
|
||||||
|
redirectUrl,
|
||||||
|
discoveryUrl,
|
||||||
|
refreshToken,
|
||||||
|
scopes,
|
||||||
|
);
|
||||||
|
final tokenResponse = await _appAuthWeb.token(tokenRequest);
|
||||||
|
final tokenOIDC = tokenResponse.toTokenOIDC(
|
||||||
|
maybeAvailableRefreshToken: refreshToken,
|
||||||
|
);
|
||||||
|
if (tokenOIDC.isTokenValid()) {
|
||||||
|
return tokenOIDC;
|
||||||
|
} else {
|
||||||
|
throw AccessTokenInvalidException();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logError('$runtimeType::refreshingTokensOIDC(): $e');
|
||||||
|
throw handleException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> authenticateOidcOnBrowser(
|
||||||
|
String clientId,
|
||||||
|
String redirectUrl,
|
||||||
|
String discoveryUrl,
|
||||||
|
List<String> scopes,
|
||||||
|
) async {
|
||||||
|
await _appAuthWeb.authorizeAndExchangeCode(
|
||||||
|
AuthorizationTokenRequest(
|
||||||
clientId,
|
clientId,
|
||||||
redirectUrl,
|
redirectUrl,
|
||||||
discoveryUrl: discoveryUrl,
|
discoveryUrl: discoveryUrl,
|
||||||
scopes: scopes,
|
scopes: scopes,
|
||||||
preferEphemeralSession: true));
|
),
|
||||||
|
);
|
||||||
log('AuthClientMobile::getTokenOIDC(): token: ${authorizationTokenResponse?.accessToken}');
|
|
||||||
|
|
||||||
if (authorizationTokenResponse != null) {
|
|
||||||
final tokenOIDC = authorizationTokenResponse.toTokenOIDC();
|
|
||||||
if (tokenOIDC.isTokenValid()) {
|
|
||||||
return tokenOIDC;
|
|
||||||
} else {
|
|
||||||
throw AccessTokenInvalidException();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw NotFoundAccessTokenException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> logoutOidc(TokenId tokenId, OIDCConfiguration config, OIDCDiscoveryResponse oidcRescovery) async {
|
|
||||||
final authorizationServiceConfiguration = oidcRescovery.authorizationEndpoint == null || oidcRescovery.tokenEndpoint == null
|
|
||||||
? null
|
|
||||||
: AuthorizationServiceConfiguration(
|
|
||||||
authorizationEndpoint: oidcRescovery.authorizationEndpoint!,
|
|
||||||
tokenEndpoint: oidcRescovery.tokenEndpoint!,
|
|
||||||
endSessionEndpoint: oidcRescovery.endSessionEndpoint);
|
|
||||||
final endSession = await _appAuthWeb.endSession(EndSessionRequest(
|
|
||||||
idTokenHint: tokenId.uuid,
|
|
||||||
postLogoutRedirectUrl: config.logoutRedirectUrl,
|
|
||||||
discoveryUrl: config.discoveryUrl,
|
|
||||||
serviceConfiguration: authorizationServiceConfiguration
|
|
||||||
));
|
|
||||||
return endSession != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<TokenOIDC> refreshingTokensOIDC(String clientId, String redirectUrl,
|
|
||||||
String discoveryUrl, List<String> scopes, String refreshToken) async {
|
|
||||||
final tokenResponse = await _appAuthWeb.token(TokenRequest(
|
|
||||||
clientId,
|
|
||||||
redirectUrl,
|
|
||||||
discoveryUrl: discoveryUrl,
|
|
||||||
refreshToken: refreshToken,
|
|
||||||
grantType: 'refresh_token',
|
|
||||||
scopes: scopes));
|
|
||||||
|
|
||||||
if (tokenResponse != null) {
|
|
||||||
final tokenOIDC = tokenResponse.toTokenOIDC(maybeAvailableRefreshToken: refreshToken);
|
|
||||||
if (tokenOIDC.isTokenValid()) {
|
|
||||||
return tokenOIDC;
|
|
||||||
} else {
|
|
||||||
throw AccessTokenInvalidException();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw NotFoundAccessTokenException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> authenticateOidcOnBrowser(String clientId, String redirectUrl,
|
|
||||||
String discoveryUrl, List<String> scopes) async {
|
|
||||||
await _appAuthWeb.authorizeAndExchangeCode(
|
|
||||||
AuthorizationTokenRequest(
|
|
||||||
clientId,
|
|
||||||
redirectUrl,
|
|
||||||
discoveryUrl: discoveryUrl,
|
|
||||||
scopes: scopes));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import 'package:model/oidc/token_oidc.dart';
|
|||||||
import 'package:tmail_ui_user/features/login/data/local/account_cache_manager.dart';
|
import 'package:tmail_ui_user/features/login/data/local/account_cache_manager.dart';
|
||||||
import 'package:tmail_ui_user/features/login/data/local/token_oidc_cache_manager.dart';
|
import 'package:tmail_ui_user/features/login/data/local/token_oidc_cache_manager.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/exceptions/oauth_authorization_error.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';
|
||||||
import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart';
|
import 'package:tmail_ui_user/features/upload/data/network/file_uploader.dart';
|
||||||
import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart';
|
import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart';
|
||||||
@@ -152,7 +153,14 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logError('AuthorizationInterceptors::onError:Exception: $e');
|
logError('AuthorizationInterceptors::onError:Exception: $e');
|
||||||
return super.onError(err.copyWith(error: e), handler);
|
if (e is ServerError || e is TemporarilyUnavailable) {
|
||||||
|
return super.onError(
|
||||||
|
DioError(requestOptions: err.requestOptions, error: e),
|
||||||
|
handler,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return super.onError(err.copyWith(error: e), handler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ class InvalidBaseUrl extends AuthenticationException {
|
|||||||
InvalidBaseUrl() : super(AuthenticationException.invalidBaseUrl);
|
InvalidBaseUrl() : super(AuthenticationException.invalidBaseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
class NotFoundAccessTokenException implements Exception {}
|
|
||||||
|
|
||||||
class AccessTokenInvalidException implements Exception {}
|
class AccessTokenInvalidException implements Exception {}
|
||||||
|
|
||||||
class DownloadAttachmentHasTokenExpiredException implements Exception {
|
class DownloadAttachmentHasTokenExpiredException implements Exception {
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
class OAuthAuthorizationError {
|
||||||
|
static const String serverError = 'server_error'; // HTTP 500 error code
|
||||||
|
static const String temporarilyUnavailable =
|
||||||
|
'temporarily_unavailable'; // HTTP 503 error code
|
||||||
|
|
||||||
|
final String error;
|
||||||
|
final String? errorDescription;
|
||||||
|
|
||||||
|
const OAuthAuthorizationError({
|
||||||
|
required this.error,
|
||||||
|
this.errorDescription,
|
||||||
|
});
|
||||||
|
|
||||||
|
static OAuthAuthorizationError fromErrorCode(
|
||||||
|
String error, {
|
||||||
|
String? errorDescription,
|
||||||
|
}) {
|
||||||
|
switch (error) {
|
||||||
|
case serverError:
|
||||||
|
return ServerError(errorDescription: errorDescription);
|
||||||
|
case temporarilyUnavailable:
|
||||||
|
return TemporarilyUnavailable(errorDescription: errorDescription);
|
||||||
|
default:
|
||||||
|
return OAuthAuthorizationError(
|
||||||
|
error: error,
|
||||||
|
errorDescription: errorDescription,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServerError extends OAuthAuthorizationError {
|
||||||
|
const ServerError({super.errorDescription})
|
||||||
|
: super(error: OAuthAuthorizationError.serverError);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TemporarilyUnavailable extends OAuthAuthorizationError {
|
||||||
|
const TemporarilyUnavailable({super.errorDescription})
|
||||||
|
: super(error: OAuthAuthorizationError.temporarilyUnavailable);
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import 'package:core/presentation/state/failure.dart';
|
|
||||||
import 'package:core/presentation/state/success.dart';
|
|
||||||
import 'package:model/oidc/token_oidc.dart';
|
|
||||||
|
|
||||||
class RefreshTokenOIDCSuccess extends UIState {
|
|
||||||
|
|
||||||
final TokenOIDC tokenOIDC;
|
|
||||||
|
|
||||||
RefreshTokenOIDCSuccess(this.tokenOIDC);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object> get props => [tokenOIDC];
|
|
||||||
}
|
|
||||||
|
|
||||||
class RefreshTokenOIDCFailure extends FeatureFailure {
|
|
||||||
|
|
||||||
RefreshTokenOIDCFailure(dynamic exception) : super(exception: exception);
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@ import 'package:get/get_connect/http/src/status/http_status.dart';
|
|||||||
import 'package:jmap_dart_client/jmap/core/error/method/error_method_response.dart';
|
import 'package:jmap_dart_client/jmap/core/error/method/error_method_response.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/error/method/exception/error_method_response_exception.dart';
|
import 'package:jmap_dart_client/jmap/core/error/method/exception/error_method_response_exception.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/exceptions/oauth_authorization_error.dart';
|
||||||
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart'
|
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart'
|
||||||
if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart';
|
if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart';
|
||||||
import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart';
|
import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart';
|
||||||
@@ -28,48 +29,66 @@ class RemoteExceptionThrower extends ExceptionThrower {
|
|||||||
|
|
||||||
void handleDioError(dynamic error) {
|
void handleDioError(dynamic error) {
|
||||||
if (error is DioError) {
|
if (error is DioError) {
|
||||||
logError('RemoteExceptionThrower::throwException():type: ${error.type} | response: ${error.response} | error: ${error.error}');
|
logError(
|
||||||
if (error.response != null) {
|
'RemoteExceptionThrower::throwException():type: ${error.type} | response: ${error.response} | error: ${error.error}',
|
||||||
if (error.response!.statusCode == HttpStatus.internalServerError) {
|
);
|
||||||
throw const InternalServerError();
|
|
||||||
} else if (error.response!.statusCode == HttpStatus.badGateway) {
|
final response = error.response;
|
||||||
throw BadGateway();
|
final statusCode = response?.statusCode;
|
||||||
} else if (error.response!.statusCode == HttpStatus.unauthorized) {
|
|
||||||
throw const BadCredentialsException();
|
if (response != null) {
|
||||||
} else {
|
switch (statusCode) {
|
||||||
throw UnknownError(
|
case HttpStatus.internalServerError:
|
||||||
code: error.response!.statusCode,
|
throw const InternalServerError();
|
||||||
message: error.response!.statusMessage);
|
case HttpStatus.badGateway:
|
||||||
}
|
throw BadGateway();
|
||||||
} else {
|
case HttpStatus.unauthorized:
|
||||||
switch (error.type) {
|
|
||||||
case DioErrorType.connectionTimeout:
|
|
||||||
throw ConnectionTimeout(message: error.message);
|
|
||||||
case DioErrorType.connectionError:
|
|
||||||
throw ConnectionError(message: error.message);
|
|
||||||
case DioErrorType.badResponse:
|
|
||||||
throw const BadCredentialsException();
|
throw const BadCredentialsException();
|
||||||
default:
|
default:
|
||||||
if (error.error is SocketException) {
|
throw UnknownError(
|
||||||
throw const SocketError();
|
code: statusCode,
|
||||||
} else if (error.error != null) {
|
message: response.statusMessage,
|
||||||
throw UnknownError(message: error.error);
|
);
|
||||||
} else {
|
|
||||||
throw const UnknownError();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (error is ErrorMethodResponseException) {
|
|
||||||
|
return _handleDioErrorWithoutResponse(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error is ErrorMethodResponseException) {
|
||||||
final errorResponse = error.errorResponse as ErrorMethodResponse;
|
final errorResponse = error.errorResponse as ErrorMethodResponse;
|
||||||
if (errorResponse is CannotCalculateChangesMethodResponse) {
|
if (errorResponse is CannotCalculateChangesMethodResponse) {
|
||||||
throw CannotCalculateChangesMethodResponseException();
|
throw CannotCalculateChangesMethodResponseException();
|
||||||
} else {
|
} else {
|
||||||
throw MethodLevelErrors(
|
throw MethodLevelErrors(
|
||||||
errorResponse.type,
|
errorResponse.type,
|
||||||
message: errorResponse.description);
|
message: errorResponse.description,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
throw error;
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleDioErrorWithoutResponse(DioError error) {
|
||||||
|
switch (error.type) {
|
||||||
|
case DioErrorType.connectionTimeout:
|
||||||
|
throw ConnectionTimeout(message: error.message);
|
||||||
|
case DioErrorType.connectionError:
|
||||||
|
throw ConnectionError(message: error.message);
|
||||||
|
case DioErrorType.badResponse:
|
||||||
|
throw const BadCredentialsException();
|
||||||
|
default:
|
||||||
|
final underlyingError = error.error;
|
||||||
|
if (underlyingError is SocketException) {
|
||||||
|
throw const SocketError();
|
||||||
|
} else if (underlyingError is OAuthAuthorizationError) {
|
||||||
|
throw underlyingError;
|
||||||
|
} else if (underlyingError != null) {
|
||||||
|
throw UnknownError(message: underlyingError);
|
||||||
|
} else {
|
||||||
|
throw const UnknownError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ 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:flutter/services.dart';
|
||||||
|
import 'package:flutter_appauth_web/authorization_exception.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/error/method/error_method_response.dart';
|
import 'package:jmap_dart_client/jmap/core/error/method/error_method_response.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/error/method/exception/error_method_response_exception.dart';
|
import 'package:jmap_dart_client/jmap/core/error/method/exception/error_method_response_exception.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/error/set_error.dart';
|
import 'package:jmap_dart_client/jmap/core/error/set_error.dart';
|
||||||
@@ -23,6 +24,7 @@ import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.d
|
|||||||
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/login/domain/exceptions/oauth_authorization_error.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox/domain/state/clear_mailbox_state.dart';
|
import 'package:tmail_ui_user/features/mailbox/domain/state/clear_mailbox_state.dart';
|
||||||
import 'package:tmail_ui_user/features/manage_account/domain/state/add_recipient_in_forwarding_state.dart';
|
import 'package:tmail_ui_user/features/manage_account/domain/state/add_recipient_in_forwarding_state.dart';
|
||||||
import 'package:tmail_ui_user/features/manage_account/domain/state/delete_recipient_in_forwarding_state.dart';
|
import 'package:tmail_ui_user/features/manage_account/domain/state/delete_recipient_in_forwarding_state.dart';
|
||||||
@@ -98,7 +100,7 @@ class ToastManager {
|
|||||||
return '[${exception.type.value}] ${exception.message}';
|
return '[${exception.type.value}] ${exception.message}';
|
||||||
} else if (exception is SetError) {
|
} else if (exception is SetError) {
|
||||||
return '[${exception.type.value}] ${exception.description}';
|
return '[${exception.type.value}] ${exception.description}';
|
||||||
}else if (exception is PlatformException &&
|
} else if (exception is PlatformException &&
|
||||||
exception.message?.isNotEmpty == true) {
|
exception.message?.isNotEmpty == true) {
|
||||||
return exception.message!;
|
return exception.message!;
|
||||||
} else if (exception is NotGrantedPermissionStorageException) {
|
} else if (exception is NotGrantedPermissionStorageException) {
|
||||||
@@ -114,6 +116,12 @@ class ToastManager {
|
|||||||
final firstError = mapErrors.values.first;
|
final firstError = mapErrors.values.first;
|
||||||
return '[${firstError.type.value}] ${firstError.description}';
|
return '[${firstError.type.value}] ${firstError.description}';
|
||||||
}
|
}
|
||||||
|
} else if (exception is ServerError) {
|
||||||
|
return '[${exception.error}] ${exception.errorDescription}';
|
||||||
|
} else if (exception is TemporarilyUnavailable) {
|
||||||
|
return '[${exception.error}] ${exception.errorDescription}';
|
||||||
|
} else if (exception is AutoRedirectToAppAfterStoreAuthorizeDestinationUrlException) {
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (useDefaultMessage) {
|
if (useDefaultMessage) {
|
||||||
|
|||||||
+8
-8
@@ -697,27 +697,27 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_appauth
|
name: flutter_appauth
|
||||||
sha256: f2696d4cf437f627fa09bc4864afdd8c80273f2e293fde544b18202a627754b1
|
sha256: b09fa8e3eaba12ec341c69ec45063e06eb565304e24cc35caaf105bbae2e955c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.6"
|
version: "9.0.1"
|
||||||
flutter_appauth_platform_interface:
|
flutter_appauth_platform_interface:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_appauth_platform_interface
|
name: flutter_appauth_platform_interface
|
||||||
sha256: "44feaa7058191b5d3cd7c9ff195262725773643121bcada172d49c2ddcff71cb"
|
sha256: fd2920b853d09741aff2e1178e044ea2ade0c87799cd8e63f094ab35b00fdf70
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "9.0.0"
|
||||||
flutter_appauth_web:
|
flutter_appauth_web:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: upgrade-flutter-appauth
|
||||||
resolved-ref: "39f9806f27f45da34bb86b2b9b22847be9153516"
|
resolved-ref: "0952346bf781bd87b93b8c61619f81a264be52db"
|
||||||
url: "https://github.com/CarlosPacheco/flutter_appauth_web.git"
|
url: "https://github.com/linagora/flutter_appauth_web.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.3"
|
version: "1.0.0"
|
||||||
flutter_charset_detector:
|
flutter_charset_detector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
+4
-4
@@ -82,8 +82,8 @@ dependencies:
|
|||||||
|
|
||||||
flutter_appauth_web:
|
flutter_appauth_web:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/CarlosPacheco/flutter_appauth_web.git
|
url: https://github.com/linagora/flutter_appauth_web.git
|
||||||
ref: main
|
ref: upgrade-flutter-appauth
|
||||||
|
|
||||||
flutter_date_range_picker:
|
flutter_date_range_picker:
|
||||||
git:
|
git:
|
||||||
@@ -172,7 +172,7 @@ dependencies:
|
|||||||
|
|
||||||
better_open_file: 3.6.4
|
better_open_file: 3.6.4
|
||||||
|
|
||||||
flutter_appauth: 6.0.6
|
flutter_appauth: 9.0.1
|
||||||
|
|
||||||
percent_indicator: 4.2.2
|
percent_indicator: 4.2.2
|
||||||
|
|
||||||
@@ -206,7 +206,7 @@ dependencies:
|
|||||||
|
|
||||||
debounce_throttle: 2.0.0
|
debounce_throttle: 2.0.0
|
||||||
|
|
||||||
flutter_appauth_platform_interface: 6.0.0
|
flutter_appauth_platform_interface: 9.0.0
|
||||||
|
|
||||||
intl: 0.19.0
|
intl: 0.19.0
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user