TF-3833 Fix failed to keep mobile app connect after network issue

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-07-18 09:55:24 +07:00
committed by Dat H. Pham
parent c435529ce4
commit d4a56afaa1
12 changed files with 352 additions and 189 deletions
@@ -1,4 +1,3 @@
import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/response/oidc_discovery_response.dart';
import 'package:model/oidc/token_id.dart';
@@ -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;
}
}
@@ -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/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_interaction_mixin.dart';
import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.dart';
import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart';
import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuration_extensions.dart';
class AuthenticationClientMobile implements AuthenticationClientBase {
class AuthenticationClientMobile with AuthenticationClientInteractionMixin
implements AuthenticationClientBase {
final FlutterAppAuth _appAuth;
AuthenticationClientMobile(this._appAuth);
@override
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl,
String discoveryUrl, List<String> scopes) async {
final authorizationTokenResponse = await _appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
Future<TokenOIDC> getTokenOIDC(
String clientId,
String redirectUrl,
String discoveryUrl,
List<String> scopes,
) async {
final authorizationTokenRequest = getAuthorizationTokenRequest(
clientId,
redirectUrl,
discoveryUrl: discoveryUrl,
scopes: scopes,
preferEphemeralSession: true));
log('AuthenticationClientMobile::getTokenOIDC(): token: ${authorizationTokenResponse?.accessToken}');
if (authorizationTokenResponse != null) {
discoveryUrl,
scopes,
);
final authorizationTokenResponse = await _appAuth.authorizeAndExchangeCode(
authorizationTokenRequest,
);
log('$runtimeType::getTokenOIDC(): token: ${authorizationTokenResponse.accessToken}');
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 _appAuth.endSession(EndSessionRequest(
idTokenHint: tokenId.uuid,
postLogoutRedirectUrl: config.logoutRedirectUrl,
discoveryUrl: config.discoveryUrl,
serviceConfiguration: authorizationServiceConfiguration,
preferEphemeralSession: true,
));
log('AuthenticationClientMobile::logoutOidc(): ${endSession?.state}');
return endSession?.state?.isNotEmpty == true;
Future<bool> logoutOidc(
TokenId tokenId,
OIDCConfiguration config,
OIDCDiscoveryResponse discoveryResponse,
) async {
final endSessionRequest = getEndSessionRequest(
tokenId,
config,
discoveryResponse,
);
final endSession = await _appAuth.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 {
final tokenResponse = await _appAuth.token(TokenRequest(
Future<TokenOIDC> refreshingTokensOIDC(
String clientId,
String redirectUrl,
String discoveryUrl,
List<String> scopes,
String refreshToken,
) async {
try {
final tokenRequest = getRefreshTokenRequest(
clientId,
redirectUrl,
discoveryUrl: discoveryUrl,
refreshToken: refreshToken,
scopes: scopes));
log('AuthenticationClientMobile::refreshingTokensOIDC(): refreshToken: ${tokenResponse?.accessToken}');
if (tokenResponse != null) {
final tokenOIDC = tokenResponse.toTokenOIDC(maybeAvailableRefreshToken: refreshToken);
discoveryUrl,
refreshToken,
scopes,
);
final tokenResponse = await _appAuth.token(tokenRequest);
log('$runtimeType::refreshingTokensOIDC():Token: ${tokenResponse.accessToken}');
final tokenOIDC = tokenResponse.toTokenOIDC(
maybeAvailableRefreshToken: refreshToken,
);
if (tokenOIDC.isTokenValid()) {
return tokenOIDC;
} else {
throw AccessTokenInvalidException();
}
} else {
throw NotFoundAccessTokenException();
} catch (e) {
logError('$runtimeType::refreshingTokensOIDC(): $e');
throw handleException(e);
}
}
@@ -104,7 +110,7 @@ class AuthenticationClientMobile implements AuthenticationClientBase {
intentFlags: ephemeralIntentFlags,
),
);
log('AuthenticationClientMobile::signInTwakeWorkplace():Uri = $uri');
log('$runtimeType::signInTwakeWorkplace():Uri = $uri');
return TokenOIDC.fromUri(uri);
}
@@ -117,7 +123,7 @@ class AuthenticationClientMobile implements AuthenticationClientBase {
intentFlags: ephemeralIntentFlags,
),
);
log('AuthenticationClientMobile::signUpTwakeWorkplace():Uri = $uri');
log('$runtimeType::signUpTwakeWorkplace():Uri = $uri');
return TokenOIDC.fromUri(uri);
}
}
@@ -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/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_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/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;
AuthenticationClientWeb(this._appAuthWeb);
@override
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl,
String discoveryUrl, List<String> scopes) async {
final authorizationTokenResponse = await _appAuthWeb.authorizeAndExchangeCode(AuthorizationTokenRequest(
Future<TokenOIDC> getTokenOIDC(
String clientId,
String redirectUrl,
String discoveryUrl,
List<String> scopes,
) async {
final authorizationTokenRequest = getAuthorizationTokenRequest(
clientId,
redirectUrl,
discoveryUrl: discoveryUrl,
scopes: scopes,
preferEphemeralSession: true));
log('AuthClientMobile::getTokenOIDC(): token: ${authorizationTokenResponse?.accessToken}');
if (authorizationTokenResponse != null) {
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();
}
} 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;
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 {
final tokenResponse = await _appAuthWeb.token(TokenRequest(
Future<TokenOIDC> refreshingTokensOIDC(
String clientId,
String redirectUrl,
String discoveryUrl,
List<String> scopes,
String refreshToken,
) async {
try {
final tokenRequest = getRefreshTokenRequest(
clientId,
redirectUrl,
discoveryUrl: discoveryUrl,
refreshToken: refreshToken,
grantType: 'refresh_token',
scopes: scopes));
if (tokenResponse != null) {
final tokenOIDC = tokenResponse.toTokenOIDC(maybeAvailableRefreshToken: refreshToken);
discoveryUrl,
refreshToken,
scopes,
);
final tokenResponse = await _appAuthWeb.token(tokenRequest);
final tokenOIDC = tokenResponse.toTokenOIDC(
maybeAvailableRefreshToken: refreshToken,
);
if (tokenOIDC.isTokenValid()) {
return tokenOIDC;
} else {
throw AccessTokenInvalidException();
}
} else {
throw NotFoundAccessTokenException();
} catch (e) {
logError('$runtimeType::refreshingTokensOIDC(): $e');
throw handleException(e);
}
}
@override
Future<void> authenticateOidcOnBrowser(String clientId, String redirectUrl,
String discoveryUrl, List<String> scopes) async {
Future<void> authenticateOidcOnBrowser(
String clientId,
String redirectUrl,
String discoveryUrl,
List<String> scopes,
) async {
await _appAuthWeb.authorizeAndExchangeCode(
AuthorizationTokenRequest(
clientId,
redirectUrl,
discoveryUrl: discoveryUrl,
scopes: scopes));
scopes: scopes,
),
);
}
@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/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/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/upload/data/network/file_uploader.dart';
import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart';
@@ -152,9 +153,16 @@ class AuthorizationInterceptors extends QueuedInterceptorsWrapper {
}
} catch (e) {
logError('AuthorizationInterceptors::onError:Exception: $e');
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);
}
}
}
Stream<List<int>>? _getDataUploadRequest(dynamic mapUploadExtra) {
try {
@@ -25,8 +25,6 @@ class InvalidBaseUrl extends AuthenticationException {
InvalidBaseUrl() : super(AuthenticationException.invalidBaseUrl);
}
class NotFoundAccessTokenException implements Exception {}
class AccessTokenInvalidException 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/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/oauth_authorization_error.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';
import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart';
@@ -28,20 +29,48 @@ class RemoteExceptionThrower extends ExceptionThrower {
void handleDioError(dynamic error) {
if (error is DioError) {
logError('RemoteExceptionThrower::throwException():type: ${error.type} | response: ${error.response} | error: ${error.error}');
if (error.response != null) {
if (error.response!.statusCode == HttpStatus.internalServerError) {
logError(
'RemoteExceptionThrower::throwException():type: ${error.type} | response: ${error.response} | error: ${error.error}',
);
final response = error.response;
final statusCode = response?.statusCode;
if (response != null) {
switch (statusCode) {
case HttpStatus.internalServerError:
throw const InternalServerError();
} else if (error.response!.statusCode == HttpStatus.badGateway) {
case HttpStatus.badGateway:
throw BadGateway();
} else if (error.response!.statusCode == HttpStatus.unauthorized) {
case HttpStatus.unauthorized:
throw const BadCredentialsException();
} else {
default:
throw UnknownError(
code: error.response!.statusCode,
message: error.response!.statusMessage);
code: statusCode,
message: response.statusMessage,
);
}
}
return _handleDioErrorWithoutResponse(error);
}
if (error is ErrorMethodResponseException) {
final errorResponse = error.errorResponse as ErrorMethodResponse;
if (errorResponse is CannotCalculateChangesMethodResponse) {
throw CannotCalculateChangesMethodResponseException();
} else {
throw MethodLevelErrors(
errorResponse.type,
message: errorResponse.description,
);
}
}
throw error;
}
void _handleDioErrorWithoutResponse(DioError error) {
switch (error.type) {
case DioErrorType.connectionTimeout:
throw ConnectionTimeout(message: error.message);
@@ -50,26 +79,16 @@ class RemoteExceptionThrower extends ExceptionThrower {
case DioErrorType.badResponse:
throw const BadCredentialsException();
default:
if (error.error is SocketException) {
final underlyingError = error.error;
if (underlyingError is SocketException) {
throw const SocketError();
} else if (error.error != null) {
throw UnknownError(message: error.error);
} else if (underlyingError is OAuthAuthorizationError) {
throw underlyingError;
} else if (underlyingError != null) {
throw UnknownError(message: underlyingError);
} else {
throw const UnknownError();
}
}
}
} else if (error is ErrorMethodResponseException) {
final errorResponse = error.errorResponse as ErrorMethodResponse;
if (errorResponse is CannotCalculateChangesMethodResponse) {
throw CannotCalculateChangesMethodResponseException();
} else {
throw MethodLevelErrors(
errorResponse.type,
message: errorResponse.description);
}
} else {
throw error;
}
}
}
+9 -1
View File
@@ -8,6 +8,7 @@ import 'package:core/presentation/utils/app_toast.dart';
import 'package:core/utils/app_logger.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package: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/exception/error_method_response_exception.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/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/oauth_authorization_error.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/delete_recipient_in_forwarding_state.dart';
@@ -98,7 +100,7 @@ class ToastManager {
return '[${exception.type.value}] ${exception.message}';
} else if (exception is SetError) {
return '[${exception.type.value}] ${exception.description}';
}else if (exception is PlatformException &&
} else if (exception is PlatformException &&
exception.message?.isNotEmpty == true) {
return exception.message!;
} else if (exception is NotGrantedPermissionStorageException) {
@@ -114,6 +116,12 @@ class ToastManager {
final firstError = mapErrors.values.first;
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) {
+8 -8
View File
@@ -697,27 +697,27 @@ packages:
dependency: "direct main"
description:
name: flutter_appauth
sha256: f2696d4cf437f627fa09bc4864afdd8c80273f2e293fde544b18202a627754b1
sha256: b09fa8e3eaba12ec341c69ec45063e06eb565304e24cc35caaf105bbae2e955c
url: "https://pub.dev"
source: hosted
version: "6.0.6"
version: "9.0.1"
flutter_appauth_platform_interface:
dependency: "direct main"
description:
name: flutter_appauth_platform_interface
sha256: "44feaa7058191b5d3cd7c9ff195262725773643121bcada172d49c2ddcff71cb"
sha256: fd2920b853d09741aff2e1178e044ea2ade0c87799cd8e63f094ab35b00fdf70
url: "https://pub.dev"
source: hosted
version: "6.0.0"
version: "9.0.0"
flutter_appauth_web:
dependency: "direct main"
description:
path: "."
ref: main
resolved-ref: "39f9806f27f45da34bb86b2b9b22847be9153516"
url: "https://github.com/CarlosPacheco/flutter_appauth_web.git"
ref: upgrade-flutter-appauth
resolved-ref: "0952346bf781bd87b93b8c61619f81a264be52db"
url: "https://github.com/linagora/flutter_appauth_web.git"
source: git
version: "0.0.3"
version: "1.0.0"
flutter_charset_detector:
dependency: transitive
description:
+4 -4
View File
@@ -82,8 +82,8 @@ dependencies:
flutter_appauth_web:
git:
url: https://github.com/CarlosPacheco/flutter_appauth_web.git
ref: main
url: https://github.com/linagora/flutter_appauth_web.git
ref: upgrade-flutter-appauth
flutter_date_range_picker:
git:
@@ -172,7 +172,7 @@ dependencies:
better_open_file: 3.6.4
flutter_appauth: 6.0.6
flutter_appauth: 9.0.1
percent_indicator: 4.2.2
@@ -206,7 +206,7 @@ dependencies:
debounce_throttle: 2.0.0
flutter_appauth_platform_interface: 6.0.0
flutter_appauth_platform_interface: 9.0.0
intl: 0.19.0