@udibo/oauth2 0.41.0 → 0.43.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 +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +9 -0
- package/dist/identity/errors.d.ts +2 -2
- package/dist/identity/errors.js +2 -0
- package/dist/identity/events.d.ts +44 -2
- package/dist/identity/events.js +2 -1
- package/dist/identity/passkeys/mod.d.ts +55 -0
- package/dist/identity/passkeys/mod.js +55 -0
- package/dist/identity/passkeys/service.d.ts +469 -0
- package/dist/identity/passkeys/service.js +558 -0
- package/dist/models/token.d.ts +7 -0
- package/dist/react/components/internal.d.ts +5 -1
- package/dist/react/components/internal.js +9 -0
- package/dist/react/components/mfa-challenge-form.d.ts +9 -3
- package/dist/react/components/mfa-challenge-form.js +3 -3
- package/dist/react/components/mod.d.ts +1 -1
- package/dist/react/components/sign-in-form.d.ts +11 -3
- package/dist/react/components/sign-in-form.js +4 -4
- package/dist/react/components/types.d.ts +12 -0
- package/dist/server/_resource-indicator.d.ts +7 -0
- package/dist/server/_resource-indicator.js +67 -0
- package/dist/server/authorization-server.js +2 -0
- package/dist/server/grants/client-credentials.d.ts +6 -0
- package/dist/server/grants/client-credentials.js +22 -3
- package/dist/server/grants/grant.d.ts +4 -2
- package/dist/server/grants/grant.js +17 -3
- package/dist/server/grants/refresh-token.d.ts +2 -0
- package/dist/server/grants/refresh-token.js +8 -1
- package/dist/server/mod.d.ts +1 -1
- package/dist/server/mod.js +1 -1
- package/dist/server/services/token.d.ts +20 -2
- package/dist/server/services/token.js +1 -1
- package/dist/server/signing-keys.d.ts +3 -1
- package/dist/server/signing-keys.js +2 -2
- package/dist/testing/contract/mod.d.ts +5 -3
- package/dist/testing/contract/mod.js +5 -3
- package/dist/testing/contract/passkey-store.d.ts +66 -0
- package/dist/testing/contract/passkey-store.js +196 -0
- package/dist/testing/contract/token.d.ts +7 -0
- package/dist/testing/contract/token.js +17 -0
- package/dist/testing/mod.d.ts +5 -0
- package/dist/testing/mod.js +5 -0
- package/dist/testing/passkey-authenticator.d.ts +86 -0
- package/dist/testing/passkey-authenticator.js +199 -0
- package/dist/testing/services.js +4 -0
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -164,6 +164,7 @@ and examples in the [reference](https://jsr.io/@udibo/oauth2/doc).
|
|
|
164
164
|
| `/hono/bff` | BFF routes, server-held tokens, session and pending-login stores |
|
|
165
165
|
| `/identity` | App-owned signup, login, password reset, email verification, passwordless, and protection hooks |
|
|
166
166
|
| `/identity/mfa` | TOTP, recovery codes, and MFA storage contract |
|
|
167
|
+
| `/identity/passkeys` | Passkey (WebAuthn) registration and sign-in, as a first or second factor |
|
|
167
168
|
| `/identity/external` | Social and external OIDC sign-in for your own login pages |
|
|
168
169
|
| `/identity/migration` | Verify imported password hashes and upgrade them at login |
|
|
169
170
|
| `/hono/identity` | Hono routes for `IdentityService` |
|
package/dist/errors.d.ts
CHANGED
|
@@ -159,6 +159,11 @@ export declare const InsufficientPermissionsError: OAuth2ErrorClass;
|
|
|
159
159
|
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
|
|
160
160
|
*/
|
|
161
161
|
export declare const UnsupportedResponseTypeError: OAuth2ErrorClass;
|
|
162
|
+
/**
|
|
163
|
+
* The requested resource is missing, malformed or not allowed by the server's
|
|
164
|
+
* resource policy (RFC 8707 section 2). Descriptions should not echo targets.
|
|
165
|
+
*/
|
|
166
|
+
export declare const InvalidTargetError: OAuth2ErrorClass;
|
|
162
167
|
/**
|
|
163
168
|
* The requested scope is invalid, unknown, or malformed.
|
|
164
169
|
*
|
package/dist/errors.js
CHANGED
|
@@ -205,6 +205,15 @@ export const UnsupportedResponseTypeError = createHttpErrorClass({
|
|
|
205
205
|
error: "unsupported_response_type",
|
|
206
206
|
},
|
|
207
207
|
});
|
|
208
|
+
/**
|
|
209
|
+
* The requested resource is missing, malformed or not allowed by the server's
|
|
210
|
+
* resource policy (RFC 8707 section 2). Descriptions should not echo targets.
|
|
211
|
+
*/
|
|
212
|
+
export const InvalidTargetError = createHttpErrorClass({
|
|
213
|
+
name: "Invalid Target",
|
|
214
|
+
status: 400,
|
|
215
|
+
extensions: { error: "invalid_target" },
|
|
216
|
+
});
|
|
208
217
|
/**
|
|
209
218
|
* The requested scope is invalid, unknown, or malformed.
|
|
210
219
|
*
|
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
9
|
* Stable, machine-readable identity error codes. The package raises
|
|
10
|
-
* `rate_limited`, `weak_password`, and the `mfa_*` codes, and the Hono route
|
|
10
|
+
* `rate_limited`, `weak_password`, and the `mfa_*` and `passkey_*` codes, and the Hono route
|
|
11
11
|
* factory answers `forbidden_origin` (its same-origin guard, before a handler
|
|
12
12
|
* runs) and `invalid_credentials` / `invalid_token` (a failed sign-in or token
|
|
13
13
|
* check); the rest are for your store and routes to throw (e.g. `identifier_taken` on
|
|
14
14
|
* a duplicate sign-up, `captcha_failed` after `verifyCaptcha`).
|
|
15
15
|
*/
|
|
16
|
-
export type IdentityErrorCode = "invalid_credentials" | "invalid_token" | "rate_limited" | "weak_password" | "identifier_taken" | "email_not_verified" | "mfa_not_enrolled" | "mfa_already_enrolled" | "captcha_failed" | "forbidden_origin";
|
|
16
|
+
export type IdentityErrorCode = "invalid_credentials" | "invalid_token" | "rate_limited" | "weak_password" | "identifier_taken" | "email_not_verified" | "mfa_not_enrolled" | "mfa_already_enrolled" | "passkey_limit_reached" | "passkey_not_registered" | "captcha_failed" | "forbidden_origin";
|
|
17
17
|
/** Options for {@link IdentityError}. */
|
|
18
18
|
export interface IdentityErrorOptions extends ErrorOptions {
|
|
19
19
|
/** For `rate_limited`: milliseconds until the caller may retry. */
|
package/dist/identity/errors.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* {@link IdentityService} emits an {@link IdentityEvent} after each significant
|
|
5
5
|
* auth outcome (sign-in success/failure, sign-up, credential upgrade-on-login,
|
|
6
6
|
* reset and verification lifecycle, passwordless sign-in links and codes),
|
|
7
|
-
* `MfaService` emits the `mfa.*` events,
|
|
7
|
+
* `MfaService` emits the `mfa.*` events, `PasskeyService` the `passkey.*`
|
|
8
|
+
* events, and a password-policy validator emits
|
|
8
9
|
* `password_policy.check_unavailable` when it cannot complete its check. Wire
|
|
9
10
|
* an {@link IdentityEventHook} via the service's `onEvent`
|
|
10
11
|
* option to persist them in your own audit store — storage stays app-owned,
|
|
@@ -34,7 +35,8 @@ export type SignInFailureReason = "unknown_identifier" | "no_password" | "wrong_
|
|
|
34
35
|
*/
|
|
35
36
|
export type SignInCodeFailureReason = "invalid" | "expired" | "locked" | "unknown_email";
|
|
36
37
|
/**
|
|
37
|
-
* An auth outcome emitted by {@link IdentityService}, `MfaService`,
|
|
38
|
+
* An auth outcome emitted by {@link IdentityService}, `MfaService`,
|
|
39
|
+
* `PasskeyService`, or a
|
|
38
40
|
* password-policy validator. Discriminated on `type`; `userId` is present when
|
|
39
41
|
* the flow resolved an account.
|
|
40
42
|
*/
|
|
@@ -251,6 +253,46 @@ export type IdentityEvent = {
|
|
|
251
253
|
} | {
|
|
252
254
|
type: "mfa.disabled";
|
|
253
255
|
userId: string;
|
|
256
|
+
} | {
|
|
257
|
+
type: "passkey.registered";
|
|
258
|
+
userId: string;
|
|
259
|
+
credentialId: string;
|
|
260
|
+
/** Whether the passkey may sync between devices. */
|
|
261
|
+
multiDevice: boolean;
|
|
262
|
+
} | {
|
|
263
|
+
type: "passkey.registration.failed";
|
|
264
|
+
userId: string;
|
|
265
|
+
reason: "expired_challenge" | "invalid" | "duplicate" | "limit_reached";
|
|
266
|
+
} | {
|
|
267
|
+
type: "passkey.verify.succeeded";
|
|
268
|
+
userId: string;
|
|
269
|
+
credentialId: string;
|
|
270
|
+
/** Whether the authenticator verified the person (PIN or biometric). */
|
|
271
|
+
userVerified: boolean;
|
|
272
|
+
} | {
|
|
273
|
+
type: "passkey.verify.failed";
|
|
274
|
+
/**
|
|
275
|
+
* `"sign_count"` means the authenticator's counter went backwards — a
|
|
276
|
+
* possible cloned authenticator, worth an alert.
|
|
277
|
+
*/
|
|
278
|
+
reason: "expired_challenge" | "unknown_passkey" | "invalid" | "sign_count" | "replayed";
|
|
279
|
+
/** Present when the passkey resolved to an account. */
|
|
280
|
+
userId?: string;
|
|
281
|
+
credentialId?: string;
|
|
282
|
+
} | {
|
|
283
|
+
type: "passkey.verify.rate_limited";
|
|
284
|
+
userId?: string;
|
|
285
|
+
credentialId?: string;
|
|
286
|
+
retryAfterMs: number;
|
|
287
|
+
enforced: boolean;
|
|
288
|
+
} | {
|
|
289
|
+
type: "passkey.renamed";
|
|
290
|
+
userId: string;
|
|
291
|
+
credentialId: string;
|
|
292
|
+
} | {
|
|
293
|
+
type: "passkey.removed";
|
|
294
|
+
userId: string;
|
|
295
|
+
credentialId: string;
|
|
254
296
|
};
|
|
255
297
|
/**
|
|
256
298
|
* Receives each {@link IdentityEvent} after the flow's outcome is decided. The
|
package/dist/identity/events.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* {@link IdentityService} emits an {@link IdentityEvent} after each significant
|
|
5
5
|
* auth outcome (sign-in success/failure, sign-up, credential upgrade-on-login,
|
|
6
6
|
* reset and verification lifecycle, passwordless sign-in links and codes),
|
|
7
|
-
* `MfaService` emits the `mfa.*` events,
|
|
7
|
+
* `MfaService` emits the `mfa.*` events, `PasskeyService` the `passkey.*`
|
|
8
|
+
* events, and a password-policy validator emits
|
|
8
9
|
* `password_policy.check_unavailable` when it cannot complete its check. Wire
|
|
9
10
|
* an {@link IdentityEventHook} via the service's `onEvent`
|
|
10
11
|
* option to persist them in your own audit store — storage stays app-owned,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Passkeys (WebAuthn) for apps that own their login: registration and
|
|
3
|
+
* sign-in ceremonies over app-owned stores, usable as a first factor and as a
|
|
4
|
+
* second factor. Verification is delegated to `@simplewebauthn/server`; this
|
|
5
|
+
* module applies a consumer-friendly policy on top:
|
|
6
|
+
*
|
|
7
|
+
* - **No attestation.** Any passkey manager, platform, or FIDO2 security key
|
|
8
|
+
* works; nothing is allow-listed by authenticator model.
|
|
9
|
+
* - **Discoverable credentials**, so a person can sign in without typing an
|
|
10
|
+
* identifier, and sign-in without a known user lists no credentials and so
|
|
11
|
+
* reveals nothing about which accounts exist.
|
|
12
|
+
* - **User verification preferred and reported**, so the app decides whether a
|
|
13
|
+
* passkey alone satisfies its MFA policy.
|
|
14
|
+
* - **Single-use challenges**, a per-user cap (25 by default), sign-count clone
|
|
15
|
+
* detection, rate limiting, and `passkey.*` events.
|
|
16
|
+
*
|
|
17
|
+
* - {@link PasskeyService} — `startRegistration` / `finishRegistration`,
|
|
18
|
+
* `startAuthentication` / `finishAuthentication`, and list, rename, remove.
|
|
19
|
+
* - {@link PasskeyStore} and {@link PasskeyChallengeStore} — the app's
|
|
20
|
+
* persistence seams, with {@link MemoryPasskeyStore} and
|
|
21
|
+
* {@link MemoryPasskeyChallengeStore} for development and tests.
|
|
22
|
+
* - {@link PASSKEY_AMR} — the `amr` value a passkey contributes.
|
|
23
|
+
*
|
|
24
|
+
* The browser side is `navigator.credentials.create()` / `.get()` with the
|
|
25
|
+
* returned options; `@simplewebauthn/browser` or the native
|
|
26
|
+
* `PublicKeyCredential.parseCreationOptionsFromJSON` handle the conversion.
|
|
27
|
+
* `TestPasskeyAuthenticator` in `@udibo/oauth2/testing` produces real
|
|
28
|
+
* responses for tests.
|
|
29
|
+
*
|
|
30
|
+
* @example Serve a passkey sign-in
|
|
31
|
+
* ```ts
|
|
32
|
+
* import {
|
|
33
|
+
* MemoryPasskeyChallengeStore,
|
|
34
|
+
* MemoryPasskeyStore,
|
|
35
|
+
* PasskeyService,
|
|
36
|
+
* } from "@udibo/oauth2/identity/passkeys";
|
|
37
|
+
*
|
|
38
|
+
* const passkeys = new PasskeyService({
|
|
39
|
+
* store: new MemoryPasskeyStore(),
|
|
40
|
+
* challenges: new MemoryPasskeyChallengeStore(),
|
|
41
|
+
* });
|
|
42
|
+
* const relyingParty = {
|
|
43
|
+
* id: "login.example.com",
|
|
44
|
+
* name: "Example",
|
|
45
|
+
* origins: ["https://login.example.com"],
|
|
46
|
+
* };
|
|
47
|
+
* const { ceremonyId, options } = await passkeys.startAuthentication({
|
|
48
|
+
* relyingParty,
|
|
49
|
+
* });
|
|
50
|
+
* // set ceremonyId in an HttpOnly cookie and send options to the browser
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @module
|
|
54
|
+
*/
|
|
55
|
+
export { type FinishPasskeyAuthenticationOptions, type FinishPasskeyRegistrationOptions, MemoryPasskeyChallengeStore, MemoryPasskeyStore, type Passkey, PASSKEY_AMR, type PasskeyAddResult, type PasskeyAuthenticationOptions, type PasskeyAuthenticationResponse, type PasskeyCeremony, type PasskeyChallenge, type PasskeyChallengeStore, type PasskeyRegistration, type PasskeyRegistrationFailureReason, type PasskeyRegistrationOptions, type PasskeyRegistrationResponse, type PasskeyRelyingParty, PasskeyService, type PasskeyServiceOptions, type PasskeyStore, type PasskeyUse, type PasskeyUser, type PasskeyVerification, type PasskeyVerifyFailureReason, type StartPasskeyAuthenticationOptions, type StartPasskeyRegistrationOptions, } from "./service.ts";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Passkeys (WebAuthn) for apps that own their login: registration and
|
|
3
|
+
* sign-in ceremonies over app-owned stores, usable as a first factor and as a
|
|
4
|
+
* second factor. Verification is delegated to `@simplewebauthn/server`; this
|
|
5
|
+
* module applies a consumer-friendly policy on top:
|
|
6
|
+
*
|
|
7
|
+
* - **No attestation.** Any passkey manager, platform, or FIDO2 security key
|
|
8
|
+
* works; nothing is allow-listed by authenticator model.
|
|
9
|
+
* - **Discoverable credentials**, so a person can sign in without typing an
|
|
10
|
+
* identifier, and sign-in without a known user lists no credentials and so
|
|
11
|
+
* reveals nothing about which accounts exist.
|
|
12
|
+
* - **User verification preferred and reported**, so the app decides whether a
|
|
13
|
+
* passkey alone satisfies its MFA policy.
|
|
14
|
+
* - **Single-use challenges**, a per-user cap (25 by default), sign-count clone
|
|
15
|
+
* detection, rate limiting, and `passkey.*` events.
|
|
16
|
+
*
|
|
17
|
+
* - {@link PasskeyService} — `startRegistration` / `finishRegistration`,
|
|
18
|
+
* `startAuthentication` / `finishAuthentication`, and list, rename, remove.
|
|
19
|
+
* - {@link PasskeyStore} and {@link PasskeyChallengeStore} — the app's
|
|
20
|
+
* persistence seams, with {@link MemoryPasskeyStore} and
|
|
21
|
+
* {@link MemoryPasskeyChallengeStore} for development and tests.
|
|
22
|
+
* - {@link PASSKEY_AMR} — the `amr` value a passkey contributes.
|
|
23
|
+
*
|
|
24
|
+
* The browser side is `navigator.credentials.create()` / `.get()` with the
|
|
25
|
+
* returned options; `@simplewebauthn/browser` or the native
|
|
26
|
+
* `PublicKeyCredential.parseCreationOptionsFromJSON` handle the conversion.
|
|
27
|
+
* `TestPasskeyAuthenticator` in `@udibo/oauth2/testing` produces real
|
|
28
|
+
* responses for tests.
|
|
29
|
+
*
|
|
30
|
+
* @example Serve a passkey sign-in
|
|
31
|
+
* ```ts
|
|
32
|
+
* import {
|
|
33
|
+
* MemoryPasskeyChallengeStore,
|
|
34
|
+
* MemoryPasskeyStore,
|
|
35
|
+
* PasskeyService,
|
|
36
|
+
* } from "@udibo/oauth2/identity/passkeys";
|
|
37
|
+
*
|
|
38
|
+
* const passkeys = new PasskeyService({
|
|
39
|
+
* store: new MemoryPasskeyStore(),
|
|
40
|
+
* challenges: new MemoryPasskeyChallengeStore(),
|
|
41
|
+
* });
|
|
42
|
+
* const relyingParty = {
|
|
43
|
+
* id: "login.example.com",
|
|
44
|
+
* name: "Example",
|
|
45
|
+
* origins: ["https://login.example.com"],
|
|
46
|
+
* };
|
|
47
|
+
* const { ceremonyId, options } = await passkeys.startAuthentication({
|
|
48
|
+
* relyingParty,
|
|
49
|
+
* });
|
|
50
|
+
* // set ceremonyId in an HttpOnly cookie and send options to the browser
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @module
|
|
54
|
+
*/
|
|
55
|
+
export { MemoryPasskeyChallengeStore, MemoryPasskeyStore, PASSKEY_AMR, PasskeyService, } from "./service.js";
|