diff --git a/packages/uni_app/lib/session/flows/federated/client.dart b/packages/uni_app/lib/session/flows/federated/client.dart new file mode 100644 index 000000000..3079210b1 --- /dev/null +++ b/packages/uni_app/lib/session/flows/federated/client.dart @@ -0,0 +1,17 @@ +import 'package:http/http.dart' as http; +import 'package:uni/http/client/timeout.dart'; + +class FederatedDefaultClient extends http.BaseClient { + FederatedDefaultClient() + : inner = TimeoutClient( + http.Client(), + timeout: const Duration(seconds: 5), + ); + + final http.Client inner; + + @override + Future send(http.BaseRequest request) { + return inner.send(request); + } +} diff --git a/packages/uni_app/lib/session/flows/federated/initiator.dart b/packages/uni_app/lib/session/flows/federated/initiator.dart index 1e7387a0d..11896d702 100644 --- a/packages/uni_app/lib/session/flows/federated/initiator.dart +++ b/packages/uni_app/lib/session/flows/federated/initiator.dart @@ -1,6 +1,11 @@ +import 'dart:async'; + import 'package:http/http.dart' as http; import 'package:openid_client/openid_client.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:uni/session/exception.dart'; import 'package:uni/session/flows/base/initiator.dart'; +import 'package:uni/session/flows/federated/client.dart'; import 'package:uni/session/flows/federated/request.dart'; class FederatedSessionInitiator extends SessionInitiator { @@ -14,11 +19,30 @@ class FederatedSessionInitiator extends SessionInitiator { final String clientId; final Future Function(Flow flow) performAuthentication; + Future _handleOpenIdExceptions( + Future future, { + required AuthenticationException onError, + }) { + T reportExceptionAndFail(E error, StackTrace st) { + unawaited(Sentry.captureException(error, stackTrace: st)); + throw onError; + } + + return future + .onError(reportExceptionAndFail) + .onError(reportExceptionAndFail); + } + @override Future initiate([http.Client? httpClient]) async { - final issuer = await Issuer.discover(realm); - final client = Client(issuer, clientId, httpClient: httpClient); + httpClient ??= FederatedDefaultClient(); + final issuer = await _handleOpenIdExceptions( + Issuer.discover(realm, httpClient: httpClient), + onError: const AuthenticationException('Failed to discover OIDC issuer'), + ); + + final client = Client(issuer, clientId, httpClient: httpClient); final flow = Flow.authorizationCodeWithPKCE( client, scopes: [ @@ -32,7 +56,10 @@ class FederatedSessionInitiator extends SessionInitiator { ); final uri = await performAuthentication(flow); - final credential = await flow.callback(uri.queryParameters); + final credential = await _handleOpenIdExceptions( + flow.callback(uri.queryParameters), + onError: const AuthenticationException('Failed to execute flow callback'), + ); return FederatedSessionRequest(credential: credential); } diff --git a/packages/uni_app/lib/session/flows/federated/request.dart b/packages/uni_app/lib/session/flows/federated/request.dart index 99728aa55..2c619623e 100644 --- a/packages/uni_app/lib/session/flows/federated/request.dart +++ b/packages/uni_app/lib/session/flows/federated/request.dart @@ -1,10 +1,15 @@ +import 'dart:async'; + import 'package:http/http.dart' as http; import 'package:openid_client/openid_client.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:uni/controller/fetchers/faculties_fetcher.dart'; import 'package:uni/session/exception.dart'; import 'package:uni/session/flows/base/request.dart'; +import 'package:uni/session/flows/federated/client.dart'; import 'package:uni/session/flows/federated/session.dart'; import 'package:uni/sigarra/endpoints/oidc/oidc.dart'; +import 'package:uni/sigarra/endpoints/oidc/token/response.dart'; import 'package:uni/sigarra/options.dart'; class FederatedSessionUserInfo { @@ -38,18 +43,29 @@ class FederatedSessionRequest extends SessionRequest { final Credential credential; + TokenFailedResponse _reportExceptionAndFail( + E error, + StackTrace st, + ) { + unawaited(Sentry.captureException(error, stackTrace: st)); + return const TokenFailedResponse(); + } + @override Future perform([http.Client? httpClient]) async { - final client = httpClient ?? http.Client(); + httpClient ??= FederatedDefaultClient(); - final authorizedClient = credential.createHttpClient(client); + final authorizedClient = credential.createHttpClient(httpClient); final oidc = SigarraOidc(); - final response = await oidc.token.call( - options: BaseRequestOptions( - client: authorizedClient, - ), - ); + final response = await oidc.token + .call( + options: BaseRequestOptions( + client: authorizedClient, + ), + ) + .onError(_reportExceptionAndFail) + .onError(_reportExceptionAndFail); if (!response.success) { throw const AuthenticationException('Failed to get OIDC token'); @@ -65,7 +81,7 @@ class FederatedSessionRequest extends SessionRequest { credential: credential, ); - final faculties = await getStudentFaculties(tempSession, client); + final faculties = await getStudentFaculties(tempSession, httpClient); return FederatedSession( username: userInfo.username,