Skip to content

Commit 96227eb

Browse files
RobertsrAskVAL
Roberts
authored andcommitted
A0-3946: Fix password saving function not working properly
1 parent 59bacbb commit 96227eb

File tree

5 files changed

+73
-35
lines changed

5 files changed

+73
-35
lines changed

packages/extension-base/src/background/handlers/Extension.spec.ts

+5
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ const authUrls = {
3333

3434
chromeStub.windows.getAll.resolves([]);
3535

36+
// @ts-expect-error the "sinon-chrome" mocking library does not provide stubs for session storage, so we have to append them ourselves
37+
chromeStub.storage.session = {
38+
get: () => Promise.resolve({})
39+
};
40+
3641
const stubChromeStorage = (data: Record<string, unknown> = {}) => chromeStub.storage.local.get.resolves({
3742
authUrls,
3843
...data

packages/extension-base/src/background/handlers/Extension.ts

+27-21
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@ import { accounts as accountsObservable } from '@polkadot/ui-keyring/observable/
1818
import { assert, isHex, u8aToHex } from '@polkadot/util';
1919
import { keyExtractSuri, mnemonicGenerate, mnemonicValidate } from '@polkadot/util-crypto';
2020

21+
import chromeStorage from './chromeStorage';
2122
import { POPUP_CREATE_WINDOW_DATA } from './consts';
2223
import { openCenteredWindow } from './helpers';
2324
import State from './State';
2425
import { createSubscription, unsubscribe } from './subscriptions';
2526

26-
type CachedUnlocks = Record<string, number>;
27-
2827
type GetContentPort = (tabId: number) => chrome.runtime.Port
2928

3029
const SEED_DEFAULT_LENGTH = 12;
@@ -40,12 +39,9 @@ function isJsonPayload (value: SignerPayloadJSON | SignerPayloadRaw): value is S
4039
}
4140

4241
export default class Extension {
43-
readonly #cachedUnlocks: CachedUnlocks;
44-
4542
readonly #state: State;
4643

4744
constructor (state: State) {
48-
this.#cachedUnlocks = {};
4945
this.#state = state;
5046
}
5147

@@ -137,20 +133,26 @@ export default class Extension {
137133
return true;
138134
}
139135

140-
private refreshAccountPasswordCache (pair: KeyringPair): number {
136+
private async refreshAccountPasswordCache (pair: KeyringPair, options?: {clearCache?: boolean}): Promise<{remainingTime: number, cachedPassword?: string}> {
141137
const { address } = pair;
142138

143-
const savedExpiry = this.#cachedUnlocks[address] || 0;
144-
const remainingTime = savedExpiry - Date.now();
139+
const savedPasswords = await chromeStorage.getItem('password-cache') ?? {};
140+
const expiresAt = savedPasswords?.[address]?.expiresAt ?? 0;
141+
const remainingTime = expiresAt - Date.now();
142+
const cachedPassword = savedPasswords?.[address]?.password;
145143

146-
if (remainingTime < 0) {
147-
this.#cachedUnlocks[address] = 0;
148-
pair.lock();
144+
if (remainingTime > 0 && !options?.clearCache) {
145+
return { remainingTime, cachedPassword };
146+
}
149147

150-
return 0;
148+
if (Object.keys(savedPasswords).find((key) => key === address)) {
149+
delete savedPasswords[address];
150+
await chromeStorage.setItem('password-cache', () => (savedPasswords));
151151
}
152152

153-
return remainingTime;
153+
pair.lock();
154+
155+
return { remainingTime: 0 };
154156
}
155157

156158
private accountsShow ({ address, isShowing }: RequestAccountShow): boolean {
@@ -334,14 +336,18 @@ export default class Extension {
334336
};
335337
}
336338

337-
private async signingApprovePassword ({ id, password, savePass }: RequestSigningApprovePassword, getContentPort: GetContentPort): Promise<void> {
339+
private async signingApprovePassword ({ id, password: inputPassword, savePass }: RequestSigningApprovePassword, getContentPort: GetContentPort): Promise<void> {
338340
const queued = await this.#state.getSignRequest(id);
339341

340342
assert(queued, 'Unable to find request');
341343

342344
const { payload } = queued;
343345
const pair = keyring.getPair(queued.account.address);
344346

347+
const { cachedPassword } = await this.refreshAccountPasswordCache(pair);
348+
349+
const password = cachedPassword ?? inputPassword;
350+
345351
if (!pair) {
346352
const error = new Error('Unable to find pair');
347353

@@ -351,10 +357,10 @@ export default class Extension {
351357
throw error;
352358
}
353359

354-
this.refreshAccountPasswordCache(pair);
360+
await this.refreshAccountPasswordCache(pair);
355361

356362
// if the keyring pair is locked, the password is needed
357-
if (pair.isLocked && !password) {
363+
if (!password) {
358364
const error = new Error('Password needed to unlock the account');
359365

360366
await this.#state.removeSignRequest(id);
@@ -408,13 +414,14 @@ export default class Extension {
408414
.createType('ExtrinsicPayload', payload, { version: payload.version })
409415
.sign(pair);
410416

411-
if (savePass) {
417+
if (savePass && password) {
412418
// unlike queued.account.address the following
413419
// address is encoded with the default prefix
414420
// which what is used for password caching mapping
415-
this.#cachedUnlocks[pair.address] = Date.now() + PASSWORD_EXPIRY_MS;
421+
422+
await chromeStorage.setItem('password-cache', (savedPasswords) => ({ ...savedPasswords, [pair.address]: { password, expiresAt: Date.now() + PASSWORD_EXPIRY_MS } }));
416423
} else {
417-
pair.lock();
424+
await this.refreshAccountPasswordCache(pair, { clearCache: true });
418425
}
419426

420427
await this.#state.removeSignRequest(id);
@@ -449,10 +456,9 @@ export default class Extension {
449456

450457
assert(pair, 'Unable to find pair');
451458

452-
const remainingTime = this.refreshAccountPasswordCache(pair);
459+
const { remainingTime } = await this.refreshAccountPasswordCache(pair);
453460

454461
return {
455-
isLocked: pair.isLocked,
456462
remainingTime
457463
};
458464
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { z } from 'zod';
2+
3+
// use namespaces to add typization for your storage data
4+
const namespaces = { 'password-cache': z.record(z.string(), z.object({ password: z.string(), expiresAt: z.number() })) } as const satisfies Record<string, z.ZodSchema>;
5+
6+
export const setItem = async <N extends keyof typeof namespaces>(namespace: N, updater: (currData?: z.TypeOf<typeof namespaces[N]>) => z.TypeOf<typeof namespaces[N]>) => {
7+
const currentData = await getItem(namespace);
8+
9+
await chrome.storage.session.set({ [namespace]: updater(currentData) });
10+
};
11+
12+
export const getItem = async <N extends keyof typeof namespaces>(namespace: N): Promise<z.infer<typeof namespaces[N]> | undefined> => {
13+
const { [namespace]: cachedData } = await chrome.storage.session.get(namespace);
14+
15+
try {
16+
return namespaces[namespace].parse(cachedData);
17+
} catch {
18+
return undefined;
19+
}
20+
};
21+
22+
export const removeItem = async <N extends keyof typeof namespaces>(namespace: N) => {
23+
await chrome.storage.session.remove(namespace);
24+
};
25+
26+
const chromeStorage = {
27+
getItem,
28+
setItem,
29+
removeItem
30+
};
31+
32+
export default chromeStorage;

packages/extension-base/src/background/types.ts

-1
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,6 @@ export interface RequestSigningIsLocked {
306306
}
307307

308308
export interface ResponseSigningIsLocked {
309-
isLocked: boolean;
310309
remainingTime: number;
311310
}
312311

packages/extension-ui/src/Popup/Signing/Request/SignArea.tsx

+9-13
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,17 @@ function SignArea({ buttonText, className, error, isExternal, isFirst, isLast, s
3737

3838
!isExternal &&
3939
isSignLocked(signId)
40-
.then(({ isLocked, remainingTime }) => {
41-
setIsLocked(isLocked);
40+
.then(({ remainingTime }) => {
41+
setIsLocked(remainingTime <= 0);
4242
timeout = setTimeout(() => {
4343
setIsLocked(true);
4444
}, remainingTime);
4545

4646
// if the account was unlocked check the remember me
4747
// automatically to prolong the unlock period
48-
!isLocked && setSavePass(true);
48+
if(remainingTime > 0) {
49+
setSavePass(true);
50+
}
4951
})
5052
.catch((error: Error) => console.error(error));
5153

@@ -92,20 +94,14 @@ function SignArea({ buttonText, className, error, isExternal, isFirst, isLast, s
9294

9395
const StyledCheckbox = styled(Checkbox)`
9496
margin-left: 8px;
95-
`;
97+
`;
9698

9799
const RememberPasswordCheckbox = () => (
98100
<StyledCheckbox
99101
checked={savePass}
100-
label={
101-
isLocked
102-
? t<string>('Remember password for {{expiration}} minutes', {
103-
replace: { expiration: PASSWORD_EXPIRY_MIN }
104-
})
105-
: t<string>('Extend the period without password by {{expiration}} minutes', {
106-
replace: { expiration: PASSWORD_EXPIRY_MIN }
107-
})
108-
}
102+
label={t<string>('Remember password for {{expiration}} minutes', {
103+
replace: { expiration: PASSWORD_EXPIRY_MIN }
104+
})}
109105
onChange={setSavePass}
110106
/>
111107
);

0 commit comments

Comments
 (0)