@udibo/oauth2 0.4.1 → 0.5.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 +3 -3
- package/SECURITY.md +0 -4
- package/esm/adapters/hono/_common.d.ts +10 -13
- package/esm/adapters/hono/_common.js +11 -20
- package/esm/adapters/hono/authorization-server.d.ts +16 -10
- package/esm/adapters/hono/authorization-server.js +13 -8
- package/esm/adapters/hono/bff/auth-request-store.d.ts +4 -0
- package/esm/adapters/hono/bff/auth-request-store.js +4 -0
- package/esm/adapters/hono/bff/bff.d.ts +29 -18
- package/esm/adapters/hono/bff/bff.js +15 -11
- package/esm/adapters/hono/bff/proxy.js +7 -55
- package/esm/adapters/hono/bff/session-store.d.ts +9 -9
- package/esm/adapters/hono/bff/session-store.js +3 -2
- package/esm/adapters/hono/bff/testing.d.ts +14 -3
- package/esm/adapters/hono/bff/testing.js +14 -3
- package/esm/adapters/hono/identity.d.ts +5 -3
- package/esm/adapters/hono/identity.js +1 -17
- package/esm/adapters/hono/resource-server.d.ts +3 -2
- package/esm/adapters/hono/resource-server.js +3 -2
- package/esm/client/_http.d.ts +4 -7
- package/esm/client/_http.js +4 -37
- package/esm/client/base.d.ts +3 -6
- package/esm/client/bff-client.d.ts +7 -6
- package/esm/client/bff-client.js +7 -6
- package/esm/client/browser-storage.d.ts +33 -34
- package/esm/client/browser-storage.js +47 -32
- package/esm/client/check.d.ts +10 -3
- package/esm/client/check.js +28 -8
- package/esm/client/direct-client.d.ts +49 -29
- package/esm/client/direct-client.js +59 -59
- package/esm/client/discovery-cache.d.ts +12 -5
- package/esm/client/discovery-cache.js +11 -4
- package/esm/client/events.d.ts +11 -3
- package/esm/client/events.js +1 -1
- package/esm/client/mod.d.ts +6 -4
- package/esm/client/mod.js +5 -3
- package/esm/client/storage.d.ts +53 -23
- package/esm/client/storage.js +43 -15
- package/esm/errors.d.ts +15 -12
- package/esm/errors.js +12 -8
- package/esm/identity/delivery.d.ts +7 -7
- package/esm/identity/delivery.js +1 -2
- package/esm/identity/errors.d.ts +10 -4
- package/esm/identity/errors.js +4 -1
- package/esm/identity/events.d.ts +21 -5
- package/esm/identity/events.js +4 -2
- package/esm/identity/external/_shared.d.ts +5 -7
- package/esm/identity/external/_shared.js +5 -7
- package/esm/identity/external/_token-exchange.d.ts +3 -6
- package/esm/identity/external/_token-exchange.js +2 -5
- package/esm/identity/external/apple-client-secret.d.ts +13 -8
- package/esm/identity/external/apple-client-secret.js +9 -6
- package/esm/identity/external/apple.d.ts +17 -10
- package/esm/identity/external/apple.js +12 -9
- package/esm/identity/external/discord.d.ts +5 -1
- package/esm/identity/external/errors.d.ts +6 -2
- package/esm/identity/external/errors.js +6 -2
- package/esm/identity/external/flow.d.ts +9 -6
- package/esm/identity/external/flow.js +6 -5
- package/esm/identity/external/github.d.ts +7 -3
- package/esm/identity/external/github.js +2 -2
- package/esm/identity/external/google.d.ts +5 -1
- package/esm/identity/external/oauth2.d.ts +13 -2
- package/esm/identity/external/oauth2.js +3 -3
- package/esm/identity/external/oidc.d.ts +21 -10
- package/esm/identity/external/oidc.js +11 -6
- package/esm/identity/external/provider.d.ts +4 -2
- package/esm/identity/identifier.d.ts +6 -5
- package/esm/identity/identifier.js +6 -5
- package/esm/identity/lockout.d.ts +4 -3
- package/esm/identity/lockout.js +4 -3
- package/esm/identity/mfa/mod.d.ts +2 -1
- package/esm/identity/mfa/mod.js +2 -1
- package/esm/identity/mfa/recovery-codes.d.ts +2 -1
- package/esm/identity/mfa/service.d.ts +29 -11
- package/esm/identity/mfa/service.js +35 -11
- package/esm/identity/mfa/totp.d.ts +4 -2
- package/esm/identity/mfa/totp.js +4 -2
- package/esm/identity/migration.d.ts +8 -7
- package/esm/identity/migration.js +9 -12
- package/esm/identity/mod.d.ts +2 -2
- package/esm/identity/mod.js +2 -2
- package/esm/identity/otp.d.ts +23 -16
- package/esm/identity/otp.js +13 -12
- package/esm/identity/password-policy.d.ts +3 -3
- package/esm/identity/password-policy.js +3 -3
- package/esm/identity/password.d.ts +23 -20
- package/esm/identity/password.js +15 -16
- package/esm/identity/rate-limit.d.ts +1 -7
- package/esm/identity/rate-limit.js +1 -2
- package/esm/identity/service.d.ts +76 -44
- package/esm/identity/service.js +86 -60
- package/esm/identity/session.d.ts +5 -4
- package/esm/identity/session.js +5 -4
- package/esm/identity/token-flow.d.ts +8 -8
- package/esm/identity/token-flow.js +5 -6
- package/esm/models/client.d.ts +7 -4
- package/esm/models/token.d.ts +2 -3
- package/esm/react/callback.d.ts +3 -3
- package/esm/react/callback.js +3 -3
- package/esm/react/components/class-names.d.ts +3 -6
- package/esm/react/components/class-names.js +3 -6
- package/esm/react/components/internal.d.ts +7 -17
- package/esm/react/components/internal.js +7 -17
- package/esm/react/components/mod.d.ts +7 -5
- package/esm/react/components/mod.js +7 -5
- package/esm/react/components/types.d.ts +8 -6
- package/esm/react/context.d.ts +3 -3
- package/esm/react/provider.d.ts +3 -2
- package/esm/react/provider.js +0 -13
- package/esm/react/require-auth.d.ts +0 -3
- package/esm/react/require-auth.js +0 -3
- package/esm/react/testing.d.ts +3 -2
- package/esm/react/testing.js +3 -3
- package/esm/react/use-oauth2.d.ts +1 -1
- package/esm/server/authorization-server.d.ts +81 -54
- package/esm/server/authorization-server.js +76 -100
- package/esm/server/grants/authorization-code.d.ts +7 -3
- package/esm/server/grants/authorization-code.js +6 -2
- package/esm/server/grants/device-authorization.d.ts +1 -1
- package/esm/server/grants/grant.d.ts +15 -8
- package/esm/server/grants/grant.js +26 -18
- package/esm/server/grants/password.d.ts +1 -1
- package/esm/server/grants/refresh-token.d.ts +6 -3
- package/esm/server/grants/refresh-token.js +4 -3
- package/esm/server/introspection-token-reader.d.ts +16 -7
- package/esm/server/introspection-token-reader.js +9 -3
- package/esm/server/jwks-token-reader.d.ts +3 -3
- package/esm/server/jwks-token-reader.js +3 -3
- package/esm/server/redirect-uri.d.ts +6 -9
- package/esm/server/redirect-uri.js +7 -34
- package/esm/server/resource-server.d.ts +5 -3
- package/esm/server/resource-server.js +5 -3
- package/esm/server/services/client.d.ts +5 -1
- package/esm/server/services/device-authorization.d.ts +6 -6
- package/esm/server/services/token.d.ts +15 -8
- package/esm/server/services/token.js +1 -1
- package/esm/server/signing-keys.d.ts +24 -9
- package/esm/server/signing-keys.js +20 -9
- package/esm/testing/contract/mod.d.ts +3 -3
- package/esm/testing/contract/mod.js +3 -3
- package/esm/testing/mod.d.ts +4 -2
- package/esm/testing/mod.js +4 -2
- package/esm/testing/services.d.ts +1 -1
- package/esm/testing/services.js +1 -1
- package/esm/testing/tenant.d.ts +13 -4
- package/esm/utils/_buffer.d.ts +3 -2
- package/esm/utils/_buffer.js +3 -2
- package/esm/utils/_timing-safe.d.ts +8 -6
- package/esm/utils/_timing-safe.js +8 -6
- package/esm/utils/basic-auth.d.ts +5 -12
- package/esm/utils/basic-auth.js +5 -12
- package/esm/utils/crypto.d.ts +2 -1
- package/esm/utils/crypto.js +2 -1
- package/esm/utils/text.d.ts +2 -9
- package/esm/utils/text.js +2 -9
- package/esm/utils/url.d.ts +2 -2
- package/esm/utils/url.js +0 -2
- package/package.json +1 -1
|
@@ -67,7 +67,8 @@ async function importPrivateKey(providerId, pem) {
|
|
|
67
67
|
*
|
|
68
68
|
* @throws {ExternalAuthError} `configuration` naming the offending input when
|
|
69
69
|
* the team id / key id / client id is empty, the `.p8` is not a valid P-256
|
|
70
|
-
* key, or the requested TTL
|
|
70
|
+
* key, or the requested TTL is not a finite number between 1 and Apple's
|
|
71
|
+
* cap (`NaN` and `Infinity` are rejected, never signed into `exp`).
|
|
71
72
|
*
|
|
72
73
|
* @example
|
|
73
74
|
* ```ts
|
|
@@ -86,7 +87,8 @@ export async function generateAppleClientSecret(options) {
|
|
|
86
87
|
const clientId = requireNonEmpty(providerId, options.clientId, "client id (Services ID)");
|
|
87
88
|
const ttl = options.expiresInSeconds ??
|
|
88
89
|
DEFAULT_APPLE_CLIENT_SECRET_TTL_SECONDS;
|
|
89
|
-
if (ttl
|
|
90
|
+
if (!Number.isFinite(ttl) || ttl <= 0 ||
|
|
91
|
+
ttl > APPLE_CLIENT_SECRET_MAX_TTL_SECONDS) {
|
|
90
92
|
throw configError(providerId, `Apple client secret TTL must be between 1 and ` +
|
|
91
93
|
`${APPLE_CLIENT_SECRET_MAX_TTL_SECONDS} seconds (6 months); ` +
|
|
92
94
|
`got ${ttl}.`);
|
|
@@ -111,10 +113,11 @@ function encodeSegment(value) {
|
|
|
111
113
|
/**
|
|
112
114
|
* Build a self-renewing Apple client-secret source. It signs a secret with
|
|
113
115
|
* {@link generateAppleClientSecret} on first call, caches it, and re-signs
|
|
114
|
-
* automatically once the cached secret is within `renewBeforeSeconds`
|
|
115
|
-
* — so a long-lived {@link appleProvider} never presents
|
|
116
|
-
* The key material is validated on the first call,
|
|
117
|
-
* as a `configuration`
|
|
116
|
+
* automatically once the cached secret is within `renewBeforeSeconds` (default
|
|
117
|
+
* one hour) of expiry — so a long-lived {@link appleProvider} never presents
|
|
118
|
+
* an expired assertion. The key material is validated on the first call, not
|
|
119
|
+
* at construction, surfacing a misconfiguration as a `configuration`
|
|
120
|
+
* {@link ExternalAuthError}; a failed call caches nothing.
|
|
118
121
|
*/
|
|
119
122
|
export function createAppleClientSecretFactory(options) {
|
|
120
123
|
const ttl = options.expiresInSeconds ??
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* {@link oidcProvider}: its client secret is a signed ES256 JWT rather than a
|
|
4
4
|
* static string (see {@link generateAppleClientSecret}), it authenticates the
|
|
5
5
|
* client in the token body (`client_secret_post`), and it requires
|
|
6
|
-
* `response_mode=form_post`
|
|
7
|
-
* dedicated {@link ExternalProvider} that exchanges the code, then
|
|
8
|
-
* `id_token` signature against Apple's JWKS.
|
|
6
|
+
* `response_mode=form_post` when the `name` or `email` scope is requested.
|
|
7
|
+
* This is a dedicated {@link ExternalProvider} that exchanges the code, then
|
|
8
|
+
* verifies the `id_token` signature against Apple's JWKS.
|
|
9
9
|
*
|
|
10
10
|
* The user's name is returned by Apple **only** on the first authorization, in
|
|
11
11
|
* the `form_post` body's `user` field — never in the `id_token`. This connector
|
|
@@ -33,20 +33,27 @@ export interface AppleProviderOptions extends Pick<AppleClientSecretOptions, "te
|
|
|
33
33
|
* `.p8` key material. Defaults to a cached factory over the key material.
|
|
34
34
|
*/
|
|
35
35
|
clientSecret?: AppleClientSecretFactory;
|
|
36
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Fetch implementation for all provider traffic. Defaults to
|
|
38
|
+
* `globalThis.fetch`. A wrapper must pass `init` through intact: its
|
|
39
|
+
* `redirect` and `signal` carry the connector's redirect refusal and timeout.
|
|
40
|
+
*/
|
|
37
41
|
fetch?: typeof fetch;
|
|
38
42
|
}
|
|
39
43
|
/**
|
|
40
44
|
* Creates a Sign in with Apple connector with id `"apple"`.
|
|
41
45
|
*
|
|
42
|
-
* The flow requests `response_mode=form_post`
|
|
43
|
-
* or `email`
|
|
44
|
-
* with
|
|
46
|
+
* The flow requests `response_mode=form_post` whenever any scope is requested
|
|
47
|
+
* (Apple requires it for `name` or `email`), exchanges the code at Apple's
|
|
48
|
+
* token endpoint with the client-secret JWT from the factory (by default
|
|
49
|
+
* cached and re-signed an hour before it expires), and verifies the returned
|
|
45
50
|
* `id_token`'s RS256 signature against Apple's published keys before trusting
|
|
46
51
|
* its claims (`iss`, `aud`, `azp`, `exp`, and the `nonce` when one was sent,
|
|
47
|
-
* checked
|
|
48
|
-
*
|
|
49
|
-
*
|
|
52
|
+
* checked as {@link oidcProvider} checks them, with the default `azp` policy).
|
|
53
|
+
* `emailVerified` is `true` when Apple's `email_verified` claim is `true` or
|
|
54
|
+
* the string `"true"`; private-relay addresses
|
|
55
|
+
* (`@privaterelay.appleid.com`) are returned as-is. The raw `id_token`
|
|
56
|
+
* claims — including
|
|
50
57
|
* `is_private_email` — stay on {@link ExternalProfile.raw}.
|
|
51
58
|
*
|
|
52
59
|
* @example
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* {@link oidcProvider}: its client secret is a signed ES256 JWT rather than a
|
|
4
4
|
* static string (see {@link generateAppleClientSecret}), it authenticates the
|
|
5
5
|
* client in the token body (`client_secret_post`), and it requires
|
|
6
|
-
* `response_mode=form_post`
|
|
7
|
-
* dedicated {@link ExternalProvider} that exchanges the code, then
|
|
8
|
-
* `id_token` signature against Apple's JWKS.
|
|
6
|
+
* `response_mode=form_post` when the `name` or `email` scope is requested.
|
|
7
|
+
* This is a dedicated {@link ExternalProvider} that exchanges the code, then
|
|
8
|
+
* verifies the `id_token` signature against Apple's JWKS.
|
|
9
9
|
*
|
|
10
10
|
* The user's name is returned by Apple **only** on the first authorization, in
|
|
11
11
|
* the `form_post` body's `user` field — never in the `id_token`. This connector
|
|
@@ -33,14 +33,17 @@ const SIGNATURE_NOT_VERIFIED = `Apple id_token signature did not verify against
|
|
|
33
33
|
/**
|
|
34
34
|
* Creates a Sign in with Apple connector with id `"apple"`.
|
|
35
35
|
*
|
|
36
|
-
* The flow requests `response_mode=form_post`
|
|
37
|
-
* or `email`
|
|
38
|
-
* with
|
|
36
|
+
* The flow requests `response_mode=form_post` whenever any scope is requested
|
|
37
|
+
* (Apple requires it for `name` or `email`), exchanges the code at Apple's
|
|
38
|
+
* token endpoint with the client-secret JWT from the factory (by default
|
|
39
|
+
* cached and re-signed an hour before it expires), and verifies the returned
|
|
39
40
|
* `id_token`'s RS256 signature against Apple's published keys before trusting
|
|
40
41
|
* its claims (`iss`, `aud`, `azp`, `exp`, and the `nonce` when one was sent,
|
|
41
|
-
* checked
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* checked as {@link oidcProvider} checks them, with the default `azp` policy).
|
|
43
|
+
* `emailVerified` is `true` when Apple's `email_verified` claim is `true` or
|
|
44
|
+
* the string `"true"`; private-relay addresses
|
|
45
|
+
* (`@privaterelay.appleid.com`) are returned as-is. The raw `id_token`
|
|
46
|
+
* claims — including
|
|
44
47
|
* `is_private_email` — stay on {@link ExternalProfile.raw}.
|
|
45
48
|
*
|
|
46
49
|
* @example
|
|
@@ -12,7 +12,11 @@ export interface DiscordProviderOptions {
|
|
|
12
12
|
clientSecret: string;
|
|
13
13
|
/** Scopes requested by default. Defaults to `["identify", "email"]`. */
|
|
14
14
|
scopes?: string[];
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Fetch implementation for all provider traffic. Defaults to
|
|
17
|
+
* `globalThis.fetch`. A wrapper must pass `init` through intact: its
|
|
18
|
+
* `redirect` and `signal` carry the connector's redirect refusal and timeout.
|
|
19
|
+
*/
|
|
16
20
|
fetch?: typeof fetch;
|
|
17
21
|
}
|
|
18
22
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The error contract for external-provider (social login) flows.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Failures the external connectors detect are thrown as an
|
|
5
5
|
* {@link ExternalAuthError} whose message names the provider, what failed, and
|
|
6
6
|
* the likely fix — misconfiguration is meant to be diagnosable from the
|
|
7
7
|
* message alone. The {@link ExternalAuthErrorCode} tells app code how to
|
|
@@ -44,5 +44,9 @@ export declare class ExternalAuthError extends Error {
|
|
|
44
44
|
/** Builds the error; `message` is prefixed with `[provider]` automatically. */
|
|
45
45
|
constructor(provider: string, code: ExternalAuthErrorCode, message: string, options?: ErrorOptions);
|
|
46
46
|
}
|
|
47
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Type guard for {@link ExternalAuthError}. Uses `instanceof`, so an error from
|
|
49
|
+
* another realm or a second copy of the package fails it — check
|
|
50
|
+
* `name === "ExternalAuthError"` there.
|
|
51
|
+
*/
|
|
48
52
|
export declare function isExternalAuthError(value: unknown): value is ExternalAuthError;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The error contract for external-provider (social login) flows.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Failures the external connectors detect are thrown as an
|
|
5
5
|
* {@link ExternalAuthError} whose message names the provider, what failed, and
|
|
6
6
|
* the likely fix — misconfiguration is meant to be diagnosable from the
|
|
7
7
|
* message alone. The {@link ExternalAuthErrorCode} tells app code how to
|
|
@@ -30,7 +30,11 @@ export class ExternalAuthError extends Error {
|
|
|
30
30
|
this.code = code;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Type guard for {@link ExternalAuthError}. Uses `instanceof`, so an error from
|
|
35
|
+
* another realm or a second copy of the package fails it — check
|
|
36
|
+
* `name === "ExternalAuthError"` there.
|
|
37
|
+
*/
|
|
34
38
|
export function isExternalAuthError(value) {
|
|
35
39
|
return value instanceof ExternalAuthError;
|
|
36
40
|
}
|
|
@@ -36,7 +36,9 @@ export interface ExternalAuthStartResult {
|
|
|
36
36
|
url: string;
|
|
37
37
|
/**
|
|
38
38
|
* Per-attempt state to persist in a cookie/session and hand back to
|
|
39
|
-
* {@link ExternalAuthFlow.finish}. JSON-serializable
|
|
39
|
+
* {@link ExternalAuthFlow.finish}. JSON-serializable and contains no tokens,
|
|
40
|
+
* but the PKCE verifier and nonce are secrets — keep it sealed or
|
|
41
|
+
* server-side.
|
|
40
42
|
*/
|
|
41
43
|
transient: ExternalAuthTransient;
|
|
42
44
|
}
|
|
@@ -94,11 +96,12 @@ export declare class ExternalAuthFlow {
|
|
|
94
96
|
*/
|
|
95
97
|
start(input: ExternalAuthStartInput): Promise<ExternalAuthStartResult>;
|
|
96
98
|
/**
|
|
97
|
-
* Completes a sign-in attempt from the provider callback:
|
|
98
|
-
* transient
|
|
99
|
-
* `state` with a timing-safe compare,
|
|
100
|
-
*
|
|
101
|
-
*
|
|
99
|
+
* Completes a sign-in attempt from the provider callback: checks the
|
|
100
|
+
* transient belongs to this flow's provider, enforces the max transient
|
|
101
|
+
* age, verifies `state` with a timing-safe compare, and only then surfaces a
|
|
102
|
+
* provider `error` callback param; finally the provider exchanges the code
|
|
103
|
+
* and (for nonce-using providers) validates the id_token nonce, and the
|
|
104
|
+
* normalized profile is returned.
|
|
102
105
|
*
|
|
103
106
|
* @throws {ExternalAuthError} with a code describing the failure — see
|
|
104
107
|
* {@link ExternalAuthErrorCode} for how app code should respond to each.
|
|
@@ -88,11 +88,12 @@ export class ExternalAuthFlow {
|
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* Completes a sign-in attempt from the provider callback:
|
|
92
|
-
* transient
|
|
93
|
-
* `state` with a timing-safe compare,
|
|
94
|
-
*
|
|
95
|
-
*
|
|
91
|
+
* Completes a sign-in attempt from the provider callback: checks the
|
|
92
|
+
* transient belongs to this flow's provider, enforces the max transient
|
|
93
|
+
* age, verifies `state` with a timing-safe compare, and only then surfaces a
|
|
94
|
+
* provider `error` callback param; finally the provider exchanges the code
|
|
95
|
+
* and (for nonce-using providers) validates the id_token nonce, and the
|
|
96
|
+
* normalized profile is returned.
|
|
96
97
|
*
|
|
97
98
|
* @throws {ExternalAuthError} with a code describing the failure — see
|
|
98
99
|
* {@link ExternalAuthErrorCode} for how app code should respond to each.
|
|
@@ -13,14 +13,18 @@ export interface GithubProviderOptions {
|
|
|
13
13
|
clientSecret: string;
|
|
14
14
|
/** Scopes requested by default. Defaults to `["read:user", "user:email"]`. */
|
|
15
15
|
scopes?: string[];
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Fetch implementation for all provider traffic. Defaults to
|
|
18
|
+
* `globalThis.fetch`. A wrapper must pass `init` through intact: its
|
|
19
|
+
* `redirect` and `signal` carry the connector's redirect refusal and timeout.
|
|
20
|
+
*/
|
|
17
21
|
fetch?: typeof fetch;
|
|
18
22
|
}
|
|
19
23
|
/**
|
|
20
24
|
* Creates a GitHub connector with id `"github"`.
|
|
21
25
|
*
|
|
22
|
-
* GitHub speaks plain OAuth2 (no discovery, no id_token,
|
|
23
|
-
*
|
|
26
|
+
* GitHub speaks plain OAuth2 (no discovery, no id_token), so the connector
|
|
27
|
+
* uses GitHub's fixed endpoints, sends no PKCE challenge, and builds the
|
|
24
28
|
* profile from `GET /user` plus `GET /user/emails`: the primary email is used
|
|
25
29
|
* (falling back to the first verified, then the first listed), and
|
|
26
30
|
* `emailVerified` reflects GitHub's flag for whichever email was chosen. When
|
|
@@ -14,8 +14,8 @@ const GITHUB_EMAILS_URL = "https://api.github.com/user/emails";
|
|
|
14
14
|
/**
|
|
15
15
|
* Creates a GitHub connector with id `"github"`.
|
|
16
16
|
*
|
|
17
|
-
* GitHub speaks plain OAuth2 (no discovery, no id_token,
|
|
18
|
-
*
|
|
17
|
+
* GitHub speaks plain OAuth2 (no discovery, no id_token), so the connector
|
|
18
|
+
* uses GitHub's fixed endpoints, sends no PKCE challenge, and builds the
|
|
19
19
|
* profile from `GET /user` plus `GET /user/emails`: the primary email is used
|
|
20
20
|
* (falling back to the first verified, then the first listed), and
|
|
21
21
|
* `emailVerified` reflects GitHub's flag for whichever email was chosen. When
|
|
@@ -21,7 +21,11 @@ export interface GoogleProviderOptions {
|
|
|
21
21
|
* `aud` is this one. See {@link AzpPolicy}.
|
|
22
22
|
*/
|
|
23
23
|
azp?: AzpPolicy;
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Fetch implementation for all provider traffic. Defaults to
|
|
26
|
+
* `globalThis.fetch`. A wrapper must pass `init` through intact: its
|
|
27
|
+
* `redirect` and `signal` carry the connector's redirect refusal and timeout.
|
|
28
|
+
*/
|
|
25
29
|
fetch?: typeof fetch;
|
|
26
30
|
/**
|
|
27
31
|
* Cache Google's discovery document is read from and written to, shared with
|
|
@@ -76,9 +76,20 @@ export interface OAuth2ProviderOptions {
|
|
|
76
76
|
scopeSeparator?: string;
|
|
77
77
|
/** Whether to run PKCE (S256). Defaults to `false`. */
|
|
78
78
|
usesPkce?: boolean;
|
|
79
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* Extra fixed query params to add to the authorize URL (an `audience`, a
|
|
81
|
+
* tenant hint). The protocol params always win: `response_type`,
|
|
82
|
+
* `client_id`, `redirect_uri`, `scope`, `state`, the PKCE `code_challenge`
|
|
83
|
+
* pair, and a `prompt` the flow passes are written after these, so a
|
|
84
|
+
* same-named key here cannot replace the connector's CSRF `state` or
|
|
85
|
+
* downgrade PKCE.
|
|
86
|
+
*/
|
|
80
87
|
authorizationParams?: Record<string, string>;
|
|
81
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Fetch implementation for all provider traffic. Defaults to
|
|
90
|
+
* `globalThis.fetch`. A wrapper must pass `init` through intact: its
|
|
91
|
+
* `redirect` and `signal` carry the connector's redirect refusal and timeout.
|
|
92
|
+
*/
|
|
82
93
|
fetch?: typeof fetch;
|
|
83
94
|
}
|
|
84
95
|
/**
|
|
@@ -100,14 +100,14 @@ export function oauth2Provider(options) {
|
|
|
100
100
|
usesNonce: false,
|
|
101
101
|
async buildAuthorizationUrl(input) {
|
|
102
102
|
const url = new URL(options.authorizationEndpoint);
|
|
103
|
+
for (const [key, value] of Object.entries(options.authorizationParams ?? {})) {
|
|
104
|
+
url.searchParams.set(key, value);
|
|
105
|
+
}
|
|
103
106
|
url.searchParams.set("response_type", "code");
|
|
104
107
|
url.searchParams.set("client_id", options.clientId);
|
|
105
108
|
url.searchParams.set("redirect_uri", input.redirectUri);
|
|
106
109
|
url.searchParams.set("scope", input.scopes.join(scopeSeparator));
|
|
107
110
|
url.searchParams.set("state", input.state);
|
|
108
|
-
for (const [key, value] of Object.entries(options.authorizationParams ?? {})) {
|
|
109
|
-
url.searchParams.set(key, value);
|
|
110
|
-
}
|
|
111
111
|
if (input.codeVerifier) {
|
|
112
112
|
url.searchParams.set("code_challenge", await generateCodeChallenge(input.codeVerifier));
|
|
113
113
|
url.searchParams.set("code_challenge_method", "S256");
|
|
@@ -14,9 +14,11 @@ export interface OidcProviderOptions {
|
|
|
14
14
|
/** Human-readable name for buttons and logs. Defaults to `"OpenID Connect"`. */
|
|
15
15
|
displayName?: string;
|
|
16
16
|
/**
|
|
17
|
-
* The provider's issuer URL. Endpoints are resolved from
|
|
18
|
-
* `${issuer}/.well-known/
|
|
19
|
-
*
|
|
17
|
+
* The provider's issuer URL. Endpoints are resolved on first use from
|
|
18
|
+
* `${issuer}/.well-known/oauth-authorization-server` (RFC 8414), falling
|
|
19
|
+
* back to `${issuer}/.well-known/openid-configuration`, and cached. Must be
|
|
20
|
+
* `https` (plain `http` only for `localhost`, `127.0.0.1`, or `[::1]`), as
|
|
21
|
+
* must every endpoint the discovery document advertises.
|
|
20
22
|
*/
|
|
21
23
|
issuer: string;
|
|
22
24
|
/** The client id registered with the provider. */
|
|
@@ -36,7 +38,11 @@ export interface OidcProviderOptions {
|
|
|
36
38
|
* see {@link AzpPolicy}.
|
|
37
39
|
*/
|
|
38
40
|
azp?: AzpPolicy;
|
|
39
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Fetch implementation for all provider traffic. Defaults to
|
|
43
|
+
* `globalThis.fetch`. A wrapper must pass `init` through intact: its
|
|
44
|
+
* `redirect` and `signal` carry the connector's redirect refusal and timeout.
|
|
45
|
+
*/
|
|
40
46
|
fetch?: typeof fetch;
|
|
41
47
|
/**
|
|
42
48
|
* Cache the discovery document is read from and written to, shared with any
|
|
@@ -53,18 +59,23 @@ export interface OidcProviderOptions {
|
|
|
53
59
|
* The exchange reuses the package's `DirectClient` (discovery caching with
|
|
54
60
|
* both well-known paths, Basic client auth, RFC 6749 error mapping) with PKCE
|
|
55
61
|
* always on. The profile is built from the id_token claims, merged with the
|
|
56
|
-
* UserInfo response when the provider advertises a `userinfo_endpoint
|
|
57
|
-
* UserInfo
|
|
58
|
-
*
|
|
62
|
+
* UserInfo response when the provider advertises a `userinfo_endpoint`;
|
|
63
|
+
* UserInfo values override id_token claims of the same name (including
|
|
64
|
+
* `email_verified`). A UserInfo response whose `sub` differs from the
|
|
65
|
+
* id_token `sub` (OIDC Core §5.3.2) is discarded, and a UserInfo failure is
|
|
66
|
+
* non-fatal — the profile then comes from the id_token claims alone.
|
|
59
67
|
*
|
|
60
68
|
* **id_token validation:** the `iss`, `aud`, `azp` (when the token has multiple
|
|
61
69
|
* audiences or an `azp` claim, unless `azp: "ignore"` is configured), `exp`,
|
|
62
70
|
* and `nonce` claims are checked per OpenID Connect Core §3.1.3.7. The
|
|
63
71
|
* signature is deliberately **not** verified: the token arrives directly from
|
|
64
72
|
* the token endpoint over TLS, where §3.1.3.7 permits relying on TLS server
|
|
65
|
-
* validation instead of signature checks (rule 6).
|
|
66
|
-
*
|
|
67
|
-
* id_tokens obtained out-of-band.
|
|
73
|
+
* validation instead of signature checks (rule 6). Only the claim checks
|
|
74
|
+
* listed above are made — not `iat`, `acr`, or `auth_time`. Do not feed this
|
|
75
|
+
* connector id_tokens obtained out-of-band.
|
|
76
|
+
*
|
|
77
|
+
* @throws {ExternalAuthError} `configuration` when `issuer` is not an absolute
|
|
78
|
+
* `https` URL (see {@link OidcProviderOptions.issuer}).
|
|
68
79
|
*
|
|
69
80
|
* @example
|
|
70
81
|
* ```ts
|
|
@@ -16,18 +16,23 @@ import { ExternalAuthError } from "./errors.js";
|
|
|
16
16
|
* The exchange reuses the package's `DirectClient` (discovery caching with
|
|
17
17
|
* both well-known paths, Basic client auth, RFC 6749 error mapping) with PKCE
|
|
18
18
|
* always on. The profile is built from the id_token claims, merged with the
|
|
19
|
-
* UserInfo response when the provider advertises a `userinfo_endpoint
|
|
20
|
-
* UserInfo
|
|
21
|
-
*
|
|
19
|
+
* UserInfo response when the provider advertises a `userinfo_endpoint`;
|
|
20
|
+
* UserInfo values override id_token claims of the same name (including
|
|
21
|
+
* `email_verified`). A UserInfo response whose `sub` differs from the
|
|
22
|
+
* id_token `sub` (OIDC Core §5.3.2) is discarded, and a UserInfo failure is
|
|
23
|
+
* non-fatal — the profile then comes from the id_token claims alone.
|
|
22
24
|
*
|
|
23
25
|
* **id_token validation:** the `iss`, `aud`, `azp` (when the token has multiple
|
|
24
26
|
* audiences or an `azp` claim, unless `azp: "ignore"` is configured), `exp`,
|
|
25
27
|
* and `nonce` claims are checked per OpenID Connect Core §3.1.3.7. The
|
|
26
28
|
* signature is deliberately **not** verified: the token arrives directly from
|
|
27
29
|
* the token endpoint over TLS, where §3.1.3.7 permits relying on TLS server
|
|
28
|
-
* validation instead of signature checks (rule 6).
|
|
29
|
-
*
|
|
30
|
-
* id_tokens obtained out-of-band.
|
|
30
|
+
* validation instead of signature checks (rule 6). Only the claim checks
|
|
31
|
+
* listed above are made — not `iat`, `acr`, or `auth_time`. Do not feed this
|
|
32
|
+
* connector id_tokens obtained out-of-band.
|
|
33
|
+
*
|
|
34
|
+
* @throws {ExternalAuthError} `configuration` when `issuer` is not an absolute
|
|
35
|
+
* `https` URL (see {@link OidcProviderOptions.issuer}).
|
|
31
36
|
*
|
|
32
37
|
* @example
|
|
33
38
|
* ```ts
|
|
@@ -27,7 +27,8 @@ export interface ExternalProfile {
|
|
|
27
27
|
email?: string;
|
|
28
28
|
/**
|
|
29
29
|
* `true` only when the provider positively asserted the email is verified
|
|
30
|
-
* (OIDC `email_verified: true`, GitHub verified
|
|
30
|
+
* (OIDC `email_verified: true`, GitHub's `verified` flag on the email the
|
|
31
|
+
* connector chose). Defaults to
|
|
31
32
|
* `false` — treat unverified emails as attacker-controllable input and do
|
|
32
33
|
* not auto-link accounts on them.
|
|
33
34
|
*/
|
|
@@ -100,7 +101,8 @@ export interface ExternalProfileInput {
|
|
|
100
101
|
* verification, transient expiry, callback error surfacing); the provider
|
|
101
102
|
* owns the wire protocol (authorize URL shape, code exchange, id_token nonce
|
|
102
103
|
* validation, profile normalization). Implement this only for providers the
|
|
103
|
-
* built-in
|
|
104
|
+
* built-in connectors, including the generic {@link oidcProvider} and
|
|
105
|
+
* {@link oauth2Provider}, cannot cover.
|
|
104
106
|
*/
|
|
105
107
|
export interface ExternalProvider {
|
|
106
108
|
/** Stable id used in {@link ExternalProfile.provider} and error messages. */
|
|
@@ -13,8 +13,8 @@ export type IdentifierKind = "email" | "username" | "phone";
|
|
|
13
13
|
* Classify a sign-in identifier as an email, phone number, or username.
|
|
14
14
|
*
|
|
15
15
|
* Heuristic: an `@` → `email`; otherwise a value made only of phone characters
|
|
16
|
-
* (`+`, digits, spaces, `-`, `()`, `.`) with at least
|
|
17
|
-
* everything else → `username`. The value is trimmed first.
|
|
16
|
+
* (an optional leading `+`, then digits, spaces, `-`, `()`, `.`) with at least
|
|
17
|
+
* 7 digits → `phone`; everything else → `username`. The value is trimmed first.
|
|
18
18
|
*/
|
|
19
19
|
export declare function classifyIdentifier(identifier: string): IdentifierKind;
|
|
20
20
|
/** Per-kind user lookups supplied by the app. Omit a kind to disallow it. */
|
|
@@ -28,9 +28,10 @@ export interface IdentifierLookups<User> {
|
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Build a resolver that classifies an identifier ({@link classifyIdentifier})
|
|
31
|
-
* and dispatches to the matching app-supplied lookup. Returns
|
|
32
|
-
* no lookup is configured for the classified kind, or the
|
|
33
|
-
* callers should treat both as "invalid credentials"
|
|
31
|
+
* and dispatches the trimmed value to the matching app-supplied lookup. Returns
|
|
32
|
+
* `undefined` when no lookup is configured for the classified kind, or the
|
|
33
|
+
* user isn't found — callers should treat both as "invalid credentials"
|
|
34
|
+
* without revealing which.
|
|
34
35
|
*
|
|
35
36
|
* @example
|
|
36
37
|
* ```ts
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* Classify a sign-in identifier as an email, phone number, or username.
|
|
12
12
|
*
|
|
13
13
|
* Heuristic: an `@` → `email`; otherwise a value made only of phone characters
|
|
14
|
-
* (`+`, digits, spaces, `-`, `()`, `.`) with at least
|
|
15
|
-
* everything else → `username`. The value is trimmed first.
|
|
14
|
+
* (an optional leading `+`, then digits, spaces, `-`, `()`, `.`) with at least
|
|
15
|
+
* 7 digits → `phone`; everything else → `username`. The value is trimmed first.
|
|
16
16
|
*/
|
|
17
17
|
export function classifyIdentifier(identifier) {
|
|
18
18
|
const value = identifier.trim();
|
|
@@ -25,9 +25,10 @@ export function classifyIdentifier(identifier) {
|
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Build a resolver that classifies an identifier ({@link classifyIdentifier})
|
|
28
|
-
* and dispatches to the matching app-supplied lookup. Returns
|
|
29
|
-
* no lookup is configured for the classified kind, or the
|
|
30
|
-
* callers should treat both as "invalid credentials"
|
|
28
|
+
* and dispatches the trimmed value to the matching app-supplied lookup. Returns
|
|
29
|
+
* `undefined` when no lookup is configured for the classified kind, or the
|
|
30
|
+
* user isn't found — callers should treat both as "invalid credentials"
|
|
31
|
+
* without revealing which.
|
|
31
32
|
*
|
|
32
33
|
* @example
|
|
33
34
|
* ```ts
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* database to share state across instances — e.g. columns on your users table,
|
|
10
10
|
* which also gives admins visibility).
|
|
11
11
|
*
|
|
12
|
-
* `AccountLockout`
|
|
12
|
+
* `AccountLockout` only records state: it never decides whether to block.
|
|
13
13
|
* `IdentityService` consults it and applies its `protectionMode`
|
|
14
14
|
* (enforce vs log-only), so adopters can observe lockout behavior before
|
|
15
15
|
* turning it on.
|
|
@@ -124,8 +124,9 @@ export interface AccountLockoutLike {
|
|
|
124
124
|
/**
|
|
125
125
|
* Consecutive-failure lockout with safe defaults (10 failures → 15-minute
|
|
126
126
|
* lock) — the default {@link AccountLockoutLike}. Wire into
|
|
127
|
-
* {@link IdentityService} via its `lockout` option
|
|
128
|
-
*
|
|
127
|
+
* {@link IdentityService} via its `lockout` option, which resets it on a
|
|
128
|
+
* successful sign-in, password reset, or {@link IdentityService.unlockAccount};
|
|
129
|
+
* call {@link reset} yourself only from an unlock flow of your own.
|
|
129
130
|
*/
|
|
130
131
|
export declare class AccountLockout implements AccountLockoutLike {
|
|
131
132
|
#private;
|
package/esm/identity/lockout.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* database to share state across instances — e.g. columns on your users table,
|
|
10
10
|
* which also gives admins visibility).
|
|
11
11
|
*
|
|
12
|
-
* `AccountLockout`
|
|
12
|
+
* `AccountLockout` only records state: it never decides whether to block.
|
|
13
13
|
* `IdentityService` consults it and applies its `protectionMode`
|
|
14
14
|
* (enforce vs log-only), so adopters can observe lockout behavior before
|
|
15
15
|
* turning it on.
|
|
@@ -21,8 +21,9 @@ const DEFAULT_LOCK_DURATION_MS = 15 * 60 * 1000;
|
|
|
21
21
|
/**
|
|
22
22
|
* Consecutive-failure lockout with safe defaults (10 failures → 15-minute
|
|
23
23
|
* lock) — the default {@link AccountLockoutLike}. Wire into
|
|
24
|
-
* {@link IdentityService} via its `lockout` option
|
|
25
|
-
*
|
|
24
|
+
* {@link IdentityService} via its `lockout` option, which resets it on a
|
|
25
|
+
* successful sign-in, password reset, or {@link IdentityService.unlockAccount};
|
|
26
|
+
* call {@link reset} yourself only from an unlock flow of your own.
|
|
26
27
|
*/
|
|
27
28
|
export class AccountLockout {
|
|
28
29
|
#maxAttempts;
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* {@link verifyRecoveryCode} — readable single-use fallback codes, hashed at
|
|
13
13
|
* rest.
|
|
14
14
|
* - {@link MfaService} over an app-implemented {@link MfaStore} — enrollment
|
|
15
|
-
* (pending → confirmed), verification with
|
|
15
|
+
* (pending → confirmed), verification with replay rejection and
|
|
16
|
+
* recovery-code burning through the store's atomic methods,
|
|
16
17
|
* regeneration, disable, plus the layer's usual seams (`rateLimiter`,
|
|
17
18
|
* `onEvent` for `mfa.*` audit events). {@link MemoryMfaStore} ships for
|
|
18
19
|
* dev/tests.
|
package/esm/identity/mfa/mod.js
CHANGED
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
* {@link verifyRecoveryCode} — readable single-use fallback codes, hashed at
|
|
13
13
|
* rest.
|
|
14
14
|
* - {@link MfaService} over an app-implemented {@link MfaStore} — enrollment
|
|
15
|
-
* (pending → confirmed), verification with
|
|
15
|
+
* (pending → confirmed), verification with replay rejection and
|
|
16
|
+
* recovery-code burning through the store's atomic methods,
|
|
16
17
|
* regeneration, disable, plus the layer's usual seams (`rateLimiter`,
|
|
17
18
|
* `onEvent` for `mfa.*` audit events). {@link MemoryMfaStore} ships for
|
|
18
19
|
* dev/tests.
|
|
@@ -83,7 +83,8 @@ export type RecoveryCodeVerification = {
|
|
|
83
83
|
* The index of the matching hash. Burn it with an atomic delete-by-value
|
|
84
84
|
* (a conditional `DELETE` on the hash, never read-filter-write) so the
|
|
85
85
|
* code cannot be redeemed again — the library does not do this for you at
|
|
86
|
-
* this level ({@link MfaService} does,
|
|
86
|
+
* this level ({@link MfaService} does, through its store's
|
|
87
|
+
* `consumeRecoveryHash`).
|
|
87
88
|
*/
|
|
88
89
|
index: number;
|
|
89
90
|
};
|