Skip to content

Commit

Permalink
A0-3946: Fix password saving function not working properly
Browse files Browse the repository at this point in the history
  • Loading branch information
Roberts committed Feb 2, 2024
1 parent 77b1b52 commit 6f4415b
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 19 deletions.
38 changes: 22 additions & 16 deletions packages/extension-base/src/background/handlers/Extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import type { KeypairType } from '@polkadot/util-crypto/types';
import type { AccountJson, AllowedPath, MessageTypes, RequestAccountChangePassword, RequestAccountCreateHardware, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountForget, RequestAccountShow, RequestAccountTie, RequestAccountValidate, RequestActiveTabUrlUpdate, RequestAuthorizeApprove, RequestAuthorizeReject, RequestBatchRestore, RequestDeriveCreate, RequestDeriveValidate, RequestJsonRestore, RequestMetadataApprove, RequestMetadataReject, RequestSeedCreate, RequestSeedValidate, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSigningIsLocked, RequestTypes, RequestUpdateAuthorizedAccounts, ResponseAccountExport, ResponseAuthorizeList, ResponseDeriveValidate, ResponseJsonGetAccountInfo, ResponseSeedCreate, ResponseSeedValidate, ResponseSigningIsLocked } from '../types';

import { ALLOWED_PATH, PASSWORD_EXPIRY_MS } from '@polkadot/extension-base/defaults';
import { ALLOWED_PATH } from '@polkadot/extension-base/defaults';
import { isJsonAuthentic, signJson } from '@polkadot/extension-base/utils/accountJsonIntegrity';
import { metadataExpand } from '@polkadot/extension-chains';
import { wrapBytes } from '@polkadot/extension-dapp';
Expand All @@ -18,13 +18,12 @@ import { accounts as accountsObservable } from '@polkadot/ui-keyring/observable/
import { assert, isHex, u8aToHex } from '@polkadot/util';
import { keyExtractSuri, mnemonicGenerate, mnemonicValidate } from '@polkadot/util-crypto';

import chromeStorage from './chromeStorage';
import { POPUP_CREATE_WINDOW_DATA } from './consts';
import { openCenteredWindow } from './helpers';
import State from './State';
import { createSubscription, unsubscribe } from './subscriptions';

type CachedUnlocks = Record<string, number>;

type GetContentPort = (tabId: number) => chrome.runtime.Port

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

export default class Extension {
readonly #cachedUnlocks: CachedUnlocks;

readonly #state: State;

constructor (state: State) {
this.#cachedUnlocks = {};
this.#state = state;
}

Expand Down Expand Up @@ -137,14 +133,15 @@ export default class Extension {
return true;
}

private refreshAccountPasswordCache (pair: KeyringPair): number {
private async refreshAccountPasswordCache (pair: KeyringPair): Promise<number> {
const { address } = pair;

const savedExpiry = this.#cachedUnlocks[address] || 0;
const remainingTime = savedExpiry - Date.now();
const savedPassword = await chromeStorage.getPassword(address);

const remainingTime = savedPassword?.expiry - Date.now();

if (remainingTime < 0) {
this.#cachedUnlocks[address] = 0;
await chromeStorage.removePassword(address);
pair.lock();

return 0;
Expand Down Expand Up @@ -334,13 +331,16 @@ export default class Extension {
};
}

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

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

const { payload } = queued;
const pair = keyring.getPair(queued.account.address);
const savedPassword = await chromeStorage.getPassword(pair.address);

const password = savedPassword?.expiry > 0 ? savedPassword?.password : inputPassword;

if (!pair) {
const error = new Error('Unable to find pair');
Expand All @@ -351,7 +351,7 @@ export default class Extension {
throw error;
}

this.refreshAccountPasswordCache(pair);
await this.refreshAccountPasswordCache(pair);

// if the keyring pair is locked, the password is needed
if (pair.isLocked && !password) {
Expand Down Expand Up @@ -408,13 +408,19 @@ export default class Extension {
.createType('ExtrinsicPayload', payload, { version: payload.version })
.sign(pair);

if (savePass) {
if (savePass && password) {
// unlike queued.account.address the following
// address is encoded with the default prefix
// which what is used for password caching mapping
this.#cachedUnlocks[pair.address] = Date.now() + PASSWORD_EXPIRY_MS;
await chromeStorage.setPassword(pair.address, password);
} else {
pair.lock();
const savedExpiry = await chromeStorage.getPassword(pair.address);

const remainingTime = savedExpiry?.expiry - Date.now();

if (remainingTime <= 0) {
pair.lock();
}
}

await this.#state.removeSignRequest(id);
Expand Down Expand Up @@ -449,7 +455,7 @@ export default class Extension {

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

const remainingTime = this.refreshAccountPasswordCache(pair);
const remainingTime = await this.refreshAccountPasswordCache(pair);

return {
isLocked: pair.isLocked,
Expand Down
61 changes: 61 additions & 0 deletions packages/extension-base/src/background/handlers/chromeStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { z } from 'zod';

import { PASSWORD_EXPIRY_MS } from '../../defaults';

const addressSchema = z.string();
const passwordSchema = z.record(z.string(), z.object({ expiry: z.number(), password: z.string() }));

type Password = { expiry: number, password?: string }

async function getPassword (address: string): Promise<Password> {
if (addressSchema.safeParse(address).success) {
const { savePass } = await chrome.storage.session.get('savePass');

const pass = passwordSchema.safeParse(savePass);

if (pass.success) {
return pass.data[address];
} else {
return { expiry: 0 };
}
} else {
return { expiry: 0 };
}
}

async function setPassword (address: string, password: string): Promise<void> {
if (addressSchema.safeParse(address).success) {
const { savePass } = await chrome.storage.session.get('savePass');

const savedPasswords = passwordSchema.safeParse(savePass);

if (savedPasswords.success) {
await chrome.storage.session.set({ savePass: { ...savedPasswords.data, [address]: { password, expiry: Date.now() + PASSWORD_EXPIRY_MS } } });
} else {
await chrome.storage.session.set({ savePass: { [address]: { expiry: Date.now() + PASSWORD_EXPIRY_MS, password } } });
}
} else {
console.error('Provided address did not pass validation');
}
}

async function removePassword (address: string): Promise<void> {
if (addressSchema.safeParse(address).success) {
const { savePass } = await chrome.storage.session.get('savePass');

const pass = passwordSchema.safeParse(savePass);

if (pass.success) {
delete pass.data[address];
await chrome.storage.session.set({ savePass: pass.data });
}
}
}

const chromeStorage = {
setPassword,
getPassword,
removePassword
};

export default chromeStorage;
6 changes: 3 additions & 3 deletions packages/extension-ui/src/Popup/Signing/Request/SignArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ function SignArea({ buttonText, className, error, isExternal, isFirst, isLast, s

!isExternal &&
isSignLocked(signId)
.then(({ isLocked, remainingTime }) => {
setIsLocked(isLocked);
.then(({ remainingTime }) => {
setIsLocked(remainingTime <= 0);
timeout = setTimeout(() => {
setIsLocked(true);
}, remainingTime);

// if the account was unlocked check the remember me
// automatically to prolong the unlock period
!isLocked && setSavePass(true);
remainingTime > 0 && setSavePass(true);
})
.catch((error: Error) => console.error(error));

Expand Down

0 comments on commit 6f4415b

Please sign in to comment.