From 6704c8d8e63831e5c49138fb14c056aa191d0cdd Mon Sep 17 00:00:00 2001 From: Remon Nashid Date: Wed, 9 Sep 2020 23:31:41 -0700 Subject: [PATCH] add pri(accounts.changePassword) message (#450) --- .../src/background/handlers/Extension.test.ts | 66 ++++++++++++++----- .../src/background/handlers/Extension.ts | 25 ++++++- .../extension-base/src/background/types.ts | 7 ++ 3 files changed, 80 insertions(+), 18 deletions(-) diff --git a/packages/extension-base/src/background/handlers/Extension.test.ts b/packages/extension-base/src/background/handlers/Extension.test.ts index 04efc15c..f2427c6a 100644 --- a/packages/extension-base/src/background/handlers/Extension.test.ts +++ b/packages/extension-base/src/background/handlers/Extension.test.ts @@ -12,6 +12,10 @@ import Extension from './Extension'; import State from './State'; describe('Extension', () => { + let extension: Extension; + const suri = 'seed sock milk update focus rotate barely fade car face mechanic mercy'; + const password = 'passw0rd'; + async function createExtension (): Promise { await cryptoWaitReady(); @@ -20,15 +24,23 @@ describe('Extension', () => { return new Extension(new State()); } - let extension: Extension; + const createAccount = async (): Promise => { + await extension.handle('id', 'pri(accounts.create.suri)', { + name: 'parent', + password, + suri + }, {} as chrome.runtime.Port); + const { address } = await extension.handle('id', 'pri(seed.validate)', { + suri + }, {} as chrome.runtime.Port); + + return address; + }; beforeAll(async () => { extension = await createExtension(); }); - const suri = 'seed sock milk update focus rotate barely fade car face mechanic mercy'; - const password = 'passw0rd'; - test('exports account from keyring', async () => { const { pair: { address } } = keyring.addUri(suri, password); const result = await extension.handle('id', 'pri(accounts.export)', { @@ -41,19 +53,6 @@ describe('Extension', () => { }); describe('account derivation', () => { - const createAccount = async (): Promise => { - await extension.handle('id', 'pri(accounts.create.suri)', { - name: 'parent', - password, - suri - }, {} as chrome.runtime.Port); - const { address } = await extension.handle('id', 'pri(seed.validate)', { - suri - }, {} as chrome.runtime.Port); - - return address; - }; - let address: string; beforeEach(async () => { @@ -111,4 +110,37 @@ describe('Extension', () => { expect(keyring.getAccount('5FP3TT3EruYBNh8YM8yoxsreMx7uZv1J1zNX7fFhoC5enwmN')?.meta.parentAddress).toEqual(address); }); }); + + describe('account management', () => { + let address: string; + + beforeEach(async () => { + address = await createAccount(); + }); + + test('pri(accounts.changePassword) changes account password', async () => { + const newPass = 'pa55word'; + const wrongPass = 'ZZzzZZzz'; + + await expect(extension.handle('id', 'pri(accounts.changePassword)', { + address, + newPass, + oldPass: wrongPass + }, {} as chrome.runtime.Port)).rejects.toStrictEqual(new Error('oldPass is invalid')); + + await expect(extension.handle('id', 'pri(accounts.changePassword)', { + address, + newPass, + oldPass: password + }, {} as chrome.runtime.Port)).resolves.toEqual(true); + + const pair = keyring.getPair(address); + + expect(pair.decodePkcs8(newPass)).toEqual(undefined); + + expect(() => { + pair.decodePkcs8(password); + }).toThrowError('Unable to decode using the supplied passphrase'); + }); + }); }); diff --git a/packages/extension-base/src/background/handlers/Extension.ts b/packages/extension-base/src/background/handlers/Extension.ts index be311c64..0318c071 100644 --- a/packages/extension-base/src/background/handlers/Extension.ts +++ b/packages/extension-base/src/background/handlers/Extension.ts @@ -4,7 +4,7 @@ import { MetadataDef } from '@polkadot/extension-inject/types'; import { SubjectInfo } from '@polkadot/ui-keyring/observable/types'; -import { AccountJson, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountCreateExternal, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountShow, RequestAccountTie, RequestAccountValidate, RequestAuthorizeApprove, RequestAuthorizeReject, RequestDeriveCreate, ResponseDeriveValidate, RequestMetadataApprove, RequestMetadataReject, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSigningIsLocked, RequestSeedCreate, RequestTypes, ResponseAccountExport, RequestAccountForget, ResponseSeedCreate, RequestSeedValidate, RequestDeriveValidate, ResponseSeedValidate, ResponseType, SigningRequest, RequestJsonRestore, ResponseJsonRestore } from '../types'; +import { AccountJson, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountCreateExternal, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountShow, RequestAccountTie, RequestAccountValidate, RequestAuthorizeApprove, RequestAuthorizeReject, RequestDeriveCreate, ResponseDeriveValidate, RequestMetadataApprove, RequestMetadataReject, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSigningIsLocked, RequestSeedCreate, RequestTypes, ResponseAccountExport, RequestAccountForget, ResponseSeedCreate, RequestSeedValidate, RequestDeriveValidate, ResponseSeedValidate, ResponseType, SigningRequest, RequestJsonRestore, ResponseJsonRestore, RequestAccountChangePassword } from '../types'; import chrome from '@polkadot/extension-inject/chrome'; import keyring from '@polkadot/ui-keyring'; @@ -55,6 +55,26 @@ export default class Extension { return true; } + private accountsChangePassword ({ address, newPass, oldPass }: RequestAccountChangePassword): boolean { + const pair = keyring.getPair(address); + + assert(pair, 'Unable to find pair'); + + try { + if (!pair.isLocked) { + pair.lock(); + } + + pair.decodePkcs8(oldPass); + } catch (error) { + throw new Error('oldPass is invalid'); + } + + keyring.encryptAccount(pair, newPass); + + return true; + } + private accountsEdit ({ address, name }: RequestAccountEdit): boolean { const pair = keyring.getPair(address); @@ -425,6 +445,9 @@ export default class Extension { case 'pri(accounts.create.suri)': return this.accountsCreateSuri(request as RequestAccountCreateSuri); + case 'pri(accounts.changePassword)': + return this.accountsChangePassword(request as RequestAccountChangePassword); + case 'pri(accounts.edit)': return this.accountsEdit(request as RequestAccountEdit); diff --git a/packages/extension-base/src/background/types.ts b/packages/extension-base/src/background/types.ts index 92012793..8cfe9179 100644 --- a/packages/extension-base/src/background/types.ts +++ b/packages/extension-base/src/background/types.ts @@ -77,6 +77,7 @@ export interface RequestSignatures { 'pri(accounts.tie)': [RequestAccountTie, boolean]; 'pri(accounts.subscribe)': [RequestAccountSubscribe, boolean, AccountJson[]]; 'pri(accounts.validate)': [RequestAccountValidate, boolean]; + 'pri(accounts.changePassword)': [RequestAccountChangePassword, boolean]; 'pri(authorize.approve)': [RequestAuthorizeApprove, boolean]; 'pri(authorize.reject)': [RequestAuthorizeReject, boolean]; 'pri(authorize.requests)': [RequestAuthorizeSubscribe, boolean, AuthorizeRequest[]]; @@ -170,6 +171,12 @@ export interface RequestAccountCreateSuri { type?: KeypairType; } +export interface RequestAccountChangePassword { + address: string; + oldPass: string; + newPass: string; +} + export interface RequestAccountEdit { address: string; genesisHash?: string | null;