Skip to content
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

Feat (Auth): Add fetchCurrentDevice API #5251

Merged
merged 26 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c97bc6e
feat: interface implementation for fetchCurrentDevice
hahnandrew Jun 25, 2024
fab82e5
feat: added categories for fetchCurrentDevice
hahnandrew Jun 25, 2024
6f09fee
fix: changed docs link from gen1 -> gen2
hahnandrew Jun 25, 2024
4127efd
feat: added core docs
hahnandrew Jun 25, 2024
1ee6921
fix: changed docs wording
hahnandrew Jun 25, 2024
bb8618c
feat: added integration test suite
hahnandrew Jun 25, 2024
2e7fab7
feat: added methods for integration test suite
hahnandrew Jun 25, 2024
fa5ab69
fix: edit test suite title
hahnandrew Jun 25, 2024
4f9a5e7
feat: added unit tests
hahnandrew Jun 25, 2024
808da1c
feat: added stub for integration test
hahnandrew Jun 25, 2024
e809ae3
feat: implemented fetchCurrentDevice API
hahnandrew Jun 25, 2024
3822c43
fix: variable name change for clarity
hahnandrew Jun 27, 2024
e68304c
chore: append test to file name
hahnandrew Jul 1, 2024
63b7c63
test: restructure fetchcurrentdevice test
hahnandrew Jul 15, 2024
ebc177a
test: improve integration test wording
hahnandrew Jul 16, 2024
e5287a5
Update packages/auth/amplify_auth_cognito_test/test/plugin/fetch_curr…
hahnandrew Jul 16, 2024
e3801cf
Update packages/auth/amplify_auth_cognito_test/test/plugin/fetch_curr…
hahnandrew Jul 16, 2024
190dca9
Update packages/auth/amplify_auth_cognito_test/test/plugin/fetch_curr…
hahnandrew Jul 16, 2024
7f44479
Update packages/auth/amplify_auth_cognito_test/test/plugin/fetch_curr…
hahnandrew Jul 16, 2024
52677c7
test: improve wording for clarity
hahnandrew Jul 16, 2024
3d932d3
docs: change naming for api example
hahnandrew Aug 7, 2024
0feae85
temp: generate goldens png
hahnandrew Aug 7, 2024
461f350
test: fixing context mounted issue
hahnandrew Aug 7, 2024
6ef2aab
chore: add todo comment about deprecated member use
hahnandrew Aug 8, 2024
e176318
Merge branch 'main' into feat/fetchCurrentDevice
hahnandrew Aug 12, 2024
9d6780f
chore: update test suite fetchcurrentdevice api
hahnandrew Aug 12, 2024
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
11 changes: 11 additions & 0 deletions packages/amplify_core/doc/lib/auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,17 @@ Future<void> forgetSpecificDevice(AuthDevice myDevice) async {
}
// #enddocregion forget-specific-device

// #docregion fetch-current-device
Future<void> fetchCurrentDevice() async {
try {
final device = await Amplify.Auth.fetchCurrentDevice();
safePrint('Device: $device');
} on AuthException catch (e) {
safePrint('Fetch current device failed with error: $e');
}
}
// #enddocregion fetch-current-device

// #docregion fetch-devices
Future<void> fetchAllDevices() async {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1355,6 +1355,37 @@ class AuthCategory extends AmplifyCategory<AuthPluginInterface> {
() => defaultPlugin.rememberDevice(),
);

/// {@template amplify_core.amplify_auth_category.fetch_current_device}
/// Retrieves the current device.
///
/// For more information about device tracking, see the
/// [Amplify docs](https://docs.amplify.aws/flutter/build-a-backend/auth/manage-users/manage-devices/#fetch-current-device).
///
/// ## Examples
///
/// <?code-excerpt "doc/lib/auth.dart" region="imports"?>
/// ```dart
/// import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
/// import 'package:amplify_flutter/amplify_flutter.dart';
/// ```
///
/// <?code-excerpt "doc/lib/auth.dart" region="fetch-current-device"?>
/// ```dart
/// Future<AuthDevice> getCurrentUserDevice() async {
/// try {
/// final device = await Amplify.Auth.fetchCurrentDevice();
/// safePrint('Device: $device');
/// } on AuthException catch (e) {
/// safePrint('Fetch current device failed with error: $e');
/// }
/// }
/// ```
/// {@endtemplate}
Future<AuthDevice> fetchCurrentDevice() => identifyCall(
AuthCategoryMethod.fetchCurrentDevice,
() => defaultPlugin.fetchCurrentDevice(),
);

/// {@template amplify_core.amplify_auth_category.forget_device}
/// Forgets the current device.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ enum AuthCategoryMethod with AmplifyCategoryMethod {
setMfaPreference('49'),
getMfaPreference('50'),
setUpTotp('51'),
verifyTotpSetup('52');
verifyTotpSetup('52'),
fetchCurrentDevice('59');

const AuthCategoryMethod(this.method);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ abstract class AuthPluginInterface extends AmplifyPluginInterface {
throw UnimplementedError('forgetDevice() has not been implemented.');
}

/// {@macro amplify_core.amplify_auth_category.fetch_current_device}
Future<AuthDevice> fetchCurrentDevice() {
throw UnimplementedError('fetchCurrentDevice() has not been implemented.');
}

/// {@macro amplify_core.amplify_auth_category.fetch_devices}
Future<List<AuthDevice>> fetchDevices() {
throw UnimplementedError('fetchDevices() has not been implemented.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,45 @@ void main() {
await expectLater(Amplify.Auth.rememberDevice(), completes);
});

asyncTest('fetchCurrentDevice returns the current device', (_) async {
await expectLater(Amplify.Auth.fetchCurrentDevice(), completes);
final currentTestDevice = await Amplify.Auth.fetchCurrentDevice();
final currentDeviceKey = await getDeviceKey();
expect(currentDeviceKey, currentTestDevice.id);
});

asyncTest(
'The device from fetchCurrentDevice isnt equal to another device.',
(_) async {
final previousDeviceKey = await getDeviceKey();
await signOutUser();
await deleteDevice(cognitoUsername, previousDeviceKey!);
await signIn();
final newCurrentTestDevice = await Amplify.Auth.fetchCurrentDevice();
expect(newCurrentTestDevice.id, isNot(previousDeviceKey));
});

asyncTest(
'fetchCurrentDevice throws a DeviceNotTrackedException when device is forgotten.',
(_) async {
expect(await getDeviceState(), DeviceState.remembered);
await Amplify.Auth.forgetDevice();
await expectLater(
Amplify.Auth.fetchCurrentDevice,
throwsA(isA<DeviceNotTrackedException>()),
);
});

asyncTest(
'fetchCurrentDevice throws a SignedOutException when device signs out.',
(_) async {
await signOutUser();
await expectLater(
Amplify.Auth.fetchCurrentDevice,
throwsA(isA<SignedOutException>()),
);
});

asyncTest('forgetDevice stops tracking', (_) async {
expect(await getDeviceState(), DeviceState.remembered);
await Amplify.Auth.forgetDevice();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ Future<List<AuthUserAttribute>> fetchUserAttributes() async {
return Amplify.Auth.fetchUserAttributes();
}

Future<AuthDevice> fetchCurrentDevice() async {
return Amplify.Auth.fetchCurrentDevice();
}

Future<List<AuthDevice>> fetchDevices() async {
return Amplify.Auth.fetchDevices();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import 'package:amplify_auth_cognito_dart/src/sdk/cognito_identity_provider.dart
ForgotPasswordRequest,
GetUserAttributeVerificationCodeRequest,
GetUserRequest,
GetDeviceRequest,
ListDevicesRequest,
ResendConfirmationCodeRequest,
UserContextDataType,
Expand All @@ -39,6 +40,7 @@ import 'package:amplify_auth_cognito_dart/src/sdk/cognito_identity_provider.dart
VerifyUserAttributeRequest;
import 'package:amplify_auth_cognito_dart/src/sdk/sdk_bridge.dart';
import 'package:amplify_auth_cognito_dart/src/sdk/src/cognito_identity_provider/model/analytics_metadata_type.dart';
import 'package:amplify_auth_cognito_dart/src/sdk/src/cognito_identity_provider/model/get_device_response.dart';
import 'package:amplify_auth_cognito_dart/src/state/cognito_state_machine.dart';
import 'package:amplify_auth_cognito_dart/src/state/state.dart';
import 'package:amplify_auth_cognito_dart/src/util/cognito_iam_auth_provider.dart';
Expand Down Expand Up @@ -97,6 +99,7 @@ class AmplifyAuthCognitoDart extends AuthPluginInterface
late CognitoAuthStateMachine _stateMachine = CognitoAuthStateMachine(
dependencyManager: dependencies,
);

StreamSubscription<AuthState>? _stateMachineSubscription;

/// The underlying state machine, for use in subclasses.
Expand Down Expand Up @@ -993,6 +996,46 @@ class AmplifyAuthCognitoDart extends AuthPluginInterface
.result;
}

@override
Future<CognitoDevice> fetchCurrentDevice() async {
final tokens = await stateMachine.getUserPoolTokens();
final deviceSecrets = await _deviceRepo.get(tokens.username);
final deviceKey = deviceSecrets?.deviceKey;
if (deviceSecrets == null || deviceKey == null) {
throw const DeviceNotTrackedException();
}

late GetDeviceResponse response;

try {
response = await _cognitoIdp
.getDevice(
cognito.GetDeviceRequest(
deviceKey: deviceKey,
accessToken: tokens.accessToken.raw,
),
)
.result;
} on Exception catch (error) {
throw AuthException.fromException(error);
}

final device = response.device;
final attributes =
device.deviceAttributes ?? const <cognito.AttributeType>[];

return CognitoDevice(
id: deviceKey,
attributes: {
for (final attribute in attributes)
attribute.name: attribute.value ?? '',
},
createdDate: device.deviceCreateDate,
lastAuthenticatedDate: device.deviceLastAuthenticatedDate,
lastModifiedDate: device.deviceLastModifiedDate,
);
}

@override
Future<List<CognitoDevice>> fetchDevices() async {
final allDevices = <CognitoDevice>[];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import 'package:amplify_auth_cognito_dart/amplify_auth_cognito_dart.dart';
import 'package:amplify_auth_cognito_dart/src/credentials/cognito_keys.dart';
import 'package:amplify_auth_cognito_dart/src/credentials/device_metadata_repository.dart';
import 'package:amplify_auth_cognito_dart/src/sdk/cognito_identity_provider.dart';
import 'package:amplify_auth_cognito_test/common/mock_clients.dart';
import 'package:amplify_auth_cognito_test/common/mock_config.dart';
import 'package:amplify_auth_cognito_test/common/mock_secure_storage.dart';
import 'package:amplify_core/amplify_core.dart';
import 'package:test/test.dart';

void main() {
AmplifyLogger().logLevel = LogLevel.verbose;

final userPoolKeys = CognitoUserPoolKeys(userPoolConfig.appClientId);
final identityPoolKeys = CognitoIdentityPoolKeys(identityPoolConfig.poolId);
final testAuthRepo = AmplifyAuthProviderRepository();
final mockDevice = DeviceType(deviceKey: deviceKey);
final mockDeviceResponse = GetDeviceResponse(device: mockDevice);

late DeviceMetadataRepository repo;
late AmplifyAuthCognitoDart plugin;

group('fetchCurrentDevice', () {
setUp(() async {
final secureStorage = MockSecureStorage();
seedStorage(
secureStorage,
userPoolKeys: userPoolKeys,
identityPoolKeys: identityPoolKeys,
deviceKeys: CognitoDeviceKeys(userPoolConfig.appClientId, username),
);
plugin = AmplifyAuthCognitoDart(
secureStorageFactory: (_) => secureStorage,
);
await plugin.configure(
config: mockConfig,
authProviderRepo: testAuthRepo,
);
repo = plugin.stateMachine.getOrCreate<DeviceMetadataRepository>();
});

group('should successfully', () {
setUp(() async {
final mockIdp = MockCognitoIdentityProviderClient(
getDevice: () async => mockDeviceResponse,
forgetDevice: () async {},
);
plugin.stateMachine.addInstance<CognitoIdentityProviderClient>(mockIdp);
});

test(
'return the current device where the current device id is equal to the local device id',
() async {
final secrets = await repo.get(username);
final currentDeviceKey = secrets?.deviceKey;
expect(currentDeviceKey, isNotNull);
final currentDevice = await plugin.fetchCurrentDevice();
expect(currentDeviceKey, currentDevice.id);
});

test('throw a DeviceNotTrackedException when current device key is null',
() async {
await plugin.forgetDevice();
await expectLater(
plugin.fetchCurrentDevice,
throwsA(isA<DeviceNotTrackedException>()),
);
});
});

group('should throw', () {
setUp(() async {
final mockIdp = MockCognitoIdentityProviderClient(
getDevice: () async => throw AWSHttpException(
AWSHttpRequest.get(Uri.parse('https://aws.amazon.com/cognito/')),
),
);
plugin.stateMachine.addInstance<CognitoIdentityProviderClient>(mockIdp);
});

test('a NetworkException', () async {
await expectLater(
plugin.fetchCurrentDevice,
throwsA(isA<NetworkException>()),
);
});
});

tearDown(() async {
await plugin.close();
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,13 @@ class AmplifyAuthCognitoStub extends AuthPluginInterface
);
}

@override
Future<AuthDevice> fetchCurrentDevice() async {
throw UnimplementedError(
'fetchCurrentDevice is not implemented.',
);
}

@override
Future<void> forgetDevice([AuthDevice? device]) async {
throw UnimplementedError(
Expand All @@ -391,7 +398,6 @@ class AmplifyAuthCognitoStub extends AuthPluginInterface
}

class MockCognitoUser {

factory MockCognitoUser({
required String username,
required String password,
Expand Down
Loading