Skip to content

Commit c6a06ef

Browse files
authored
feat(Auth): Add fetchCurrentDevice API (#5251)
feat(Auth): Add fetchCurrentDevice API (#5251)
1 parent ee26d74 commit c6a06ef

File tree

9 files changed

+238
-2
lines changed

9 files changed

+238
-2
lines changed

packages/amplify_core/doc/lib/auth.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,17 @@ Future<void> forgetSpecificDevice(AuthDevice myDevice) async {
483483
}
484484
// #enddocregion forget-specific-device
485485

486+
// #docregion fetch-current-device
487+
Future<void> fetchCurrentDevice() async {
488+
try {
489+
final device = await Amplify.Auth.fetchCurrentDevice();
490+
safePrint('Device: $device');
491+
} on AuthException catch (e) {
492+
safePrint('Fetch current device failed with error: $e');
493+
}
494+
}
495+
// #enddocregion fetch-current-device
496+
486497
// #docregion fetch-devices
487498
Future<void> fetchAllDevices() async {
488499
try {

packages/amplify_core/lib/src/category/amplify_auth_category.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,6 +1355,37 @@ class AuthCategory extends AmplifyCategory<AuthPluginInterface> {
13551355
() => defaultPlugin.rememberDevice(),
13561356
);
13571357

1358+
/// {@template amplify_core.amplify_auth_category.fetch_current_device}
1359+
/// Retrieves the current device.
1360+
///
1361+
/// For more information about device tracking, see the
1362+
/// [Amplify docs](https://docs.amplify.aws/flutter/build-a-backend/auth/manage-users/manage-devices/#fetch-current-device).
1363+
///
1364+
/// ## Examples
1365+
///
1366+
/// <?code-excerpt "doc/lib/auth.dart" region="imports"?>
1367+
/// ```dart
1368+
/// import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
1369+
/// import 'package:amplify_flutter/amplify_flutter.dart';
1370+
/// ```
1371+
///
1372+
/// <?code-excerpt "doc/lib/auth.dart" region="fetch-current-device"?>
1373+
/// ```dart
1374+
/// Future<AuthDevice> getCurrentUserDevice() async {
1375+
/// try {
1376+
/// final device = await Amplify.Auth.fetchCurrentDevice();
1377+
/// safePrint('Device: $device');
1378+
/// } on AuthException catch (e) {
1379+
/// safePrint('Fetch current device failed with error: $e');
1380+
/// }
1381+
/// }
1382+
/// ```
1383+
/// {@endtemplate}
1384+
Future<AuthDevice> fetchCurrentDevice() => identifyCall(
1385+
AuthCategoryMethod.fetchCurrentDevice,
1386+
() => defaultPlugin.fetchCurrentDevice(),
1387+
);
1388+
13581389
/// {@template amplify_core.amplify_auth_category.forget_device}
13591390
/// Forgets the current device.
13601391
///

packages/amplify_core/lib/src/http/amplify_category_method.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ enum AuthCategoryMethod with AmplifyCategoryMethod {
5252
setMfaPreference('49'),
5353
getMfaPreference('50'),
5454
setUpTotp('51'),
55-
verifyTotpSetup('52');
55+
verifyTotpSetup('52'),
56+
fetchCurrentDevice('59');
5657

5758
const AuthCategoryMethod(this.method);
5859

packages/amplify_core/lib/src/plugin/amplify_auth_plugin_interface.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ abstract class AuthPluginInterface extends AmplifyPluginInterface {
189189
throw UnimplementedError('forgetDevice() has not been implemented.');
190190
}
191191

192+
/// {@macro amplify_core.amplify_auth_category.fetch_current_device}
193+
Future<AuthDevice> fetchCurrentDevice() {
194+
throw UnimplementedError('fetchCurrentDevice() has not been implemented.');
195+
}
196+
192197
/// {@macro amplify_core.amplify_auth_category.fetch_devices}
193198
Future<List<AuthDevice>> fetchDevices() {
194199
throw UnimplementedError('fetchDevices() has not been implemented.');

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,45 @@ void main() {
240240
await expectLater(Amplify.Auth.rememberDevice(), completes);
241241
});
242242

243+
asyncTest('fetchCurrentDevice returns the current device', (_) async {
244+
await expectLater(Amplify.Auth.fetchCurrentDevice(), completes);
245+
final currentTestDevice = await Amplify.Auth.fetchCurrentDevice();
246+
final currentDeviceKey = await getDeviceKey();
247+
expect(currentDeviceKey, currentTestDevice.id);
248+
});
249+
250+
asyncTest(
251+
'The device from fetchCurrentDevice isnt equal to another device.',
252+
(_) async {
253+
final previousDeviceKey = await getDeviceKey();
254+
await signOutUser();
255+
await deleteDevice(cognitoUsername, previousDeviceKey!);
256+
await signIn();
257+
final newCurrentTestDevice = await Amplify.Auth.fetchCurrentDevice();
258+
expect(newCurrentTestDevice.id, isNot(previousDeviceKey));
259+
});
260+
261+
asyncTest(
262+
'fetchCurrentDevice throws a DeviceNotTrackedException when device is forgotten.',
263+
(_) async {
264+
expect(await getDeviceState(), DeviceState.remembered);
265+
await Amplify.Auth.forgetDevice();
266+
await expectLater(
267+
Amplify.Auth.fetchCurrentDevice,
268+
throwsA(isA<DeviceNotTrackedException>()),
269+
);
270+
});
271+
272+
asyncTest(
273+
'fetchCurrentDevice throws a SignedOutException when device signs out.',
274+
(_) async {
275+
await signOutUser();
276+
await expectLater(
277+
Amplify.Auth.fetchCurrentDevice,
278+
throwsA(isA<SignedOutException>()),
279+
);
280+
});
281+
243282
asyncTest('forgetDevice stops tracking', (_) async {
244283
expect(await getDeviceState(), DeviceState.remembered);
245284
await Amplify.Auth.forgetDevice();

packages/auth/amplify_auth_cognito_dart/example/lib/common.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ Future<List<AuthUserAttribute>> fetchUserAttributes() async {
104104
return Amplify.Auth.fetchUserAttributes();
105105
}
106106

107+
Future<AuthDevice> fetchCurrentDevice() async {
108+
return Amplify.Auth.fetchCurrentDevice();
109+
}
110+
107111
Future<List<AuthDevice>> fetchDevices() async {
108112
return Amplify.Auth.fetchDevices();
109113
}

packages/auth/amplify_auth_cognito_dart/lib/src/auth_plugin_impl.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import 'package:amplify_auth_cognito_dart/src/sdk/cognito_identity_provider.dart
3131
ForgotPasswordRequest,
3232
GetUserAttributeVerificationCodeRequest,
3333
GetUserRequest,
34+
GetDeviceRequest,
3435
ListDevicesRequest,
3536
ResendConfirmationCodeRequest,
3637
UserContextDataType,
@@ -39,6 +40,7 @@ import 'package:amplify_auth_cognito_dart/src/sdk/cognito_identity_provider.dart
3940
VerifyUserAttributeRequest;
4041
import 'package:amplify_auth_cognito_dart/src/sdk/sdk_bridge.dart';
4142
import 'package:amplify_auth_cognito_dart/src/sdk/src/cognito_identity_provider/model/analytics_metadata_type.dart';
43+
import 'package:amplify_auth_cognito_dart/src/sdk/src/cognito_identity_provider/model/get_device_response.dart';
4244
import 'package:amplify_auth_cognito_dart/src/state/cognito_state_machine.dart';
4345
import 'package:amplify_auth_cognito_dart/src/state/state.dart';
4446
import 'package:amplify_auth_cognito_dart/src/util/cognito_iam_auth_provider.dart';
@@ -97,6 +99,7 @@ class AmplifyAuthCognitoDart extends AuthPluginInterface
9799
late CognitoAuthStateMachine _stateMachine = CognitoAuthStateMachine(
98100
dependencyManager: dependencies,
99101
);
102+
100103
StreamSubscription<AuthState>? _stateMachineSubscription;
101104

102105
/// The underlying state machine, for use in subclasses.
@@ -993,6 +996,46 @@ class AmplifyAuthCognitoDart extends AuthPluginInterface
993996
.result;
994997
}
995998

999+
@override
1000+
Future<CognitoDevice> fetchCurrentDevice() async {
1001+
final tokens = await stateMachine.getUserPoolTokens();
1002+
final deviceSecrets = await _deviceRepo.get(tokens.username);
1003+
final deviceKey = deviceSecrets?.deviceKey;
1004+
if (deviceSecrets == null || deviceKey == null) {
1005+
throw const DeviceNotTrackedException();
1006+
}
1007+
1008+
late GetDeviceResponse response;
1009+
1010+
try {
1011+
response = await _cognitoIdp
1012+
.getDevice(
1013+
cognito.GetDeviceRequest(
1014+
deviceKey: deviceKey,
1015+
accessToken: tokens.accessToken.raw,
1016+
),
1017+
)
1018+
.result;
1019+
} on Exception catch (error) {
1020+
throw AuthException.fromException(error);
1021+
}
1022+
1023+
final device = response.device;
1024+
final attributes =
1025+
device.deviceAttributes ?? const <cognito.AttributeType>[];
1026+
1027+
return CognitoDevice(
1028+
id: deviceKey,
1029+
attributes: {
1030+
for (final attribute in attributes)
1031+
attribute.name: attribute.value ?? '',
1032+
},
1033+
createdDate: device.deviceCreateDate,
1034+
lastAuthenticatedDate: device.deviceLastAuthenticatedDate,
1035+
lastModifiedDate: device.deviceLastModifiedDate,
1036+
);
1037+
}
1038+
9961039
@override
9971040
Future<List<CognitoDevice>> fetchDevices() async {
9981041
final allDevices = <CognitoDevice>[];
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import 'package:amplify_auth_cognito_dart/amplify_auth_cognito_dart.dart';
5+
import 'package:amplify_auth_cognito_dart/src/credentials/cognito_keys.dart';
6+
import 'package:amplify_auth_cognito_dart/src/credentials/device_metadata_repository.dart';
7+
import 'package:amplify_auth_cognito_dart/src/sdk/cognito_identity_provider.dart';
8+
import 'package:amplify_auth_cognito_test/common/mock_clients.dart';
9+
import 'package:amplify_auth_cognito_test/common/mock_config.dart';
10+
import 'package:amplify_auth_cognito_test/common/mock_secure_storage.dart';
11+
import 'package:amplify_core/amplify_core.dart';
12+
import 'package:test/test.dart';
13+
14+
void main() {
15+
AmplifyLogger().logLevel = LogLevel.verbose;
16+
17+
final userPoolKeys = CognitoUserPoolKeys(userPoolConfig.appClientId);
18+
final identityPoolKeys = CognitoIdentityPoolKeys(identityPoolConfig.poolId);
19+
final testAuthRepo = AmplifyAuthProviderRepository();
20+
final mockDevice = DeviceType(deviceKey: deviceKey);
21+
final mockDeviceResponse = GetDeviceResponse(device: mockDevice);
22+
23+
late DeviceMetadataRepository repo;
24+
late AmplifyAuthCognitoDart plugin;
25+
26+
group('fetchCurrentDevice', () {
27+
setUp(() async {
28+
final secureStorage = MockSecureStorage();
29+
seedStorage(
30+
secureStorage,
31+
userPoolKeys: userPoolKeys,
32+
identityPoolKeys: identityPoolKeys,
33+
deviceKeys: CognitoDeviceKeys(userPoolConfig.appClientId, username),
34+
);
35+
plugin = AmplifyAuthCognitoDart(
36+
secureStorageFactory: (_) => secureStorage,
37+
);
38+
await plugin.configure(
39+
config: mockConfig,
40+
authProviderRepo: testAuthRepo,
41+
);
42+
repo = plugin.stateMachine.getOrCreate<DeviceMetadataRepository>();
43+
});
44+
45+
group('should successfully', () {
46+
setUp(() async {
47+
final mockIdp = MockCognitoIdentityProviderClient(
48+
getDevice: () async => mockDeviceResponse,
49+
forgetDevice: () async {},
50+
);
51+
plugin.stateMachine.addInstance<CognitoIdentityProviderClient>(mockIdp);
52+
});
53+
54+
test(
55+
'return the current device where the current device id is equal to the local device id',
56+
() async {
57+
final secrets = await repo.get(username);
58+
final currentDeviceKey = secrets?.deviceKey;
59+
expect(currentDeviceKey, isNotNull);
60+
final currentDevice = await plugin.fetchCurrentDevice();
61+
expect(currentDeviceKey, currentDevice.id);
62+
});
63+
64+
test('throw a DeviceNotTrackedException when current device key is null',
65+
() async {
66+
await plugin.forgetDevice();
67+
await expectLater(
68+
plugin.fetchCurrentDevice,
69+
throwsA(isA<DeviceNotTrackedException>()),
70+
);
71+
});
72+
});
73+
74+
group('should throw', () {
75+
setUp(() async {
76+
final mockIdp = MockCognitoIdentityProviderClient(
77+
getDevice: () async => throw AWSHttpException(
78+
AWSHttpRequest.get(Uri.parse('https://aws.amazon.com/cognito/')),
79+
),
80+
);
81+
plugin.stateMachine.addInstance<CognitoIdentityProviderClient>(mockIdp);
82+
});
83+
84+
test('a NetworkException', () async {
85+
await expectLater(
86+
plugin.fetchCurrentDevice,
87+
throwsA(isA<NetworkException>()),
88+
);
89+
});
90+
});
91+
92+
tearDown(() async {
93+
await plugin.close();
94+
});
95+
});
96+
}

packages/test/amplify_integration_test/lib/src/stubs/amplify_auth_cognito_stub.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,13 @@ class AmplifyAuthCognitoStub extends AuthPluginInterface
368368
);
369369
}
370370

371+
@override
372+
Future<AuthDevice> fetchCurrentDevice() async {
373+
throw UnimplementedError(
374+
'fetchCurrentDevice is not implemented.',
375+
);
376+
}
377+
371378
@override
372379
Future<void> forgetDevice([AuthDevice? device]) async {
373380
throw UnimplementedError(
@@ -391,7 +398,6 @@ class AmplifyAuthCognitoStub extends AuthPluginInterface
391398
}
392399

393400
class MockCognitoUser {
394-
395401
factory MockCognitoUser({
396402
required String username,
397403
required String password,

0 commit comments

Comments
 (0)