Skip to content

Commit a3afef3

Browse files
author
Llorenç Muntaner
authored
Add optional createdAt property to last used identities (#3414)
* Add optional createdAt property to last used identities * Add tests for util * Update last used identities * Format * Remove unused imports * Refactor to use nanosToMillis and add create_at field to endpoint * Revert `updateLastUsedIdentity` * Remove comment * Avoid two calls
1 parent 0808bc3 commit a3afef3

File tree

11 files changed

+41
-5
lines changed

11 files changed

+41
-5
lines changed

src/frontend/src/lib/flows/authFlow.svelte.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
decodeJWT,
3737
extractIssuerTemplateClaims,
3838
} from "$lib/utils/openID";
39+
import { nanosToMillis } from "$lib/utils/time";
3940

4041
interface AuthFlowOptions {
4142
trackLastUsed?: boolean;
@@ -111,6 +112,7 @@ export class AuthFlow {
111112
identityNumber,
112113
name: info.name[0],
113114
authMethod: { passkey: { credentialId } },
115+
createdAtMillis: info.created_at.map(nanosToMillis)[0],
114116
});
115117
}
116118
return identityNumber;
@@ -211,6 +213,7 @@ export class AuthFlow {
211213
authMethod: {
212214
openid: { iss, sub, metadata: authnMethod?.metadata, loginHint },
213215
},
216+
createdAtMillis: info.created_at.map(nanosToMillis)[0],
214217
});
215218
}
216219
return { identityNumber, type: "signIn" };
@@ -313,6 +316,7 @@ export class AuthFlow {
313316
identityNumber,
314317
name: passkeyIdentity.getName(),
315318
authMethod: { passkey: { credentialId } },
319+
createdAtMillis: Date.now(),
316320
});
317321
}
318322
this.#captcha = undefined;
@@ -425,6 +429,7 @@ export class AuthFlow {
425429
identityNumber,
426430
name,
427431
authMethod: { openid: { iss, sub, loginHint, metadata } },
432+
createdAtMillis: Date.now(),
428433
});
429434
}
430435
this.#captcha = undefined;

src/frontend/src/lib/flows/migrationFlow.svelte.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ export class MigrationFlow {
170170
credentialId: new Uint8Array(credentialId),
171171
},
172172
},
173+
// Legacy identities do not have the `created_at` property
174+
createdAtMillis: undefined,
173175
});
174176
};
175177

src/frontend/src/lib/flows/registerAccessMethodFlow.svelte.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { DiscoverableDummyIdentity } from "$lib/utils/discoverableDummyIdentity"
1111
import { DiscoverablePasskeyIdentity } from "$lib/utils/discoverablePasskeyIdentity";
1212
import { inferPasskeyAlias, loadUAParser } from "$lib/legacy/flows/register";
1313
import { lastUsedIdentitiesStore } from "$lib/stores/last-used-identities.store";
14+
import { nanosToMillis } from "$lib/utils/time";
1415

1516
const POLL_INTERVAL = 3000; // Should be frequent enough
1617

@@ -20,6 +21,7 @@ export class RegisterAccessMethodFlow {
2021
>("continueFromExistingDevice");
2122
#confirmationCode = $state<string>();
2223
#identityName = $state<string>();
24+
#createdAtMillis = $state<number>();
2325
#identityNumber = $state<bigint>();
2426
#existingDeviceLink = $state<URL>();
2527

@@ -79,6 +81,7 @@ export class RegisterAccessMethodFlow {
7981
// Show confirm sign-in view if session has been confirmed
8082
if (nonNullish(info)) {
8183
this.#identityName = info.name[0] ?? identityNumber.toString(10);
84+
this.#createdAtMillis = info.created_at.map(nanosToMillis)[0];
8285
this.#view = "confirmSignIn";
8386
return;
8487
}
@@ -134,6 +137,7 @@ export class RegisterAccessMethodFlow {
134137
credentialId: new Uint8Array(credentialId),
135138
},
136139
},
140+
createdAtMillis: this.#createdAtMillis,
137141
});
138142
return this.#identityNumber;
139143
};

src/frontend/src/lib/generated/internet_identity_idl.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,10 @@ export const idlFactory = ({ IDL }) => {
192192
const AuthnMethodSecuritySettingsReplaceError = IDL.Variant({
193193
'AuthnMethodNotFound' : IDL.Null,
194194
});
195-
const AuthnMethodSessionInfo = IDL.Record({ 'name' : IDL.Opt(IDL.Text) });
195+
const AuthnMethodSessionInfo = IDL.Record({
196+
'name' : IDL.Opt(IDL.Text),
197+
'created_at' : IDL.Opt(Timestamp),
198+
});
196199
const CheckCaptchaArg = IDL.Record({ 'solution' : IDL.Text });
197200
const RegistrationFlowNextStep = IDL.Variant({
198201
'CheckCaptcha' : IDL.Record({ 'captcha_png_base64' : IDL.Text }),

src/frontend/src/lib/generated/internet_identity_types.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,10 @@ export type AuthnMethodSecuritySettingsReplaceError = {
225225
*/
226226
'AuthnMethodNotFound' : null
227227
};
228-
export interface AuthnMethodSessionInfo { 'name' : [] | [string] }
228+
export interface AuthnMethodSessionInfo {
229+
'name' : [] | [string],
230+
'created_at' : [] | [Timestamp],
231+
}
229232
export interface BufferedArchiveEntry {
230233
'sequence_number' : bigint,
231234
'entry' : Uint8Array | number[],

src/frontend/src/lib/stores/last-used-identities.store.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,12 @@ describe("lastUsedIdentitiesStore", () => {
4747
});
4848

4949
it("should add the first identity correctly", () => {
50+
const createdAtMillis = 1690000000000;
5051
lastUsedIdentitiesStore.addLastUsedIdentity({
5152
identityNumber: identity1,
5253
name: name1,
5354
authMethod: { passkey: { credentialId: credId1 } },
55+
createdAtMillis,
5456
});
5557

5658
const expected: LastUsedIdentities = {
@@ -59,6 +61,7 @@ describe("lastUsedIdentitiesStore", () => {
5961
name: name1,
6062
authMethod: { passkey: { credentialId: credId1 } },
6163
lastUsedTimestampMillis: mockTimestamp1,
64+
createdAtMillis,
6265
},
6366
};
6467
expect(get(lastUsedIdentitiesStore).identities).toEqual(expected);
@@ -176,17 +179,20 @@ describe("lastUsedIdentityStore (derived store)", () => {
176179
});
177180

178181
it("should return the only identity when one is added", () => {
182+
const createdAtMillis = 1690000000000;
179183
lastUsedIdentitiesStore.addLastUsedIdentity({
180184
identityNumber: identity1,
181185
name: name1,
182186
authMethod: { passkey: { credentialId: credId1 } },
187+
createdAtMillis,
183188
});
184189

185190
const expected: LastUsedIdentity = {
186191
identityNumber: identity1,
187192
name: name1,
188193
authMethod: { passkey: { credentialId: credId1 } },
189194
lastUsedTimestampMillis: mockTimestamp1,
195+
createdAtMillis,
190196
};
191197
expect(get(lastUsedIdentityStore)).toEqual(expected);
192198
});

src/frontend/src/lib/stores/last-used-identities.store.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export type LastUsedIdentity = {
3434
};
3535
accounts?: LastUsedAccounts;
3636
lastUsedTimestampMillis: number;
37+
createdAtMillis?: number;
3738
};
3839
export type LastUsedIdentities = {
3940
[identityNumber: string]: LastUsedIdentity;
@@ -43,7 +44,10 @@ type LastUsedIdentitiesStore = Readable<{
4344
selected?: LastUsedIdentity;
4445
}> & {
4546
addLastUsedIdentity: (
46-
params: Pick<LastUsedIdentity, "identityNumber" | "name" | "authMethod">,
47+
params: Pick<
48+
LastUsedIdentity,
49+
"identityNumber" | "name" | "authMethod" | "createdAtMillis"
50+
>,
4751
) => void;
4852
addLastUsedAccount: (
4953
params: Omit<LastUsedAccount, "lastUsedTimestampMillis">,

src/frontend/src/lib/utils/time.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,6 @@ export const formatLastUsage = (timestamp: Date): string => {
2828

2929
return relativeTime;
3030
};
31+
32+
export const nanosToMillis = (nanos: bigint): number =>
33+
Number(nanos / BigInt(1_000_000));

src/internet_identity/internet_identity.did

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ type AuthnMethodConfirmationCode = record {
516516

517517
type AuthnMethodSessionInfo = record {
518518
name : opt text;
519+
created_at : opt Timestamp;
519520
};
520521

521522
type RegistrationId = text;

src/internet_identity/src/main.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,8 +1022,12 @@ mod v2_api {
10221022
// Return session info if caller matches confirmed session
10231023
tentative_device_registration::get_confirmed_session(identity_number)
10241024
.is_some_and(|confirmed_session| confirmed_session == caller())
1025-
.then_some(AuthnMethodSessionInfo {
1026-
name: state::anchor(identity_number).name(),
1025+
.then(|| {
1026+
let anchor = state::anchor(identity_number);
1027+
AuthnMethodSessionInfo {
1028+
name: anchor.name(),
1029+
created_at: anchor.created_at(),
1030+
}
10271031
})
10281032
}
10291033

0 commit comments

Comments
 (0)