Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/apptive_grid_core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2.3.1

- **FIX**: A saved token whose refresh fails for want of a connection no longer falls through to `authenticate()`. Only the auth server rejecting the credential (`OpenIdException`) leads to the login; any other failure keeps the credential and propagates from `checkAuthentication`, so an app that is merely offline can answer from its cache instead of sending the user to a login page they cannot load. `performSetup` completes either way, so `isAuthenticated` never hangs on a failed restore.

## 2.3.0

- **FEAT**: Add `Address` DataType, `Address` model and `AddressDataEntity`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,16 @@ class ApptiveGridAuthenticator {

if (client.options.authenticationOptions.persistCredentials) {
_authenticationStorage ??= const FlutterSecureStorageCredentialStorage();
await checkAuthentication(requestNewToken: false)
.then((_) => _setupCompleter.complete());
try {
await checkAuthentication(requestNewToken: false);
} catch (error) {
// Restoring the session failed for want of a connection — a
// rejected credential is handled inside and never thrown. Start
// without a token; the next authenticated request tries again.
debugPrint('Could not restore the session: $error');
} finally {
_setupCompleter.complete();
}
} else {
_setupCompleter.complete();
}
Expand Down Expand Up @@ -342,11 +350,17 @@ class ApptiveGridAuthenticator {
setToken(token);
return;
} on OpenIdException catch (openIdError) {
// The auth server turned the credential down: the session is
// over, and a login is the only way on.
setCredential(null);
debugPrint('Could not refresh saved token: $openIdError');
} catch (error) {
debugPrint('Error refreshing token: $error');
}
// Anything else — no connection, a timeout, a server having a
// bad day — says nothing about the session. The credential stays
// for the next attempt and the failure propagates, instead of
// falling through to a login: sending someone who is merely
// offline to a login page they cannot load helps nobody, and the
// caller may well have a cached answer to fall back on.
} else {
setCredential(null);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/apptive_grid_core/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: apptive_grid_core
description: Core Library for ApptiveGrid used to provide general ApptiveGrid functionality to other Packages or Apps
version: 2.3.0
version: 2.3.1
homepage: https://www.apptivegrid.de
repository: https://github.com/ApptiveGrid/apptive_grid_flutter/tree/main/packages/apptive_grid_core

Expand Down
81 changes: 76 additions & 5 deletions packages/apptive_grid_core/test/authenticator_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1060,8 +1060,12 @@ void main() {

test(
'Storage Provided '
'Error '
'Calls authenticate', () async {
'Connection error while refreshing '
'Keeps the credential, throws, does not log in', () async {
// No connection is not "no session". Sending the user to a login page
// they cannot load — over an app that could answer from its cache —
// was the old behaviour. Now the failure surfaces and the credential
// stays for the next attempt.
final tokenTime = DateTime.now();
final httpClient = MockHttpClient();
final authClient = MockAuthClient();
Expand Down Expand Up @@ -1134,10 +1138,77 @@ void main() {
when(() => client.issuer).thenReturn(zweidenkerIssuer);
when(() => client.clientId).thenReturn('clientId');

await authenticator.checkAuthentication();
await expectLater(authenticator.checkAuthentication(), throwsException);

verify(testAuthenticator.authorize).called(1);
expect(await authenticator.isAuthenticated, equals(true));
verifyNever(testAuthenticator.authorize);
verifyNever(() => storage.saveCredential(null));
expect(await authenticator.isAuthenticatedWithToken, isFalse);
});

test(
'Storage Provided '
'Connection error while restoring at setup '
'Completes setup without a token', () async {
// The constructor restores the session silently. When that fails for
// want of a connection the authenticator must still finish setting
// up, or isAuthenticated would wait forever.
final tokenTime = DateTime.now();
final httpClient = MockHttpClient();
final authClient = MockAuthClient();
when(() => authClient.issuer).thenReturn(zweidenkerIssuer);
when(() => authClient.clientSecret).thenReturn('');
when(() => authClient.clientId).thenReturn('test');
when(() => authClient.httpClient).thenReturn(httpClient);

final tokenResponse = TokenResponse.fromJson({
'token_type': 'Bearer',
'access_token': '12345',
'expires_at': tokenTime.millisecondsSinceEpoch,
'expires_in': tokenTime.microsecondsSinceEpoch,
});
final credential = Credential.fromJson({
'issuer': authClient.issuer.metadata.toJson(),
'client_id': authClient.clientId,
'client_secret': authClient.clientSecret,
'token': tokenResponse.toJson(),
'nonce': null,
});
when(
() => httpClient.post(
any(),
body: any(named: 'body'),
headers: any(named: 'headers'),
encoding: any(named: 'encoding'),
),
).thenAnswer(
(invocation) => Future.error(Exception('Network is unreachable')),
);

final storage = MockAuthenticationStorage();
when(() => storage.credential)
.thenAnswer((invocation) => jsonEncode(credential.toJson()));
final agClient = MockApptiveGridClient();
when(() => agClient.options).thenReturn(
const ApptiveGridOptions(
authenticationOptions: ApptiveGridAuthenticationOptions(
autoAuthenticate: true,
persistCredentials: true,
),
),
);
final testAuthenticator = MockAuthenticator();

authenticator = ApptiveGridAuthenticator(
client: agClient,
authenticationStorage: storage,
httpClient: httpClient,
);
authenticator.testAuthenticator = testAuthenticator;
authenticator.setAuthClient(authClient);

expect(await authenticator.isAuthenticatedWithToken, isFalse);
verifyNever(testAuthenticator.authorize);
verifyNever(() => storage.saveCredential(null));
});

test(
Expand Down
Loading