@structured-id/ui-core 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -10
- package/package.json +25 -43
- package/src/components/SidStatusBadge.vue +1 -5
- package/src/composables/useAccountTransport.test.ts +2 -1
- package/src/composables/useGrpc.test.ts +17 -14
- package/src/composables/useGrpc.ts +4 -2
- package/src/index.ts +2 -120
- package/src/profile/components/ConsentManager.vue +5 -1
- package/src/profile/components/ProfileEditor.vue +1 -1
- package/src/profile/components/SecuritySettings.vue +5 -1
- package/src/profile/components/SessionList.vue +2 -1
- package/src/profile/components/SidRegistrationForm.test.ts +173 -1
- package/src/profile/components/SidRegistrationForm.vue +88 -32
- package/src/profile/components/index.ts +1 -4
- package/src/profile/composables/index.ts +1 -0
- package/src/profile/composables/operationPlan.test.ts +155 -0
- package/src/profile/composables/operationPlan.ts +218 -0
- package/src/profile/composables/useAccountApi.ts +4 -4
- package/src/profile/composables/useAuth.test.ts +312 -31
- package/src/profile/composables/useAuth.ts +176 -30
- package/src/profile/composables/useAuthApi.opaque.test.ts +13 -6
- package/src/profile/composables/useAuthApi.ts +40 -18
- package/src/profile/composables/useCurrentPasswordRequirement.test.ts +196 -0
- package/src/profile/composables/useCurrentPasswordRequirement.ts +95 -0
- package/src/profile/composables/useEnrollmentApi.test.ts +7 -3
- package/src/profile/composables/useEnrollmentApi.ts +3 -5
- package/src/profile/composables/useIdentityApi.ts +4 -4
- package/src/profile/composables/useSecurityLevel.test.ts +1 -1
- package/src/profile/composables/useSecurityLevel.ts +1 -1
- package/src/quasar/SidPasswordCeremony.test.ts +253 -0
- package/src/quasar/SidPasswordCeremony.vue +456 -0
- package/src/quasar/index.ts +2 -0
- package/src/utils/rpcRefusal.test.ts +4 -4
- package/src/utils/rpcRefusal.ts +8 -4
- package/dist/browser-vp/index.cjs +0 -349
- package/dist/browser-vp/index.d.cts +0 -276
- package/dist/browser-vp/index.d.ts +0 -276
- package/dist/browser-vp/index.js +0 -349
- package/dist/chunk-C3Q766CY.cjs +0 -125
- package/dist/chunk-J5YXRXB5.js +0 -125
- package/dist/index.cjs +0 -23581
- package/dist/index.d.cts +0 -11539
- package/dist/index.d.ts +0 -11539
- package/dist/index.js +0 -23581
- package/dist/storage-4CB2JTLW.cjs +0 -22
- package/dist/storage-THRWREZP.js +0 -22
- package/src/browser-vp/crypto.ts +0 -253
- package/src/browser-vp/index.test.ts +0 -10
- package/src/browser-vp/index.ts +0 -116
- package/src/browser-vp/prf.ts +0 -179
- package/src/browser-vp/storage.test.ts +0 -9
- package/src/browser-vp/storage.ts +0 -163
- package/src/browser-vp/types.ts +0 -95
- package/src/browser-vp/vp.test.ts +0 -43
- package/src/browser-vp/vp.ts +0 -111
- package/src/generated/google/api/http.ts +0 -687
- package/src/generated/google/api/httpbody.ts +0 -159
- package/src/generated/google/protobuf/any.ts +0 -326
- package/src/generated/google/protobuf/descriptor.ts +0 -4865
- package/src/generated/google/protobuf/duration.ts +0 -230
- package/src/generated/google/protobuf/empty.ts +0 -94
- package/src/generated/google/protobuf/struct.ts +0 -484
- package/src/generated/google/protobuf/timestamp.ts +0 -287
- package/src/generated/google/rpc/error_details.ts +0 -1386
- package/src/generated/google/rpc/status.ts +0 -127
- package/src/generated/sid/v1/account/account.client.ts +0 -527
- package/src/generated/sid/v1/account/account.ts +0 -3261
- package/src/generated/sid/v1/admin/admin.client.ts +0 -139
- package/src/generated/sid/v1/admin/admin.ts +0 -781
- package/src/generated/sid/v1/admin/admin_roles.client.ts +0 -181
- package/src/generated/sid/v1/admin/admin_roles.ts +0 -1606
- package/src/generated/sid/v1/admin/analytics.client.ts +0 -106
- package/src/generated/sid/v1/admin/analytics.ts +0 -1017
- package/src/generated/sid/v1/admin/branding.client.ts +0 -166
- package/src/generated/sid/v1/admin/branding.ts +0 -1906
- package/src/generated/sid/v1/admin/enrollment.client.ts +0 -177
- package/src/generated/sid/v1/admin/enrollment.ts +0 -1766
- package/src/generated/sid/v1/admin/events.client.ts +0 -195
- package/src/generated/sid/v1/admin/events.ts +0 -1900
- package/src/generated/sid/v1/admin/flow.client.ts +0 -183
- package/src/generated/sid/v1/admin/flow.ts +0 -1552
- package/src/generated/sid/v1/admin/keys.client.ts +0 -107
- package/src/generated/sid/v1/admin/keys.ts +0 -931
- package/src/generated/sid/v1/admin/login_experience.client.ts +0 -160
- package/src/generated/sid/v1/admin/login_experience.ts +0 -1061
- package/src/generated/sid/v1/admin/migration.client.ts +0 -127
- package/src/generated/sid/v1/admin/migration.ts +0 -1948
- package/src/generated/sid/v1/admin/notifications.client.ts +0 -134
- package/src/generated/sid/v1/admin/notifications.ts +0 -1273
- package/src/generated/sid/v1/admin/operations.client.ts +0 -241
- package/src/generated/sid/v1/admin/operations.ts +0 -2633
- package/src/generated/sid/v1/admin/organizations.client.ts +0 -57
- package/src/generated/sid/v1/admin/organizations.ts +0 -332
- package/src/generated/sid/v1/admin/provisioning.client.ts +0 -232
- package/src/generated/sid/v1/admin/provisioning.ts +0 -1383
- package/src/generated/sid/v1/admin/scopes.client.ts +0 -171
- package/src/generated/sid/v1/admin/scopes.ts +0 -1324
- package/src/generated/sid/v1/admin/security.client.ts +0 -410
- package/src/generated/sid/v1/admin/security.ts +0 -4077
- package/src/generated/sid/v1/admin/sessions.client.ts +0 -114
- package/src/generated/sid/v1/admin/sessions.ts +0 -923
- package/src/generated/sid/v1/admin/settings.client.ts +0 -351
- package/src/generated/sid/v1/admin/settings.ts +0 -2458
- package/src/generated/sid/v1/admin/user_profile.client.ts +0 -57
- package/src/generated/sid/v1/admin/user_profile.ts +0 -730
- package/src/generated/sid/v1/admin/webhooks.client.ts +0 -269
- package/src/generated/sid/v1/admin/webhooks.ts +0 -2804
- package/src/generated/sid/v1/attestation/attestation.client.ts +0 -115
- package/src/generated/sid/v1/attestation/attestation.ts +0 -813
- package/src/generated/sid/v1/authn/auth.client.ts +0 -943
- package/src/generated/sid/v1/authn/auth.ts +0 -9719
- package/src/generated/sid/v1/authn/flows.client.ts +0 -413
- package/src/generated/sid/v1/authn/flows.ts +0 -3205
- package/src/generated/sid/v1/authn/issuer.client.ts +0 -489
- package/src/generated/sid/v1/authn/issuer.ts +0 -1600
- package/src/generated/sid/v1/authn/password_history.client.ts +0 -57
- package/src/generated/sid/v1/authn/password_history.ts +0 -578
- package/src/generated/sid/v1/authn/system_integration.client.ts +0 -73
- package/src/generated/sid/v1/authn/system_integration.ts +0 -218
- package/src/generated/sid/v1/authz/authz.client.ts +0 -459
- package/src/generated/sid/v1/authz/authz.ts +0 -6524
- package/src/generated/sid/v1/authz/forward_auth.client.ts +0 -97
- package/src/generated/sid/v1/authz/forward_auth.ts +0 -79
- package/src/generated/sid/v1/authz/governance.client.ts +0 -102
- package/src/generated/sid/v1/authz/governance.ts +0 -875
- package/src/generated/sid/v1/common/errors.ts +0 -710
- package/src/generated/sid/v1/common/pagination.ts +0 -152
- package/src/generated/sid/v1/common/transport.ts +0 -227
- package/src/generated/sid/v1/common/types.ts +0 -151
- package/src/generated/sid/v1/events/event_stream.client.ts +0 -67
- package/src/generated/sid/v1/events/event_stream.ts +0 -375
- package/src/generated/sid/v1/federation/upstream.client.ts +0 -142
- package/src/generated/sid/v1/federation/upstream.ts +0 -1736
- package/src/generated/sid/v1/identity/identity.client.ts +0 -467
- package/src/generated/sid/v1/identity/identity.ts +0 -5676
- package/src/generated/sid/v1/ids/ids.ts +0 -395
- package/src/generated/sid/v1/machine/machine_user.client.ts +0 -219
- package/src/generated/sid/v1/machine/machine_user.ts +0 -2364
- package/src/generated/sid/v1/machine/pat.client.ts +0 -130
- package/src/generated/sid/v1/machine/pat.ts +0 -1083
- package/src/generated/sid/v1/projects/projects.client.ts +0 -404
- package/src/generated/sid/v1/projects/projects.ts +0 -4446
- package/src/generated/sid/v1/scim/scim.client.ts +0 -520
- package/src/generated/sid/v1/scim/scim.ts +0 -3182
- package/src/generated/sid/v1/test/test.client.ts +0 -232
- package/src/generated/sid/v1/test/test.ts +0 -1136
- package/src/transport/adaptive.test.ts +0 -728
- package/src/transport/adaptive.ts +0 -308
- package/src/transport/framing.test.ts +0 -227
- package/src/transport/framing.ts +0 -98
- package/src/transport/webtransport.test.ts +0 -728
- package/src/transport/webtransport.ts +0 -323
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* on Pallas.
|
|
12
12
|
*
|
|
13
13
|
* The client-side crypto is injected as {@link ZkppClientApi} (implemented by
|
|
14
|
-
* `@structured-id/opaque
|
|
14
|
+
* `@structured-id/opaque`), keeping kernel delivery at the app layer.
|
|
15
15
|
* gRPC calls go through useAuthApi wrappers.
|
|
16
16
|
*/
|
|
17
17
|
import {
|
|
@@ -46,12 +46,38 @@ export interface RegisterResult {
|
|
|
46
46
|
credentialId: string;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
import {
|
|
50
|
+
CONFIRMED_CHANGE_STEPS,
|
|
51
|
+
INSTALL_STEPS,
|
|
52
|
+
OperationPlan,
|
|
53
|
+
type PasswordOperationProgress,
|
|
54
|
+
} from "./operationPlan";
|
|
55
|
+
|
|
56
|
+
/** A password change was refused on this device: the current password is wrong. */
|
|
57
|
+
export class WrongCurrentPasswordError extends Error {
|
|
58
|
+
constructor(cause: unknown) {
|
|
59
|
+
super("The current password is not correct", { cause });
|
|
60
|
+
this.name = "WrongCurrentPasswordError";
|
|
61
|
+
}
|
|
53
62
|
}
|
|
54
63
|
|
|
64
|
+
/** A password change of the signed-in user. */
|
|
65
|
+
export interface PasswordChange {
|
|
66
|
+
/** The password credential to change. */
|
|
67
|
+
credentialId: string;
|
|
68
|
+
newPassword: string;
|
|
69
|
+
/** The current password, proved inside the change; required while the
|
|
70
|
+
* server requires it, accepted whenever given. */
|
|
71
|
+
currentPassword?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
DEFAULT_STEP_MS,
|
|
76
|
+
OperationPlan,
|
|
77
|
+
type PasswordOperationProgress,
|
|
78
|
+
type PasswordOperationStep,
|
|
79
|
+
} from "./operationPlan";
|
|
80
|
+
|
|
55
81
|
/** The operation's history context as the ZKPP client takes it. */
|
|
56
82
|
export interface ZkppHistoryContext {
|
|
57
83
|
/** The operation, 16 bytes; every later step carries it. */
|
|
@@ -93,7 +119,8 @@ export interface ZkppClientApi {
|
|
|
93
119
|
/** True once the client answers no more calls (a worker it runs on stopped). */
|
|
94
120
|
readonly stopped: boolean;
|
|
95
121
|
registrationStart(password: string): Promise<ZkppRegistrationStart>;
|
|
96
|
-
/** `null`
|
|
122
|
+
/** `null` when the circuit cannot hold the password; the server alone
|
|
123
|
+
* decides whether proof-free setup is permitted. This never means verified. */
|
|
97
124
|
historyRequest(
|
|
98
125
|
password: string,
|
|
99
126
|
ownerDomain: Uint8Array,
|
|
@@ -116,16 +143,22 @@ export interface ZkppClientApi {
|
|
|
116
143
|
response: Uint8Array,
|
|
117
144
|
): Promise<Uint8Array>;
|
|
118
145
|
loginStart(password: string): Promise<{ request: Uint8Array; state: string }>;
|
|
146
|
+
/** `context` is the OPAQUE context (RFC 9807 §6): empty for an ordinary
|
|
147
|
+
* sign-in, the operation's own for a sign-in inside another operation. A
|
|
148
|
+
* sign-in that does not verify fails with an error named
|
|
149
|
+
* `ZkppInvalidLoginError`; any other failure keeps its own error. */
|
|
119
150
|
loginFinish(
|
|
120
151
|
password: string,
|
|
121
152
|
state: string,
|
|
122
153
|
response: Uint8Array,
|
|
154
|
+
context: Uint8Array,
|
|
123
155
|
): Promise<Uint8Array>;
|
|
124
156
|
}
|
|
125
157
|
|
|
126
158
|
/**
|
|
127
|
-
* Loads the client on first use
|
|
128
|
-
*
|
|
159
|
+
* Loads the client on first use: public TypeScript or an application-supplied
|
|
160
|
+
* kernel. Automatic selection may use TS after a pre-operation WASM load failure.
|
|
161
|
+
* Errors after an operation starts propagate without moving its state to another kernel.
|
|
129
162
|
*/
|
|
130
163
|
export type ZkppClientLoader = () => Promise<ZkppClientApi>;
|
|
131
164
|
|
|
@@ -181,11 +214,57 @@ function historyEvaluations(
|
|
|
181
214
|
});
|
|
182
215
|
}
|
|
183
216
|
|
|
217
|
+
/**
|
|
218
|
+
* A sign-in the client refused: it did not verify, so the password was judged
|
|
219
|
+
* wrong. Recognised by the name the client contract gives it, whichever
|
|
220
|
+
* kernel or package copy produced it.
|
|
221
|
+
*/
|
|
222
|
+
function isRefusedSignIn(e: unknown): boolean {
|
|
223
|
+
return e instanceof Error && e.name === "ZkppInvalidLoginError";
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const CHANGE_CONTEXT_LABEL = new TextEncoder().encode("SID-PASSWORD-CHANGE-v1");
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The OPAQUE context (RFC 9807 §6) of the current-password sign-in inside a
|
|
230
|
+
* change: the label, the operation and the digest of the new registration
|
|
231
|
+
* request the operation fixed, so the sign-in confirms this change only.
|
|
232
|
+
*/
|
|
233
|
+
async function changeContext(
|
|
234
|
+
operationId: Uint8Array,
|
|
235
|
+
registrationRequest: Uint8Array,
|
|
236
|
+
): Promise<Uint8Array> {
|
|
237
|
+
// WebCrypto takes only ArrayBuffer-backed views; the copy is one.
|
|
238
|
+
const digest = new Uint8Array(
|
|
239
|
+
await crypto.subtle.digest("SHA-256", new Uint8Array(registrationRequest)),
|
|
240
|
+
);
|
|
241
|
+
const context = new Uint8Array(
|
|
242
|
+
CHANGE_CONTEXT_LABEL.length + operationId.length + digest.length,
|
|
243
|
+
);
|
|
244
|
+
context.set(CHANGE_CONTEXT_LABEL);
|
|
245
|
+
context.set(operationId, CHANGE_CONTEXT_LABEL.length);
|
|
246
|
+
context.set(digest, CHANGE_CONTEXT_LABEL.length + operationId.length);
|
|
247
|
+
return context;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The proof as the finish sends it, with the evaluator's proofs relayed
|
|
252
|
+
* unchanged, in domain order: the server's history checker verifies them
|
|
253
|
+
* against the proof's own blinded input and evaluated elements.
|
|
254
|
+
*/
|
|
184
255
|
function wireProof(
|
|
185
256
|
proof: ZkppProof | null,
|
|
257
|
+
evaluations: ZkppHistoryEvaluation[],
|
|
186
258
|
): PasswordRegistrationProof | undefined {
|
|
187
259
|
return proof
|
|
188
|
-
? {
|
|
260
|
+
? {
|
|
261
|
+
zkppProof: proof.proof,
|
|
262
|
+
instances: proof.instances,
|
|
263
|
+
evaluationProofs: evaluations.map((e) => ({
|
|
264
|
+
challenge: e.proof.challenge,
|
|
265
|
+
response: e.proof.response,
|
|
266
|
+
})),
|
|
267
|
+
}
|
|
189
268
|
: undefined;
|
|
190
269
|
}
|
|
191
270
|
|
|
@@ -226,22 +305,32 @@ export function createAuth(loadClient: ZkppClientLoader) {
|
|
|
226
305
|
}
|
|
227
306
|
|
|
228
307
|
/**
|
|
229
|
-
* The steps every operation shares: the OPAQUE start
|
|
230
|
-
*
|
|
308
|
+
* The steps every operation shares: the OPAQUE start (`started` when the
|
|
309
|
+
* operation already fixed it, with the client that made it), `exchange`
|
|
310
|
+
* (the RPC that answers the request and names the operation), the history
|
|
231
311
|
* evaluation, the proof and the record.
|
|
232
312
|
*/
|
|
233
313
|
async function install(
|
|
234
314
|
password: string,
|
|
235
315
|
exchange: (request: Uint8Array) => Promise<Exchange>,
|
|
236
|
-
|
|
316
|
+
plan: OperationPlan,
|
|
317
|
+
started?: { client: ZkppClientApi; start: ZkppRegistrationStart },
|
|
237
318
|
): Promise<Installed> {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const
|
|
319
|
+
// A start stays with the client that made it: its state means nothing to
|
|
320
|
+
// another kernel, so a client that stopped meanwhile fails here as itself.
|
|
321
|
+
const c = started?.client ?? (await zkpp());
|
|
322
|
+
let start: ZkppRegistrationStart;
|
|
323
|
+
if (started) {
|
|
324
|
+
start = started.start;
|
|
325
|
+
} else {
|
|
326
|
+
plan.enter("protect");
|
|
327
|
+
start = await c.registrationStart(password);
|
|
328
|
+
}
|
|
241
329
|
const { context: wire, registrationResponse } = await exchange(
|
|
242
330
|
start.request,
|
|
243
331
|
);
|
|
244
332
|
const context = historyContext(wire);
|
|
333
|
+
plan.enter("compare");
|
|
245
334
|
const request = await c.historyRequest(password, context.ownerDomain);
|
|
246
335
|
const history = request
|
|
247
336
|
? {
|
|
@@ -251,21 +340,24 @@ export function createAuth(loadClient: ZkppClientLoader) {
|
|
|
251
340
|
),
|
|
252
341
|
}
|
|
253
342
|
: null;
|
|
343
|
+
plan.enter("prove");
|
|
254
344
|
const proof = await c.prove(password, start, {
|
|
255
345
|
context,
|
|
256
346
|
history,
|
|
257
|
-
|
|
347
|
+
// The prover's own time-based 0..1 fills the proof's share.
|
|
348
|
+
onProgress: (p) => plan.within(p.fraction),
|
|
258
349
|
});
|
|
350
|
+
// The record (the client's stretching) and the server's check.
|
|
351
|
+
plan.enter("verify");
|
|
259
352
|
const record = await c.registrationFinish(
|
|
260
353
|
password,
|
|
261
354
|
start.state,
|
|
262
355
|
registrationResponse,
|
|
263
356
|
);
|
|
264
|
-
onProgress?.({ fraction: 1, label: "Done" });
|
|
265
357
|
return {
|
|
266
358
|
operationId: context.operationId,
|
|
267
359
|
record,
|
|
268
|
-
proof: wireProof(proof),
|
|
360
|
+
proof: wireProof(proof, history?.evaluations ?? []),
|
|
269
361
|
};
|
|
270
362
|
}
|
|
271
363
|
|
|
@@ -280,6 +372,7 @@ export function createAuth(loadClient: ZkppClientLoader) {
|
|
|
280
372
|
claimToken?: string,
|
|
281
373
|
onProgress?: (p: PasswordOperationProgress) => void,
|
|
282
374
|
): Promise<RegisterResult> {
|
|
375
|
+
const plan = new OperationPlan(onProgress);
|
|
283
376
|
const done = await install(
|
|
284
377
|
password,
|
|
285
378
|
async (request) => {
|
|
@@ -293,13 +386,14 @@ export function createAuth(loadClient: ZkppClientLoader) {
|
|
|
293
386
|
registrationResponse: started.registrationResponse,
|
|
294
387
|
};
|
|
295
388
|
},
|
|
296
|
-
|
|
389
|
+
plan,
|
|
297
390
|
);
|
|
298
391
|
const finished = await opaqueZkppRegistrationFinish(
|
|
299
392
|
done.operationId,
|
|
300
393
|
done.record,
|
|
301
394
|
done.proof,
|
|
302
395
|
);
|
|
396
|
+
plan.finish();
|
|
303
397
|
return {
|
|
304
398
|
profileId: finished.profileId,
|
|
305
399
|
credentialId: finished.credentialId,
|
|
@@ -307,30 +401,76 @@ export function createAuth(loadClient: ZkppClientLoader) {
|
|
|
307
401
|
}
|
|
308
402
|
|
|
309
403
|
/**
|
|
310
|
-
* Replace the signed-in user's password
|
|
311
|
-
*
|
|
312
|
-
*
|
|
404
|
+
* Replace the signed-in user's password with `change.newPassword`: the
|
|
405
|
+
* challenge fixes the new password's OPAQUE request and names the
|
|
406
|
+
* operation, the execute step answers it, the finish carries the record
|
|
407
|
+
* with the proof. With `change.currentPassword` the change signs in with
|
|
408
|
+
* it inside the same steps (KE1 in the challenge, KE3 in execute) under
|
|
409
|
+
* the change's own context, which the server requires unless its policy
|
|
410
|
+
* says the session's authentication is recent enough
|
|
411
|
+
* ({@link useCurrentPasswordRequirement}); a wrong current password is
|
|
412
|
+
* refused before anything new is proved.
|
|
313
413
|
*/
|
|
314
414
|
async function changePassword(
|
|
315
|
-
|
|
316
|
-
newPassword: string,
|
|
415
|
+
change: PasswordChange,
|
|
317
416
|
onProgress?: (p: PasswordOperationProgress) => void,
|
|
318
417
|
): Promise<void> {
|
|
319
|
-
const
|
|
418
|
+
const { credentialId, newPassword, currentPassword } = change;
|
|
419
|
+
// An empty current password is given, not omitted: it is proved and refused.
|
|
420
|
+
const proves = currentPassword !== undefined;
|
|
421
|
+
const plan = new OperationPlan(
|
|
422
|
+
onProgress,
|
|
423
|
+
proves ? CONFIRMED_CHANGE_STEPS : INSTALL_STEPS,
|
|
424
|
+
);
|
|
425
|
+
const c = await zkpp();
|
|
426
|
+
let challenge: Awaited<ReturnType<typeof passwordChangeChallenge>>;
|
|
427
|
+
let credentialFinalization: Uint8Array = new Uint8Array();
|
|
428
|
+
// The new password's start is protection work in either case; the
|
|
429
|
+
// confirmation (the current password's sign-in) is bound to its request.
|
|
430
|
+
plan.enter("protect");
|
|
431
|
+
const start = await c.registrationStart(newPassword);
|
|
432
|
+
if (currentPassword !== undefined) {
|
|
433
|
+
plan.enter("confirm");
|
|
434
|
+
const login = await c.loginStart(currentPassword);
|
|
435
|
+
challenge = await passwordChangeChallenge(
|
|
436
|
+
credentialId,
|
|
437
|
+
start.request,
|
|
438
|
+
login.request,
|
|
439
|
+
);
|
|
440
|
+
const operation = historyContext(challenge.history).operationId;
|
|
441
|
+
const context = await changeContext(operation, start.request);
|
|
442
|
+
try {
|
|
443
|
+
credentialFinalization = await c.loginFinish(
|
|
444
|
+
currentPassword,
|
|
445
|
+
login.state,
|
|
446
|
+
challenge.credentialResponse,
|
|
447
|
+
context,
|
|
448
|
+
);
|
|
449
|
+
} catch (e) {
|
|
450
|
+
// Only the client's refusal judged the password: the server already
|
|
451
|
+
// counted the guess when it issued KE2, so nothing more is sent. Any
|
|
452
|
+
// other failure (a stopped client, a malformed response) is itself.
|
|
453
|
+
if (isRefusedSignIn(e)) throw new WrongCurrentPasswordError(e);
|
|
454
|
+
throw e;
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
challenge = await passwordChangeChallenge(credentialId, start.request);
|
|
458
|
+
}
|
|
320
459
|
const context = historyContext(challenge.history);
|
|
321
460
|
const done = await install(
|
|
322
461
|
newPassword,
|
|
323
|
-
async (
|
|
462
|
+
async () => ({
|
|
324
463
|
context: challenge.history,
|
|
325
464
|
registrationResponse: (
|
|
326
465
|
await passwordChangeExecute(
|
|
327
466
|
context.operationId,
|
|
328
467
|
credentialId,
|
|
329
|
-
|
|
468
|
+
credentialFinalization,
|
|
330
469
|
)
|
|
331
470
|
).registrationResponse,
|
|
332
471
|
}),
|
|
333
|
-
|
|
472
|
+
plan,
|
|
473
|
+
{ client: c, start },
|
|
334
474
|
);
|
|
335
475
|
await passwordChangeFinish(
|
|
336
476
|
done.operationId,
|
|
@@ -338,6 +478,7 @@ export function createAuth(loadClient: ZkppClientLoader) {
|
|
|
338
478
|
done.record,
|
|
339
479
|
done.proof,
|
|
340
480
|
);
|
|
481
|
+
plan.finish();
|
|
341
482
|
}
|
|
342
483
|
|
|
343
484
|
/**
|
|
@@ -350,6 +491,7 @@ export function createAuth(loadClient: ZkppClientLoader) {
|
|
|
350
491
|
newPassword: string,
|
|
351
492
|
onProgress?: (p: PasswordOperationProgress) => void,
|
|
352
493
|
): Promise<CompletePasswordResetResponse> {
|
|
494
|
+
const plan = new OperationPlan(onProgress);
|
|
353
495
|
const prepared = historyContext(context);
|
|
354
496
|
const done = await install(
|
|
355
497
|
newPassword,
|
|
@@ -359,14 +501,16 @@ export function createAuth(loadClient: ZkppClientLoader) {
|
|
|
359
501
|
await executePasswordReset(prepared.operationId, request)
|
|
360
502
|
).registrationResponse,
|
|
361
503
|
}),
|
|
362
|
-
|
|
504
|
+
plan,
|
|
363
505
|
);
|
|
364
|
-
|
|
506
|
+
const completed = await completePasswordReset(
|
|
365
507
|
resetSessionId,
|
|
366
508
|
done.operationId,
|
|
367
509
|
done.record,
|
|
368
510
|
done.proof,
|
|
369
511
|
);
|
|
512
|
+
plan.finish();
|
|
513
|
+
return completed;
|
|
370
514
|
}
|
|
371
515
|
|
|
372
516
|
/**
|
|
@@ -383,10 +527,12 @@ export function createAuth(loadClient: ZkppClientLoader) {
|
|
|
383
527
|
onStep?.(2);
|
|
384
528
|
const started = await opaqueLoginStart(identifier, start.request);
|
|
385
529
|
onStep?.(3);
|
|
530
|
+
// An ordinary sign-in runs under the empty context.
|
|
386
531
|
const finalization = await c.loginFinish(
|
|
387
532
|
password,
|
|
388
533
|
start.state,
|
|
389
534
|
started.credentialResponse,
|
|
535
|
+
new Uint8Array(),
|
|
390
536
|
);
|
|
391
537
|
const finished = await opaqueLoginFinish(
|
|
392
538
|
identifier,
|
|
@@ -14,12 +14,19 @@ const mockOpaqueRegistrationFinish = vi.fn();
|
|
|
14
14
|
|
|
15
15
|
vi.mock("../../index", () => ({
|
|
16
16
|
getTransport: vi.fn(),
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
getAccountTransport: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
vi.mock("@structured-id/proto/sid/v1/authn/auth.client", () => ({
|
|
21
|
+
// Called with `new`: a constructor mock must be a `function`, not an arrow.
|
|
22
|
+
AuthServiceClient: vi.fn().mockImplementation(function () {
|
|
23
|
+
return {
|
|
24
|
+
opaqueLoginStart: mockOpaqueLoginStart,
|
|
25
|
+
opaqueLoginFinish: mockOpaqueLoginFinish,
|
|
26
|
+
opaqueRegistrationStart: mockOpaqueRegistrationStart,
|
|
27
|
+
opaqueRegistrationFinish: mockOpaqueRegistrationFinish,
|
|
28
|
+
};
|
|
29
|
+
}),
|
|
23
30
|
}));
|
|
24
31
|
|
|
25
32
|
// authMeta needs to be mocked since it uses useAuth() which needs Vue context
|
|
@@ -12,10 +12,9 @@
|
|
|
12
12
|
* ceremonies over them (client-side crypto, proofs) live in useAuth.ts,
|
|
13
13
|
* behind the injected ZkppClientApi: no dependency on the client package.
|
|
14
14
|
*/
|
|
15
|
+
import { getTransport, getAccountTransport } from "../../index";
|
|
16
|
+
import { AuthServiceClient } from "@structured-id/proto/sid/v1/authn/auth.client";
|
|
15
17
|
import {
|
|
16
|
-
getTransport,
|
|
17
|
-
getAccountTransport,
|
|
18
|
-
AuthServiceClient,
|
|
19
18
|
StepUpMethod,
|
|
20
19
|
type OpaqueLoginStartResponse,
|
|
21
20
|
type OpaqueLoginFinishResponse,
|
|
@@ -58,12 +57,13 @@ import {
|
|
|
58
57
|
type OpaqueZkppRegistrationFinishResponse,
|
|
59
58
|
type PasswordChangeChallengeResponse,
|
|
60
59
|
type PasswordChangeExecuteResponse,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
} from "
|
|
60
|
+
} from "@structured-id/proto/sid/v1/authn/auth";
|
|
61
|
+
import type {
|
|
62
|
+
PasswordHistoryContext,
|
|
63
|
+
PasswordHistoryEvaluation,
|
|
64
|
+
PasswordRegistrationProof,
|
|
65
|
+
} from "@structured-id/proto/sid/v1/authn/password_history";
|
|
66
|
+
import { PasswordHistoryEvaluatorServiceClient } from "@structured-id/proto/sid/v1/authn/password_history.client";
|
|
67
67
|
import { authMeta } from "../auth";
|
|
68
68
|
|
|
69
69
|
/** The history evaluator: in CE served beside the credential service. */
|
|
@@ -631,42 +631,66 @@ export async function evaluatePasswordHistory(
|
|
|
631
631
|
return response.evaluations;
|
|
632
632
|
}
|
|
633
633
|
|
|
634
|
-
/**
|
|
634
|
+
/**
|
|
635
|
+
* Prepare a change of the signed-in user's own password `credentialId`:
|
|
636
|
+
* `registrationRequest` is the new password's OPAQUE start, which the
|
|
637
|
+
* operation fixes; `credentialRequest` (KE1) begins the sign-in that proves
|
|
638
|
+
* the current password, empty when the change does not prove it.
|
|
639
|
+
*/
|
|
635
640
|
export async function passwordChangeChallenge(
|
|
636
641
|
credentialId: string,
|
|
642
|
+
registrationRequest: Uint8Array,
|
|
643
|
+
credentialRequest: Uint8Array = new Uint8Array(),
|
|
637
644
|
): Promise<PasswordChangeChallengeResponse> {
|
|
638
645
|
const { response } = await account().passwordChangeChallenge(
|
|
639
|
-
{ credentialId },
|
|
646
|
+
{ credentialId, credentialRequest, registrationRequest },
|
|
640
647
|
{ meta: authMeta() },
|
|
641
648
|
);
|
|
642
649
|
return response;
|
|
643
650
|
}
|
|
644
651
|
|
|
645
|
-
/**
|
|
652
|
+
/**
|
|
653
|
+
* The server's answer to the registration request the challenge fixed, with
|
|
654
|
+
* the KE3 of the current-password sign-in the challenge began (empty when
|
|
655
|
+
* it began none).
|
|
656
|
+
*/
|
|
646
657
|
export async function passwordChangeExecute(
|
|
647
658
|
operationId: Uint8Array,
|
|
648
659
|
credentialId: string,
|
|
649
|
-
|
|
660
|
+
credentialFinalization: Uint8Array = new Uint8Array(),
|
|
650
661
|
): Promise<PasswordChangeExecuteResponse> {
|
|
651
662
|
const { response } = await account().passwordChangeExecute(
|
|
652
663
|
{
|
|
653
664
|
operationId: operation(operationId),
|
|
654
665
|
credentialId,
|
|
655
|
-
|
|
666
|
+
credentialFinalization,
|
|
656
667
|
},
|
|
657
668
|
{ meta: authMeta() },
|
|
658
669
|
);
|
|
659
670
|
return response;
|
|
660
671
|
}
|
|
661
672
|
|
|
673
|
+
/**
|
|
674
|
+
* How long, in milliseconds from the server's answer, the signed-in session
|
|
675
|
+
* may still change its password without the current password; 0 when it
|
|
676
|
+
* needs it now.
|
|
677
|
+
*/
|
|
678
|
+
export async function passwordChangeRequirementMs(): Promise<number> {
|
|
679
|
+
const { response: left } = await account().getPasswordChangeRequirement(
|
|
680
|
+
{},
|
|
681
|
+
{ meta: authMeta() },
|
|
682
|
+
);
|
|
683
|
+
return Number(left.seconds) * 1000 + Math.floor(left.nanos / 1_000_000);
|
|
684
|
+
}
|
|
685
|
+
|
|
662
686
|
/** Finish the change with the new password's record and, when proved, the proof. */
|
|
663
687
|
export async function passwordChangeFinish(
|
|
664
688
|
operationId: Uint8Array,
|
|
665
689
|
credentialId: string,
|
|
666
690
|
registrationRecord: Uint8Array,
|
|
667
691
|
proof?: PasswordRegistrationProof,
|
|
668
|
-
): Promise<
|
|
669
|
-
|
|
692
|
+
): Promise<void> {
|
|
693
|
+
await account().passwordChangeFinish(
|
|
670
694
|
{
|
|
671
695
|
operationId: operation(operationId),
|
|
672
696
|
credentialId,
|
|
@@ -675,7 +699,6 @@ export async function passwordChangeFinish(
|
|
|
675
699
|
},
|
|
676
700
|
{ meta: authMeta() },
|
|
677
701
|
);
|
|
678
|
-
return response;
|
|
679
702
|
}
|
|
680
703
|
|
|
681
704
|
// ── OPAQUE ──
|
|
@@ -778,7 +801,6 @@ export type {
|
|
|
778
801
|
OpaqueZkppRegistrationFinishResponse,
|
|
779
802
|
PasswordChangeChallengeResponse,
|
|
780
803
|
PasswordChangeExecuteResponse,
|
|
781
|
-
PasswordChangeFinishResponse,
|
|
782
804
|
PasswordHistoryContext,
|
|
783
805
|
PasswordHistoryEvaluation,
|
|
784
806
|
PasswordRegistrationProof,
|