Skip to content

fix(auth)!: Fetch Auth Session offline behavior #2585

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
de04055
feat(auth): update fetch auth session behavior
Jordan-Nelson Jan 6, 2023
db43e95
fix(auth): remove getAWSCredentials arg from native plugin
Jordan-Nelson Jan 6, 2023
c728e66
chore: update federateToIdentityPool test
Jordan-Nelson Jan 6, 2023
09bf02a
chore: CognitoAuthSessionResult doc comments
Jordan-Nelson Jan 9, 2023
2075777
chore: add/update tests for fetchAuthSession
Jordan-Nelson Jan 17, 2023
453b6f2
chore: add tests for unauth access, fix failing tests
Jordan-Nelson Jan 18, 2023
558dbfe
chore: update fetchAuthSession integ tests
Jordan-Nelson Jan 18, 2023
c8a0992
fix(authenticator)!: Update authenticator offline behavior
Jordan-Nelson Jan 18, 2023
c022843
chore: add comment to CognitoAuthSession
Jordan-Nelson Jan 18, 2023
36c880d
chore: update failing android test
Jordan-Nelson Jan 18, 2023
5c6aadf
chore: expose results from CognitoAuthSession, throw AuthExceptions
Jordan-Nelson Jan 23, 2023
3a5806b
chore: make tests more realistic
Jordan-Nelson Jan 23, 2023
0130b1a
chore: make AWSResult AWSDebuggable
Jordan-Nelson Jan 23, 2023
116c625
chore: early exit for no identity pool
Jordan-Nelson Jan 23, 2023
f2ee6f3
chore: update CognitoAuthSession.toJson
Jordan-Nelson Jan 24, 2023
165e56f
chore: add doc comment to AWSResultType
Jordan-Nelson Jan 24, 2023
ed256cd
chore: update fetchAuthSession stub
Jordan-Nelson Jan 24, 2023
63700c1
chore: undo sample app changes
Jordan-Nelson Jan 24, 2023
17dc8b9
chore: simplify AuthProvider
Jordan-Nelson Jan 24, 2023
381931c
chore: revert changes to SignedOutException
Jordan-Nelson Jan 25, 2023
0ad80c3
chore: add valueOrNull to AWSResult
Jordan-Nelson Jan 25, 2023
16524b8
chore: add back members to CognitoAuthSession, update toJson
Jordan-Nelson Jan 25, 2023
84a3ee9
chore: add getAWSCredentials back as deprecated
Jordan-Nelson Jan 25, 2023
d220427
chore: remove empty CognitoSessionOptions
Jordan-Nelson Jan 25, 2023
c3cf041
chore: refactor exception handling
Jordan-Nelson Jan 25, 2023
760f539
chore: force unwrap awsCredentials & identityId
Jordan-Nelson Jan 25, 2023
2a94899
chore: update authenticator to catch Exception not object
Jordan-Nelson Jan 25, 2023
9c4ec36
chore: add stacktrace to AWSResult
Jordan-Nelson Jan 25, 2023
dc791a5
fix: amplify_test
Jordan-Nelson Jan 25, 2023
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
1 change: 1 addition & 0 deletions packages/amplify_core/lib/amplify_core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export 'src/types/exception/codegen_exception.dart';
export 'src/types/exception/error/amplify_error.dart';
export 'src/types/exception/error/configuration_error.dart';
export 'src/types/exception/error/plugin_error.dart';
export 'src/types/exception/network_exception.dart';
export 'src/types/exception/url_launcher_exception.dart';

/// Model-based types used in datastore and API
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ abstract class AuthException extends AmplifyException with AWSDebuggable {
underlyingException: e.underlyingException,
);
}
if (e is AWSHttpException) {
return NetworkException(
'The request failed due to a network error.',
recoverySuggestion: 'Ensure that you have an active network connection',
underlyingException: e,
);
}
String message;
try {
message = (e as dynamic).message as String;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import 'package:amplify_core/amplify_core.dart';

/// {@template amplify_core.network_exception}
/// Exception thrown when the requested operation fails due to a network
/// failure.
/// {@endtemplate}
class NetworkException extends AmplifyException
implements AuthException, StorageException {
/// {@macro amplify_core.network_exception}
const NetworkException(
super.message, {
super.recoverySuggestion,
super.underlyingException,
});
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// ignore_for_file: depend_on_referenced_packages, implementation_imports, invalid_use_of_internal_member

import 'dart:core';

import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
import 'package:amplify_auth_cognito_dart/src/jwt/jwt.dart';
import 'package:amplify_auth_cognito_dart/src/model/auth_result.dart';
import 'package:amplify_core/amplify_core.dart';

const usernameExistsException = UsernameExistsException(
Expand Down Expand Up @@ -206,7 +210,36 @@ class AmplifyAuthCognitoStub extends AuthPluginInterface
Future<AuthSession> fetchAuthSession({
AuthSessionOptions? options,
}) async {
return CognitoAuthSession(isSignedIn: _isSignedIn());
if (_currentUser == null) {
return const CognitoAuthSession(
isSignedIn: false,
userPoolTokensResult: AuthResult.error(
SignedOutException('There is no user signed in.'),
),
userSubResult: AuthResult.error(
SignedOutException('There is no user signed in.'),
),
credentialsResult: AuthResult.error(
UnknownException('credentials not available in mocks'),
),
identityIdResult: AuthResult.error(
UnknownException('identityId not available in mocks'),
),
);
}
final userPoolTokens = _currentUser!.userPoolTokens;
final userSub = _currentUser!.sub;
return CognitoAuthSession(
isSignedIn: true,
userPoolTokensResult: AuthResult.success(userPoolTokens),
userSubResult: AuthResult.success(userSub),
credentialsResult: const AuthResult.error(
UnknownException('credentials not available in mocks'),
),
identityIdResult: const AuthResult.error(
UnknownException('identityId not available in mocks'),
),
);
}

@override
Expand Down Expand Up @@ -380,6 +413,36 @@ class MockCognitoUser {
required this.email,
});

CognitoUserPoolTokens get userPoolTokens {
final accessToken = JsonWebToken(
header: const JsonWebHeader(algorithm: Algorithm.hmacSha256),
claims: JsonWebClaims(
subject: sub,
expiration: DateTime.now().add(const Duration(minutes: 60)),
customClaims: {
'username': username,
},
),
signature: const [],
);
const refreshToken = 'refreshToken';
final idToken = JsonWebToken(
header: const JsonWebHeader(algorithm: Algorithm.hmacSha256),
claims: JsonWebClaims(
subject: sub,
customClaims: {
'cognito:username': username,
},
),
signature: const [],
);
return CognitoUserPoolTokens(
accessToken: accessToken,
refreshToken: refreshToken,
idToken: idToken,
);
}

MockCognitoUser copyWith({
String? sub,
String? username,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,8 @@ void main({bool useExistingTestUser = false}) {
// would do if that was the auth mode.
final authSession =
await Amplify.Auth.fetchAuthSession() as CognitoAuthSession;
final accessToken = authSession.userPoolTokens?.accessToken.raw;
if (accessToken == null) {
throw const AuthNotAuthorizedException(
'Could not get access token from cognito.',
recoverySuggestion: 'Ensure test user signed in.',
);
}
final accessToken =
authSession.userPoolTokensResult.value.accessToken.raw;
final headers = {AWSHeaders.authorization: accessToken};
final reqThatShouldWork = GraphQLRequest<Blog>(
document: reqThatFails.document,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ void main() {

final session =
await Amplify.Auth.fetchAuthSession() as CognitoAuthSession;
expect(session.userPoolTokens, isNotNull);
expect(session.userPoolTokensResult.value, isNotNull);

final apiUrl = config.api!.awsPlugin!.values
.singleWhere((e) => e.endpointType == EndpointType.rest)
Expand All @@ -79,7 +79,8 @@ void main() {
),
headers: {
AWSHeaders.accept: 'application/json;charset=utf-8',
AWSHeaders.authorization: session.userPoolTokens!.idToken.raw,
AWSHeaders.authorization:
session.userPoolTokensResult.value.idToken.raw,
},
body: utf8.encode(payload),
);
Expand Down Expand Up @@ -115,10 +116,8 @@ void main() {
final cognitoPlugin = Amplify.Auth.getPlugin(
AmplifyAuthCognito.pluginKey,
);
final session = await cognitoPlugin.fetchAuthSession(
options: const CognitoSessionOptions(getAWSCredentials: true),
);
expect(session.credentials, isNotNull);
final session = await cognitoPlugin.fetchAuthSession();
expect(session.credentialsResult.value, isNotNull);

final restApi = config.api!.awsPlugin!.values
.singleWhere((e) => e.endpointType == EndpointType.rest);
Expand Down Expand Up @@ -170,10 +169,8 @@ void main() {
final cognitoPlugin = Amplify.Auth.getPlugin(
AmplifyAuthCognito.pluginKey,
);
final session = await cognitoPlugin.fetchAuthSession(
options: const CognitoSessionOptions(getAWSCredentials: true),
);
expect(session.credentials, isNotNull);
final session = await cognitoPlugin.fetchAuthSession();
expect(session.credentialsResult.value, isNotNull);

final restApi = config.api!.awsPlugin!.values
.singleWhere((e) => e.endpointType == EndpointType.rest);
Expand Down Expand Up @@ -226,10 +223,8 @@ void main() {
final cognitoPlugin = Amplify.Auth.getPlugin(
AmplifyAuthCognito.pluginKey,
);
final session = await cognitoPlugin.fetchAuthSession(
options: const CognitoSessionOptions(getAWSCredentials: true),
);
expect(session.credentials, isNotNull);
final session = await cognitoPlugin.fetchAuthSession();
expect(session.credentialsResult.value, isNotNull);

final restOperation = Amplify.API.post(
'/',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ void main() {
expect(signInResult.nextStep.signInStep, 'DONE');

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

asyncTest('replaces unauthenticated identity', (_) async {
// Get unauthenticated identity
final unauthSession = await cognitoPlugin.fetchAuthSession(
options: const CognitoSessionOptions(getAWSCredentials: true),
);
final unauthSession = await cognitoPlugin.fetchAuthSession();

final authSession = await federateToIdentityPool();
expect(
authSession.identityId,
unauthSession.identityId,
unauthSession.identityIdResult.value,
reason: 'Should retain unauthenticated identity',
);
expect(
authSession.credentials,
isNot(unauthSession.credentials),
isNot(unauthSession.credentialsResult.value),
reason: 'Should get new credentials',
);
});

asyncTest('can specify identity ID', (_) async {
// Get unauthenticated identity (doesn't matter, just need identity ID)
final unauthSession = await cognitoPlugin.fetchAuthSession(
options: const CognitoSessionOptions(getAWSCredentials: true),
);
final identityId = unauthSession.identityId!;
final unauthSession = await cognitoPlugin.fetchAuthSession();
final identityId = unauthSession.identityIdResult.value;

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

final userPoolTokens =
(await cognitoPlugin.fetchAuthSession()).userPoolTokens!;
(await cognitoPlugin.fetchAuthSession()).userPoolTokensResult.value;
// Clear but do not sign out so that tokens are still valid.
// ignore: invalid_use_of_protected_member
await cognitoPlugin.plugin.stateMachine.dispatch(
Expand Down Expand Up @@ -176,23 +172,23 @@ void main() {
});

asyncTest('can clear federation', (_) async {
await federateToIdentityPool();
final federateToIdentityPoolResult = await federateToIdentityPool();

await expectLater(
cognitoPlugin.clearFederationToIdentityPool(),
completes,
);

final clearedSession = await cognitoPlugin.fetchAuthSession();
final unauthSession = await cognitoPlugin.fetchAuthSession();
expect(
clearedSession.identityId,
isNull,
reason: 'Should clear session',
unauthSession.identityIdResult.value,
isNot(federateToIdentityPoolResult.identityId),
reason: 'Should clear session and refetch',
);
expect(
clearedSession.credentials,
isNull,
reason: 'Should clear session',
unauthSession.credentialsResult.value,
isNot(federateToIdentityPoolResult.credentials),
reason: 'Should clear session and refetch',
);
});

Expand Down
Loading