@cboxdk/id-js 0.17.0 → 0.19.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.
Files changed (40) hide show
  1. package/README.md +315 -3
  2. package/dist/{chunk-WYTGLCMF.js → chunk-2E25IPBA.js} +4 -3
  3. package/dist/chunk-2E25IPBA.js.map +1 -0
  4. package/dist/chunk-QQW63PGU.js +25 -0
  5. package/dist/chunk-QQW63PGU.js.map +1 -0
  6. package/dist/{chunk-ASOQN47M.js → chunk-SEUD4TE4.js} +3 -23
  7. package/dist/chunk-SEUD4TE4.js.map +1 -0
  8. package/dist/{chunk-G3CD7X4U.js → chunk-YGLMLIKL.js} +197 -9
  9. package/dist/chunk-YGLMLIKL.js.map +1 -0
  10. package/dist/{client-CdwdigzG.d.ts → client-BT-jOm0p.d.cts} +154 -2
  11. package/dist/{client-CEXddiCR.d.cts → client-xlEJalu8.d.ts} +154 -2
  12. package/dist/errors-D61xljZE.d.cts +87 -0
  13. package/dist/errors-D61xljZE.d.ts +87 -0
  14. package/dist/index.cjs +267 -17
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +84 -92
  17. package/dist/index.d.ts +84 -92
  18. package/dist/index.js +66 -13
  19. package/dist/index.js.map +1 -1
  20. package/dist/management.cjs +5092 -0
  21. package/dist/management.cjs.map +1 -0
  22. package/dist/management.d.cts +13454 -0
  23. package/dist/management.d.ts +13454 -0
  24. package/dist/management.js +5054 -0
  25. package/dist/management.js.map +1 -0
  26. package/dist/nextjs.cjs +136 -5
  27. package/dist/nextjs.cjs.map +1 -1
  28. package/dist/nextjs.d.cts +3 -2
  29. package/dist/nextjs.d.ts +3 -2
  30. package/dist/nextjs.js +4 -3
  31. package/dist/nextjs.js.map +1 -1
  32. package/dist/server.d.cts +1 -1
  33. package/dist/server.d.ts +1 -1
  34. package/dist/server.js +3 -2
  35. package/dist/{types-BYZldock.d.cts → types-CC1bz68x.d.cts} +30 -1
  36. package/dist/{types-BYZldock.d.ts → types-CC1bz68x.d.ts} +30 -1
  37. package/package.json +9 -2
  38. package/dist/chunk-ASOQN47M.js.map +0 -1
  39. package/dist/chunk-G3CD7X4U.js.map +0 -1
  40. package/dist/chunk-WYTGLCMF.js.map +0 -1
package/dist/index.d.cts CHANGED
@@ -1,5 +1,6 @@
1
- export { a as CallbackParams, C as CboxIdClient, S as StoreSecretInput, b as StoredAuthState, V as VaultClient, c as VaultLease, d as VaultSecretRef, e as VerifyWebhookOptions, v as verifyWebhook } from './client-CEXddiCR.cjs';
2
- export { c as AuthorizationPrompt, b as AuthorizationRequest, A as AuthorizationRequestOptions, d as CboxActiveOrganization, e as CboxActor, a as CboxIdConfig, f as CboxOrganization, C as CboxUser, g as ClaimSource, D as DeviceAuthorization, h as DiscoveryDocument, O as OrganizationRole, R as RefreshedTokens, i as TokenResponse, T as TokenTypeHint, j as actor, k as hasPermission, l as hasRole, m as isSupportSession, o as organization, p as permissions, r as roles, s as sessionId } from './types-BYZldock.cjs';
1
+ export { A as AuthenticationError, C as CboxIdError, a as ConfigurationError, F as FrontendApiError, I as InvalidStateError } from './errors-D61xljZE.cjs';
2
+ export { a as CallbackParams, C as CboxIdClient, P as PipeConnectOptions, b as PipeLeaseDeniedError, c as PipeLeaseError, d as PipeNotConnectedError, e as PipeProvider, f as PipeReauthorizationRequiredError, g as PipeTemporarilyUnavailableError, h as PipeToken, S as StoreSecretInput, i as StoredAuthState, V as VaultClient, j as VaultLease, k as VaultSecretRef, l as VerifyStandardWebhookOptions, m as VerifyWebhookOptions, W as WebhookHeaders, p as pipeConnectUrl, v as verifyStandardWebhook, n as verifyWebhook, w as withConnectReturn } from './client-BT-jOm0p.cjs';
3
+ export { c as AuthorizationPrompt, b as AuthorizationRequest, A as AuthorizationRequestOptions, d as CboxActiveOrganization, e as CboxActor, a as CboxIdConfig, f as CboxOrganization, C as CboxUser, g as ClaimSource, D as DeviceAuthorization, h as DiscoveryDocument, F as FEATURE_FLAGS_SCOPE, O as OrganizationRole, R as RefreshedTokens, i as TokenResponse, T as TokenTypeHint, j as actor, k as featureFlags, l as hasFeature, m as hasPermission, n as hasRole, o as isSupportSession, p as organization, q as permissions, r as roles, s as sessionId } from './types-CC1bz68x.cjs';
3
4
 
4
5
  /**
5
6
  * The handler on YOUR side of a legacy migration.
@@ -129,9 +130,35 @@ interface FrontendConfig {
129
130
  end_session: string;
130
131
  jwks: string;
131
132
  };
133
+ /**
134
+ * The social buttons to draw, exactly as the hosted sign-in page would: the environment's
135
+ * own providers, or — with `config({ organization })` — that organization's.
136
+ */
132
137
  social: SocialProvider[];
133
138
  appearance?: Appearance;
139
+ /**
140
+ * Which other sign-in methods are switched on, so a box does not draw a button whose
141
+ * endpoint answers 403. Absent from instances that predate it.
142
+ */
143
+ methods?: {
144
+ passkeys: boolean;
145
+ magic_link: boolean;
146
+ };
147
+ }
148
+ /** Options for {@link CboxIdFrontend.config}. */
149
+ interface FrontendConfigOptions {
150
+ /**
151
+ * The organization the box is for, by id or slug — the way a hosted page is addressed.
152
+ * `social` is then exactly what that organization's sign-in page offers. An organization
153
+ * the environment does not have is answered as no hint at all.
154
+ */
155
+ organization?: string;
134
156
  }
157
+ /**
158
+ * A second factor the person can answer an `mfa_required` with. Kinds only — the server
159
+ * never says which number a text would go to before one is sent.
160
+ */
161
+ type SecondFactorKind = 'totp' | 'sms' | 'recovery_code';
135
162
  /** What a sign-in attempt produced. */
136
163
  type SignInResult = {
137
164
  status: 'ok';
@@ -140,6 +167,11 @@ type SignInResult = {
140
167
  } | {
141
168
  status: 'mfa_required';
142
169
  mfaToken: string;
170
+ /**
171
+ * The factors this person can answer with (laravel-id 1.24+). Absent when the
172
+ * instance predates the list — then only an authenticator or a recovery code works.
173
+ */
174
+ factors?: SecondFactorKind[];
143
175
  } | {
144
176
  status: 'otp_required';
145
177
  mfaToken: string;
@@ -151,6 +183,30 @@ type SignInResult = {
151
183
  status: 'rate_limited';
152
184
  retryAfter: number | undefined;
153
185
  };
186
+ /** What asking for a texted sign-in code produced. */
187
+ type SmsCodeResult =
188
+ /** Sent. `to` is the number masked (`+45 ******78`); the code lasts `expiresIn` seconds. */
189
+ {
190
+ status: 'sent';
191
+ to: string;
192
+ expiresIn: number;
193
+ }
194
+ /** Too soon, or a sending limit was hit. Waiting `retryAfter` seconds may help. */
195
+ | {
196
+ status: 'rate_limited';
197
+ retryAfter: number | undefined;
198
+ }
199
+ /** The text provider failed. Try again shortly, or offer another factor. */
200
+ | {
201
+ status: 'unavailable';
202
+ }
203
+ /**
204
+ * Refused: an unknown or spent token, no number on file, a country texts are not sent
205
+ * to, or no attempts left. One answer for every reason, like a wrong password.
206
+ */
207
+ | {
208
+ status: 'invalid';
209
+ };
154
210
  /** What a browser needs to ask an authenticator for a passkey assertion. */
155
211
  interface PasskeyOptions {
156
212
  challenge: string;
@@ -196,7 +252,7 @@ declare class CboxIdFrontend {
196
252
  * fetches of the same immutable document is a waste the SDK should absorb rather than
197
253
  * ask every consumer to.
198
254
  */
199
- private configPromise;
255
+ private readonly configPromises;
200
256
  constructor(options: FrontendClientOptions);
201
257
  /** Whether this key drives real sign-ins. Handy for a "test mode" badge in your own UI. */
202
258
  get isLive(): boolean;
@@ -206,8 +262,9 @@ declare class CboxIdFrontend {
206
262
  * Fetched once per client instance. The document is small and changes when somebody
207
263
  * edits it in the console, so a long-lived page picks changes up on its next full load
208
264
  * rather than mid-session — which is the right trade for something that decides layout.
265
+ * Each `organization` is fetched and cached on its own.
209
266
  */
210
- config(): Promise<FrontendConfig>;
267
+ config(options?: FrontendConfigOptions): Promise<FrontendConfig>;
211
268
  /**
212
269
  * Who is signed in, given an access token your app already holds.
213
270
  *
@@ -255,8 +312,29 @@ declare class CboxIdFrontend {
255
312
  *
256
313
  * A wrong code costs an attempt, not the sign-in: five are allowed before the token
257
314
  * dies and the person starts from the password again.
315
+ *
316
+ * `method` says which challenge the code answers: `'mfa'` for an authenticator or
317
+ * recovery code, `'otp'` for an emailed code (`otp_required`), `'sms'` for a code sent
318
+ * with {@link sendSmsCode}. A texted code is six digits like an authenticator's, so the
319
+ * server cannot tell them apart by shape — answering one as the other is refused.
258
320
  */
259
- submitSecondFactor(mfaToken: string, code: string, method?: 'mfa' | 'otp'): Promise<SignInResult>;
321
+ submitSecondFactor(mfaToken: string, code: string, method?: 'mfa' | 'otp' | 'sms'): Promise<SignInResult>;
322
+ /**
323
+ * Text the person a sign-in code, when `mfa_required` listed `'sms'` in `factors`.
324
+ * Then finish with `submitSecondFactor(mfaToken, code, 'sms')`.
325
+ *
326
+ * Only call it when the person asks ("Text me a code"), never on page load: each send
327
+ * spends one of the token's five attempts — that is what bounds how many texts one
328
+ * proved password can cause — and every text costs the customer money.
329
+ *
330
+ * ```ts
331
+ * if (result.status === 'mfa_required' && result.factors?.includes('sms')) {
332
+ * const sent = await frontend.sendSmsCode(result.mfaToken)
333
+ * if (sent.status === 'sent') showCodeField(`We texted ${sent.to}`)
334
+ * }
335
+ * ```
336
+ */
337
+ sendSmsCode(mfaToken: string): Promise<SmsCodeResult>;
260
338
  /**
261
339
  * The WebAuthn options for a passkey sign-in, and the handle that carries the challenge.
262
340
  *
@@ -282,92 +360,6 @@ declare class CboxIdFrontend {
282
360
  private attempt;
283
361
  }
284
362
 
285
- /**
286
- * Base class for every error this SDK throws, so callers can `catch (e) { if (e
287
- * instanceof CboxIdError) ... }` in one branch.
288
- */
289
- declare class CboxIdError extends Error {
290
- constructor(message: string);
291
- }
292
- /** The client is misconfigured (a required option is missing or empty). */
293
- declare class ConfigurationError extends CboxIdError {
294
- }
295
- /**
296
- * The login `state` did not match what was stored — the callback is forged or
297
- * stale. Treat as a fresh start, not an error to surface to the user.
298
- */
299
- declare class InvalidStateError extends CboxIdError {
300
- }
301
- /**
302
- * Login could not be completed: the provider returned an error, the callback was
303
- * missing a code, or a token / id_token failed verification.
304
- *
305
- * `error` carries the RFC 6749 §5.2 code the authorization server sent, when it sent one.
306
- * It used to be discarded at every back-channel boundary, leaving a single message string
307
- * for outcomes that need opposite responses: `invalid_grant` on a refresh means the
308
- * session is over and the person has to sign in again, while `temporarily_unavailable`
309
- * means try the same token in a moment. A caller reduced to matching on prose either
310
- * retries what can never succeed, or signs out somebody who did not need to be.
311
- */
312
- declare class AuthenticationError extends CboxIdError {
313
- /** RFC 6749 §5.2 error code, when the server sent a parseable one. */
314
- readonly error?: string | undefined;
315
- /** The server's `error_description`, verbatim. Not end-user copy. */
316
- readonly errorDescription?: string | undefined;
317
- /** HTTP status, for the cases where the body says nothing useful. */
318
- readonly status?: number | undefined;
319
- /**
320
- * Seconds to wait, off the `Retry-After` header — set only on a 429.
321
- *
322
- * The distinction it carries is the one a caller most needs and could least infer:
323
- * a 429 is the ONLY back-channel failure where the same request will succeed
324
- * unchanged if you simply wait. Every other one needs a different request, or a new
325
- * sign-in. Without this the SDK reported "token refresh failed" and a caller with a
326
- * retry loop hammered a limiter that was already telling it exactly how long to stop.
327
- */
328
- readonly retryAfter?: number | undefined;
329
- constructor(message: string,
330
- /** RFC 6749 §5.2 error code, when the server sent a parseable one. */
331
- error?: string | undefined,
332
- /** The server's `error_description`, verbatim. Not end-user copy. */
333
- errorDescription?: string | undefined,
334
- /** HTTP status, for the cases where the body says nothing useful. */
335
- status?: number | undefined,
336
- /**
337
- * Seconds to wait, off the `Retry-After` header — set only on a 429.
338
- *
339
- * The distinction it carries is the one a caller most needs and could least infer:
340
- * a 429 is the ONLY back-channel failure where the same request will succeed
341
- * unchanged if you simply wait. Every other one needs a different request, or a new
342
- * sign-in. Without this the SDK reported "token refresh failed" and a caller with a
343
- * retry loop hammered a limiter that was already telling it exactly how long to stop.
344
- */
345
- retryAfter?: number | undefined);
346
- /** Whether waiting `retryAfter` seconds and repeating the same request is worth it. */
347
- get isRateLimited(): boolean;
348
- }
349
- /**
350
- * The Frontend API refused, or could not be reached.
351
- *
352
- * Carries a `code` because the four ways this goes wrong need four different responses
353
- * from the caller, and telling them apart from a message string is the kind of thing
354
- * that works until somebody rewords the message:
355
- *
356
- * - `origin_not_allowed` — the overwhelmingly likely cause of a refusal, and a
357
- * configuration problem the developer fixes once in the console.
358
- * - `rate_limited` — back off; `retryAfter` says for how long, when the server said.
359
- * - `unavailable` — the network, or a 5xx. Transient by assumption; already retried.
360
- * - `malformed` — a 200 whose body is not the document we expect, which means something
361
- * between the page and us is rewriting responses (a proxy, an extension, a captive
362
- * portal). Worth distinguishing, because retrying it never helps.
363
- */
364
- declare class FrontendApiError extends CboxIdError {
365
- readonly code: 'origin_not_allowed' | 'rate_limited' | 'unavailable' | 'malformed';
366
- readonly status?: number | undefined;
367
- readonly retryAfter?: number | undefined;
368
- constructor(message: string, code: 'origin_not_allowed' | 'rate_limited' | 'unavailable' | 'malformed', status?: number | undefined, retryAfter?: number | undefined);
369
- }
370
-
371
363
  /**
372
364
  * PKCE (RFC 7636) and the random values the login flow needs, built on Web Crypto
373
365
  * (`globalThis.crypto`) so the same code runs on Node, edge runtimes and browsers.
@@ -527,4 +519,4 @@ declare function buildManifest(declaration: AuthzDeclaration): Promise<AuthzMani
527
519
  */
528
520
  declare function publishManifest(config: ManifestPublisherConfig, declaration: AuthzDeclaration): Promise<ManifestSyncSummary>;
529
521
 
530
- export { type Appearance, AuthenticationError, type AuthzDeclaration, type AuthzManifest, CboxIdError, CboxIdFrontend, ConfigurationError, FrontendApiError, type FrontendClientOptions, type FrontendConfig, type FrontendSession, InvalidStateError, type LegacyUser, type LegacyVerifierOptions, type ManifestPermission, type ManifestPublisherConfig, type ManifestRole, type ManifestSyncSummary, type PasskeyOptions, type PermissionDefinition, type RoleDefinition, type SignInResult, type SocialProvider, buildManifest, challenge, createLegacyVerifier, createVerifier, defineAuthz, publishManifest, randomToken };
522
+ export { type Appearance, type AuthzDeclaration, type AuthzManifest, CboxIdFrontend, type FrontendClientOptions, type FrontendConfig, type FrontendConfigOptions, type FrontendSession, type LegacyUser, type LegacyVerifierOptions, type ManifestPermission, type ManifestPublisherConfig, type ManifestRole, type ManifestSyncSummary, type PasskeyOptions, type PermissionDefinition, type RoleDefinition, type SecondFactorKind, type SignInResult, type SmsCodeResult, type SocialProvider, buildManifest, challenge, createLegacyVerifier, createVerifier, defineAuthz, publishManifest, randomToken };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
- export { a as CallbackParams, C as CboxIdClient, S as StoreSecretInput, b as StoredAuthState, V as VaultClient, c as VaultLease, d as VaultSecretRef, e as VerifyWebhookOptions, v as verifyWebhook } from './client-CdwdigzG.js';
2
- export { c as AuthorizationPrompt, b as AuthorizationRequest, A as AuthorizationRequestOptions, d as CboxActiveOrganization, e as CboxActor, a as CboxIdConfig, f as CboxOrganization, C as CboxUser, g as ClaimSource, D as DeviceAuthorization, h as DiscoveryDocument, O as OrganizationRole, R as RefreshedTokens, i as TokenResponse, T as TokenTypeHint, j as actor, k as hasPermission, l as hasRole, m as isSupportSession, o as organization, p as permissions, r as roles, s as sessionId } from './types-BYZldock.js';
1
+ export { A as AuthenticationError, C as CboxIdError, a as ConfigurationError, F as FrontendApiError, I as InvalidStateError } from './errors-D61xljZE.js';
2
+ export { a as CallbackParams, C as CboxIdClient, P as PipeConnectOptions, b as PipeLeaseDeniedError, c as PipeLeaseError, d as PipeNotConnectedError, e as PipeProvider, f as PipeReauthorizationRequiredError, g as PipeTemporarilyUnavailableError, h as PipeToken, S as StoreSecretInput, i as StoredAuthState, V as VaultClient, j as VaultLease, k as VaultSecretRef, l as VerifyStandardWebhookOptions, m as VerifyWebhookOptions, W as WebhookHeaders, p as pipeConnectUrl, v as verifyStandardWebhook, n as verifyWebhook, w as withConnectReturn } from './client-xlEJalu8.js';
3
+ export { c as AuthorizationPrompt, b as AuthorizationRequest, A as AuthorizationRequestOptions, d as CboxActiveOrganization, e as CboxActor, a as CboxIdConfig, f as CboxOrganization, C as CboxUser, g as ClaimSource, D as DeviceAuthorization, h as DiscoveryDocument, F as FEATURE_FLAGS_SCOPE, O as OrganizationRole, R as RefreshedTokens, i as TokenResponse, T as TokenTypeHint, j as actor, k as featureFlags, l as hasFeature, m as hasPermission, n as hasRole, o as isSupportSession, p as organization, q as permissions, r as roles, s as sessionId } from './types-CC1bz68x.js';
3
4
 
4
5
  /**
5
6
  * The handler on YOUR side of a legacy migration.
@@ -129,9 +130,35 @@ interface FrontendConfig {
129
130
  end_session: string;
130
131
  jwks: string;
131
132
  };
133
+ /**
134
+ * The social buttons to draw, exactly as the hosted sign-in page would: the environment's
135
+ * own providers, or — with `config({ organization })` — that organization's.
136
+ */
132
137
  social: SocialProvider[];
133
138
  appearance?: Appearance;
139
+ /**
140
+ * Which other sign-in methods are switched on, so a box does not draw a button whose
141
+ * endpoint answers 403. Absent from instances that predate it.
142
+ */
143
+ methods?: {
144
+ passkeys: boolean;
145
+ magic_link: boolean;
146
+ };
147
+ }
148
+ /** Options for {@link CboxIdFrontend.config}. */
149
+ interface FrontendConfigOptions {
150
+ /**
151
+ * The organization the box is for, by id or slug — the way a hosted page is addressed.
152
+ * `social` is then exactly what that organization's sign-in page offers. An organization
153
+ * the environment does not have is answered as no hint at all.
154
+ */
155
+ organization?: string;
134
156
  }
157
+ /**
158
+ * A second factor the person can answer an `mfa_required` with. Kinds only — the server
159
+ * never says which number a text would go to before one is sent.
160
+ */
161
+ type SecondFactorKind = 'totp' | 'sms' | 'recovery_code';
135
162
  /** What a sign-in attempt produced. */
136
163
  type SignInResult = {
137
164
  status: 'ok';
@@ -140,6 +167,11 @@ type SignInResult = {
140
167
  } | {
141
168
  status: 'mfa_required';
142
169
  mfaToken: string;
170
+ /**
171
+ * The factors this person can answer with (laravel-id 1.24+). Absent when the
172
+ * instance predates the list — then only an authenticator or a recovery code works.
173
+ */
174
+ factors?: SecondFactorKind[];
143
175
  } | {
144
176
  status: 'otp_required';
145
177
  mfaToken: string;
@@ -151,6 +183,30 @@ type SignInResult = {
151
183
  status: 'rate_limited';
152
184
  retryAfter: number | undefined;
153
185
  };
186
+ /** What asking for a texted sign-in code produced. */
187
+ type SmsCodeResult =
188
+ /** Sent. `to` is the number masked (`+45 ******78`); the code lasts `expiresIn` seconds. */
189
+ {
190
+ status: 'sent';
191
+ to: string;
192
+ expiresIn: number;
193
+ }
194
+ /** Too soon, or a sending limit was hit. Waiting `retryAfter` seconds may help. */
195
+ | {
196
+ status: 'rate_limited';
197
+ retryAfter: number | undefined;
198
+ }
199
+ /** The text provider failed. Try again shortly, or offer another factor. */
200
+ | {
201
+ status: 'unavailable';
202
+ }
203
+ /**
204
+ * Refused: an unknown or spent token, no number on file, a country texts are not sent
205
+ * to, or no attempts left. One answer for every reason, like a wrong password.
206
+ */
207
+ | {
208
+ status: 'invalid';
209
+ };
154
210
  /** What a browser needs to ask an authenticator for a passkey assertion. */
155
211
  interface PasskeyOptions {
156
212
  challenge: string;
@@ -196,7 +252,7 @@ declare class CboxIdFrontend {
196
252
  * fetches of the same immutable document is a waste the SDK should absorb rather than
197
253
  * ask every consumer to.
198
254
  */
199
- private configPromise;
255
+ private readonly configPromises;
200
256
  constructor(options: FrontendClientOptions);
201
257
  /** Whether this key drives real sign-ins. Handy for a "test mode" badge in your own UI. */
202
258
  get isLive(): boolean;
@@ -206,8 +262,9 @@ declare class CboxIdFrontend {
206
262
  * Fetched once per client instance. The document is small and changes when somebody
207
263
  * edits it in the console, so a long-lived page picks changes up on its next full load
208
264
  * rather than mid-session — which is the right trade for something that decides layout.
265
+ * Each `organization` is fetched and cached on its own.
209
266
  */
210
- config(): Promise<FrontendConfig>;
267
+ config(options?: FrontendConfigOptions): Promise<FrontendConfig>;
211
268
  /**
212
269
  * Who is signed in, given an access token your app already holds.
213
270
  *
@@ -255,8 +312,29 @@ declare class CboxIdFrontend {
255
312
  *
256
313
  * A wrong code costs an attempt, not the sign-in: five are allowed before the token
257
314
  * dies and the person starts from the password again.
315
+ *
316
+ * `method` says which challenge the code answers: `'mfa'` for an authenticator or
317
+ * recovery code, `'otp'` for an emailed code (`otp_required`), `'sms'` for a code sent
318
+ * with {@link sendSmsCode}. A texted code is six digits like an authenticator's, so the
319
+ * server cannot tell them apart by shape — answering one as the other is refused.
258
320
  */
259
- submitSecondFactor(mfaToken: string, code: string, method?: 'mfa' | 'otp'): Promise<SignInResult>;
321
+ submitSecondFactor(mfaToken: string, code: string, method?: 'mfa' | 'otp' | 'sms'): Promise<SignInResult>;
322
+ /**
323
+ * Text the person a sign-in code, when `mfa_required` listed `'sms'` in `factors`.
324
+ * Then finish with `submitSecondFactor(mfaToken, code, 'sms')`.
325
+ *
326
+ * Only call it when the person asks ("Text me a code"), never on page load: each send
327
+ * spends one of the token's five attempts — that is what bounds how many texts one
328
+ * proved password can cause — and every text costs the customer money.
329
+ *
330
+ * ```ts
331
+ * if (result.status === 'mfa_required' && result.factors?.includes('sms')) {
332
+ * const sent = await frontend.sendSmsCode(result.mfaToken)
333
+ * if (sent.status === 'sent') showCodeField(`We texted ${sent.to}`)
334
+ * }
335
+ * ```
336
+ */
337
+ sendSmsCode(mfaToken: string): Promise<SmsCodeResult>;
260
338
  /**
261
339
  * The WebAuthn options for a passkey sign-in, and the handle that carries the challenge.
262
340
  *
@@ -282,92 +360,6 @@ declare class CboxIdFrontend {
282
360
  private attempt;
283
361
  }
284
362
 
285
- /**
286
- * Base class for every error this SDK throws, so callers can `catch (e) { if (e
287
- * instanceof CboxIdError) ... }` in one branch.
288
- */
289
- declare class CboxIdError extends Error {
290
- constructor(message: string);
291
- }
292
- /** The client is misconfigured (a required option is missing or empty). */
293
- declare class ConfigurationError extends CboxIdError {
294
- }
295
- /**
296
- * The login `state` did not match what was stored — the callback is forged or
297
- * stale. Treat as a fresh start, not an error to surface to the user.
298
- */
299
- declare class InvalidStateError extends CboxIdError {
300
- }
301
- /**
302
- * Login could not be completed: the provider returned an error, the callback was
303
- * missing a code, or a token / id_token failed verification.
304
- *
305
- * `error` carries the RFC 6749 §5.2 code the authorization server sent, when it sent one.
306
- * It used to be discarded at every back-channel boundary, leaving a single message string
307
- * for outcomes that need opposite responses: `invalid_grant` on a refresh means the
308
- * session is over and the person has to sign in again, while `temporarily_unavailable`
309
- * means try the same token in a moment. A caller reduced to matching on prose either
310
- * retries what can never succeed, or signs out somebody who did not need to be.
311
- */
312
- declare class AuthenticationError extends CboxIdError {
313
- /** RFC 6749 §5.2 error code, when the server sent a parseable one. */
314
- readonly error?: string | undefined;
315
- /** The server's `error_description`, verbatim. Not end-user copy. */
316
- readonly errorDescription?: string | undefined;
317
- /** HTTP status, for the cases where the body says nothing useful. */
318
- readonly status?: number | undefined;
319
- /**
320
- * Seconds to wait, off the `Retry-After` header — set only on a 429.
321
- *
322
- * The distinction it carries is the one a caller most needs and could least infer:
323
- * a 429 is the ONLY back-channel failure where the same request will succeed
324
- * unchanged if you simply wait. Every other one needs a different request, or a new
325
- * sign-in. Without this the SDK reported "token refresh failed" and a caller with a
326
- * retry loop hammered a limiter that was already telling it exactly how long to stop.
327
- */
328
- readonly retryAfter?: number | undefined;
329
- constructor(message: string,
330
- /** RFC 6749 §5.2 error code, when the server sent a parseable one. */
331
- error?: string | undefined,
332
- /** The server's `error_description`, verbatim. Not end-user copy. */
333
- errorDescription?: string | undefined,
334
- /** HTTP status, for the cases where the body says nothing useful. */
335
- status?: number | undefined,
336
- /**
337
- * Seconds to wait, off the `Retry-After` header — set only on a 429.
338
- *
339
- * The distinction it carries is the one a caller most needs and could least infer:
340
- * a 429 is the ONLY back-channel failure where the same request will succeed
341
- * unchanged if you simply wait. Every other one needs a different request, or a new
342
- * sign-in. Without this the SDK reported "token refresh failed" and a caller with a
343
- * retry loop hammered a limiter that was already telling it exactly how long to stop.
344
- */
345
- retryAfter?: number | undefined);
346
- /** Whether waiting `retryAfter` seconds and repeating the same request is worth it. */
347
- get isRateLimited(): boolean;
348
- }
349
- /**
350
- * The Frontend API refused, or could not be reached.
351
- *
352
- * Carries a `code` because the four ways this goes wrong need four different responses
353
- * from the caller, and telling them apart from a message string is the kind of thing
354
- * that works until somebody rewords the message:
355
- *
356
- * - `origin_not_allowed` — the overwhelmingly likely cause of a refusal, and a
357
- * configuration problem the developer fixes once in the console.
358
- * - `rate_limited` — back off; `retryAfter` says for how long, when the server said.
359
- * - `unavailable` — the network, or a 5xx. Transient by assumption; already retried.
360
- * - `malformed` — a 200 whose body is not the document we expect, which means something
361
- * between the page and us is rewriting responses (a proxy, an extension, a captive
362
- * portal). Worth distinguishing, because retrying it never helps.
363
- */
364
- declare class FrontendApiError extends CboxIdError {
365
- readonly code: 'origin_not_allowed' | 'rate_limited' | 'unavailable' | 'malformed';
366
- readonly status?: number | undefined;
367
- readonly retryAfter?: number | undefined;
368
- constructor(message: string, code: 'origin_not_allowed' | 'rate_limited' | 'unavailable' | 'malformed', status?: number | undefined, retryAfter?: number | undefined);
369
- }
370
-
371
363
  /**
372
364
  * PKCE (RFC 7636) and the random values the login flow needs, built on Web Crypto
373
365
  * (`globalThis.crypto`) so the same code runs on Node, edge runtimes and browsers.
@@ -527,4 +519,4 @@ declare function buildManifest(declaration: AuthzDeclaration): Promise<AuthzMani
527
519
  */
528
520
  declare function publishManifest(config: ManifestPublisherConfig, declaration: AuthzDeclaration): Promise<ManifestSyncSummary>;
529
521
 
530
- export { type Appearance, AuthenticationError, type AuthzDeclaration, type AuthzManifest, CboxIdError, CboxIdFrontend, ConfigurationError, FrontendApiError, type FrontendClientOptions, type FrontendConfig, type FrontendSession, InvalidStateError, type LegacyUser, type LegacyVerifierOptions, type ManifestPermission, type ManifestPublisherConfig, type ManifestRole, type ManifestSyncSummary, type PasskeyOptions, type PermissionDefinition, type RoleDefinition, type SignInResult, type SocialProvider, buildManifest, challenge, createLegacyVerifier, createVerifier, defineAuthz, publishManifest, randomToken };
522
+ export { type Appearance, type AuthzDeclaration, type AuthzManifest, CboxIdFrontend, type FrontendClientOptions, type FrontendConfig, type FrontendConfigOptions, type FrontendSession, type LegacyUser, type LegacyVerifierOptions, type ManifestPermission, type ManifestPublisherConfig, type ManifestRole, type ManifestSyncSummary, type PasskeyOptions, type PermissionDefinition, type RoleDefinition, type SecondFactorKind, type SignInResult, type SmsCodeResult, type SocialProvider, buildManifest, challenge, createLegacyVerifier, createVerifier, defineAuthz, publishManifest, randomToken };
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
- import { Discovery } from './chunk-G3CD7X4U.js';
2
- export { CboxIdClient, VaultClient, actor, challenge, createVerifier, hasPermission, hasRole, isSupportSession, organization, permissions, randomToken, roles, sessionId, verifyWebhook } from './chunk-G3CD7X4U.js';
3
- import { ConfigurationError, assertSecureIssuer, FrontendApiError, AuthenticationError } from './chunk-ASOQN47M.js';
4
- export { AuthenticationError, CboxIdError, ConfigurationError, FrontendApiError, InvalidStateError } from './chunk-ASOQN47M.js';
1
+ import { Discovery } from './chunk-YGLMLIKL.js';
2
+ export { CboxIdClient, FEATURE_FLAGS_SCOPE, PipeLeaseDeniedError, PipeLeaseError, PipeNotConnectedError, PipeReauthorizationRequiredError, PipeTemporarilyUnavailableError, VaultClient, actor, challenge, createVerifier, featureFlags, hasFeature, hasPermission, hasRole, isSupportSession, organization, permissions, pipeConnectUrl, randomToken, roles, sessionId, verifyStandardWebhook, verifyWebhook, withConnectReturn } from './chunk-YGLMLIKL.js';
3
+ import { assertSecureIssuer } from './chunk-QQW63PGU.js';
4
+ import { ConfigurationError, FrontendApiError, AuthenticationError } from './chunk-SEUD4TE4.js';
5
+ export { AuthenticationError, CboxIdError, ConfigurationError, FrontendApiError, InvalidStateError } from './chunk-SEUD4TE4.js';
5
6
 
6
7
  // src/legacy.ts
7
8
  function createLegacyVerifier(options) {
@@ -97,6 +98,7 @@ function json(body, status) {
97
98
  }
98
99
 
99
100
  // src/frontend.ts
101
+ var SECOND_FACTOR_KINDS = /* @__PURE__ */ new Set(["totp", "sms", "recovery_code"]);
100
102
  var CboxIdFrontend = class {
101
103
  issuer;
102
104
  publishableKey;
@@ -111,7 +113,7 @@ var CboxIdFrontend = class {
111
113
  * fetches of the same immutable document is a waste the SDK should absorb rather than
112
114
  * ask every consumer to.
113
115
  */
114
- configPromise = null;
116
+ configPromises = /* @__PURE__ */ new Map();
115
117
  constructor(options) {
116
118
  if (!options.issuer) {
117
119
  throw new ConfigurationError("issuer is required.");
@@ -138,13 +140,20 @@ var CboxIdFrontend = class {
138
140
  * Fetched once per client instance. The document is small and changes when somebody
139
141
  * edits it in the console, so a long-lived page picks changes up on its next full load
140
142
  * rather than mid-session — which is the right trade for something that decides layout.
143
+ * Each `organization` is fetched and cached on its own.
141
144
  */
142
- async config() {
143
- this.configPromise ??= this.get("/frontend/v1/config", {}, isFrontendConfig).catch((error) => {
144
- this.configPromise = null;
145
- throw error;
146
- });
147
- return this.configPromise;
145
+ async config(options = {}) {
146
+ const organization2 = options.organization ?? "";
147
+ let pending = this.configPromises.get(organization2);
148
+ if (pending === void 0) {
149
+ const path = organization2 === "" ? "/frontend/v1/config" : `/frontend/v1/config?${new URLSearchParams({ organization: organization2 }).toString()}`;
150
+ pending = this.get(path, {}, isFrontendConfig).catch((error) => {
151
+ this.configPromises.delete(organization2);
152
+ throw error;
153
+ });
154
+ this.configPromises.set(organization2, pending);
155
+ }
156
+ return pending;
148
157
  }
149
158
  /**
150
159
  * Who is signed in, given an access token your app already holds.
@@ -206,8 +215,12 @@ var CboxIdFrontend = class {
206
215
  if (body.status === "ok" && typeof body.login_ticket === "string") {
207
216
  return { status: "ok", loginTicket: body.login_ticket, expiresIn: body.expires_in ?? 60 };
208
217
  }
209
- if ((body.status === "mfa_required" || body.status === "otp_required") && typeof body.mfa_token === "string") {
210
- return { status: body.status, mfaToken: body.mfa_token };
218
+ if (body.status === "mfa_required" && typeof body.mfa_token === "string") {
219
+ const factors = Array.isArray(body.factors) ? body.factors.filter((f) => typeof f === "string" && SECOND_FACTOR_KINDS.has(f)) : void 0;
220
+ return { status: "mfa_required", mfaToken: body.mfa_token, ...factors ? { factors } : {} };
221
+ }
222
+ if (body.status === "otp_required" && typeof body.mfa_token === "string") {
223
+ return { status: "otp_required", mfaToken: body.mfa_token };
211
224
  }
212
225
  if (body.status === "sso_required") {
213
226
  return { status: "sso_required" };
@@ -224,6 +237,11 @@ var CboxIdFrontend = class {
224
237
  *
225
238
  * A wrong code costs an attempt, not the sign-in: five are allowed before the token
226
239
  * dies and the person starts from the password again.
240
+ *
241
+ * `method` says which challenge the code answers: `'mfa'` for an authenticator or
242
+ * recovery code, `'otp'` for an emailed code (`otp_required`), `'sms'` for a code sent
243
+ * with {@link sendSmsCode}. A texted code is six digits like an authenticator's, so the
244
+ * server cannot tell them apart by shape — answering one as the other is refused.
227
245
  */
228
246
  async submitSecondFactor(mfaToken, code, method = "mfa") {
229
247
  const response = await this.post("/frontend/v1/sign-in/factor", { mfa_token: mfaToken, code, method });
@@ -235,6 +253,41 @@ var CboxIdFrontend = class {
235
253
  }
236
254
  return body.status === "ok" && typeof body.login_ticket === "string" ? { status: "ok", loginTicket: body.login_ticket, expiresIn: body.expires_in ?? 60 } : { status: "invalid" };
237
255
  }
256
+ /**
257
+ * Text the person a sign-in code, when `mfa_required` listed `'sms'` in `factors`.
258
+ * Then finish with `submitSecondFactor(mfaToken, code, 'sms')`.
259
+ *
260
+ * Only call it when the person asks ("Text me a code"), never on page load: each send
261
+ * spends one of the token's five attempts — that is what bounds how many texts one
262
+ * proved password can cause — and every text costs the customer money.
263
+ *
264
+ * ```ts
265
+ * if (result.status === 'mfa_required' && result.factors?.includes('sms')) {
266
+ * const sent = await frontend.sendSmsCode(result.mfaToken)
267
+ * if (sent.status === 'sent') showCodeField(`We texted ${sent.to}`)
268
+ * }
269
+ * ```
270
+ */
271
+ async sendSmsCode(mfaToken) {
272
+ const response = await this.post("/frontend/v1/sign-in/factor/sms", { mfa_token: mfaToken });
273
+ let body;
274
+ try {
275
+ body = await response.json();
276
+ } catch {
277
+ throw new FrontendApiError("Cbox ID returned a body that is not JSON.", "malformed", response.status);
278
+ }
279
+ if (body.status === "sent" && typeof body.to === "string") {
280
+ return { status: "sent", to: body.to, expiresIn: typeof body.expires_in === "number" ? body.expires_in : 300 };
281
+ }
282
+ if (body.status === "rate_limited" || response.status === 429) {
283
+ const retryAfter = typeof body.retry_after === "number" ? body.retry_after : Number(response.headers.get("Retry-After") ?? "");
284
+ return { status: "rate_limited", retryAfter: Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : void 0 };
285
+ }
286
+ if (body.status === "unavailable") {
287
+ return { status: "unavailable" };
288
+ }
289
+ return { status: "invalid" };
290
+ }
238
291
  /**
239
292
  * The WebAuthn options for a passkey sign-in, and the handle that carries the challenge.
240
293
  *