@structured-id/ui-core 4.11.0 → 4.13.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.
@@ -13,13 +13,22 @@
13
13
  * behind the injected ZkppClientApi: no dependency on the client package.
14
14
  */
15
15
  import { getTransport, getAccountTransport } from "../../index";
16
+ import {
17
+ CredentialId,
18
+ MagicLinkId,
19
+ OpaqueLoginId,
20
+ OtpSessionId,
21
+ ResetSessionId,
22
+ SessionId,
23
+ StepUpChallengeId,
24
+ optional,
25
+ required,
26
+ } from "@structured-id/ids";
16
27
  import { AuthServiceClient } from "@structured-id/proto/sid/v1/authn/auth.client";
17
28
  import {
18
29
  StepUpMethod,
19
30
  type OpaqueLoginStartResponse,
20
31
  type OpaqueLoginFinishResponse,
21
- type OpaqueRegistrationStartResponse,
22
- type OpaqueRegistrationFinishResponse,
23
32
  type MfaVerifyResponse,
24
33
  type TotpEnrollmentChallenge,
25
34
  type FinishTotpEnrollmentResponse,
@@ -87,6 +96,13 @@ function account(): AuthServiceClient {
87
96
  return new AuthServiceClient(getAccountTransport());
88
97
  }
89
98
 
99
+ /**
100
+ * An answer with its identifier field `K` read into the typed identifier
101
+ * `V`. Reading validates it: a malformed or missing reference the answer
102
+ * owes fails here, not on a page.
103
+ */
104
+ export type WithId<T, K extends keyof T, V> = Omit<T, K> & { [P in K]: V };
105
+
90
106
  // ── MFA TOTP ──
91
107
 
92
108
  export async function startTotpEnrollment(): Promise<TotpEnrollmentChallenge> {
@@ -97,32 +113,52 @@ export async function startTotpEnrollment(): Promise<TotpEnrollmentChallenge> {
97
113
  return response;
98
114
  }
99
115
 
116
+ export type TotpEnrollmentResult = WithId<
117
+ FinishTotpEnrollmentResponse,
118
+ "credentialId",
119
+ CredentialId
120
+ >;
121
+
100
122
  export async function finishTotpEnrollment(
101
123
  code: string,
102
- ): Promise<FinishTotpEnrollmentResponse> {
124
+ ): Promise<TotpEnrollmentResult> {
103
125
  const { response } = await account().finishTotpEnrollment(
104
126
  { code },
105
127
  { meta: authMeta() },
106
128
  );
107
- return response;
129
+ return {
130
+ ...response,
131
+ credentialId: required(CredentialId, response.credentialId),
132
+ };
133
+ }
134
+
135
+ /** A second factor's answer: the session once the last required step passed. */
136
+ export type MfaVerifyResult = WithId<
137
+ MfaVerifyResponse,
138
+ "sessionId",
139
+ SessionId | undefined
140
+ >;
141
+
142
+ function mfaVerified(response: MfaVerifyResponse): MfaVerifyResult {
143
+ return { ...response, sessionId: optional(SessionId, response.sessionId) };
108
144
  }
109
145
 
110
- export async function verifyTotp(code: string): Promise<MfaVerifyResponse> {
146
+ export async function verifyTotp(code: string): Promise<MfaVerifyResult> {
111
147
  const { response } = await client().verifyTotp(
112
148
  { code },
113
149
  { meta: authMeta() },
114
150
  );
115
- return response;
151
+ return mfaVerified(response);
116
152
  }
117
153
 
118
154
  // ── MFA SMS ──
119
155
 
120
- export async function verifySmsMfa(code: string): Promise<MfaVerifyResponse> {
156
+ export async function verifySmsMfa(code: string): Promise<MfaVerifyResult> {
121
157
  const { response } = await client().verifySmsMfa(
122
158
  { code },
123
159
  { meta: authMeta() },
124
160
  );
125
- return response;
161
+ return mfaVerified(response);
126
162
  }
127
163
 
128
164
  export async function resendSmsMfa(): Promise<ResendSmsMfaResponse> {
@@ -142,32 +178,40 @@ export async function generateRecoveryCodes(): Promise<GenerateRecoveryCodesResp
142
178
 
143
179
  export async function verifyRecoveryCode(
144
180
  code: string,
145
- ): Promise<MfaVerifyResponse> {
181
+ ): Promise<MfaVerifyResult> {
146
182
  const { response } = await client().verifyRecoveryCode(
147
183
  { code },
148
184
  { meta: authMeta() },
149
185
  );
150
- return response;
186
+ return mfaVerified(response);
151
187
  }
152
188
 
153
189
  // ── OTP ──
154
190
 
191
+ /** A verified code's answer: the provisional session, set only when verified. */
192
+ export type VerifyOtpResult = WithId<
193
+ VerifyOtpResponse,
194
+ "sessionId",
195
+ SessionId | undefined
196
+ >;
197
+
198
+ /** Verify `code` of the request `otpSessionId` (RequestOtpResponse). */
155
199
  export async function verifyOtp(
156
200
  code: string,
157
- sessionId?: string,
158
- ): Promise<VerifyOtpResponse> {
201
+ otpSessionId: OtpSessionId,
202
+ ): Promise<VerifyOtpResult> {
159
203
  const { response } = await client().verifyOtp(
160
- { code, sessionId },
204
+ { code, otpSessionId: otpSessionId.toWire() },
161
205
  { meta: authMeta() },
162
206
  );
163
- return response;
207
+ return { ...response, sessionId: optional(SessionId, response.sessionId) };
164
208
  }
165
209
 
166
210
  export async function resendOtp(
167
- sessionId?: string,
211
+ otpSessionId: OtpSessionId,
168
212
  ): Promise<ResendOtpResponse> {
169
213
  const { response } = await client().resendOtp(
170
- { sessionId },
214
+ { otpSessionId: otpSessionId.toWire() },
171
215
  { meta: authMeta() },
172
216
  );
173
217
  return response;
@@ -216,16 +260,25 @@ export async function webAuthnAuthenticationStart(
216
260
  return response;
217
261
  }
218
262
 
263
+ export type PasskeySignInResult = WithId<
264
+ WebAuthnAuthenticationFinishResponse,
265
+ "sessionId",
266
+ SessionId
267
+ >;
268
+
269
+ /**
270
+ * Finish a passkey sign-in with the assertion; the server finds the ceremony
271
+ * by the challenge the assertion signs.
272
+ */
219
273
  export async function webAuthnAuthenticationFinish(
220
274
  credential: Uint8Array,
221
275
  principal?: string,
222
- stateKey?: string,
223
- ): Promise<WebAuthnAuthenticationFinishResponse> {
276
+ ): Promise<PasskeySignInResult> {
224
277
  const { response } = await client().webAuthnAuthenticationFinish(
225
- { credential, principal: principal ?? "", stateKey },
278
+ { credential, principal: principal ?? "" },
226
279
  { meta: authMeta() },
227
280
  );
228
- return response;
281
+ return { ...response, sessionId: required(SessionId, response.sessionId) };
229
282
  }
230
283
 
231
284
  export async function webAuthnRegistrationStart(
@@ -238,15 +291,24 @@ export async function webAuthnRegistrationStart(
238
291
  return response;
239
292
  }
240
293
 
294
+ export type PasskeyRegistrationResult = WithId<
295
+ WebAuthnRegistrationFinishResponse,
296
+ "credentialId",
297
+ CredentialId
298
+ >;
299
+
241
300
  export async function webAuthnRegistrationFinish(
242
301
  credential: Uint8Array,
243
302
  label?: string,
244
- ): Promise<WebAuthnRegistrationFinishResponse> {
303
+ ): Promise<PasskeyRegistrationResult> {
245
304
  const { response } = await account().webAuthnRegistrationFinish(
246
305
  { credential, label },
247
306
  { meta: authMeta() },
248
307
  );
249
- return response;
308
+ return {
309
+ ...response,
310
+ credentialId: required(CredentialId, response.credentialId),
311
+ };
250
312
  }
251
313
 
252
314
  // ── Device Authorization (RFC 8628) ──
@@ -306,27 +368,46 @@ export async function oauth2Authorize(params: {
306
368
 
307
369
  // ── Magic Link ──
308
370
 
371
+ export type MagicLinkRequest = WithId<
372
+ RequestMagicLinkResponse,
373
+ "magicLinkId",
374
+ MagicLinkId
375
+ >;
376
+
309
377
  export async function requestMagicLink(
310
378
  principal: string,
311
379
  clientId: string,
312
380
  redirectUri: string,
313
- ): Promise<RequestMagicLinkResponse> {
381
+ ): Promise<MagicLinkRequest> {
314
382
  const { response } = await client().requestMagicLink(
315
383
  { principal, clientId, redirectUri },
316
384
  { meta: authMeta() },
317
385
  );
318
- return response;
386
+ return {
387
+ ...response,
388
+ magicLinkId: required(MagicLinkId, response.magicLinkId),
389
+ };
319
390
  }
320
391
 
392
+ export type MagicLinkSignInResult = WithId<
393
+ VerifyMagicLinkResponse,
394
+ "sessionId",
395
+ SessionId
396
+ >;
397
+
398
+ /**
399
+ * Verify the magic link `magicLinkId` with its `token`; the page reads both
400
+ * from the link (`MagicLinkId.parse` of its text form).
401
+ */
321
402
  export async function verifyMagicLink(
322
- sessionId: string,
403
+ magicLinkId: MagicLinkId,
323
404
  token: string,
324
- ): Promise<VerifyMagicLinkResponse> {
405
+ ): Promise<MagicLinkSignInResult> {
325
406
  const { response } = await client().verifyMagicLink(
326
- { sessionId, token },
407
+ { magicLinkId: magicLinkId.toWire(), token },
327
408
  { meta: authMeta() },
328
409
  );
329
- return response;
410
+ return { ...response, sessionId: required(SessionId, response.sessionId) };
330
411
  }
331
412
 
332
413
  // ── IdP Link ──
@@ -446,21 +527,34 @@ export async function submitDynamicPrompt(
446
527
 
447
528
  // ── Step-Up Auth ──
448
529
 
530
+ /** A step-up challenge; `challengeId` is absent for a 1-phase method. */
531
+ export type StepUpChallengeResult = WithId<
532
+ StepUpChallenge,
533
+ "challengeId",
534
+ StepUpChallengeId | undefined
535
+ >;
536
+
537
+ /** Request a step-up challenge for the session the bearer token holds. */
449
538
  export async function requestStepUp(
450
- sessionId: string,
451
539
  method: StepUpMethod,
452
- ): Promise<StepUpChallenge> {
540
+ ): Promise<StepUpChallengeResult> {
453
541
  const { response } = await client().requestStepUp(
454
- { sessionId, method },
542
+ { method },
455
543
  { meta: authMeta() },
456
544
  );
457
- return response;
545
+ return {
546
+ ...response,
547
+ challengeId: optional(StepUpChallengeId, response.challengeId),
548
+ };
458
549
  }
459
550
 
551
+ /**
552
+ * Elevate the bearer token's session with `proof`; `challengeId` is the
553
+ * challenge's (absent for a 1-phase method).
554
+ */
460
555
  export async function completeStepUp(
461
- sessionId: string,
462
556
  method: StepUpMethod,
463
- challengeId: string,
557
+ challengeId: StepUpChallengeId | undefined,
464
558
  proof: {
465
559
  totpCode?: string;
466
560
  webauthnAssertion?: Uint8Array;
@@ -469,9 +563,8 @@ export async function completeStepUp(
469
563
  ): Promise<CompleteStepUpResponse> {
470
564
  const { response } = await client().completeStepUp(
471
565
  {
472
- sessionId,
473
566
  method,
474
- challengeId,
567
+ challengeId: challengeId?.toWire(),
475
568
  proof: proof.totpCode
476
569
  ? { oneofKind: "totpCode", totpCode: proof.totpCode }
477
570
  : proof.webauthnAssertion
@@ -491,10 +584,10 @@ export async function completeStepUp(
491
584
  // ── MFA Credential Management ──
492
585
 
493
586
  export async function deleteMfaCredential(
494
- credentialId: string,
587
+ credentialId: CredentialId,
495
588
  ): Promise<DeleteMfaCredentialResponse> {
496
589
  const { response } = await account().deleteMfaCredential(
497
- { credentialId },
590
+ { credentialId: credentialId.toWire() },
498
591
  { meta: authMeta() },
499
592
  );
500
593
  return response;
@@ -529,12 +622,17 @@ export async function requestPasswordReset(
529
622
  return response;
530
623
  }
531
624
 
625
+ /**
626
+ * Verify the reset `resetSessionId` with its `token`; the page reads both
627
+ * from the emailed link (`ResetSessionId.parse` of its text form). Later
628
+ * steps name the reset by the operation in the answer's `history`.
629
+ */
532
630
  export async function verifyPasswordReset(
533
- sessionId: string,
631
+ resetSessionId: ResetSessionId,
534
632
  token: string,
535
633
  ): Promise<VerifyPasswordResetResponse> {
536
634
  const { response } = await client().verifyPasswordReset({
537
- sessionId,
635
+ resetSessionId: resetSessionId.toWire(),
538
636
  token,
539
637
  });
540
638
  return response;
@@ -555,24 +653,29 @@ export async function executePasswordReset(
555
653
  return response;
556
654
  }
557
655
 
656
+ /** A completed reset: the session it signed in, absent when it signed in none. */
657
+ export type PasswordResetResult = WithId<
658
+ CompletePasswordResetResponse,
659
+ "sessionId",
660
+ SessionId | undefined
661
+ >;
662
+
558
663
  /**
559
664
  * Complete the reset with the replacement password's record and, when the
560
665
  * client proved it, the proof; without a proof the password installs
561
- * policy-unverified.
666
+ * policy-unverified. The operation carries the verified reset's authority.
562
667
  */
563
668
  export async function completePasswordReset(
564
- resetSessionId: string,
565
669
  operationId: Uint8Array,
566
670
  registrationRecord: Uint8Array,
567
671
  proof?: PasswordRegistrationProof,
568
- ): Promise<CompletePasswordResetResponse> {
672
+ ): Promise<PasswordResetResult> {
569
673
  const { response } = await client().completePasswordReset({
570
- resetSessionId,
571
674
  operationId: operation(operationId),
572
675
  registrationRecord,
573
676
  proof,
574
677
  });
575
- return response;
678
+ return { ...response, sessionId: optional(SessionId, response.sessionId) };
576
679
  }
577
680
 
578
681
  /** The password policy the server's proof checks a new password against. */
@@ -609,18 +712,27 @@ export async function opaqueZkppRegistrationStart(
609
712
  return response;
610
713
  }
611
714
 
715
+ export type RegistrationFinishResult = WithId<
716
+ OpaqueZkppRegistrationFinishResponse,
717
+ "credentialId",
718
+ CredentialId
719
+ >;
720
+
612
721
  /** Finish a registration with the record and, when the client proved, the proof. */
613
722
  export async function opaqueZkppRegistrationFinish(
614
723
  operationId: Uint8Array,
615
724
  registrationRecord: Uint8Array,
616
725
  proof?: PasswordRegistrationProof,
617
- ): Promise<OpaqueZkppRegistrationFinishResponse> {
726
+ ): Promise<RegistrationFinishResult> {
618
727
  const { response } = await client().opaqueZkppRegistrationFinish({
619
728
  operationId: operation(operationId),
620
729
  registrationRecord,
621
730
  proof,
622
731
  });
623
- return response;
732
+ return {
733
+ ...response,
734
+ credentialId: required(CredentialId, response.credentialId),
735
+ };
624
736
  }
625
737
 
626
738
  /**
@@ -645,12 +757,16 @@ export async function evaluatePasswordHistory(
645
757
  * the current password, empty when the change does not prove it.
646
758
  */
647
759
  export async function passwordChangeChallenge(
648
- credentialId: string,
760
+ credentialId: CredentialId,
649
761
  registrationRequest: Uint8Array,
650
762
  credentialRequest: Uint8Array = new Uint8Array(),
651
763
  ): Promise<PasswordChangeChallengeResponse> {
652
764
  const { response } = await account().passwordChangeChallenge(
653
- { credentialId, credentialRequest, registrationRequest },
765
+ {
766
+ credentialId: credentialId.toWire(),
767
+ credentialRequest,
768
+ registrationRequest,
769
+ },
654
770
  { meta: authMeta() },
655
771
  );
656
772
  return response;
@@ -663,13 +779,13 @@ export async function passwordChangeChallenge(
663
779
  */
664
780
  export async function passwordChangeExecute(
665
781
  operationId: Uint8Array,
666
- credentialId: string,
782
+ credentialId: CredentialId,
667
783
  credentialFinalization: Uint8Array = new Uint8Array(),
668
784
  ): Promise<PasswordChangeExecuteResponse> {
669
785
  const { response } = await account().passwordChangeExecute(
670
786
  {
671
787
  operationId: operation(operationId),
672
- credentialId,
788
+ credentialId: credentialId.toWire(),
673
789
  credentialFinalization,
674
790
  },
675
791
  { meta: authMeta() },
@@ -693,14 +809,14 @@ export async function passwordChangeRequirementMs(): Promise<number> {
693
809
  /** Finish the change with the new password's record and, when proved, the proof. */
694
810
  export async function passwordChangeFinish(
695
811
  operationId: Uint8Array,
696
- credentialId: string,
812
+ credentialId: CredentialId,
697
813
  registrationRecord: Uint8Array,
698
814
  proof?: PasswordRegistrationProof,
699
815
  ): Promise<void> {
700
816
  await account().passwordChangeFinish(
701
817
  {
702
818
  operationId: operation(operationId),
703
- credentialId,
819
+ credentialId: credentialId.toWire(),
704
820
  registrationRecord,
705
821
  proof,
706
822
  },
@@ -710,59 +826,42 @@ export async function passwordChangeFinish(
710
826
 
711
827
  // ── OPAQUE ──
712
828
 
829
+ /** The login the start began, for its finish to name. */
830
+ export type LoginStartResult = WithId<
831
+ OpaqueLoginStartResponse,
832
+ "loginId",
833
+ OpaqueLoginId
834
+ >;
835
+
713
836
  export async function opaqueLoginStart(
714
837
  principal: string,
715
838
  credentialRequest: Uint8Array,
716
- ): Promise<OpaqueLoginStartResponse> {
839
+ ): Promise<LoginStartResult> {
717
840
  const { response } = await client().opaqueLoginStart({
718
841
  principal,
719
842
  credentialRequest,
720
843
  });
721
- return response;
844
+ return { ...response, loginId: required(OpaqueLoginId, response.loginId) };
722
845
  }
723
846
 
847
+ export type LoginFinishResult = WithId<
848
+ OpaqueLoginFinishResponse,
849
+ "sessionId",
850
+ SessionId
851
+ >;
852
+
853
+ /** Finish the login `loginId` (from its start) with the client's KE3. */
724
854
  export async function opaqueLoginFinish(
725
855
  principal: string,
726
856
  credentialFinalization: Uint8Array,
727
- serverLoginState: string,
728
- ): Promise<OpaqueLoginFinishResponse> {
857
+ loginId: OpaqueLoginId,
858
+ ): Promise<LoginFinishResult> {
729
859
  const { response } = await client().opaqueLoginFinish({
730
860
  principal,
731
861
  credentialFinalization,
732
- serverLoginState,
862
+ loginId: loginId.toWire(),
733
863
  });
734
- return response;
735
- }
736
-
737
- /**
738
- * `claimToken`: the instance claim token (`sidclaim_...`) from the service log.
739
- * Required while the installation has no administrator; the new profile then
740
- * becomes its first administrator.
741
- */
742
- export async function opaqueRegistrationStart(
743
- principal: string,
744
- registrationRequest: Uint8Array,
745
- claimToken?: string,
746
- ): Promise<OpaqueRegistrationStartResponse> {
747
- const { response } = await client().opaqueRegistrationStart({
748
- principal,
749
- registrationRequest,
750
- claimToken,
751
- });
752
- return response;
753
- }
754
-
755
- export async function opaqueRegistrationFinish(
756
- principal: string,
757
- registrationRecord: Uint8Array,
758
- serverSetup: string,
759
- ): Promise<OpaqueRegistrationFinishResponse> {
760
- const { response } = await client().opaqueRegistrationFinish({
761
- principal,
762
- registrationRecord,
763
- serverSetup,
764
- });
765
- return response;
864
+ return { ...response, sessionId: required(SessionId, response.sessionId) };
766
865
  }
767
866
 
768
867
  // Re-export types for convenience
@@ -770,8 +869,6 @@ export { StepUpMethod };
770
869
  export type {
771
870
  OpaqueLoginStartResponse,
772
871
  OpaqueLoginFinishResponse,
773
- OpaqueRegistrationStartResponse,
774
- OpaqueRegistrationFinishResponse,
775
872
  UserOrganization,
776
873
  MfaVerifyResponse,
777
874
  TotpEnrollmentChallenge,
@@ -10,12 +10,13 @@
10
10
  * Otherwise the app's own session comes from its BFF: the handler reads it
11
11
  * from the BFF's /auth/userinfo and navigates within the app.
12
12
  */
13
+ import type { SessionId } from "@structured-id/ids";
13
14
  import { useRouter } from "vue-router";
14
15
  import { useAuthStore } from "../../index";
15
16
  import { checkBffSession } from "../../index";
16
17
 
17
18
  export interface LoginSuccessResult {
18
- sessionId?: string;
19
+ sessionId?: SessionId;
19
20
  }
20
21
 
21
22
  /** Options of {@link useLoginSuccess}. */