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 a default deviceName when remembering device #13022

Merged
merged 6 commits into from
May 2, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,21 @@ describe('fetchDevices', () => {
const dateEpoch = 1.696296885807e9;
const date = new Date(dateEpoch * 1000);
const clientResponseDevice = {
DeviceAttributes: [{ Name: 'attributeName', Value: 'attributeValue' }],
DeviceAttributes: [
{ Name: 'attributeName', Value: 'attributeValue' },
{ Name: 'device_name', Value: 'deviceNameValue' },
],
DeviceCreateDate: dateEpoch,
DeviceKey: 'DeviceKey',
DeviceLastAuthenticatedDate: dateEpoch,
DeviceLastModifiedDate: dateEpoch,
};
const apiOutputDevice = {
id: 'DeviceKey',
name: undefined,
name: 'deviceNameValue',
attributes: {
attributeName: 'attributeValue',
device_name: 'deviceNameValue',
},
createDate: date,
lastModifiedDate: date,
Expand Down
3 changes: 3 additions & 0 deletions packages/auth/src/providers/cognito/apis/fetchDevices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ const parseDevicesResponse = async (
DeviceLastModifiedDate,
DeviceLastAuthenticatedDate,
}) => {
let deviceName: string | undefined;
const attributes = DeviceAttributes.reduce(
(attrs: any, { Name, Value }) => {
if (Name && Value) {
if (Name === 'device_name') deviceName = Value;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it needed to assert for this specific device name ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deviceName is returned by the service call in DeviceAttributes on the device_name attribute. We were doing the same in V5 as well

attrs[Name] = Value;
}

Expand All @@ -72,6 +74,7 @@ const parseDevicesResponse = async (

return {
id,
name: deviceName,
attributes,
createDate: DeviceCreateDate
? new Date(DeviceCreateDate * 1000)
Expand Down
2 changes: 2 additions & 0 deletions packages/auth/src/providers/cognito/utils/signInHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
AuthAction,
assertTokenProviderConfig,
base64Encoder,
getDeviceName,
} from '@aws-amplify/core/internals/utils';

import { ClientMetadata, ConfirmSignInOptions } from '../types';
Expand Down Expand Up @@ -1072,6 +1073,7 @@ export async function getNewDeviceMetatada(
{ region: getRegion(userPoolId) },
{
AccessToken: accessToken,
DeviceName: await getDeviceName(),
DeviceKey: newDeviceMetadata?.DeviceKey,
DeviceSecretVerifierConfig: deviceSecretVerifierConfig,
},
Expand Down
4 changes: 2 additions & 2 deletions packages/aws-amplify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@
"name": "[Auth] confirmSignIn (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ confirmSignIn }",
"limit": "28.10 kB"
"limit": "28.26 kB"
},
{
"name": "[Auth] updateMFAPreference (Cognito)",
Expand Down Expand Up @@ -449,7 +449,7 @@
"name": "[Auth] Basic Auth Flow (Cognito)",
"path": "./dist/esm/auth/index.mjs",
"import": "{ signIn, signOut, fetchAuthSession, confirmSignIn }",
"limit": "29.90 kB"
"limit": "30.06 kB"
},
{
"name": "[Auth] OAuth Auth Flow (Cognito)",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/libraryUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export { amplifyUuid } from './utils/amplifyUuid';
export { AmplifyUrl, AmplifyUrlSearchParams } from './utils/amplifyUrl';
export { parseAmplifyConfig } from './utils/parseAmplifyConfig';
export { getClientInfo } from './utils';
export { getDeviceName } from './utils/deviceName';

// Auth utilities
export {
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/utils/deviceName/getDeviceName.native.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { getDeviceName as getDeviceNameNative } from '@aws-amplify/react-native';

/**
* Retrieves the device name using name in ios and model in android,
*
* @returns {Promise<string>} A promise that resolves with a string representing the device name.
*
* Example Output:
* ios: 'iPhone' / 'user's iPhone'
* android: 'sdk_gphone64_arm64'
*/
export const getDeviceName = async (): Promise<string> => getDeviceNameNative();
52 changes: 52 additions & 0 deletions packages/core/src/utils/deviceName/getDeviceName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { NavigatorUA } from './types';
/**
* Retrieves the device name using the User-Agent Client Hints API if available,
* falling back to the traditional userAgent string if not.
*
* @returns {Promise<string>} A promise that resolves with a string representing the device name.
*
* Example Output:
* navigator.userAgentData:
* 'macOS 14.2.1 arm macOS Not A(Brand/99.0.0.0;Google Chrome/121.0.6167.160;Chromium/121.0.6167.160'
* navigator.userAgent:
* 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/115.0'
*/
export const getDeviceName = async (): Promise<string> => {
const { userAgentData } = navigator as NavigatorUA;
Copy link
Member Author

@ashwinkumar6 ashwinkumar6 May 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

userAgentData is experimental and hence isn't typed but is officially recommended by google.

The support for userAgentData is still limited and so we have a fall back to option to use userAgent

Since userAgentData is experimental can we just use userAgent across all browsers ?

No, using only userAgent would result in chrome triggering Audit usage of navigator.userAgent, navigator.appVersion, and navigator.platform and reopening an old issue.


if (!userAgentData) return navigator.userAgent;

const {
platform = '',
platformVersion = '',
model = '',
architecture = '',
fullVersionList = [],
} = await userAgentData.getHighEntropyValues([
'platform',
'platformVersion',
'architecture',
'model',
'fullVersionList',
]);

const versionList = fullVersionList
.map((v: { brand: string; version: string }) => `${v.brand}/${v.version}`)
.join(';');

const deviceName = [
platform,
platformVersion,
architecture,
model,
platform,
versionList,
]
.filter(value => value)
.join(' ');

return deviceName;
};
4 changes: 4 additions & 0 deletions packages/core/src/utils/deviceName/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

export { getDeviceName } from './getDeviceName';
44 changes: 44 additions & 0 deletions packages/core/src/utils/deviceName/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// WICG Spec: https://wicg.github.io/ua-client-hints

// https://wicg.github.io/ua-client-hints/#navigatorua
export interface NavigatorUA {
readonly userAgentData?: NavigatorUAData;
}

// https://wicg.github.io/ua-client-hints/#dictdef-navigatoruabrandversion
interface NavigatorUABrandVersion {
readonly brand: string;
readonly version: string;
}

// https://wicg.github.io/ua-client-hints/#dictdef-uadatavalues
interface UADataValues {
readonly brands?: NavigatorUABrandVersion[];
readonly mobile?: boolean;
readonly platform?: string;
readonly architecture?: string;
readonly bitness?: string;
readonly formFactor?: string[];
readonly model?: string;
readonly platformVersion?: string;
/** @deprecated in favour of fullVersionList */
readonly uaFullVersion?: string;
readonly fullVersionList?: NavigatorUABrandVersion[];
readonly wow64?: boolean;
}

// https://wicg.github.io/ua-client-hints/#dictdef-ualowentropyjson
interface UALowEntropyJSON {
readonly brands: NavigatorUABrandVersion[];
readonly mobile: boolean;
readonly platform: string;
}

// https://wicg.github.io/ua-client-hints/#navigatoruadata
interface NavigatorUAData extends UALowEntropyJSON {
getHighEntropyValues(hints: string[]): Promise<UADataValues>;
toJSON(): UALowEntropyJSON;
}
Loading