Skip to content

Commit ab17696

Browse files
fix: avoid keyless credential path when enabling OneKey Cloud (#10428)
* fix: guard cloud sync invalid-password check during enable flow * fix: add keyless manual sync entry and split cloud sync last update time * fix: separate manual sync entries for OneKey ID and Keyless * fix: avoid keyless credential path when enabling OneKey Cloud --------- Co-authored-by: Leon <lixiao.dev@gmail.com>
1 parent cdf4203 commit ab17696

File tree

3 files changed

+186
-72
lines changed

3 files changed

+186
-72
lines changed

packages/kit-bg/src/services/ServicePrimeCloudSync/ServicePrimeCloudSync.tsx

Lines changed: 80 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ import type { AxiosResponse } from 'axios';
9898

9999
const nonceZero = 0;
100100

101+
// Guard for the first-enable window: server pwdHash can be temporarily empty
102+
// before initial flush/lock upload finishes.
103+
let oneKeyIdCloudSyncEnableFlowCount = 0;
104+
101105
@backgroundClass()
102106
class ServicePrimeCloudSync extends ServiceBase {
103107
constructor({ backgroundApi }: { backgroundApi: any }) {
@@ -1442,6 +1446,46 @@ class ServicePrimeCloudSync extends ServiceBase {
14421446
);
14431447
}
14441448

1449+
async buildSyncCredentialForOneKeyId({
1450+
password,
1451+
}: {
1452+
password: string;
1453+
}): Promise<ICloudSyncCredential> {
1454+
const {
1455+
masterPasswordUUID,
1456+
// encryptedSecurityPasswordR1
1457+
} = await primeMasterPasswordPersistAtom.get();
1458+
// if (!masterPasswordUUID || !encryptedSecurityPasswordR1) {
1459+
// void this.showAlertDialogIfLocalPasswordNotSet();
1460+
// throw new OneKeyError(
1461+
// 'No masterPasswordUUID or encryptedSecurityPasswordR1 in atom',
1462+
// );
1463+
// }
1464+
//
1465+
const securityPasswordR1Info =
1466+
await this.backgroundApi.serviceMasterPassword.getSecurityPasswordR1InfoSafe(
1467+
{
1468+
passcode: password,
1469+
},
1470+
);
1471+
const securityPasswordR1 = securityPasswordR1Info?.securityPasswordR1;
1472+
const accountSalt = securityPasswordR1Info?.accountSalt;
1473+
1474+
if (!securityPasswordR1) {
1475+
throw new OneKeyError('Failed to decrypt securityPasswordR1');
1476+
}
1477+
if (!accountSalt) {
1478+
throw new OneKeyError('Failed to get accountSalt');
1479+
}
1480+
1481+
return {
1482+
primeAccountSalt: accountSalt,
1483+
securityPasswordR1,
1484+
masterPasswordUUID,
1485+
keylessCredential: undefined,
1486+
};
1487+
}
1488+
14451489
// TODO remove cache when logout, lock, change password/passcode, etc.
14461490
getSyncCredentialWithCache = memoizee(
14471491
async (): Promise<ICloudSyncCredential> => {
@@ -1464,39 +1508,7 @@ class ServicePrimeCloudSync extends ServiceBase {
14641508
return this.buildSyncCredentialWithKeylessCredential(keylessCredential);
14651509
}
14661510

1467-
const {
1468-
masterPasswordUUID,
1469-
// encryptedSecurityPasswordR1
1470-
} = await primeMasterPasswordPersistAtom.get();
1471-
// if (!masterPasswordUUID || !encryptedSecurityPasswordR1) {
1472-
// void this.showAlertDialogIfLocalPasswordNotSet();
1473-
// throw new OneKeyError(
1474-
// 'No masterPasswordUUID or encryptedSecurityPasswordR1 in atom',
1475-
// );
1476-
// }
1477-
//
1478-
const securityPasswordR1Info =
1479-
await this.backgroundApi.serviceMasterPassword.getSecurityPasswordR1InfoSafe(
1480-
{
1481-
passcode: password,
1482-
},
1483-
);
1484-
const securityPasswordR1 = securityPasswordR1Info?.securityPasswordR1;
1485-
const accountSalt = securityPasswordR1Info?.accountSalt;
1486-
1487-
if (!securityPasswordR1) {
1488-
throw new OneKeyError('Failed to decrypt securityPasswordR1');
1489-
}
1490-
if (!accountSalt) {
1491-
throw new OneKeyError('Failed to get accountSalt');
1492-
}
1493-
1494-
return {
1495-
primeAccountSalt: accountSalt,
1496-
securityPasswordR1,
1497-
masterPasswordUUID,
1498-
keylessCredential: undefined,
1499-
};
1511+
return this.buildSyncCredentialForOneKeyId({ password });
15001512
},
15011513
{
15021514
max: 1,
@@ -1542,6 +1554,10 @@ class ServicePrimeCloudSync extends ServiceBase {
15421554
@backgroundMethod()
15431555
@toastIfError()
15441556
async toggleCloudSync({ enabled }: { enabled: boolean }) {
1557+
const shouldTrackEnableFlow = enabled;
1558+
if (shouldTrackEnableFlow) {
1559+
oneKeyIdCloudSyncEnableFlowCount += 1;
1560+
}
15451561
try {
15461562
if (enabled) {
15471563
const {
@@ -1581,6 +1597,12 @@ class ServicePrimeCloudSync extends ServiceBase {
15811597
await this.setCloudSyncEnabled(false);
15821598
throw error;
15831599
} finally {
1600+
if (shouldTrackEnableFlow) {
1601+
oneKeyIdCloudSyncEnableFlowCount = Math.max(
1602+
0,
1603+
oneKeyIdCloudSyncEnableFlowCount - 1,
1604+
);
1605+
}
15841606
void this.backgroundApi.servicePrime.apiFetchPrimeUserInfo();
15851607
}
15861608
}
@@ -1609,11 +1631,25 @@ class ServicePrimeCloudSync extends ServiceBase {
16091631
}
16101632

16111633
@backgroundMethod()
1612-
async updateLastSyncTime() {
1634+
async updateLastSyncTime({
1635+
syncMode,
1636+
}: {
1637+
syncMode?: ECloudSyncMode;
1638+
} = {}) {
1639+
const activeSyncMode = syncMode ?? (await this.getActiveSyncMode());
1640+
const now = Date.now();
16131641
await primeCloudSyncPersistAtom.set(
16141642
(v): IPrimeCloudSyncPersistAtomData => ({
16151643
...v,
1616-
lastSyncTime: Date.now(),
1644+
lastSyncTime: now,
1645+
lastSyncTimeOneKeyId:
1646+
activeSyncMode === ECloudSyncMode.OnekeyId
1647+
? now
1648+
: v.lastSyncTimeOneKeyId,
1649+
lastSyncTimeKeyless:
1650+
activeSyncMode === ECloudSyncMode.Keyless
1651+
? now
1652+
: v.lastSyncTimeKeyless,
16171653
}),
16181654
);
16191655
}
@@ -1680,6 +1716,9 @@ class ServicePrimeCloudSync extends ServiceBase {
16801716
}: {
16811717
serverUserInfo: IPrimeServerUserInfo;
16821718
}) {
1719+
if (oneKeyIdCloudSyncEnableFlowCount > 0) {
1720+
return;
1721+
}
16831722
if (serverUserInfo.pwdHash) {
16841723
return;
16851724
}
@@ -2065,11 +2104,12 @@ class ServicePrimeCloudSync extends ServiceBase {
20652104
}),
20662105
},
20672106
async () => {
2068-
syncCredential = await this.getSyncCredentialSafe();
2069-
// verify local password match with server master password
2070-
if (!syncCredential) {
2071-
throw new OneKeyError('Master password set failed');
2072-
}
2107+
// Force OneKey ID credential here. Enabling OneKey Cloud can start
2108+
// while Keyless is still enabled, and mode-based credential lookup
2109+
// would otherwise route to Keyless and fail.
2110+
syncCredential = await this.buildSyncCredentialForOneKeyId({
2111+
password,
2112+
});
20732113
await this.initLocalSyncItemsDB({ password, syncCredential });
20742114
let status:
20752115
| {

packages/kit-bg/src/states/jotai/atoms/prime.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export const {
4040
export type IPrimeCloudSyncPersistAtomData = {
4141
isCloudSyncEnabled: boolean;
4242
lastSyncTime?: number;
43+
lastSyncTimeOneKeyId?: number;
44+
lastSyncTimeKeyless?: number;
4345

4446
isCloudSyncEnabledKeyless?: boolean;
4547
};

packages/kit/src/views/Prime/pages/PrimeCloudSync/PagePrimeCloudSync.tsx

Lines changed: 104 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { EPrimeFeatures, EPrimePages } from '@onekeyhq/shared/src/routes/prime';
3737
import { formatDistanceToNow } from '@onekeyhq/shared/src/utils/dateUtils';
3838
import { isNeverLockDuration } from '@onekeyhq/shared/src/utils/passwordUtils';
3939
import timerUtils from '@onekeyhq/shared/src/utils/timerUtils';
40+
import { ECloudSyncMode } from '@onekeyhq/shared/types/keylessCloudSync';
4041

4142
import { AppAutoLockSettingsView } from '../../../Setting/pages/AppAutoLock';
4243
import { usePrimeRequirements } from '../../hooks/usePrimeRequirements';
@@ -45,6 +46,13 @@ function isAutoLockValueNotAllowed(value: number) {
4546
return isNeverLockDuration(value) || value === Number(ELockDuration.Hour4);
4647
}
4748

49+
function formatSyncLastUpdateTime(syncTime?: number): string {
50+
if (syncTime) {
51+
return formatDistanceToNow(new Date(syncTime));
52+
}
53+
return ' - ';
54+
}
55+
4856
function AutoLockUpdateDialogContent({
4957
onContinue,
5058
onError,
@@ -96,7 +104,13 @@ function AutoLockUpdateDialogContent({
96104
);
97105
}
98106

99-
function EnableOneKeyCloudSwitchListItem() {
107+
function EnableOneKeyCloudSwitchListItem({
108+
onManualSyncOneKeyId,
109+
onManualSyncKeyless,
110+
}: {
111+
onManualSyncOneKeyId: () => Promise<void>;
112+
onManualSyncKeyless: () => Promise<void>;
113+
}) {
100114
const [config] = usePrimeCloudSyncPersistAtom();
101115
const [devSettings] = useDevSettingsPersistAtom();
102116
const { isPrimeSubscriptionActive } = useOneKeyAuth();
@@ -108,12 +122,29 @@ function EnableOneKeyCloudSwitchListItem() {
108122

109123
const intl = useIntl();
110124

111-
const lastUpdateTime = useMemo<string>(() => {
112-
if (config.lastSyncTime) {
113-
return formatDistanceToNow(new Date(config.lastSyncTime));
114-
}
115-
return ' - ';
116-
}, [config.lastSyncTime]);
125+
const shouldUseLegacyLastSyncTime =
126+
!config.lastSyncTimeOneKeyId && !config.lastSyncTimeKeyless;
127+
128+
const oneKeyIdLastUpdateTime = useMemo<string>(() => {
129+
const syncTime = shouldUseLegacyLastSyncTime
130+
? config.lastSyncTime
131+
: config.lastSyncTimeOneKeyId;
132+
return formatSyncLastUpdateTime(syncTime);
133+
}, [
134+
config.lastSyncTime,
135+
config.lastSyncTimeOneKeyId,
136+
shouldUseLegacyLastSyncTime,
137+
]);
138+
const keylessLastUpdateTime = useMemo<string>(() => {
139+
const syncTime = shouldUseLegacyLastSyncTime
140+
? config.lastSyncTime
141+
: config.lastSyncTimeKeyless;
142+
return formatSyncLastUpdateTime(syncTime);
143+
}, [
144+
config.lastSyncTime,
145+
config.lastSyncTimeKeyless,
146+
shouldUseLegacyLastSyncTime,
147+
]);
117148
const { ensurePrimeSubscriptionActive } = usePrimeRequirements();
118149

119150
const [passwordSettings] = usePasswordPersistAtom();
@@ -138,7 +169,7 @@ function EnableOneKeyCloudSwitchListItem() {
138169
icon="CloudOutline"
139170
subtitle={`${intl.formatMessage({
140171
id: ETranslations.prime_last_update,
141-
})} : ${lastUpdateTime}`}
172+
})} : ${oneKeyIdLastUpdateTime}`}
142173
>
143174
{!isPrimeUser ? (
144175
<Badge badgeSize="sm" badgeType="default">
@@ -238,7 +269,7 @@ function EnableOneKeyCloudSwitchListItem() {
238269
icon="CloudOutline"
239270
subtitle={`${intl.formatMessage({
240271
id: ETranslations.prime_last_update,
241-
})} : ${lastUpdateTime}`}
272+
})} : ${keylessLastUpdateTime}`}
242273
>
243274
<Switch
244275
disabled={false}
@@ -263,7 +294,27 @@ function EnableOneKeyCloudSwitchListItem() {
263294
return (
264295
<>
265296
{showKeylessCloudSync ? keylessSwitchItem : null}
297+
{showKeylessCloudSync && config?.isCloudSyncEnabledKeyless ? (
298+
<ListItem
299+
title={intl.formatMessage({
300+
id: ETranslations.wallet_backup_now,
301+
})}
302+
icon="RefreshCwOutline"
303+
drillIn
304+
onPress={onManualSyncKeyless}
305+
/>
306+
) : null}
266307
{onekeyIdSwitchItem}
308+
{config?.isCloudSyncEnabled ? (
309+
<ListItem
310+
title={intl.formatMessage({
311+
id: ETranslations.wallet_backup_now,
312+
})}
313+
icon="RefreshCwOutline"
314+
drillIn
315+
onPress={onManualSyncOneKeyId}
316+
/>
317+
) : null}
267318
</>
268319
);
269320
}
@@ -316,15 +367,7 @@ function AppDataSection() {
316367

317368
const intl = useIntl();
318369

319-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
320-
const lastUpdateTime = useMemo<string>(() => {
321-
if (config.lastSyncTime) {
322-
return formatDistanceToNow(new Date(config.lastSyncTime));
323-
}
324-
return ' - ';
325-
}, [config.lastSyncTime]);
326-
327-
const handleManualSync = useCallback(async () => {
370+
const handleManualSyncOneKeyId = useCallback(async () => {
328371
if (!config.isCloudSyncEnabled) {
329372
return;
330373
}
@@ -340,10 +383,12 @@ function AppDataSection() {
340383
}),
341384
});
342385
await backgroundApiProxy.servicePrimeCloudSync.startServerSyncFlow({
343-
callerName: 'Manual Cloud Sync',
386+
callerName: 'Manual Cloud Sync OneKey ID',
344387
noDebounceUpload: true,
345388
});
346-
await backgroundApiProxy.servicePrimeCloudSync.updateLastSyncTime();
389+
await backgroundApiProxy.servicePrimeCloudSync.updateLastSyncTime({
390+
syncMode: ECloudSyncMode.OnekeyId,
391+
});
347392
} finally {
348393
manualSyncingRef.current = false;
349394
await timerUtils.wait(1000);
@@ -357,20 +402,47 @@ function AppDataSection() {
357402
});
358403
}, [config.isCloudSyncEnabled, intl]);
359404

405+
const handleManualSyncKeyless = useCallback(async () => {
406+
if (!config.isCloudSyncEnabledKeyless) {
407+
return;
408+
}
409+
if (manualSyncingRef.current) {
410+
return;
411+
}
412+
manualSyncingRef.current = true;
413+
try {
414+
await backgroundApiProxy.servicePassword.promptPasswordVerify();
415+
await backgroundApiProxy.serviceApp.showDialogLoading({
416+
title: intl.formatMessage({
417+
id: ETranslations.global_syncing,
418+
}),
419+
});
420+
await backgroundApiProxy.servicePrimeCloudSync.startServerSyncFlow({
421+
callerName: 'Manual Cloud Sync Keyless',
422+
noDebounceUpload: true,
423+
});
424+
await backgroundApiProxy.servicePrimeCloudSync.updateLastSyncTime({
425+
syncMode: ECloudSyncMode.Keyless,
426+
});
427+
} finally {
428+
manualSyncingRef.current = false;
429+
await timerUtils.wait(1000);
430+
await backgroundApiProxy.serviceApp.hideDialogLoading();
431+
}
432+
void backgroundApiProxy.serviceApp.showToast({
433+
method: 'success',
434+
title: intl.formatMessage({
435+
id: ETranslations.global_sync_successfully,
436+
}),
437+
});
438+
}, [config.isCloudSyncEnabledKeyless, intl]);
439+
360440
return (
361441
<>
362-
<EnableOneKeyCloudSwitchListItem />
363-
364-
{config?.isCloudSyncEnabled ? (
365-
<ListItem
366-
title={intl.formatMessage({
367-
id: ETranslations.wallet_backup_now,
368-
})}
369-
icon="RefreshCwOutline"
370-
drillIn
371-
onPress={handleManualSync}
372-
/>
373-
) : null}
442+
<EnableOneKeyCloudSwitchListItem
443+
onManualSyncOneKeyId={handleManualSyncOneKeyId}
444+
onManualSyncKeyless={handleManualSyncKeyless}
445+
/>
374446

375447
{config?.isCloudSyncEnabled || isServerMasterPasswordSet ? (
376448
<ListItem

0 commit comments

Comments
 (0)