Skip to content

Commit f734a4a

Browse files
feat: re-authorize new accounts (#1521)
* Add reauthorization on new added/imported account * Change Reject Label to Ignore * Fix reauthorization problem for old authDApps * Add already selected accounts approvement on ignore to Fullscreen mode --------- Co-authored-by: Amir Ekbatanifard <[email protected]>
1 parent 676c5ad commit f734a4a

File tree

10 files changed

+141
-40
lines changed

10 files changed

+141
-40
lines changed

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

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { SignerPayloadJSON, SignerPayloadRaw } from '@polkadot/types/types'
77
import type { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
88
import type { KeypairType } from '@polkadot/util-crypto/types';
99
// added for plus to import RequestUpdateMeta
10-
import type { AccountJson, AllowedPath, AuthorizeRequest, AuthUrls, MessageTypes, MetadataRequest, RequestAccountBatchExport, RequestAccountChangePassword, RequestAccountCreateExternal, RequestAccountCreateHardware, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountForget, RequestAccountShow, RequestAccountTie, RequestAccountValidate, RequestAuthorizeApprove, RequestBatchRestore, RequestDeriveCreate, RequestDeriveValidate, RequestJsonRestore, RequestMetadataApprove, RequestMetadataReject, RequestSeedCreate, RequestSeedValidate, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSigningIsLocked, RequestTypes, RequestUpdateAuthorizedAccounts, RequestUpdateMeta, ResponseAccountExport, ResponseAccountsExport, ResponseAuthorizeList, ResponseDeriveValidate, ResponseJsonGetAccountInfo, ResponseSeedCreate, ResponseSeedValidate, ResponseSigningIsLocked, ResponseType, SigningRequest } from '../types';
10+
import type { AccountJson, AllowedPath, ApplyAddedTime, AuthorizeRequest, AuthUrls, MessageTypes, MetadataRequest, RequestAccountBatchExport, RequestAccountChangePassword, RequestAccountCreateExternal, RequestAccountCreateHardware, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountForget, RequestAccountShow, RequestAccountTie, RequestAccountValidate, RequestAuthorizeApprove, RequestBatchRestore, RequestDeriveCreate, RequestDeriveValidate, RequestJsonRestore, RequestMetadataApprove, RequestMetadataReject, RequestSeedCreate, RequestSeedValidate, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSigningIsLocked, RequestTypes, RequestUpdateAuthorizedAccounts, RequestUpdateMeta, ResponseAccountExport, ResponseAccountsExport, ResponseAuthorizeList, ResponseDeriveValidate, ResponseJsonGetAccountInfo, ResponseSeedCreate, ResponseSeedValidate, ResponseSigningIsLocked, ResponseType, SigningRequest } from '../types';
1111
import type State from './State';
1212

1313
import { ALLOWED_PATH, PASSWORD_EXPIRY_MS, START_WITH_PATH } from '@polkadot/extension-base/defaults';
@@ -57,20 +57,34 @@ export default class Extension {
5757
this.#state = state;
5858
}
5959

60+
private applyAddedTime ({ pair }: ApplyAddedTime): void {
61+
assert(pair, 'Unable to find pair');
62+
63+
const addedTime = Date.now();
64+
65+
keyring.saveAccountMeta(pair, { ...pair.meta, addedTime });
66+
}
67+
6068
private accountsCreateExternal ({ address, genesisHash, name }: RequestAccountCreateExternal): boolean {
61-
keyring.addExternal(address, { genesisHash, name });
69+
const { pair } = keyring.addExternal(address, { genesisHash, name });
70+
71+
this.applyAddedTime({ pair });
6272

6373
return true;
6474
}
6575

6676
private accountsCreateHardware ({ accountIndex, address, addressOffset, genesisHash, hardwareType, name }: RequestAccountCreateHardware): boolean {
67-
keyring.addHardware(address, hardwareType, { accountIndex, addressOffset, genesisHash, name });
77+
const { pair } = keyring.addHardware(address, hardwareType, { accountIndex, addressOffset, genesisHash, name });
78+
79+
this.applyAddedTime({ pair });
6880

6981
return true;
7082
}
7183

7284
private accountsCreateSuri ({ genesisHash, name, password, suri, type }: RequestAccountCreateSuri): boolean {
73-
keyring.addUri(getSuri(suri, type), password, { genesisHash, name }, type);
85+
const { pair } = keyring.addUri(getSuri(suri, type), password, { genesisHash, name }, type);
86+
87+
this.applyAddedTime({ pair });
7488

7589
return true;
7690
}
@@ -302,7 +316,9 @@ export default class Extension {
302316

303317
private jsonRestore ({ file, password }: RequestJsonRestore): void {
304318
try {
305-
keyring.restoreAccount(file, password);
319+
const pair = keyring.restoreAccount(file, password);
320+
321+
this.applyAddedTime({ pair });
306322
} catch (error) {
307323
throw new Error((error as Error).message);
308324
}
@@ -523,7 +539,9 @@ export default class Extension {
523539
suri
524540
});
525541

526-
keyring.addPair(childPair, password);
542+
const { pair } = keyring.addPair(childPair, password);
543+
544+
this.applyAddedTime({ pair });
527545

528546
return true;
529547
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface AuthUrlInfo {
4343
origin: string;
4444
url: string;
4545
authorizedAccounts: string[];
46+
authorizedTime: number;
4647
}
4748

4849
interface MetaRequest extends Resolver<boolean> {
@@ -257,9 +258,11 @@ export default class State {
257258
const { idStr, request: { origin }, url } = this.#authRequests[id];
258259

259260
const strippedUrl = this.stripUrl(url);
261+
const authorizedTime = authorizedAccounts.length > 0 ? Date.now() : 0;
260262

261263
const authInfo: AuthUrlInfo = {
262264
authorizedAccounts,
265+
authorizedTime,
263266
count: 0,
264267
id: idStr,
265268
origin,
@@ -377,6 +380,7 @@ export default class State {
377380

378381
public async updateAuthorizedAccounts ({ authorizedAccounts, url }: UpdateAuthorizedAccounts): Promise<void> {
379382
this.#authUrls[url].authorizedAccounts = authorizedAccounts;
383+
this.#authUrls[url].authorizedTime = Date.now(); // updates the authorizedTime when the authorizedAccounts list updates
380384
await this.saveCurrentAuthList();
381385
}
382386

@@ -406,7 +410,7 @@ export default class State {
406410
this.updateIcon(shouldClose);
407411
}
408412

409-
public async authorizeUrl (url: string, request: RequestAuthorizeTab): Promise<AuthResponse> {
413+
public async authorizeUrl (url: string, request: RequestAuthorizeTab, reauthorize?: boolean): Promise<AuthResponse> {
410414
const idStr = this.stripUrl(url);
411415

412416
// Do not enqueue duplicate authorization requests.
@@ -415,7 +419,7 @@ export default class State {
415419

416420
assert(!isDuplicate, `The source ${url} has a pending authorization request`);
417421

418-
if (this.#authUrls[idStr]) {
422+
if (this.#authUrls[idStr] && !reauthorize) {
419423
// this url was seen in the past
420424
assert(this.#authUrls[idStr].authorizedAccounts, `The source ${url} is not allowed to interact with this extension`);
421425

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

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@ function transformAccounts (accounts: SubjectInfo, anyType = false): InjectedAcc
3535
.filter(({ json: { meta: { isHidden } } }) => !isHidden)
3636
.filter(({ type }) => anyType ? true : canDerive(type))
3737
.sort((a, b) => (a.json.meta.whenCreated || 0) - (b.json.meta.whenCreated || 0))
38-
.map(({ json: { address, meta: { genesisHash, name } }, type }): InjectedAccount => ({
38+
.map(({ json: { address, meta: { addedTime, genesisHash, name, whenCreated } }, type }): InjectedAccount => ({
39+
addedTime: addedTime as number | undefined,
3940
address,
4041
genesisHash,
4142
name,
42-
type
43+
type,
44+
whenCreated
4345
}));
4446
}
4547

@@ -52,8 +54,15 @@ export default class Tabs {
5254
this.#state = state;
5355
}
5456

57+
private needReAuthorize (url: string): boolean {
58+
const transformedAccounts = transformAccounts(accountsObservable.subject.getValue(), true);
59+
const auth = this.#state.authUrls[this.#state.stripUrl(url)];
60+
61+
return !auth?.authorizedTime || transformedAccounts.some(({ addedTime, whenCreated }) => (addedTime && addedTime > auth.authorizedTime) || (whenCreated && whenCreated > auth.authorizedTime));
62+
}
63+
5564
private authorize (url: string, request: RequestAuthorizeTab): Promise<AuthResponse> {
56-
return this.#state.authorizeUrl(url, request);
65+
return this.#state.authorizeUrl(url, request, this.needReAuthorize(url));
5766
}
5867

5968
private filterForAuthorizedAccounts (accounts: InjectedAccount[], url: string): InjectedAccount[] {
@@ -70,8 +79,14 @@ export default class Tabs {
7079

7180
private accountsListAuthorized (url: string, { anyType }: RequestAccountList): InjectedAccount[] {
7281
const transformedAccounts = transformAccounts(accountsObservable.subject.getValue(), anyType);
82+
const authorizedAccounts = this.filterForAuthorizedAccounts(transformedAccounts, url);
83+
84+
if (this.needReAuthorize(url)) {
85+
// since new authorization is underway hence we do not send may be previously authorized accounts to the dapp
86+
return [];
87+
}
7388

74-
return this.filterForAuthorizedAccounts(transformedAccounts, url);
89+
return authorizedAccounts;
7590
}
7691

7792
private accountsSubscribeAuthorized (url: string, id: string, port: chrome.runtime.Port): string {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface AuthUrlInfo {
2020
origin: string;
2121
url: string;
2222
authorizedAccounts: string[];
23+
authorizedTime: number;
2324
}
2425

2526
type KeysWithDefinedValues<T> = {
@@ -56,6 +57,7 @@ export interface AccountJson extends KeyringPair$Meta {
5657
isQR?: boolean;
5758
profile?: string;
5859
stakingAccount?: string;
60+
addedTime?: number; // for DApp authorization check
5961
}
6062

6163
export type AccountWithChildren = AccountJson & {
@@ -438,3 +440,7 @@ export interface ResponseJsonGetAccountInfo {
438440
export interface ResponseAuthorizeList {
439441
list: AuthUrls;
440442
}
443+
444+
export interface ApplyAddedTime {
445+
pair: KeyringPair;
446+
}

packages/extension-inject/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ export type Unsubcall = () => void;
1414

1515
export interface InjectedAccount {
1616
address: string;
17+
addedTime?: number;
1718
genesisHash?: string | null;
1819
name?: string;
1920
type?: KeypairType;
21+
whenCreated?: number;
2022
}
2123

2224
export interface InjectedAccountWithMeta {

packages/extension-polkagate/src/messaging.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,8 @@ export async function removeAuthorization (id: string): Promise<ResponseAuthoriz
206206
return sendMessage('pri(authorize.remove)', id);
207207
}
208208

209-
export async function ignoreAuthRequest (url: string): Promise<void> {
210-
return sendMessage('pri(authorize.ignore)', url);
209+
export async function ignoreAuthRequest (id: string): Promise<void> {
210+
return sendMessage('pri(authorize.ignore)', id);
211211
}
212212

213213
export async function subscribeMetadataRequests (cb: (accounts: MetadataRequest[]) => void): Promise<boolean> {

packages/extension-polkagate/src/popup/authorize/AuthFullScreenMode.tsx

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import type { AuthorizeRequest } from '@polkadot/extension-base/background/types
77

88
import { KeyboardDoubleArrowLeft as KeyboardDoubleArrowLeftIcon, KeyboardDoubleArrowRight as KeyboardDoubleArrowRightIcon } from '@mui/icons-material';
99
import { Avatar, Grid, IconButton, Typography, useTheme } from '@mui/material';
10-
import React, { useCallback, useContext, useMemo, useState } from 'react';
10+
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
1111

1212
import { FULLSCREEN_WIDTH } from '@polkadot/extension-polkagate/src/util/constants';
1313

1414
import { AccountContext, AccountsTable, ActionContext, TwoButtons, VaadinIcon, Warning } from '../../components';
1515
import { FullScreenHeader } from '../../fullscreen/governance/FullScreenHeader';
1616
import { useFavIcon, useFullscreen, useTranslation } from '../../hooks';
17-
import { approveAuthRequest, ignoreAuthRequest } from '../../messaging';
17+
import { approveAuthRequest, getAuthList, ignoreAuthRequest } from '../../messaging';
1818
import { areArraysEqual, extractBaseUrl } from '../../util/utils';
1919

2020
interface Props {
@@ -28,27 +28,52 @@ function AuthFullScreenMode ({ onNextAuth, onPreviousAuth, requestIndex, request
2828
useFullscreen();
2929
const { t } = useTranslation();
3030
const theme = useTheme();
31-
const { accounts } = useContext(AccountContext);
31+
3232
const onAction = useContext(ActionContext);
33+
const { accounts } = useContext(AccountContext);
34+
35+
const url = requests[requestIndex].url;
36+
const faviconUrl = useFavIcon(url);
3337

3438
const [selectedAccounts, setSelectedAccounts] = useState<string[]>([]);
39+
const [alreadySelectedAccounts, setAlreadySelectedAccounts] = useState<string[]>([]);
3540

36-
const faviconUrl = useFavIcon(requests[requestIndex].url);
3741

3842
const allAccounts = useMemo(() => accounts.map(({ address }) => address), [accounts]);
3943
const areAllCheck = useMemo(() => areArraysEqual([allAccounts, selectedAccounts]), [allAccounts, selectedAccounts]);
4044

45+
useEffect(() => {
46+
getAuthList()
47+
.then(({ list: authList }) => {
48+
const dappURL = extractBaseUrl(url);
49+
50+
const availableDapp = Object.values(authList).find(({ url }) => dappURL === extractBaseUrl(url));
51+
52+
if (availableDapp) {
53+
const alreadySelectedAccounts = availableDapp.authorizedAccounts ?? [];
54+
55+
setSelectedAccounts(alreadySelectedAccounts);
56+
setAlreadySelectedAccounts(alreadySelectedAccounts);
57+
}
58+
})
59+
.catch(console.error);
60+
}, [url]);
61+
4162
const onApprove = useCallback((): void => {
4263
approveAuthRequest(selectedAccounts, requests[requestIndex].id)
4364
.then(() => onAction())
4465
.catch((error: Error) => console.error(error));
4566
}, [onAction, requestIndex, requests, selectedAccounts]);
4667

47-
const onReject = useCallback((): void => {
48-
ignoreAuthRequest(requests[requestIndex].id)
49-
.then(() => onAction())
68+
const onIgnore = useCallback((): void => {
69+
const id = requests[requestIndex].id;
70+
71+
(alreadySelectedAccounts.length
72+
? approveAuthRequest(alreadySelectedAccounts, id)
73+
: ignoreAuthRequest(id)
74+
).then(() => onAction())
5075
.catch((error: Error) => console.error(error));
51-
}, [onAction, requestIndex, requests]);
76+
}, [alreadySelectedAccounts, onAction, requestIndex, requests]);
5277

5378
return (
5479
<Grid bgcolor='backgroundFL.primary' container item justifyContent='center'>
@@ -93,7 +118,7 @@ function AuthFullScreenMode ({ onNextAuth, onPreviousAuth, requestIndex, request
93118
variant='circular'
94119
/>
95120
<span style={{ fontSize: '15px', fontWeight: 400, overflowWrap: 'anywhere' }}>
96-
{extractBaseUrl(requests[requestIndex].url)}
121+
{extractBaseUrl(url)}
97122
</span>
98123
</Grid>
99124
<Grid container item sx={{ marginBottom: '15px', marginTop: '10px' }}>
@@ -121,9 +146,9 @@ function AuthFullScreenMode ({ onNextAuth, onPreviousAuth, requestIndex, request
121146
disabled={selectedAccounts.length === 0}
122147
mt='15px'
123148
onPrimaryClick={onApprove}
124-
onSecondaryClick={onReject}
149+
onSecondaryClick={onIgnore}
125150
primaryBtnText={t('Allow')}
126-
secondaryBtnText={t('Reject')}
151+
secondaryBtnText={t('Ignore')}
127152
/>
128153
</Grid>
129154
</Grid>

0 commit comments

Comments
 (0)