Skip to content

Commit fd04159

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

File tree

4 files changed

+76
-23
lines changed

4 files changed

+76
-23
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-ignore 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

+33-19
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,27 @@ export default class Extension {
137133
return true;
138134
}
139135

140-
private refreshAccountPasswordCache (pair: KeyringPair): number {
136+
private async refreshAccountPasswordCache (pair: KeyringPair): Promise<number> {
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+
141+
if (savedPasswords) {
142+
const remainingTime = savedPasswords[address]?.expiresAt - Date.now();
143+
144+
if (remainingTime < 0) {
145+
delete savedPasswords[address];
145146

146-
if (remainingTime < 0) {
147-
this.#cachedUnlocks[address] = 0;
148-
pair.lock();
147+
await chromeStorage.setItem('password-cache', () => (savedPasswords));
148+
pair.lock();
149+
150+
return 0;
151+
}
149152

150-
return 0;
153+
return remainingTime;
151154
}
152155

153-
return remainingTime;
156+
return 0;
154157
}
155158

156159
private accountsShow ({ address, isShowing }: RequestAccountShow): boolean {
@@ -334,14 +337,18 @@ export default class Extension {
334337
};
335338
}
336339

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

340343
assert(queued, 'Unable to find request');
341344

342345
const { payload } = queued;
343346
const pair = keyring.getPair(queued.account.address);
344347

348+
const savedPasswords = await chromeStorage.getItem('password-cache');
349+
350+
const password = savedPasswords && savedPasswords[pair.address]?.expiresAt - Date.now() > 0 ? savedPasswords[pair.address].password : inputPassword;
351+
345352
if (!pair) {
346353
const error = new Error('Unable to find pair');
347354

@@ -351,7 +358,7 @@ export default class Extension {
351358
throw error;
352359
}
353360

354-
this.refreshAccountPasswordCache(pair);
361+
await this.refreshAccountPasswordCache(pair);
355362

356363
// if the keyring pair is locked, the password is needed
357364
if (pair.isLocked && !password) {
@@ -408,13 +415,20 @@ export default class Extension {
408415
.createType('ExtrinsicPayload', payload, { version: payload.version })
409416
.sign(pair);
410417

411-
if (savePass) {
418+
if (savePass && password) {
412419
// unlike queued.account.address the following
413420
// address is encoded with the default prefix
414421
// which what is used for password caching mapping
415-
this.#cachedUnlocks[pair.address] = Date.now() + PASSWORD_EXPIRY_MS;
422+
423+
await chromeStorage.setItem('password-cache', (savedPasswords) => ({ ...savedPasswords, [pair.address]: { password, expiresAt: Date.now() + PASSWORD_EXPIRY_MS } }));
416424
} else {
417-
pair.lock();
425+
const savedPasswords = await chromeStorage.getItem('password-cache');
426+
427+
const remainingTime = (savedPasswords && savedPasswords[pair.address]?.expiresAt - Date.now()) || 0;
428+
429+
if (remainingTime <= 0) {
430+
pair.lock();
431+
}
418432
}
419433

420434
await this.#state.removeSignRequest(id);
@@ -449,7 +463,7 @@ export default class Extension {
449463

450464
assert(pair, 'Unable to find pair');
451465

452-
const remainingTime = this.refreshAccountPasswordCache(pair);
466+
const remainingTime = await this.refreshAccountPasswordCache(pair);
453467

454468
return {
455469
isLocked: pair.isLocked,
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-ui/src/Popup/Signing/Request/SignArea.tsx

+6-4
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,7 +94,7 @@ 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

0 commit comments

Comments
 (0)