Skip to content

Commit bf87c11

Browse files
author
Frederik Rothenberger
committed
Fix device type checks based on alias
This PR fixes checks for recovery devices and recovery phrases that were based on the device name. This change is required because the public endpoints no longer give information about the alias. This PR also includes some typing improvements around the alias being stripped on the public 'lookup' endpoint.
1 parent 239ce91 commit bf87c11

File tree

8 files changed

+71
-37
lines changed

8 files changed

+71
-37
lines changed

src/frontend/src/flows/manage/deviceSettings.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { unreachable } from "../../utils/utils";
1111
import { DeviceData } from "../../../generated/internet_identity_types";
1212
import { phraseRecoveryPage } from "../recovery/recoverWith/phrase";
1313
import { mainWindow } from "../../components/mainWindow";
14+
import { recoveryDeviceToLabel } from "../../utils/recoveryDeviceLabel";
1415

1516
// The "device settings" page where users can view information about a device,
1617
// remove a device, make a recovery phrase protected, etc.
@@ -32,7 +33,9 @@ const deviceSettingsTemplate = ({
3233
}) => {
3334
const pageContentSlot = html` <article id="deviceSettings">
3435
<h1 class="t-title">
35-
${isRecovery(device) ? "" : "Device"} ${device.alias}
36+
${isRecovery(device)
37+
? recoveryDeviceToLabel(device)
38+
: `Device ${device.alias}`}
3639
</h1>
3740
3841
<div class="l-stack">

src/frontend/src/flows/manage/index.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { chooseDeviceAddFlow } from "../addDevice/manage";
2121
import { addLocalDevice } from "../addDevice/manage/addLocalDevice";
2222
import { warnBox } from "../../components/warnBox";
2323
import { mainWindow } from "../../components/mainWindow";
24+
import { recoveryDeviceToLabel } from "../../utils/recoveryDeviceLabel";
2425

2526
/* Template for the authbox when authenticating to II */
2627
export const authnTemplateManage = (): AuthnTemplates => {
@@ -228,17 +229,21 @@ const recoverySection = (
228229
`;
229230
};
230231

231-
const deviceListItem = (device: DeviceData) => html`
232-
<div class="c-action-list__label">${device.alias}</div>
233-
<button
234-
type="button"
235-
aria-label="settings"
236-
data-action="settings"
237-
class="c-action-list__action"
238-
>
239-
${settingsIcon}
240-
</button>
241-
`;
232+
const deviceListItem = (device: DeviceData) => {
233+
const label =
234+
"recovery" in device.purpose ? recoveryDeviceToLabel(device) : device.alias;
235+
return html`
236+
<div class="c-action-list__label">${label}</div>
237+
<button
238+
type="button"
239+
aria-label="settings"
240+
data-action="settings"
241+
class="c-action-list__action"
242+
>
243+
${settingsIcon}
244+
</button>
245+
`;
246+
};
242247

243248
const recoveryNag = ({ onAddRecovery }: { onAddRecovery: () => void }) =>
244249
warnBox({

src/frontend/src/flows/recovery/chooseRecoveryMechanism.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export const chooseRecoveryMechanism = async ({
8888
message,
8989
cancelText,
9090
}: {
91-
devices: DeviceData[];
91+
devices: Omit<DeviceData, "alias">[];
9292
} & ChooseRecoveryProps): Promise<RecoveryMechanism | null> => {
9393
return new Promise((resolve) => {
9494
return chooseRecoveryMechanismPage({
@@ -104,7 +104,12 @@ export const chooseRecoveryMechanism = async ({
104104
});
105105
};
106106

107-
const hasRecoveryPhrase = (devices: DeviceData[]): boolean =>
108-
devices.some((device) => device.alias === "Recovery phrase");
109-
const hasRecoveryKey = (devices: DeviceData[]): boolean =>
110-
devices.some((device) => device.alias === "Recovery key");
107+
const hasRecoveryPhrase = (devices: Omit<DeviceData, "alias">[]): boolean =>
108+
devices.some(
109+
(device) => "seed_phrase" in device.key_type && "recovery" in device.purpose
110+
);
111+
const hasRecoveryKey = (devices: Omit<DeviceData, "alias">[]): boolean =>
112+
devices.some(
113+
(device) =>
114+
!("seed_phrase" in device.key_type) && "recovery" in device.purpose
115+
);

src/frontend/src/flows/recovery/pickRecoveryDevice.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { html, render } from "lit-html";
22
import { DeviceData } from "../../../generated/internet_identity_types";
33
import { mainWindow } from "../../components/mainWindow";
44
import { securityKeyIcon, seedPhraseIcon } from "../../components/icons";
5+
import { recoveryDeviceToLabel } from "../../utils/recoveryDeviceLabel";
56

67
const pageContent = () => {
78
const pageContentSlot = html`
@@ -22,14 +23,16 @@ const pageContent = () => {
2223
};
2324

2425
export const pickRecoveryDevice = async (
25-
devices: DeviceData[]
26-
): Promise<DeviceData> => {
26+
devices: Omit<DeviceData, "alias">[]
27+
): Promise<Omit<DeviceData, "alias">> => {
2728
const container = document.getElementById("pageContent") as HTMLElement;
2829
render(pageContent(), container);
2930
return init(devices);
3031
};
3132

32-
export const init = (devices: DeviceData[]): Promise<DeviceData> =>
33+
export const init = (
34+
devices: Omit<DeviceData, "alias">[]
35+
): Promise<Omit<DeviceData, "alias">> =>
3336
new Promise((resolve) => {
3437
const deviceList = document.getElementById("deviceList") as HTMLElement;
3538
deviceList.innerHTML = ``;
@@ -44,11 +47,11 @@ export const init = (devices: DeviceData[]): Promise<DeviceData> =>
4447
html`<div class="deviceItemAlias">
4548
<button class="c-button c-button--secondary">
4649
<span aria-hidden="true"
47-
>${device.alias === "Recovery Phrase"
50+
>${"seed_phrase" in device.key_type
4851
? seedPhraseIcon
4952
: securityKeyIcon}</span
5053
>
51-
<div class="t-strong">${device.alias}</div>
54+
<div class="t-strong">${recoveryDeviceToLabel(device)}</div>
5255
</button>
5356
</div>`,
5457
identityElement

src/frontend/src/flows/recovery/recoverWith/device.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const pageContent = () => {
4949
export const deviceRecoveryPage = async (
5050
userNumber: bigint,
5151
connection: Connection,
52-
device: DeviceData
52+
device: Omit<DeviceData, "alias">
5353
): Promise<LoginFlowSuccess | LoginFlowCanceled> => {
5454
const container = document.getElementById("pageContent") as HTMLElement;
5555
render(pageContent(), container);
@@ -59,7 +59,7 @@ export const deviceRecoveryPage = async (
5959
const init = (
6060
userNumber: bigint,
6161
connection: Connection,
62-
device: DeviceData
62+
device: Omit<DeviceData, "alias">
6363
): Promise<LoginFlowSuccess | LoginFlowCanceled> =>
6464
new Promise((resolve) => {
6565
const buttonContinue = document.getElementById(

src/frontend/src/flows/recovery/recoverWith/phrase.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ const pageContent = (userNumber: bigint, message?: string) => {
8080
export const phraseRecoveryPage = async (
8181
userNumber: bigint,
8282
connection: Connection,
83-
device: DeviceData,
83+
device: Omit<DeviceData, "alias">,
8484
prefilledPhrase?: string,
8585
message?: string
8686
): Promise<LoginFlowSuccess | LoginFlowCanceled> => {
@@ -92,7 +92,7 @@ export const phraseRecoveryPage = async (
9292
const init = (
9393
userNumber: bigint,
9494
connection: Connection,
95-
device: DeviceData,
95+
device: Omit<DeviceData, "alias">,
9696
prefilledPhrase?: string /* if set, prefilled as input */,
9797
message?: string
9898
): Promise<LoginFlowSuccess | LoginFlowCanceled> =>

src/frontend/src/utils/iiConnection.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ export class Connection {
181181
};
182182

183183
login = async (userNumber: bigint): Promise<LoginResult> => {
184-
let devices: DeviceData[];
184+
let devices: Omit<DeviceData, "alias">[];
185185
try {
186186
devices = await this.lookupAuthenticators(userNumber);
187187
} catch (e: unknown) {
@@ -204,7 +204,7 @@ export class Connection {
204204

205205
fromWebauthnDevices = async (
206206
userNumber: bigint,
207-
devices: DeviceData[]
207+
devices: Omit<DeviceData, "alias">[]
208208
): Promise<LoginResult> => {
209209
/* Recover the Identity (i.e. key pair) used when creating the anchor.
210210
* If "II_DUMMY_AUTH" is set, we use a dummy identity, the same identity
@@ -262,7 +262,7 @@ export class Connection {
262262
};
263263

264264
private updateKeyTypeIfNecessary(
265-
devices: DeviceData[],
265+
devices: Omit<DeviceData, "alias">[],
266266
attachmentInfo: {
267267
credentialId: ArrayBuffer;
268268
authenticatorAttachment: AuthenticatorAttachment;
@@ -320,7 +320,7 @@ export class Connection {
320320
fromSeedPhrase = async (
321321
userNumber: bigint,
322322
seedPhrase: string,
323-
expected: DeviceData
323+
expected: Omit<DeviceData, "alias">
324324
): Promise<LoginResult> => {
325325
const identity = await fromMnemonicWithoutValidation(
326326
seedPhrase,
@@ -352,7 +352,9 @@ export class Connection {
352352
};
353353
};
354354

355-
lookupAll = async (userNumber: UserNumber): Promise<DeviceData[]> => {
355+
lookupAll = async (
356+
userNumber: UserNumber
357+
): Promise<Omit<DeviceData, "alias">[]> => {
356358
const actor = await this.createActor();
357359
return await actor.lookup(userNumber);
358360
};
@@ -366,7 +368,7 @@ export class Connection {
366368

367369
lookupAuthenticators = async (
368370
userNumber: UserNumber
369-
): Promise<DeviceData[]> => {
371+
): Promise<Omit<DeviceData, "alias">[]> => {
370372
const actor = await this.createActor();
371373
const allDevices = await actor.lookup(userNumber);
372374
return allDevices.filter((device) => "authentication" in device.purpose);
@@ -393,7 +395,9 @@ export class Connection {
393395
});
394396
};
395397

396-
lookupRecovery = async (userNumber: UserNumber): Promise<DeviceData[]> => {
398+
lookupRecovery = async (
399+
userNumber: UserNumber
400+
): Promise<Omit<DeviceData, "alias">[]> => {
397401
const actor = await this.createActor();
398402
const allDevices = await actor.lookup(userNumber);
399403
return allDevices.filter((device) => "recovery" in device.purpose);
@@ -570,7 +574,7 @@ export class AuthenticatedConnection extends Connection {
570574
// credentials.get), see
571575
// * https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/Attestation_and_Assertion
572576
export const creationOptions = (
573-
exclude: DeviceData[] = [],
577+
exclude: Omit<DeviceData, "alias">[] = [],
574578
authenticatorAttachment?: AuthenticatorAttachment
575579
): PublicKeyCredentialCreationOptions => {
576580
return {
@@ -623,10 +627,10 @@ export const bufferEqual = (buf1: ArrayBuffer, buf2: ArrayBuffer): boolean => {
623627
return true;
624628
};
625629

626-
function findDeviceByCredentialId(
627-
devices: DeviceData[],
630+
function findDeviceByCredentialId<T extends Omit<DeviceData, "alias">>(
631+
devices: T[],
628632
credentialId: ArrayBuffer
629-
) {
633+
): T | undefined {
630634
return devices.find((device) => {
631635
const id = device.credential_id[0];
632636
if (id === undefined) {
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { DeviceData } from "../../generated/internet_identity_types";
2+
3+
export const recoveryDeviceToLabel = (
4+
device: Omit<DeviceData, "alias">
5+
): string => {
6+
if (!("recovery" in device.purpose)) {
7+
throw new Error(`${device} is not a recovery device`);
8+
}
9+
10+
if ("seed_phrase" in device.key_type) {
11+
return "Recovery phrase";
12+
}
13+
return "Recovery key";
14+
};

0 commit comments

Comments
 (0)