Skip to content

Commit 9d65949

Browse files
authored
Merge pull request Expensify#89704 from bernhardoj/feat/86580-device-management
#2 - Add device management feature
2 parents d63051f + 5af949e commit 9d65949

6 files changed

Lines changed: 51 additions & 14 deletions

File tree

src/libs/API/parameters/SignUpUserParams.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type Locale from '@src/types/onyx/Locale';
33
type SignUpUserParams = {
44
email?: string;
55
preferredLocale: Locale | null;
6+
deviceInfo: string;
67
};
78

89
export default SignUpUserParams;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
type ValidateSecondaryLoginParams = {partnerUserID: string; validateCode: string};
1+
type ValidateSecondaryLoginParams = {partnerUserID: string; validateCode: string; deviceInfo: string};
22

33
export default ValidateSecondaryLoginParams;

src/libs/actions/Session/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -643,9 +643,10 @@ function signUpUser(preferredLocale: Locale | undefined) {
643643
},
644644
];
645645

646-
const params: SignUpUserParams = {email: credentials.login, preferredLocale: preferredLocale ?? null};
647-
648-
API.write(WRITE_COMMANDS.SIGN_UP_USER, params, {optimisticData, successData, failureData});
646+
Device.getDeviceInfoWithID().then((deviceInfo) => {
647+
const params: SignUpUserParams = {email: credentials.login, preferredLocale: preferredLocale ?? null, deviceInfo};
648+
API.write(WRITE_COMMANDS.SIGN_UP_USER, params, {optimisticData, successData, failureData});
649+
});
649650
}
650651

651652
function setupNewDotAfterTransitionFromOldDot(hybridAppSettings: HybridAppSettings, tryNewDot?: TryNewDot) {

src/libs/actions/User.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import {getDeviceInfoWithID} from './Device';
6666
import {openOldDotLink} from './Link';
6767
import {showReportActionNotification} from './Report';
6868
import {resendValidateCode as sessionResendValidateCode} from './Session';
69+
import redirectToSignIn from './SignInRedirect';
6970

7071
type DomainOnyxUpdate =
7172
| OnyxUpdate<`${typeof ONYXKEYS.COLLECTION.DOMAIN}${string}`>
@@ -81,7 +82,11 @@ type LockAccountOnyxKey =
8182
| `${typeof ONYXKEYS.COLLECTION.DOMAIN_PENDING_ACTIONS}${string}`
8283
| `${typeof ONYXKEYS.COLLECTION.DOMAIN_ERRORS}${string}`;
8384

84-
function revokeDevice(login: NewLogin) {
85+
function revokeDevice(login: NewLogin, autoGeneratedLogin: string | undefined) {
86+
if (!autoGeneratedLogin) {
87+
return;
88+
}
89+
8590
const loginKey = getLoginKey(login);
8691
const optimisticData: Array<OnyxUpdate<typeof ONYXKEYS.LOGINS>> = [
8792
{
@@ -124,6 +129,11 @@ function revokeDevice(login: NewLogin) {
124129
optimisticData,
125130
successData,
126131
failureData,
132+
}).then(() => {
133+
if (login.partnerUserID !== autoGeneratedLogin) {
134+
return;
135+
}
136+
redirectToSignIn();
127137
});
128138
}
129139

@@ -642,9 +652,10 @@ function validateSecondaryLogin(contactMethod: string, validateCode: string, for
642652
failureData.push(optimisticResetActionCode);
643653
}
644654

645-
const parameters: ValidateSecondaryLoginParams = {partnerUserID: contactMethod, validateCode};
646-
647-
API.write(WRITE_COMMANDS.VALIDATE_SECONDARY_LOGIN, parameters, {optimisticData, successData, failureData});
655+
getDeviceInfoWithID().then((deviceInfo) => {
656+
const parameters: ValidateSecondaryLoginParams = {partnerUserID: contactMethod, validateCode, deviceInfo};
657+
API.write(WRITE_COMMANDS.VALIDATE_SECONDARY_LOGIN, parameters, {optimisticData, successData, failureData});
658+
});
648659
}
649660

650661
/**

src/pages/settings/Security/DeviceManagementPage.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import {FlashList} from '@shopify/flash-list';
22
import type {ListRenderItemInfo} from '@shopify/flash-list';
33
import React from 'react';
44
import {View} from 'react-native';
5+
import type {OnyxEntry} from 'react-native-onyx';
56
import Button from '@components/Button';
67
import HeaderWithBackButton from '@components/HeaderWithBackButton';
78
import OfflineWithFeedback from '@components/OfflineWithFeedback';
8-
import RenderHTML from '@components/RenderHTML';
99
import ScreenWrapper from '@components/ScreenWrapper';
1010
import Text from '@components/Text';
1111
import useLocalize from '@hooks/useLocalize';
@@ -15,13 +15,19 @@ import {clearRevokeError, revokeDevice} from '@libs/actions/User';
1515
import Navigation from '@libs/Navigation/Navigation';
1616
import {getDeviceLogins, getLastLogin, getLoginKey} from '@libs/UserUtils';
1717
import ONYXKEYS from '@src/ONYXKEYS';
18+
import type {Credentials} from '@src/types/onyx';
1819
import type {Login} from '@src/types/onyx/Logins';
1920

21+
function autoGeneratedLoginSelector(credentials: OnyxEntry<Credentials>) {
22+
return credentials?.autoGeneratedLogin;
23+
}
24+
2025
function DeviceManagementPage() {
2126
const styles = useThemeStyles();
2227
const {translate, datetimeToRelative} = useLocalize();
2328

2429
const [logins] = useOnyx(ONYXKEYS.LOGINS, {selector: getDeviceLogins});
30+
const [autoGeneratedLogin] = useOnyx(ONYXKEYS.CREDENTIALS, {selector: autoGeneratedLoginSelector});
2531

2632
const renderItem = ({item}: ListRenderItemInfo<Login>) => {
2733
const {deviceName, deviceVersion, os, osVersion} = item.additionalData ?? {};
@@ -40,7 +46,7 @@ function DeviceManagementPage() {
4046
danger
4147
small
4248
text={translate('deviceManagementPage.revoke')}
43-
onPress={() => revokeDevice(item)}
49+
onPress={() => revokeDevice(item, autoGeneratedLogin)}
4450
/>
4551
</OfflineWithFeedback>
4652
);
@@ -55,9 +61,7 @@ function DeviceManagementPage() {
5561
title={translate('deviceManagementPage.title')}
5662
onBackButtonPress={Navigation.goBack}
5763
/>
58-
<View style={[styles.ph5, styles.pv3]}>
59-
<RenderHTML html={translate('deviceManagementPage.description')} />
60-
</View>
64+
<Text style={[styles.ph5, styles.pv3]}>{translate('deviceManagementPage.description')}</Text>
6165
<FlashList
6266
data={logins}
6367
renderItem={renderItem}

tests/actions/UserTest.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import CONST from '@src/CONST';
66
import type {OnyxKey} from '@src/ONYXKEYS';
77
import ONYXKEYS from '@src/ONYXKEYS';
88
import type {NewLogin} from '@src/types/onyx';
9+
import redirectToSignIn from '../../src/libs/actions/SignInRedirect';
910
import * as UserActions from '../../src/libs/actions/User';
1011
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
1112

1213
jest.mock('@libs/API');
14+
jest.mock('../../src/libs/actions/SignInRedirect');
15+
1316
const mockAPI = API as jest.Mocked<typeof API>;
1417

1518
describe('actions/User', () => {
@@ -880,7 +883,7 @@ describe('actions/User', () => {
880883
const login = {partnerID, partnerUserID} as NewLogin;
881884

882885
// When revokeDevice is called
883-
UserActions.revokeDevice(login);
886+
UserActions.revokeDevice(login, 'a');
884887
await waitForBatchedUpdates();
885888

886889
// Then API.write should be called with correct command and parameters
@@ -933,5 +936,22 @@ describe('actions/User', () => {
933936
},
934937
});
935938
});
939+
940+
it('should call redirectToSignIn when the device belongs to the current user', async () => {
941+
// Given a device to revoke that belongs to the current user
942+
const partnerID = CONST.PARTNER_ID.IPHONE;
943+
const partnerUserID = 'device_123';
944+
const autoGeneratedLogin = 'device_123';
945+
const login = {partnerID, partnerUserID} as NewLogin;
946+
947+
(mockAPI.write as jest.Mock).mockResolvedValue(null);
948+
949+
// When revokeDevice is called
950+
UserActions.revokeDevice(login, autoGeneratedLogin);
951+
await waitForBatchedUpdates();
952+
953+
// Then redirectToSignIn should be called
954+
expect(redirectToSignIn).toHaveBeenCalled();
955+
});
936956
});
937957
});

0 commit comments

Comments
 (0)