Skip to content

Commit dc2a3f4

Browse files
fix(auth)!: Fetch Auth Session offline behavior (#2585)
* feat(auth): update fetch auth session behavior * fix(auth): remove getAWSCredentials arg from native plugin * chore: update federateToIdentityPool test * chore: CognitoAuthSessionResult doc comments * chore: add/update tests for fetchAuthSession * chore: add tests for unauth access, fix failing tests * chore: update fetchAuthSession integ tests * fix(authenticator)!: Update authenticator offline behavior * chore: add comment to CognitoAuthSession * chore: update failing android test * chore: expose results from CognitoAuthSession, throw AuthExceptions * chore: make tests more realistic * chore: make AWSResult AWSDebuggable * chore: early exit for no identity pool * chore: update CognitoAuthSession.toJson * chore: add doc comment to AWSResultType * chore: update fetchAuthSession stub * chore: undo sample app changes * chore: simplify AuthProvider * chore: revert changes to SignedOutException * chore: add valueOrNull to AWSResult * chore: add back members to CognitoAuthSession, update toJson * chore: add getAWSCredentials back as deprecated * chore: remove empty CognitoSessionOptions * chore: refactor exception handling * chore: force unwrap awsCredentials & identityId * chore: update authenticator to catch Exception not object * chore: add stacktrace to AWSResult * fix: amplify_test
1 parent 317a409 commit dc2a3f4

File tree

44 files changed

+1702
-916
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1702
-916
lines changed

packages/amplify_core/lib/amplify_core.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export 'src/types/exception/codegen_exception.dart';
8181
export 'src/types/exception/error/amplify_error.dart';
8282
export 'src/types/exception/error/configuration_error.dart';
8383
export 'src/types/exception/error/plugin_error.dart';
84+
export 'src/types/exception/network_exception.dart';
8485
export 'src/types/exception/url_launcher_exception.dart';
8586

8687
/// Model-based types used in datastore and API

packages/amplify_core/lib/src/types/exception/auth/auth_exception.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ abstract class AuthException extends AmplifyException with AWSDebuggable {
2828
underlyingException: e.underlyingException,
2929
);
3030
}
31+
if (e is AWSHttpException) {
32+
return NetworkException(
33+
'The request failed due to a network error.',
34+
recoverySuggestion: 'Ensure that you have an active network connection',
35+
underlyingException: e,
36+
);
37+
}
3138
String message;
3239
try {
3340
message = (e as dynamic).message as String;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import 'package:amplify_core/amplify_core.dart';
5+
6+
/// {@template amplify_core.network_exception}
7+
/// Exception thrown when the requested operation fails due to a network
8+
/// failure.
9+
/// {@endtemplate}
10+
class NetworkException extends AmplifyException
11+
implements AuthException, StorageException {
12+
/// {@macro amplify_core.network_exception}
13+
const NetworkException(
14+
super.message, {
15+
super.recoverySuggestion,
16+
super.underlyingException,
17+
});
18+
}

packages/amplify_test/lib/src/stubs/amplify_auth_cognito_stub.dart

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
// ignore_for_file: depend_on_referenced_packages, implementation_imports, invalid_use_of_internal_member
5+
46
import 'dart:core';
57

68
import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
9+
import 'package:amplify_auth_cognito_dart/src/jwt/jwt.dart';
10+
import 'package:amplify_auth_cognito_dart/src/model/auth_result.dart';
711
import 'package:amplify_core/amplify_core.dart';
812

913
const usernameExistsException = UsernameExistsException(
@@ -206,7 +210,36 @@ class AmplifyAuthCognitoStub extends AuthPluginInterface
206210
Future<AuthSession> fetchAuthSession({
207211
AuthSessionOptions? options,
208212
}) async {
209-
return CognitoAuthSession(isSignedIn: _isSignedIn());
213+
if (_currentUser == null) {
214+
return const CognitoAuthSession(
215+
isSignedIn: false,
216+
userPoolTokensResult: AuthResult.error(
217+
SignedOutException('There is no user signed in.'),
218+
),
219+
userSubResult: AuthResult.error(
220+
SignedOutException('There is no user signed in.'),
221+
),
222+
credentialsResult: AuthResult.error(
223+
UnknownException('credentials not available in mocks'),
224+
),
225+
identityIdResult: AuthResult.error(
226+
UnknownException('identityId not available in mocks'),
227+
),
228+
);
229+
}
230+
final userPoolTokens = _currentUser!.userPoolTokens;
231+
final userSub = _currentUser!.sub;
232+
return CognitoAuthSession(
233+
isSignedIn: true,
234+
userPoolTokensResult: AuthResult.success(userPoolTokens),
235+
userSubResult: AuthResult.success(userSub),
236+
credentialsResult: const AuthResult.error(
237+
UnknownException('credentials not available in mocks'),
238+
),
239+
identityIdResult: const AuthResult.error(
240+
UnknownException('identityId not available in mocks'),
241+
),
242+
);
210243
}
211244

212245
@override
@@ -380,6 +413,36 @@ class MockCognitoUser {
380413
required this.email,
381414
});
382415

416+
CognitoUserPoolTokens get userPoolTokens {
417+
final accessToken = JsonWebToken(
418+
header: const JsonWebHeader(algorithm: Algorithm.hmacSha256),
419+
claims: JsonWebClaims(
420+
subject: sub,
421+
expiration: DateTime.now().add(const Duration(minutes: 60)),
422+
customClaims: {
423+
'username': username,
424+
},
425+
),
426+
signature: const [],
427+
);
428+
const refreshToken = 'refreshToken';
429+
final idToken = JsonWebToken(
430+
header: const JsonWebHeader(algorithm: Algorithm.hmacSha256),
431+
claims: JsonWebClaims(
432+
subject: sub,
433+
customClaims: {
434+
'cognito:username': username,
435+
},
436+
),
437+
signature: const [],
438+
);
439+
return CognitoUserPoolTokens(
440+
accessToken: accessToken,
441+
refreshToken: refreshToken,
442+
idToken: idToken,
443+
);
444+
}
445+
383446
MockCognitoUser copyWith({
384447
String? sub,
385448
String? username,

packages/api/amplify_api/example/integration_test/graphql/iam_test.dart

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -212,13 +212,8 @@ void main({bool useExistingTestUser = false}) {
212212
// would do if that was the auth mode.
213213
final authSession =
214214
await Amplify.Auth.fetchAuthSession() as CognitoAuthSession;
215-
final accessToken = authSession.userPoolTokens?.accessToken.raw;
216-
if (accessToken == null) {
217-
throw const AuthNotAuthorizedException(
218-
'Could not get access token from cognito.',
219-
recoverySuggestion: 'Ensure test user signed in.',
220-
);
221-
}
215+
final accessToken =
216+
authSession.userPoolTokensResult.value.accessToken.raw;
222217
final headers = {AWSHeaders.authorization: accessToken};
223218
final reqThatShouldWork = GraphQLRequest<Blog>(
224219
document: reqThatFails.document,

packages/auth/amplify_auth_cognito/example/integration_test/custom_authorizer_test.dart

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ void main() {
5454

5555
final session =
5656
await Amplify.Auth.fetchAuthSession() as CognitoAuthSession;
57-
expect(session.userPoolTokens, isNotNull);
57+
expect(session.userPoolTokensResult.value, isNotNull);
5858

5959
final apiUrl = config.api!.awsPlugin!.values
6060
.singleWhere((e) => e.endpointType == EndpointType.rest)
@@ -79,7 +79,8 @@ void main() {
7979
),
8080
headers: {
8181
AWSHeaders.accept: 'application/json;charset=utf-8',
82-
AWSHeaders.authorization: session.userPoolTokens!.idToken.raw,
82+
AWSHeaders.authorization:
83+
session.userPoolTokensResult.value.idToken.raw,
8384
},
8485
body: utf8.encode(payload),
8586
);
@@ -115,10 +116,8 @@ void main() {
115116
final cognitoPlugin = Amplify.Auth.getPlugin(
116117
AmplifyAuthCognito.pluginKey,
117118
);
118-
final session = await cognitoPlugin.fetchAuthSession(
119-
options: const CognitoSessionOptions(getAWSCredentials: true),
120-
);
121-
expect(session.credentials, isNotNull);
119+
final session = await cognitoPlugin.fetchAuthSession();
120+
expect(session.credentialsResult.value, isNotNull);
122121

123122
final restApi = config.api!.awsPlugin!.values
124123
.singleWhere((e) => e.endpointType == EndpointType.rest);
@@ -170,10 +169,8 @@ void main() {
170169
final cognitoPlugin = Amplify.Auth.getPlugin(
171170
AmplifyAuthCognito.pluginKey,
172171
);
173-
final session = await cognitoPlugin.fetchAuthSession(
174-
options: const CognitoSessionOptions(getAWSCredentials: true),
175-
);
176-
expect(session.credentials, isNotNull);
172+
final session = await cognitoPlugin.fetchAuthSession();
173+
expect(session.credentialsResult.value, isNotNull);
177174

178175
final restApi = config.api!.awsPlugin!.values
179176
.singleWhere((e) => e.endpointType == EndpointType.rest);
@@ -226,10 +223,8 @@ void main() {
226223
final cognitoPlugin = Amplify.Auth.getPlugin(
227224
AmplifyAuthCognito.pluginKey,
228225
);
229-
final session = await cognitoPlugin.fetchAuthSession(
230-
options: const CognitoSessionOptions(getAWSCredentials: true),
231-
);
232-
expect(session.credentials, isNotNull);
226+
final session = await cognitoPlugin.fetchAuthSession();
227+
expect(session.credentialsResult.value, isNotNull);
233228

234229
final restOperation = Amplify.API.post(
235230
'/',

packages/auth/amplify_auth_cognito/example/integration_test/federated_sign_in_test.dart

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ void main() {
6262
expect(signInResult.nextStep.signInStep, 'DONE');
6363

6464
final userPoolTokens =
65-
(await cognitoPlugin.fetchAuthSession()).userPoolTokens!;
65+
(await cognitoPlugin.fetchAuthSession()).userPoolTokensResult.value;
6666
// Clear but do not sign out so that tokens are still valid.
6767
// ignore: invalid_use_of_protected_member
6868
await cognitoPlugin.plugin.stateMachine.dispatch(
@@ -105,29 +105,25 @@ void main() {
105105

106106
asyncTest('replaces unauthenticated identity', (_) async {
107107
// Get unauthenticated identity
108-
final unauthSession = await cognitoPlugin.fetchAuthSession(
109-
options: const CognitoSessionOptions(getAWSCredentials: true),
110-
);
108+
final unauthSession = await cognitoPlugin.fetchAuthSession();
111109

112110
final authSession = await federateToIdentityPool();
113111
expect(
114112
authSession.identityId,
115-
unauthSession.identityId,
113+
unauthSession.identityIdResult.value,
116114
reason: 'Should retain unauthenticated identity',
117115
);
118116
expect(
119117
authSession.credentials,
120-
isNot(unauthSession.credentials),
118+
isNot(unauthSession.credentialsResult.value),
121119
reason: 'Should get new credentials',
122120
);
123121
});
124122

125123
asyncTest('can specify identity ID', (_) async {
126124
// Get unauthenticated identity (doesn't matter, just need identity ID)
127-
final unauthSession = await cognitoPlugin.fetchAuthSession(
128-
options: const CognitoSessionOptions(getAWSCredentials: true),
129-
);
130-
final identityId = unauthSession.identityId!;
125+
final unauthSession = await cognitoPlugin.fetchAuthSession();
126+
final identityId = unauthSession.identityIdResult.value;
131127

132128
final signInResult = await cognitoPlugin.signIn(
133129
username: username,
@@ -136,7 +132,7 @@ void main() {
136132
expect(signInResult.nextStep.signInStep, 'DONE');
137133

138134
final userPoolTokens =
139-
(await cognitoPlugin.fetchAuthSession()).userPoolTokens!;
135+
(await cognitoPlugin.fetchAuthSession()).userPoolTokensResult.value;
140136
// Clear but do not sign out so that tokens are still valid.
141137
// ignore: invalid_use_of_protected_member
142138
await cognitoPlugin.plugin.stateMachine.dispatch(
@@ -176,23 +172,23 @@ void main() {
176172
});
177173

178174
asyncTest('can clear federation', (_) async {
179-
await federateToIdentityPool();
175+
final federateToIdentityPoolResult = await federateToIdentityPool();
180176

181177
await expectLater(
182178
cognitoPlugin.clearFederationToIdentityPool(),
183179
completes,
184180
);
185181

186-
final clearedSession = await cognitoPlugin.fetchAuthSession();
182+
final unauthSession = await cognitoPlugin.fetchAuthSession();
187183
expect(
188-
clearedSession.identityId,
189-
isNull,
190-
reason: 'Should clear session',
184+
unauthSession.identityIdResult.value,
185+
isNot(federateToIdentityPoolResult.identityId),
186+
reason: 'Should clear session and refetch',
191187
);
192188
expect(
193-
clearedSession.credentials,
194-
isNull,
195-
reason: 'Should clear session',
189+
unauthSession.credentialsResult.value,
190+
isNot(federateToIdentityPoolResult.credentials),
191+
reason: 'Should clear session and refetch',
196192
);
197193
});
198194

0 commit comments

Comments
 (0)