@cboxdk/id-js 0.18.0 → 0.20.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/CHANGELOG.md +185 -0
- package/README.md +121 -4
- package/RELEASE-CANDIDATE.md +11 -0
- package/dist/api-keys-BE3R3q_A.d.ts +105 -0
- package/dist/api-keys-C8-Q4eFA.d.cts +105 -0
- package/dist/chunk-EVZ6RB3K.js +98 -0
- package/dist/chunk-EVZ6RB3K.js.map +1 -0
- package/dist/{chunk-CXGKOJRJ.js → chunk-YGLMLIKL.js} +144 -9
- package/dist/chunk-YGLMLIKL.js.map +1 -0
- package/dist/{client-BxUHm5hX.d.cts → client-BT-jOm0p.d.cts} +126 -2
- package/dist/{client-BSZASrMo.d.ts → client-xlEJalu8.d.ts} +126 -2
- package/dist/decisions-C84Fzah4.d.ts +23 -0
- package/dist/decisions-DNd9UXwi.d.cts +23 -0
- package/dist/index.cjs +338 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -6
- package/dist/index.d.ts +84 -6
- package/dist/index.js +87 -15
- package/dist/index.js.map +1 -1
- package/dist/management.cjs +1115 -62
- package/dist/management.cjs.map +1 -1
- package/dist/management.d.cts +3618 -135
- package/dist/management.d.ts +3618 -135
- package/dist/management.js +1115 -63
- package/dist/management.js.map +1 -1
- package/dist/nextjs.cjs +136 -5
- package/dist/nextjs.cjs.map +1 -1
- package/dist/nextjs.d.cts +4 -3
- package/dist/nextjs.d.ts +4 -3
- package/dist/nextjs.js +1 -1
- package/dist/server.cjs +103 -9
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +3 -105
- package/dist/server.d.ts +3 -105
- package/dist/server.js +1 -0
- package/dist/{types-BYZldock.d.cts → types-CC1bz68x.d.cts} +30 -1
- package/dist/{types-BYZldock.d.ts → types-CC1bz68x.d.ts} +30 -1
- package/package.json +8 -3
- package/dist/chunk-CXGKOJRJ.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
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, S as StoreSecretInput,
|
|
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, O as OrganizationRole, R as RefreshedTokens, i as TokenResponse, T as TokenTypeHint, j as actor, k as
|
|
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';
|
|
4
|
+
export { P as PermissionDecision, a as PermissionDecisionConfig, c as checkPermissions } from './decisions-DNd9UXwi.cjs';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* The handler on YOUR side of a legacy migration.
|
|
@@ -130,9 +131,35 @@ interface FrontendConfig {
|
|
|
130
131
|
end_session: string;
|
|
131
132
|
jwks: string;
|
|
132
133
|
};
|
|
134
|
+
/**
|
|
135
|
+
* The social buttons to draw, exactly as the hosted sign-in page would: the environment's
|
|
136
|
+
* own providers, or — with `config({ organization })` — that organization's.
|
|
137
|
+
*/
|
|
133
138
|
social: SocialProvider[];
|
|
134
139
|
appearance?: Appearance;
|
|
140
|
+
/**
|
|
141
|
+
* Which other sign-in methods are switched on, so a box does not draw a button whose
|
|
142
|
+
* endpoint answers 403. Absent from instances that predate it.
|
|
143
|
+
*/
|
|
144
|
+
methods?: {
|
|
145
|
+
passkeys: boolean;
|
|
146
|
+
magic_link: boolean;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/** Options for {@link CboxIdFrontend.config}. */
|
|
150
|
+
interface FrontendConfigOptions {
|
|
151
|
+
/**
|
|
152
|
+
* The organization the box is for, by id or slug — the way a hosted page is addressed.
|
|
153
|
+
* `social` is then exactly what that organization's sign-in page offers. An organization
|
|
154
|
+
* the environment does not have is answered as no hint at all.
|
|
155
|
+
*/
|
|
156
|
+
organization?: string;
|
|
135
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* A second factor the person can answer an `mfa_required` with. Kinds only — the server
|
|
160
|
+
* never says which number a text would go to before one is sent.
|
|
161
|
+
*/
|
|
162
|
+
type SecondFactorKind = 'totp' | 'sms' | 'recovery_code';
|
|
136
163
|
/** What a sign-in attempt produced. */
|
|
137
164
|
type SignInResult = {
|
|
138
165
|
status: 'ok';
|
|
@@ -141,6 +168,11 @@ type SignInResult = {
|
|
|
141
168
|
} | {
|
|
142
169
|
status: 'mfa_required';
|
|
143
170
|
mfaToken: string;
|
|
171
|
+
/**
|
|
172
|
+
* The factors this person can answer with (laravel-id 1.24+). Absent when the
|
|
173
|
+
* instance predates the list — then only an authenticator or a recovery code works.
|
|
174
|
+
*/
|
|
175
|
+
factors?: SecondFactorKind[];
|
|
144
176
|
} | {
|
|
145
177
|
status: 'otp_required';
|
|
146
178
|
mfaToken: string;
|
|
@@ -152,6 +184,30 @@ type SignInResult = {
|
|
|
152
184
|
status: 'rate_limited';
|
|
153
185
|
retryAfter: number | undefined;
|
|
154
186
|
};
|
|
187
|
+
/** What asking for a texted sign-in code produced. */
|
|
188
|
+
type SmsCodeResult =
|
|
189
|
+
/** Sent. `to` is the number masked (`+45 ******78`); the code lasts `expiresIn` seconds. */
|
|
190
|
+
{
|
|
191
|
+
status: 'sent';
|
|
192
|
+
to: string;
|
|
193
|
+
expiresIn: number;
|
|
194
|
+
}
|
|
195
|
+
/** Too soon, or a sending limit was hit. Waiting `retryAfter` seconds may help. */
|
|
196
|
+
| {
|
|
197
|
+
status: 'rate_limited';
|
|
198
|
+
retryAfter: number | undefined;
|
|
199
|
+
}
|
|
200
|
+
/** The text provider failed. Try again shortly, or offer another factor. */
|
|
201
|
+
| {
|
|
202
|
+
status: 'unavailable';
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Refused: an unknown or spent token, no number on file, a country texts are not sent
|
|
206
|
+
* to, or no attempts left. One answer for every reason, like a wrong password.
|
|
207
|
+
*/
|
|
208
|
+
| {
|
|
209
|
+
status: 'invalid';
|
|
210
|
+
};
|
|
155
211
|
/** What a browser needs to ask an authenticator for a passkey assertion. */
|
|
156
212
|
interface PasskeyOptions {
|
|
157
213
|
challenge: string;
|
|
@@ -197,7 +253,7 @@ declare class CboxIdFrontend {
|
|
|
197
253
|
* fetches of the same immutable document is a waste the SDK should absorb rather than
|
|
198
254
|
* ask every consumer to.
|
|
199
255
|
*/
|
|
200
|
-
private
|
|
256
|
+
private readonly configPromises;
|
|
201
257
|
constructor(options: FrontendClientOptions);
|
|
202
258
|
/** Whether this key drives real sign-ins. Handy for a "test mode" badge in your own UI. */
|
|
203
259
|
get isLive(): boolean;
|
|
@@ -207,8 +263,9 @@ declare class CboxIdFrontend {
|
|
|
207
263
|
* Fetched once per client instance. The document is small and changes when somebody
|
|
208
264
|
* edits it in the console, so a long-lived page picks changes up on its next full load
|
|
209
265
|
* rather than mid-session — which is the right trade for something that decides layout.
|
|
266
|
+
* Each `organization` is fetched and cached on its own.
|
|
210
267
|
*/
|
|
211
|
-
config(): Promise<FrontendConfig>;
|
|
268
|
+
config(options?: FrontendConfigOptions): Promise<FrontendConfig>;
|
|
212
269
|
/**
|
|
213
270
|
* Who is signed in, given an access token your app already holds.
|
|
214
271
|
*
|
|
@@ -256,8 +313,29 @@ declare class CboxIdFrontend {
|
|
|
256
313
|
*
|
|
257
314
|
* A wrong code costs an attempt, not the sign-in: five are allowed before the token
|
|
258
315
|
* dies and the person starts from the password again.
|
|
316
|
+
*
|
|
317
|
+
* `method` says which challenge the code answers: `'mfa'` for an authenticator or
|
|
318
|
+
* recovery code, `'otp'` for an emailed code (`otp_required`), `'sms'` for a code sent
|
|
319
|
+
* with {@link sendSmsCode}. A texted code is six digits like an authenticator's, so the
|
|
320
|
+
* server cannot tell them apart by shape — answering one as the other is refused.
|
|
321
|
+
*/
|
|
322
|
+
submitSecondFactor(mfaToken: string, code: string, method?: 'mfa' | 'otp' | 'sms'): Promise<SignInResult>;
|
|
323
|
+
/**
|
|
324
|
+
* Text the person a sign-in code, when `mfa_required` listed `'sms'` in `factors`.
|
|
325
|
+
* Then finish with `submitSecondFactor(mfaToken, code, 'sms')`.
|
|
326
|
+
*
|
|
327
|
+
* Only call it when the person asks ("Text me a code"), never on page load: each send
|
|
328
|
+
* spends one of the token's five attempts — that is what bounds how many texts one
|
|
329
|
+
* proved password can cause — and every text costs the customer money.
|
|
330
|
+
*
|
|
331
|
+
* ```ts
|
|
332
|
+
* if (result.status === 'mfa_required' && result.factors?.includes('sms')) {
|
|
333
|
+
* const sent = await frontend.sendSmsCode(result.mfaToken)
|
|
334
|
+
* if (sent.status === 'sent') showCodeField(`We texted ${sent.to}`)
|
|
335
|
+
* }
|
|
336
|
+
* ```
|
|
259
337
|
*/
|
|
260
|
-
|
|
338
|
+
sendSmsCode(mfaToken: string): Promise<SmsCodeResult>;
|
|
261
339
|
/**
|
|
262
340
|
* The WebAuthn options for a passkey sign-in, and the handle that carries the challenge.
|
|
263
341
|
*
|
|
@@ -442,4 +520,4 @@ declare function buildManifest(declaration: AuthzDeclaration): Promise<AuthzMani
|
|
|
442
520
|
*/
|
|
443
521
|
declare function publishManifest(config: ManifestPublisherConfig, declaration: AuthzDeclaration): Promise<ManifestSyncSummary>;
|
|
444
522
|
|
|
445
|
-
export { type Appearance, type AuthzDeclaration, type AuthzManifest, CboxIdFrontend, type FrontendClientOptions, type FrontendConfig, type FrontendSession, 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 };
|
|
523
|
+
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,6 +1,7 @@
|
|
|
1
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, S as StoreSecretInput,
|
|
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, O as OrganizationRole, R as RefreshedTokens, i as TokenResponse, T as TokenTypeHint, j as actor, k as
|
|
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';
|
|
4
|
+
export { P as PermissionDecision, a as PermissionDecisionConfig, c as checkPermissions } from './decisions-C84Fzah4.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* The handler on YOUR side of a legacy migration.
|
|
@@ -130,9 +131,35 @@ interface FrontendConfig {
|
|
|
130
131
|
end_session: string;
|
|
131
132
|
jwks: string;
|
|
132
133
|
};
|
|
134
|
+
/**
|
|
135
|
+
* The social buttons to draw, exactly as the hosted sign-in page would: the environment's
|
|
136
|
+
* own providers, or — with `config({ organization })` — that organization's.
|
|
137
|
+
*/
|
|
133
138
|
social: SocialProvider[];
|
|
134
139
|
appearance?: Appearance;
|
|
140
|
+
/**
|
|
141
|
+
* Which other sign-in methods are switched on, so a box does not draw a button whose
|
|
142
|
+
* endpoint answers 403. Absent from instances that predate it.
|
|
143
|
+
*/
|
|
144
|
+
methods?: {
|
|
145
|
+
passkeys: boolean;
|
|
146
|
+
magic_link: boolean;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/** Options for {@link CboxIdFrontend.config}. */
|
|
150
|
+
interface FrontendConfigOptions {
|
|
151
|
+
/**
|
|
152
|
+
* The organization the box is for, by id or slug — the way a hosted page is addressed.
|
|
153
|
+
* `social` is then exactly what that organization's sign-in page offers. An organization
|
|
154
|
+
* the environment does not have is answered as no hint at all.
|
|
155
|
+
*/
|
|
156
|
+
organization?: string;
|
|
135
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* A second factor the person can answer an `mfa_required` with. Kinds only — the server
|
|
160
|
+
* never says which number a text would go to before one is sent.
|
|
161
|
+
*/
|
|
162
|
+
type SecondFactorKind = 'totp' | 'sms' | 'recovery_code';
|
|
136
163
|
/** What a sign-in attempt produced. */
|
|
137
164
|
type SignInResult = {
|
|
138
165
|
status: 'ok';
|
|
@@ -141,6 +168,11 @@ type SignInResult = {
|
|
|
141
168
|
} | {
|
|
142
169
|
status: 'mfa_required';
|
|
143
170
|
mfaToken: string;
|
|
171
|
+
/**
|
|
172
|
+
* The factors this person can answer with (laravel-id 1.24+). Absent when the
|
|
173
|
+
* instance predates the list — then only an authenticator or a recovery code works.
|
|
174
|
+
*/
|
|
175
|
+
factors?: SecondFactorKind[];
|
|
144
176
|
} | {
|
|
145
177
|
status: 'otp_required';
|
|
146
178
|
mfaToken: string;
|
|
@@ -152,6 +184,30 @@ type SignInResult = {
|
|
|
152
184
|
status: 'rate_limited';
|
|
153
185
|
retryAfter: number | undefined;
|
|
154
186
|
};
|
|
187
|
+
/** What asking for a texted sign-in code produced. */
|
|
188
|
+
type SmsCodeResult =
|
|
189
|
+
/** Sent. `to` is the number masked (`+45 ******78`); the code lasts `expiresIn` seconds. */
|
|
190
|
+
{
|
|
191
|
+
status: 'sent';
|
|
192
|
+
to: string;
|
|
193
|
+
expiresIn: number;
|
|
194
|
+
}
|
|
195
|
+
/** Too soon, or a sending limit was hit. Waiting `retryAfter` seconds may help. */
|
|
196
|
+
| {
|
|
197
|
+
status: 'rate_limited';
|
|
198
|
+
retryAfter: number | undefined;
|
|
199
|
+
}
|
|
200
|
+
/** The text provider failed. Try again shortly, or offer another factor. */
|
|
201
|
+
| {
|
|
202
|
+
status: 'unavailable';
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Refused: an unknown or spent token, no number on file, a country texts are not sent
|
|
206
|
+
* to, or no attempts left. One answer for every reason, like a wrong password.
|
|
207
|
+
*/
|
|
208
|
+
| {
|
|
209
|
+
status: 'invalid';
|
|
210
|
+
};
|
|
155
211
|
/** What a browser needs to ask an authenticator for a passkey assertion. */
|
|
156
212
|
interface PasskeyOptions {
|
|
157
213
|
challenge: string;
|
|
@@ -197,7 +253,7 @@ declare class CboxIdFrontend {
|
|
|
197
253
|
* fetches of the same immutable document is a waste the SDK should absorb rather than
|
|
198
254
|
* ask every consumer to.
|
|
199
255
|
*/
|
|
200
|
-
private
|
|
256
|
+
private readonly configPromises;
|
|
201
257
|
constructor(options: FrontendClientOptions);
|
|
202
258
|
/** Whether this key drives real sign-ins. Handy for a "test mode" badge in your own UI. */
|
|
203
259
|
get isLive(): boolean;
|
|
@@ -207,8 +263,9 @@ declare class CboxIdFrontend {
|
|
|
207
263
|
* Fetched once per client instance. The document is small and changes when somebody
|
|
208
264
|
* edits it in the console, so a long-lived page picks changes up on its next full load
|
|
209
265
|
* rather than mid-session — which is the right trade for something that decides layout.
|
|
266
|
+
* Each `organization` is fetched and cached on its own.
|
|
210
267
|
*/
|
|
211
|
-
config(): Promise<FrontendConfig>;
|
|
268
|
+
config(options?: FrontendConfigOptions): Promise<FrontendConfig>;
|
|
212
269
|
/**
|
|
213
270
|
* Who is signed in, given an access token your app already holds.
|
|
214
271
|
*
|
|
@@ -256,8 +313,29 @@ declare class CboxIdFrontend {
|
|
|
256
313
|
*
|
|
257
314
|
* A wrong code costs an attempt, not the sign-in: five are allowed before the token
|
|
258
315
|
* dies and the person starts from the password again.
|
|
316
|
+
*
|
|
317
|
+
* `method` says which challenge the code answers: `'mfa'` for an authenticator or
|
|
318
|
+
* recovery code, `'otp'` for an emailed code (`otp_required`), `'sms'` for a code sent
|
|
319
|
+
* with {@link sendSmsCode}. A texted code is six digits like an authenticator's, so the
|
|
320
|
+
* server cannot tell them apart by shape — answering one as the other is refused.
|
|
321
|
+
*/
|
|
322
|
+
submitSecondFactor(mfaToken: string, code: string, method?: 'mfa' | 'otp' | 'sms'): Promise<SignInResult>;
|
|
323
|
+
/**
|
|
324
|
+
* Text the person a sign-in code, when `mfa_required` listed `'sms'` in `factors`.
|
|
325
|
+
* Then finish with `submitSecondFactor(mfaToken, code, 'sms')`.
|
|
326
|
+
*
|
|
327
|
+
* Only call it when the person asks ("Text me a code"), never on page load: each send
|
|
328
|
+
* spends one of the token's five attempts — that is what bounds how many texts one
|
|
329
|
+
* proved password can cause — and every text costs the customer money.
|
|
330
|
+
*
|
|
331
|
+
* ```ts
|
|
332
|
+
* if (result.status === 'mfa_required' && result.factors?.includes('sms')) {
|
|
333
|
+
* const sent = await frontend.sendSmsCode(result.mfaToken)
|
|
334
|
+
* if (sent.status === 'sent') showCodeField(`We texted ${sent.to}`)
|
|
335
|
+
* }
|
|
336
|
+
* ```
|
|
259
337
|
*/
|
|
260
|
-
|
|
338
|
+
sendSmsCode(mfaToken: string): Promise<SmsCodeResult>;
|
|
261
339
|
/**
|
|
262
340
|
* The WebAuthn options for a passkey sign-in, and the handle that carries the challenge.
|
|
263
341
|
*
|
|
@@ -442,4 +520,4 @@ declare function buildManifest(declaration: AuthzDeclaration): Promise<AuthzMani
|
|
|
442
520
|
*/
|
|
443
521
|
declare function publishManifest(config: ManifestPublisherConfig, declaration: AuthzDeclaration): Promise<ManifestSyncSummary>;
|
|
444
522
|
|
|
445
|
-
export { type Appearance, type AuthzDeclaration, type AuthzManifest, CboxIdFrontend, type FrontendClientOptions, type FrontendConfig, type FrontendSession, 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 };
|
|
523
|
+
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,5 +1,6 @@
|
|
|
1
|
-
import { Discovery } from './chunk-
|
|
2
|
-
export { CboxIdClient, VaultClient, actor, challenge, createVerifier, hasPermission, hasRole, isSupportSession, organization, permissions, randomToken, roles, sessionId, verifyStandardWebhook, verifyWebhook } from './chunk-
|
|
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
|
+
export { PermissionDecision, checkPermissions } from './chunk-EVZ6RB3K.js';
|
|
3
4
|
import { assertSecureIssuer } from './chunk-QQW63PGU.js';
|
|
4
5
|
import { ConfigurationError, FrontendApiError, AuthenticationError } from './chunk-SEUD4TE4.js';
|
|
5
6
|
export { AuthenticationError, CboxIdError, ConfigurationError, FrontendApiError, InvalidStateError } from './chunk-SEUD4TE4.js';
|
|
@@ -98,6 +99,7 @@ function json(body, status) {
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
// src/frontend.ts
|
|
102
|
+
var SECOND_FACTOR_KINDS = /* @__PURE__ */ new Set(["totp", "sms", "recovery_code"]);
|
|
101
103
|
var CboxIdFrontend = class {
|
|
102
104
|
issuer;
|
|
103
105
|
publishableKey;
|
|
@@ -112,7 +114,7 @@ var CboxIdFrontend = class {
|
|
|
112
114
|
* fetches of the same immutable document is a waste the SDK should absorb rather than
|
|
113
115
|
* ask every consumer to.
|
|
114
116
|
*/
|
|
115
|
-
|
|
117
|
+
configPromises = /* @__PURE__ */ new Map();
|
|
116
118
|
constructor(options) {
|
|
117
119
|
if (!options.issuer) {
|
|
118
120
|
throw new ConfigurationError("issuer is required.");
|
|
@@ -139,13 +141,20 @@ var CboxIdFrontend = class {
|
|
|
139
141
|
* Fetched once per client instance. The document is small and changes when somebody
|
|
140
142
|
* edits it in the console, so a long-lived page picks changes up on its next full load
|
|
141
143
|
* rather than mid-session — which is the right trade for something that decides layout.
|
|
144
|
+
* Each `organization` is fetched and cached on its own.
|
|
142
145
|
*/
|
|
143
|
-
async config() {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
async config(options = {}) {
|
|
147
|
+
const organization2 = options.organization ?? "";
|
|
148
|
+
let pending = this.configPromises.get(organization2);
|
|
149
|
+
if (pending === void 0) {
|
|
150
|
+
const path = organization2 === "" ? "/frontend/v1/config" : `/frontend/v1/config?${new URLSearchParams({ organization: organization2 }).toString()}`;
|
|
151
|
+
pending = this.get(path, {}, isFrontendConfig).catch((error) => {
|
|
152
|
+
this.configPromises.delete(organization2);
|
|
153
|
+
throw error;
|
|
154
|
+
});
|
|
155
|
+
this.configPromises.set(organization2, pending);
|
|
156
|
+
}
|
|
157
|
+
return pending;
|
|
149
158
|
}
|
|
150
159
|
/**
|
|
151
160
|
* Who is signed in, given an access token your app already holds.
|
|
@@ -207,8 +216,12 @@ var CboxIdFrontend = class {
|
|
|
207
216
|
if (body.status === "ok" && typeof body.login_ticket === "string") {
|
|
208
217
|
return { status: "ok", loginTicket: body.login_ticket, expiresIn: body.expires_in ?? 60 };
|
|
209
218
|
}
|
|
210
|
-
if (
|
|
211
|
-
|
|
219
|
+
if (body.status === "mfa_required" && typeof body.mfa_token === "string") {
|
|
220
|
+
const factors = Array.isArray(body.factors) ? body.factors.filter((f) => typeof f === "string" && SECOND_FACTOR_KINDS.has(f)) : void 0;
|
|
221
|
+
return { status: "mfa_required", mfaToken: body.mfa_token, ...factors ? { factors } : {} };
|
|
222
|
+
}
|
|
223
|
+
if (body.status === "otp_required" && typeof body.mfa_token === "string") {
|
|
224
|
+
return { status: "otp_required", mfaToken: body.mfa_token };
|
|
212
225
|
}
|
|
213
226
|
if (body.status === "sso_required") {
|
|
214
227
|
return { status: "sso_required" };
|
|
@@ -225,6 +238,11 @@ var CboxIdFrontend = class {
|
|
|
225
238
|
*
|
|
226
239
|
* A wrong code costs an attempt, not the sign-in: five are allowed before the token
|
|
227
240
|
* dies and the person starts from the password again.
|
|
241
|
+
*
|
|
242
|
+
* `method` says which challenge the code answers: `'mfa'` for an authenticator or
|
|
243
|
+
* recovery code, `'otp'` for an emailed code (`otp_required`), `'sms'` for a code sent
|
|
244
|
+
* with {@link sendSmsCode}. A texted code is six digits like an authenticator's, so the
|
|
245
|
+
* server cannot tell them apart by shape — answering one as the other is refused.
|
|
228
246
|
*/
|
|
229
247
|
async submitSecondFactor(mfaToken, code, method = "mfa") {
|
|
230
248
|
const response = await this.post("/frontend/v1/sign-in/factor", { mfa_token: mfaToken, code, method });
|
|
@@ -236,6 +254,41 @@ var CboxIdFrontend = class {
|
|
|
236
254
|
}
|
|
237
255
|
return body.status === "ok" && typeof body.login_ticket === "string" ? { status: "ok", loginTicket: body.login_ticket, expiresIn: body.expires_in ?? 60 } : { status: "invalid" };
|
|
238
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Text the person a sign-in code, when `mfa_required` listed `'sms'` in `factors`.
|
|
259
|
+
* Then finish with `submitSecondFactor(mfaToken, code, 'sms')`.
|
|
260
|
+
*
|
|
261
|
+
* Only call it when the person asks ("Text me a code"), never on page load: each send
|
|
262
|
+
* spends one of the token's five attempts — that is what bounds how many texts one
|
|
263
|
+
* proved password can cause — and every text costs the customer money.
|
|
264
|
+
*
|
|
265
|
+
* ```ts
|
|
266
|
+
* if (result.status === 'mfa_required' && result.factors?.includes('sms')) {
|
|
267
|
+
* const sent = await frontend.sendSmsCode(result.mfaToken)
|
|
268
|
+
* if (sent.status === 'sent') showCodeField(`We texted ${sent.to}`)
|
|
269
|
+
* }
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
async sendSmsCode(mfaToken) {
|
|
273
|
+
const response = await this.post("/frontend/v1/sign-in/factor/sms", { mfa_token: mfaToken });
|
|
274
|
+
let body;
|
|
275
|
+
try {
|
|
276
|
+
body = await response.json();
|
|
277
|
+
} catch {
|
|
278
|
+
throw new FrontendApiError("Cbox ID returned a body that is not JSON.", "malformed", response.status);
|
|
279
|
+
}
|
|
280
|
+
if (body.status === "sent" && typeof body.to === "string") {
|
|
281
|
+
return { status: "sent", to: body.to, expiresIn: typeof body.expires_in === "number" ? body.expires_in : 300 };
|
|
282
|
+
}
|
|
283
|
+
if (body.status === "rate_limited" || response.status === 429) {
|
|
284
|
+
const retryAfter = typeof body.retry_after === "number" ? body.retry_after : Number(response.headers.get("Retry-After") ?? "");
|
|
285
|
+
return { status: "rate_limited", retryAfter: Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : void 0 };
|
|
286
|
+
}
|
|
287
|
+
if (body.status === "unavailable") {
|
|
288
|
+
return { status: "unavailable" };
|
|
289
|
+
}
|
|
290
|
+
return { status: "invalid" };
|
|
291
|
+
}
|
|
239
292
|
/**
|
|
240
293
|
* The WebAuthn options for a passkey sign-in, and the handle that carries the challenge.
|
|
241
294
|
*
|
|
@@ -435,6 +488,11 @@ async function publishManifest(config, declaration) {
|
|
|
435
488
|
throw new ConfigurationError("Publishing a manifest needs issuer, clientId and clientSecret.");
|
|
436
489
|
}
|
|
437
490
|
const timeoutMs = config.timeoutMs ?? 1e4;
|
|
491
|
+
assertSecureIssuer(config.issuer);
|
|
492
|
+
const issuer = new URL(config.issuer);
|
|
493
|
+
if (issuer.username || issuer.password || issuer.search || issuer.hash) {
|
|
494
|
+
throw new ConfigurationError("The manifest issuer must not contain credentials, a query or a fragment.");
|
|
495
|
+
}
|
|
438
496
|
const manifest = await buildManifest(declaration);
|
|
439
497
|
const discovery = new Discovery(config.issuer, timeoutMs, 36e5);
|
|
440
498
|
const token = await mintToken(config, discovery, timeoutMs);
|
|
@@ -442,6 +500,7 @@ async function publishManifest(config, declaration) {
|
|
|
442
500
|
`${config.issuer.replace(/\/$/, "")}/api/v1/apps/manifest`,
|
|
443
501
|
{
|
|
444
502
|
method: "POST",
|
|
503
|
+
redirect: "error",
|
|
445
504
|
headers: {
|
|
446
505
|
authorization: `Bearer ${token}`,
|
|
447
506
|
accept: "application/json",
|
|
@@ -452,10 +511,20 @@ async function publishManifest(config, declaration) {
|
|
|
452
511
|
timeoutMs
|
|
453
512
|
);
|
|
454
513
|
if (!response.ok) {
|
|
455
|
-
throw new AuthenticationError(`Manifest push failed: HTTP ${response.status}
|
|
514
|
+
throw new AuthenticationError(`Manifest push failed: HTTP ${response.status}`);
|
|
515
|
+
}
|
|
516
|
+
const json2 = await response.json().catch(() => {
|
|
517
|
+
throw new AuthenticationError("Cbox ID returned an invalid manifest acknowledgement.");
|
|
518
|
+
});
|
|
519
|
+
const checksum = await sha256Hex(canonicalManifestJson(declaration.permissions ?? [], declaration.roles ?? []));
|
|
520
|
+
if (json2 === null || typeof json2 !== "object" || Array.isArray(json2)) {
|
|
521
|
+
throw new AuthenticationError("Cbox ID did not acknowledge the authorization manifest.");
|
|
522
|
+
}
|
|
523
|
+
const summary = json2;
|
|
524
|
+
if (summary.client_id !== config.clientId || summary.version !== manifest.version || summary.checksum !== checksum || typeof summary.unchanged !== "boolean" || !Number.isInteger(summary.roles_declared) || !Number.isInteger(summary.permissions_declared) || (summary.roles_declared ?? -1) < 0 || (summary.permissions_declared ?? -1) < 0 || !summary.unchanged && (summary.roles_declared !== manifest.roles.length || summary.permissions_declared !== manifest.permissions.length)) {
|
|
525
|
+
throw new AuthenticationError("Cbox ID did not acknowledge this app and its complete authorization manifest.");
|
|
456
526
|
}
|
|
457
|
-
|
|
458
|
-
return json2 !== null && typeof json2 === "object" ? json2 : {};
|
|
527
|
+
return summary;
|
|
459
528
|
}
|
|
460
529
|
async function mintToken(config, discovery, timeoutMs) {
|
|
461
530
|
const response = await fetchWithTimeout(
|
|
@@ -463,6 +532,7 @@ async function mintToken(config, discovery, timeoutMs) {
|
|
|
463
532
|
{
|
|
464
533
|
method: "POST",
|
|
465
534
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
535
|
+
redirect: "error",
|
|
466
536
|
body: new URLSearchParams({
|
|
467
537
|
grant_type: "client_credentials",
|
|
468
538
|
client_id: config.clientId,
|
|
@@ -473,7 +543,7 @@ async function mintToken(config, discovery, timeoutMs) {
|
|
|
473
543
|
timeoutMs
|
|
474
544
|
);
|
|
475
545
|
const json2 = await response.json().catch(() => ({}));
|
|
476
|
-
if (typeof json2.access_token !== "string" || json2.access_token === "") {
|
|
546
|
+
if (!response.ok || typeof json2.access_token !== "string" || json2.access_token === "") {
|
|
477
547
|
throw new AuthenticationError(
|
|
478
548
|
"Could not obtain an apps.manifest access token \u2014 check the client credentials and that the client holds the apps.manifest scope."
|
|
479
549
|
);
|
|
@@ -595,6 +665,8 @@ async function fetchWithTimeout(url, init, timeoutMs) {
|
|
|
595
665
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
596
666
|
try {
|
|
597
667
|
return await fetch(url, { ...init, signal: controller.signal });
|
|
668
|
+
} catch {
|
|
669
|
+
throw new AuthenticationError("Cbox ID manifest publication could not be verified.");
|
|
598
670
|
} finally {
|
|
599
671
|
clearTimeout(timer);
|
|
600
672
|
}
|