Skip to content

Commit d4a56af

Browse files
dab246hoangdat
authored andcommitted
TF-3833 Fix failed to keep mobile app connect after network issue
Signed-off-by: dab246 <tdvu@linagora.com>
1 parent c435529 commit d4a56af

12 files changed

+336
-173
lines changed

lib/features/login/data/network/authentication_client/authentication_client_base.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
import 'package:model/oidc/oidc_configuration.dart';
32
import 'package:model/oidc/response/oidc_discovery_response.dart';
43
import 'package:model/oidc/token_id.dart';
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import 'package:core/utils/app_logger.dart';
2+
import 'package:core/utils/platform_info.dart';
3+
import 'package:flutter_appauth_platform_interface/flutter_appauth_platform_interface.dart';
4+
import 'package:model/oidc/oidc_configuration.dart';
5+
import 'package:model/oidc/response/oidc_discovery_response.dart';
6+
import 'package:model/oidc/token_id.dart';
7+
import 'package:tmail_ui_user/features/login/domain/exceptions/oauth_authorization_error.dart';
8+
import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuration_extensions.dart';
9+
10+
mixin AuthenticationClientInteractionMixin {
11+
EndSessionRequest getEndSessionRequest(
12+
TokenId tokenId,
13+
OIDCConfiguration config,
14+
OIDCDiscoveryResponse discoveryResponse,
15+
) {
16+
final authorizationEndpoint = discoveryResponse.authorizationEndpoint;
17+
final tokenEndpoint = discoveryResponse.tokenEndpoint;
18+
AuthorizationServiceConfiguration? serviceConfiguration;
19+
20+
if (authorizationEndpoint != null && tokenEndpoint != null) {
21+
serviceConfiguration = AuthorizationServiceConfiguration(
22+
authorizationEndpoint: authorizationEndpoint,
23+
tokenEndpoint: tokenEndpoint,
24+
endSessionEndpoint: discoveryResponse.endSessionEndpoint,
25+
);
26+
}
27+
28+
return EndSessionRequest(
29+
idTokenHint: tokenId.uuid,
30+
postLogoutRedirectUrl: config.logoutRedirectUrl,
31+
discoveryUrl: config.discoveryUrl,
32+
serviceConfiguration: serviceConfiguration,
33+
externalUserAgent: getExternalUserAgent(),
34+
);
35+
}
36+
37+
ExternalUserAgent getExternalUserAgent() => PlatformInfo.isIOS
38+
? ExternalUserAgent.ephemeralAsWebAuthenticationSession
39+
: ExternalUserAgent.asWebAuthenticationSession;
40+
41+
TokenRequest getRefreshTokenRequest(
42+
String clientId,
43+
String redirectUrl,
44+
String discoveryUrl,
45+
String refreshToken,
46+
List<String> scopes,
47+
) {
48+
return TokenRequest(
49+
clientId,
50+
redirectUrl,
51+
discoveryUrl: discoveryUrl,
52+
refreshToken: refreshToken,
53+
grantType: GrantType.refreshToken,
54+
scopes: scopes,
55+
);
56+
}
57+
58+
AuthorizationTokenRequest getAuthorizationTokenRequest(
59+
String clientId,
60+
String redirectUrl,
61+
String discoveryUrl,
62+
List<String> scopes,
63+
) {
64+
return AuthorizationTokenRequest(
65+
clientId,
66+
redirectUrl,
67+
discoveryUrl: discoveryUrl,
68+
scopes: scopes,
69+
externalUserAgent: getExternalUserAgent(),
70+
);
71+
}
72+
73+
dynamic handleException(dynamic exception) {
74+
if (exception is FlutterAppAuthPlatformException) {
75+
logError('$runtimeType::handleException: ErrorDetails = ${exception.platformErrorDetails.toString()}');
76+
final errorCode = exception.platformErrorDetails.error;
77+
if (errorCode != null) {
78+
final oauthErrorCode = OAuthAuthorizationError.fromErrorCode(
79+
errorCode,
80+
errorDescription: exception.platformErrorDetails.errorDescription,
81+
);
82+
return oauthErrorCode;
83+
}
84+
}
85+
86+
return exception;
87+
}
88+
}

lib/features/login/data/network/authentication_client/authentication_client_mobile.dart

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,82 +10,88 @@ import 'package:model/oidc/token_oidc.dart';
1010
import 'package:tmail_ui_user/features/login/data/extensions/authentication_token_extension.dart';
1111
import 'package:tmail_ui_user/features/login/data/extensions/token_response_extension.dart';
1212
import 'package:tmail_ui_user/features/login/data/network/authentication_client/authentication_client_base.dart';
13+
import 'package:tmail_ui_user/features/login/data/network/authentication_client/authentication_client_interaction_mixin.dart';
1314
import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.dart';
1415
import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart';
1516
import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuration_extensions.dart';
1617

17-
class AuthenticationClientMobile implements AuthenticationClientBase {
18+
class AuthenticationClientMobile with AuthenticationClientInteractionMixin
19+
implements AuthenticationClientBase {
1820

1921
final FlutterAppAuth _appAuth;
2022

2123
AuthenticationClientMobile(this._appAuth);
2224

2325
@override
24-
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl,
25-
String discoveryUrl, List<String> scopes) async {
26+
Future<TokenOIDC> getTokenOIDC(
27+
String clientId,
28+
String redirectUrl,
29+
String discoveryUrl,
30+
List<String> scopes,
31+
) async {
32+
final authorizationTokenRequest = getAuthorizationTokenRequest(
33+
clientId,
34+
redirectUrl,
35+
discoveryUrl,
36+
scopes,
37+
);
2638
final authorizationTokenResponse = await _appAuth.authorizeAndExchangeCode(
27-
AuthorizationTokenRequest(
28-
clientId,
29-
redirectUrl,
30-
discoveryUrl: discoveryUrl,
31-
scopes: scopes,
32-
preferEphemeralSession: true));
33-
34-
log('AuthenticationClientMobile::getTokenOIDC(): token: ${authorizationTokenResponse?.accessToken}');
35-
36-
if (authorizationTokenResponse != null) {
37-
final tokenOIDC = authorizationTokenResponse.toTokenOIDC();
38-
if (tokenOIDC.isTokenValid()) {
39-
return tokenOIDC;
40-
} else {
41-
throw AccessTokenInvalidException();
42-
}
39+
authorizationTokenRequest,
40+
);
41+
log('$runtimeType::getTokenOIDC(): token: ${authorizationTokenResponse.accessToken}');
42+
final tokenOIDC = authorizationTokenResponse.toTokenOIDC();
43+
if (tokenOIDC.isTokenValid()) {
44+
return tokenOIDC;
4345
} else {
44-
throw NotFoundAccessTokenException();
46+
throw AccessTokenInvalidException();
4547
}
4648
}
4749

4850
@override
49-
Future<bool> logoutOidc(TokenId tokenId, OIDCConfiguration config, OIDCDiscoveryResponse oidcRescovery) async {
50-
final authorizationServiceConfiguration = oidcRescovery.authorizationEndpoint == null || oidcRescovery.tokenEndpoint == null
51-
? null
52-
: AuthorizationServiceConfiguration(
53-
authorizationEndpoint: oidcRescovery.authorizationEndpoint!,
54-
tokenEndpoint: oidcRescovery.tokenEndpoint!,
55-
endSessionEndpoint: oidcRescovery.endSessionEndpoint);
56-
57-
final endSession = await _appAuth.endSession(EndSessionRequest(
58-
idTokenHint: tokenId.uuid,
59-
postLogoutRedirectUrl: config.logoutRedirectUrl,
60-
discoveryUrl: config.discoveryUrl,
61-
serviceConfiguration: authorizationServiceConfiguration,
62-
preferEphemeralSession: true,
63-
));
64-
log('AuthenticationClientMobile::logoutOidc(): ${endSession?.state}');
65-
return endSession?.state?.isNotEmpty == true;
51+
Future<bool> logoutOidc(
52+
TokenId tokenId,
53+
OIDCConfiguration config,
54+
OIDCDiscoveryResponse discoveryResponse,
55+
) async {
56+
final endSessionRequest = getEndSessionRequest(
57+
tokenId,
58+
config,
59+
discoveryResponse,
60+
);
61+
final endSession = await _appAuth.endSession(endSessionRequest);
62+
log('$runtimeType::logoutOidc(): ${endSession.state}');
63+
return endSession.state?.isNotEmpty == true;
6664
}
6765

6866
@override
69-
Future<TokenOIDC> refreshingTokensOIDC(String clientId, String redirectUrl,
70-
String discoveryUrl, List<String> scopes, String refreshToken) async {
71-
final tokenResponse = await _appAuth.token(TokenRequest(
67+
Future<TokenOIDC> refreshingTokensOIDC(
68+
String clientId,
69+
String redirectUrl,
70+
String discoveryUrl,
71+
List<String> scopes,
72+
String refreshToken,
73+
) async {
74+
try {
75+
final tokenRequest = getRefreshTokenRequest(
7276
clientId,
7377
redirectUrl,
74-
discoveryUrl: discoveryUrl,
75-
refreshToken: refreshToken,
76-
scopes: scopes));
77-
78-
log('AuthenticationClientMobile::refreshingTokensOIDC(): refreshToken: ${tokenResponse?.accessToken}');
79-
80-
if (tokenResponse != null) {
81-
final tokenOIDC = tokenResponse.toTokenOIDC(maybeAvailableRefreshToken: refreshToken);
78+
discoveryUrl,
79+
refreshToken,
80+
scopes,
81+
);
82+
final tokenResponse = await _appAuth.token(tokenRequest);
83+
log('$runtimeType::refreshingTokensOIDC():Token: ${tokenResponse.accessToken}');
84+
final tokenOIDC = tokenResponse.toTokenOIDC(
85+
maybeAvailableRefreshToken: refreshToken,
86+
);
8287
if (tokenOIDC.isTokenValid()) {
8388
return tokenOIDC;
8489
} else {
8590
throw AccessTokenInvalidException();
8691
}
87-
} else {
88-
throw NotFoundAccessTokenException();
92+
} catch (e) {
93+
logError('$runtimeType::refreshingTokensOIDC(): $e');
94+
throw handleException(e);
8995
}
9096
}
9197

@@ -104,7 +110,7 @@ class AuthenticationClientMobile implements AuthenticationClientBase {
104110
intentFlags: ephemeralIntentFlags,
105111
),
106112
);
107-
log('AuthenticationClientMobile::signInTwakeWorkplace():Uri = $uri');
113+
log('$runtimeType::signInTwakeWorkplace():Uri = $uri');
108114
return TokenOIDC.fromUri(uri);
109115
}
110116

@@ -117,7 +123,7 @@ class AuthenticationClientMobile implements AuthenticationClientBase {
117123
intentFlags: ephemeralIntentFlags,
118124
),
119125
);
120-
log('AuthenticationClientMobile::signUpTwakeWorkplace():Uri = $uri');
126+
log('$runtimeType::signUpTwakeWorkplace():Uri = $uri');
121127
return TokenOIDC.fromUri(uri);
122128
}
123129
}

0 commit comments

Comments
 (0)