@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
package/esm/client/mod.d.ts
CHANGED
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
* care — the React adapter's `<OAuth2Provider>`, a shared UI helper — should
|
|
16
16
|
* accept.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* A {@link DirectClient} keeps its token bundle and refresh token in memory
|
|
19
|
+
* by default, so they are lost on reload; a browser app that must survive one
|
|
20
|
+
* passes {@link IndexedDBRefreshTokenStorage}. The pending auth request
|
|
21
|
+
* defaults to {@link SessionStorageAuthRequestStorage} in a browser document
|
|
22
|
+
* and to memory everywhere else.
|
|
21
23
|
*
|
|
22
24
|
* @module
|
|
23
25
|
*/
|
|
@@ -27,7 +29,7 @@ export { BffClient, type BffClientOptions, type BffEndpoints, } from "./bff-clie
|
|
|
27
29
|
export { type AuthorizationCallbackResult, type AuthorizationRedirect, type AuthorizationServerEndpoints, type DeviceAuthorizationOptions, DirectClient, type DirectClientOptions, type ExchangeOptions, type ExchangeResult, type IntrospectionResult, type LoginOptions, type PollDeviceTokenOptions, } from "./direct-client.js";
|
|
28
30
|
export { DEFAULT_DISCOVERY_CACHE_MAX_ENTRIES, DEFAULT_DISCOVERY_TTL_MS, type DiscoveryCache, type DiscoveryCacheEntry, MemoryDiscoveryCache, type MemoryDiscoveryCacheOptions, } from "./discovery-cache.js";
|
|
29
31
|
export { type AuthenticatedEvent, type ErrorEvent, type LoggedOutEvent, type OAuth2ClientEvent, type OAuth2ClientEventListener, type TokenRefreshedEvent, } from "./events.js";
|
|
30
|
-
export { type AuthRequestRecord, type AuthRequestStorage, MemoryAuthRequestStorage, MemoryRefreshTokenStorage, MemoryTokenStorage, type RefreshTokenStorage, type TokenBundle, type TokenStorage, } from "./storage.js";
|
|
32
|
+
export { type AuthRequestRecord, type AuthRequestStorage, MemoryAuthRequestStorage, type MemoryAuthRequestStorageOptions, MemoryRefreshTokenStorage, MemoryTokenStorage, type RefreshTokenStorage, type TokenBundle, type TokenStorage, } from "./storage.js";
|
|
31
33
|
export { checkPermissions, type CheckPermissionsOptions, type CheckPermissionsResult, type CheckResource, } from "./check.js";
|
|
32
34
|
export { Authorization, authorizationFromClaims, } from "../models/authorization.js";
|
|
33
35
|
export type { OrganizationContext } from "../models/authorization.js";
|
package/esm/client/mod.js
CHANGED
|
@@ -15,9 +15,11 @@
|
|
|
15
15
|
* care — the React adapter's `<OAuth2Provider>`, a shared UI helper — should
|
|
16
16
|
* accept.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* A {@link DirectClient} keeps its token bundle and refresh token in memory
|
|
19
|
+
* by default, so they are lost on reload; a browser app that must survive one
|
|
20
|
+
* passes {@link IndexedDBRefreshTokenStorage}. The pending auth request
|
|
21
|
+
* defaults to {@link SessionStorageAuthRequestStorage} in a browser document
|
|
22
|
+
* and to memory everywhere else.
|
|
21
23
|
*
|
|
22
24
|
* @module
|
|
23
25
|
*/
|
package/esm/client/storage.d.ts
CHANGED
|
@@ -3,19 +3,18 @@
|
|
|
3
3
|
* tokens, so none of this applies to it.
|
|
4
4
|
*
|
|
5
5
|
* The client keeps three pieces of mutable state across calls:
|
|
6
|
-
* - the current token bundle (access
|
|
7
|
-
* - the refresh token
|
|
8
|
-
*
|
|
6
|
+
* - the current token bundle (access token, expiry, scope, id token),
|
|
7
|
+
* - the refresh token, in its own store so it can persist somewhere the
|
|
8
|
+
* access token does not,
|
|
9
9
|
* - a short-lived auth-request map (`state` → `{ codeVerifier, returnTo, … }`)
|
|
10
10
|
* used to correlate an authorize redirect with its callback.
|
|
11
11
|
*
|
|
12
|
-
* The
|
|
13
|
-
* lifetime.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* - `sessionStorage`-backed `AuthRequestStorage` for the redirect state map.
|
|
12
|
+
* The implementations here are in-memory and live only for the process / tab
|
|
13
|
+
* lifetime. `DirectClient` uses them by default, except that in a browser
|
|
14
|
+
* document its auth-request storage defaults to `sessionStorage`. A browser
|
|
15
|
+
* app that must stay signed in across reloads passes
|
|
16
|
+
* `IndexedDBRefreshTokenStorage` for the refresh token and keeps the access
|
|
17
|
+
* token in memory.
|
|
19
18
|
*
|
|
20
19
|
* @module
|
|
21
20
|
*/
|
|
@@ -42,10 +41,9 @@ export interface TokenStorage {
|
|
|
42
41
|
clear(): Promise<void> | void;
|
|
43
42
|
}
|
|
44
43
|
/**
|
|
45
|
-
* Persists the refresh token independently from the access token
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* short-lived access tokens live.
|
|
44
|
+
* Persists the refresh token independently from the access token, so it can
|
|
45
|
+
* outlive a reload (IndexedDB, a Web Worker, a server-side session) while the
|
|
46
|
+
* short-lived access token stays wherever {@link TokenStorage} keeps it.
|
|
49
47
|
*/
|
|
50
48
|
export interface RefreshTokenStorage {
|
|
51
49
|
/** Returns the stored refresh token, or `null` when none has been persisted. */
|
|
@@ -74,15 +72,29 @@ export interface AuthRequestRecord {
|
|
|
74
72
|
*/
|
|
75
73
|
export interface AuthRequestStorage {
|
|
76
74
|
/**
|
|
77
|
-
* Returns the record for {@linkcode state}, or `null` if absent
|
|
78
|
-
*
|
|
75
|
+
* Returns the record for {@linkcode state}, or `null` if absent. An
|
|
76
|
+
* implementation may also return `null` for, and prune, a record it deems
|
|
77
|
+
* expired; `DirectClient` separately rejects one older than its
|
|
78
|
+
* `authRequestTtlMs`.
|
|
79
79
|
*/
|
|
80
80
|
get(state: string): Promise<AuthRequestRecord | null> | AuthRequestRecord | null;
|
|
81
81
|
/** Stores {@linkcode value} under {@linkcode state} before the authorize redirect. */
|
|
82
82
|
set(state: string, value: AuthRequestRecord): Promise<void> | void;
|
|
83
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Returns the record for {@linkcode state} and removes it in one atomic
|
|
85
|
+
* step, or `null` if absent. `DirectClient` claims the record this way
|
|
86
|
+
* before it calls the token endpoint, so of two callbacks racing on one
|
|
87
|
+
* `state` only one reaches it. Optional for compatibility: without it the
|
|
88
|
+
* client falls back to `get` then `delete`, which still consumes the record
|
|
89
|
+
* before the token call but lets concurrent callers that both read it
|
|
90
|
+
* before either deletes it redeem it twice. Implement it with the backing
|
|
91
|
+
* store's own atomic primitive (`GETDEL`, `DELETE … RETURNING`) whenever
|
|
92
|
+
* the store is shared between processes or requests.
|
|
93
|
+
*/
|
|
94
|
+
take?(state: string): Promise<AuthRequestRecord | null> | AuthRequestRecord | null;
|
|
95
|
+
/** Removes one entry — a claimed or stale one. */
|
|
84
96
|
delete(state: string): Promise<void> | void;
|
|
85
|
-
/** Removes every entry. Called
|
|
97
|
+
/** Removes every entry. Called when the client clears its session. */
|
|
86
98
|
clear(): Promise<void> | void;
|
|
87
99
|
}
|
|
88
100
|
/** Default in-memory {@link TokenStorage}. Data is lost when the process exits. */
|
|
@@ -108,17 +120,35 @@ export declare class MemoryRefreshTokenStorage implements RefreshTokenStorage {
|
|
|
108
120
|
/** Discards the in-memory refresh token. */
|
|
109
121
|
clear(): void;
|
|
110
122
|
}
|
|
123
|
+
/** Options for {@link MemoryAuthRequestStorage}. */
|
|
124
|
+
export interface MemoryAuthRequestStorageOptions {
|
|
125
|
+
/**
|
|
126
|
+
* Age past which a record is pruned the next time any record is written,
|
|
127
|
+
* in ms. Defaults to 10 minutes; `Infinity` never prunes.
|
|
128
|
+
*/
|
|
129
|
+
ttlMs?: number;
|
|
130
|
+
}
|
|
111
131
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* the
|
|
132
|
+
* In-memory {@link AuthRequestStorage}, `DirectClient`'s default outside a
|
|
133
|
+
* browser document. Data is lost when the process exits. Every write prunes
|
|
134
|
+
* records older than {@link MemoryAuthRequestStorageOptions.ttlMs}, so logins
|
|
135
|
+
* that never return to the callback do not accumulate in a long-running
|
|
136
|
+
* server.
|
|
116
137
|
*/
|
|
117
138
|
export declare class MemoryAuthRequestStorage implements AuthRequestStorage {
|
|
118
139
|
#private;
|
|
140
|
+
/** Configures the pruning TTL, 10 minutes by default. */
|
|
141
|
+
constructor(options?: MemoryAuthRequestStorageOptions);
|
|
119
142
|
/** Returns the record for {@linkcode state}, or `null` if none is stored. */
|
|
120
143
|
get(state: string): AuthRequestRecord | null;
|
|
121
|
-
/**
|
|
144
|
+
/** Returns and removes the record for {@linkcode state} in one step. */
|
|
145
|
+
take(state: string): AuthRequestRecord | null;
|
|
146
|
+
/**
|
|
147
|
+
* Stores {@linkcode value} under {@linkcode state}, first pruning records
|
|
148
|
+
* past the TTL. Pruning walks records oldest-write-first and stops at the
|
|
149
|
+
* first one inside the TTL, so it assumes each `createdAt` is the time of
|
|
150
|
+
* its write, as `DirectClient.login` sets it.
|
|
151
|
+
*/
|
|
122
152
|
set(state: string, value: AuthRequestRecord): void;
|
|
123
153
|
/** Removes the record for {@linkcode state}. */
|
|
124
154
|
delete(state: string): void;
|
package/esm/client/storage.js
CHANGED
|
@@ -3,19 +3,18 @@
|
|
|
3
3
|
* tokens, so none of this applies to it.
|
|
4
4
|
*
|
|
5
5
|
* The client keeps three pieces of mutable state across calls:
|
|
6
|
-
* - the current token bundle (access
|
|
7
|
-
* - the refresh token
|
|
8
|
-
*
|
|
6
|
+
* - the current token bundle (access token, expiry, scope, id token),
|
|
7
|
+
* - the refresh token, in its own store so it can persist somewhere the
|
|
8
|
+
* access token does not,
|
|
9
9
|
* - a short-lived auth-request map (`state` → `{ codeVerifier, returnTo, … }`)
|
|
10
10
|
* used to correlate an authorize redirect with its callback.
|
|
11
11
|
*
|
|
12
|
-
* The
|
|
13
|
-
* lifetime.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* - `sessionStorage`-backed `AuthRequestStorage` for the redirect state map.
|
|
12
|
+
* The implementations here are in-memory and live only for the process / tab
|
|
13
|
+
* lifetime. `DirectClient` uses them by default, except that in a browser
|
|
14
|
+
* document its auth-request storage defaults to `sessionStorage`. A browser
|
|
15
|
+
* app that must stay signed in across reloads passes
|
|
16
|
+
* `IndexedDBRefreshTokenStorage` for the refresh token and keeps the access
|
|
17
|
+
* token in memory.
|
|
19
18
|
*
|
|
20
19
|
* @module
|
|
21
20
|
*/
|
|
@@ -55,19 +54,38 @@ export class MemoryRefreshTokenStorage {
|
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
56
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* the
|
|
57
|
+
* In-memory {@link AuthRequestStorage}, `DirectClient`'s default outside a
|
|
58
|
+
* browser document. Data is lost when the process exits. Every write prunes
|
|
59
|
+
* records older than {@link MemoryAuthRequestStorageOptions.ttlMs}, so logins
|
|
60
|
+
* that never return to the callback do not accumulate in a long-running
|
|
61
|
+
* server.
|
|
62
62
|
*/
|
|
63
63
|
export class MemoryAuthRequestStorage {
|
|
64
64
|
#records = new Map();
|
|
65
|
+
#ttlMs;
|
|
66
|
+
/** Configures the pruning TTL, 10 minutes by default. */
|
|
67
|
+
constructor(options = {}) {
|
|
68
|
+
this.#ttlMs = options.ttlMs ?? 10 * 60 * 1000;
|
|
69
|
+
}
|
|
65
70
|
/** Returns the record for {@linkcode state}, or `null` if none is stored. */
|
|
66
71
|
get(state) {
|
|
67
72
|
return this.#records.get(state) ?? null;
|
|
68
73
|
}
|
|
69
|
-
/**
|
|
74
|
+
/** Returns and removes the record for {@linkcode state} in one step. */
|
|
75
|
+
take(state) {
|
|
76
|
+
const record = this.#records.get(state) ?? null;
|
|
77
|
+
this.#records.delete(state);
|
|
78
|
+
return record;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Stores {@linkcode value} under {@linkcode state}, first pruning records
|
|
82
|
+
* past the TTL. Pruning walks records oldest-write-first and stops at the
|
|
83
|
+
* first one inside the TTL, so it assumes each `createdAt` is the time of
|
|
84
|
+
* its write, as `DirectClient.login` sets it.
|
|
85
|
+
*/
|
|
70
86
|
set(state, value) {
|
|
87
|
+
this.#pruneExpired();
|
|
88
|
+
this.#records.delete(state);
|
|
71
89
|
this.#records.set(state, value);
|
|
72
90
|
}
|
|
73
91
|
/** Removes the record for {@linkcode state}. */
|
|
@@ -78,4 +96,14 @@ export class MemoryAuthRequestStorage {
|
|
|
78
96
|
clear() {
|
|
79
97
|
this.#records.clear();
|
|
80
98
|
}
|
|
99
|
+
#pruneExpired() {
|
|
100
|
+
if (this.#ttlMs === Infinity)
|
|
101
|
+
return;
|
|
102
|
+
const cutoff = Date.now() - this.#ttlMs;
|
|
103
|
+
for (const [state, record] of this.#records) {
|
|
104
|
+
if (record.createdAt >= cutoff)
|
|
105
|
+
return;
|
|
106
|
+
this.#records.delete(state);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
81
109
|
}
|
package/esm/errors.d.ts
CHANGED
|
@@ -28,10 +28,9 @@ export interface OAuth2ProblemDetailsExtensions {
|
|
|
28
28
|
error_uri?: string;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* Constructor type
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* overloads and trips the `no-slow-types` rule).
|
|
31
|
+
* Constructor type shared by every OAuth2 error class in this module: each
|
|
32
|
+
* takes an optional status, message, and `HttpErrorOptions` (whose
|
|
33
|
+
* `extensions` carry the OAuth2 fields) and builds an {@linkcode OAuth2Error}.
|
|
35
34
|
*/
|
|
36
35
|
export type OAuth2ErrorClass<Extensions extends OAuth2ProblemDetailsExtensions = OAuth2ProblemDetailsExtensions> = {
|
|
37
36
|
new <E extends Extensions = Extensions>(status?: number, message?: string, options?: HttpErrorOptions<E>): HttpError<E>;
|
|
@@ -53,19 +52,23 @@ export type OAuth2Error = HttpError<OAuth2ProblemDetailsExtensions>;
|
|
|
53
52
|
* (e.g. {@linkcode InvalidRequestError}) when the condition is known.
|
|
54
53
|
*/
|
|
55
54
|
export declare const OAuth2Error: OAuth2ErrorClass;
|
|
56
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Type guard for OAuth2 errors: true for any `HttpError` whose
|
|
57
|
+
* `extensions.error` is set, whichever class constructed it.
|
|
58
|
+
*/
|
|
57
59
|
export declare function isOAuth2Error(value: unknown): value is OAuth2Error;
|
|
58
60
|
/**
|
|
59
61
|
* Converts an arbitrary error into an {@link OAuth2Error}.
|
|
60
62
|
*
|
|
61
|
-
*
|
|
62
|
-
* wrapped in a {@link ServerError} with the original error
|
|
63
|
+
* An input that is already an OAuth2Error is returned as the same object;
|
|
64
|
+
* anything else is wrapped in a {@link ServerError} with the original error
|
|
65
|
+
* attached as `cause`.
|
|
63
66
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* error format (which emits `error_uri`) and the
|
|
67
|
-
* format (which emits `type`) carry the same URI
|
|
68
|
-
* set both fields.
|
|
67
|
+
* Either way, `type` and `extensions.error_uri` are synced so that if one is
|
|
68
|
+
* set the other is too — **mutating an OAuth2Error input in place**. This lets
|
|
69
|
+
* both the standard OAuth2 error format (which emits `error_uri`) and the
|
|
70
|
+
* RFC 9457 Problem Details format (which emits `type`) carry the same URI
|
|
71
|
+
* without the handler having to set both fields.
|
|
69
72
|
*/
|
|
70
73
|
export declare function toOAuth2Error(error: unknown): OAuth2Error;
|
|
71
74
|
/**
|
package/esm/errors.js
CHANGED
|
@@ -24,21 +24,25 @@ export const OAuth2Error = createHttpErrorClass({
|
|
|
24
24
|
error: "server_error",
|
|
25
25
|
},
|
|
26
26
|
});
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Type guard for OAuth2 errors: true for any `HttpError` whose
|
|
29
|
+
* `extensions.error` is set, whichever class constructed it.
|
|
30
|
+
*/
|
|
28
31
|
export function isOAuth2Error(value) {
|
|
29
32
|
return value instanceof HttpError && value.extensions.error !== undefined;
|
|
30
33
|
}
|
|
31
34
|
/**
|
|
32
35
|
* Converts an arbitrary error into an {@link OAuth2Error}.
|
|
33
36
|
*
|
|
34
|
-
*
|
|
35
|
-
* wrapped in a {@link ServerError} with the original error
|
|
37
|
+
* An input that is already an OAuth2Error is returned as the same object;
|
|
38
|
+
* anything else is wrapped in a {@link ServerError} with the original error
|
|
39
|
+
* attached as `cause`.
|
|
36
40
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* error format (which emits `error_uri`) and the
|
|
40
|
-
* format (which emits `type`) carry the same URI
|
|
41
|
-
* set both fields.
|
|
41
|
+
* Either way, `type` and `extensions.error_uri` are synced so that if one is
|
|
42
|
+
* set the other is too — **mutating an OAuth2Error input in place**. This lets
|
|
43
|
+
* both the standard OAuth2 error format (which emits `error_uri`) and the
|
|
44
|
+
* RFC 9457 Problem Details format (which emits `type`) carry the same URI
|
|
45
|
+
* without the handler having to set both fields.
|
|
42
46
|
*/
|
|
43
47
|
export function toOAuth2Error(error) {
|
|
44
48
|
const oauth2Error = isOAuth2Error(error)
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
* The library owns the **token lifecycle** ({@link TokenFlowService}); your app
|
|
6
6
|
* owns **transport** — SMTP, SES, Resend, Twilio, etc. So delivery is a set of
|
|
7
7
|
* app-supplied async hooks, and the package ships URL builders so apps stop
|
|
8
|
-
* hand-assembling `${protocol}://${host}/verify-email?token=…` strings.
|
|
9
|
-
* the Better Auth model (`sendVerificationEmail` / `sendResetPassword`).
|
|
8
|
+
* hand-assembling `${protocol}://${host}/verify-email?token=…` strings.
|
|
10
9
|
*
|
|
11
10
|
* @module
|
|
12
11
|
*/
|
|
@@ -15,9 +14,10 @@ export interface DeliveryMessage {
|
|
|
15
14
|
/** Recipient — an email address or phone number. */
|
|
16
15
|
to: string;
|
|
17
16
|
/**
|
|
18
|
-
* The ready-to-use action link (already carrying the token)
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* The ready-to-use action link (already carrying the token).
|
|
18
|
+
* `IdentityService` sets it only when configured with a `baseUrl`, building
|
|
19
|
+
* it with the matching `build*Url` helper at its default path; otherwise it
|
|
20
|
+
* is absent and you build the link from `token` yourself.
|
|
21
21
|
*/
|
|
22
22
|
url?: string;
|
|
23
23
|
/** The raw token, for apps that compose their own URL or message. */
|
|
@@ -47,8 +47,8 @@ export interface CodeDeliveryMessage {
|
|
|
47
47
|
/** A delivery transport callback for one-time codes. */
|
|
48
48
|
export type CodeDeliveryHook = (message: CodeDeliveryMessage) => Promise<void> | void;
|
|
49
49
|
/**
|
|
50
|
-
* App-supplied delivery transports. Omit a hook
|
|
51
|
-
*
|
|
50
|
+
* App-supplied delivery transports. Omit a hook and that message is never sent
|
|
51
|
+
* (the flow still mints and stores the token or code).
|
|
52
52
|
*
|
|
53
53
|
* Trigger point `delivery` (see `docs/trigger-points.md`). The package calls a
|
|
54
54
|
* hook after minting the credential; a **throw** (e.g. a mailer outage) is
|
package/esm/identity/delivery.js
CHANGED
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
* The library owns the **token lifecycle** ({@link TokenFlowService}); your app
|
|
6
6
|
* owns **transport** — SMTP, SES, Resend, Twilio, etc. So delivery is a set of
|
|
7
7
|
* app-supplied async hooks, and the package ships URL builders so apps stop
|
|
8
|
-
* hand-assembling `${protocol}://${host}/verify-email?token=…` strings.
|
|
9
|
-
* the Better Auth model (`sendVerificationEmail` / `sendResetPassword`).
|
|
8
|
+
* hand-assembling `${protocol}://${host}/verify-email?token=…` strings.
|
|
10
9
|
*
|
|
11
10
|
* @module
|
|
12
11
|
*/
|
package/esm/identity/errors.d.ts
CHANGED
|
@@ -6,9 +6,12 @@
|
|
|
6
6
|
* @module
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* Stable, machine-readable identity error codes.
|
|
10
|
-
*
|
|
11
|
-
* same-origin guard before a handler
|
|
9
|
+
* Stable, machine-readable identity error codes. The package raises
|
|
10
|
+
* `rate_limited`, `weak_password`, and the `mfa_*` codes, and the Hono route
|
|
11
|
+
* factory answers `forbidden_origin` (its same-origin guard, before a handler
|
|
12
|
+
* runs) and `invalid_credentials` / `invalid_token` (a failed sign-in or token
|
|
13
|
+
* check); the rest are for your store and routes to throw (e.g. `identifier_taken` on
|
|
14
|
+
* a duplicate sign-up, `captcha_failed` after `verifyCaptcha`).
|
|
12
15
|
*/
|
|
13
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";
|
|
14
17
|
/** Options for {@link IdentityError}. */
|
|
@@ -31,7 +34,10 @@ export declare class IdentityError extends Error {
|
|
|
31
34
|
/** Builds an identity error; `message` defaults to the `code` when omitted. */
|
|
32
35
|
constructor(code: IdentityErrorCode, message?: string, options?: IdentityErrorOptions);
|
|
33
36
|
}
|
|
34
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* Type guard for {@link IdentityError}. An `instanceof` check, so an error from
|
|
39
|
+
* another realm or a second copy of the package fails it; compare `name` there.
|
|
40
|
+
*/
|
|
35
41
|
export declare function isIdentityError(value: unknown): value is IdentityError;
|
|
36
42
|
/** The HTTP statuses an identity error maps to (a `ContentfulStatusCode` union). */
|
|
37
43
|
export type IdentityErrorStatus = 400 | 401 | 403 | 409 | 422 | 429;
|
package/esm/identity/errors.js
CHANGED
|
@@ -24,7 +24,10 @@ export class IdentityError extends Error {
|
|
|
24
24
|
this.retryAfterMs = options?.retryAfterMs;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Type guard for {@link IdentityError}. An `instanceof` check, so an error from
|
|
29
|
+
* another realm or a second copy of the package fails it; compare `name` there.
|
|
30
|
+
*/
|
|
28
31
|
export function isIdentityError(value) {
|
|
29
32
|
return value instanceof IdentityError;
|
|
30
33
|
}
|
package/esm/identity/events.d.ts
CHANGED
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
* Some outcomes are *only* visible here, because the flow that produced them
|
|
14
14
|
* resolves uniformly by design: `delivery.failed` is the sole in-band signal
|
|
15
15
|
* that a message never went out (and, when `invalidated` is `false`, that a
|
|
16
|
-
* live credential the user never received is still outstanding)
|
|
17
|
-
*
|
|
16
|
+
* live credential the user never received is still outstanding), and
|
|
17
|
+
* `credential_mint.failed` the sole signal that a token or code store outage
|
|
18
|
+
* kept one from being minted at all. Treat this hook as a monitored surface,
|
|
19
|
+
* not just an audit log.
|
|
18
20
|
*
|
|
19
21
|
* Events describe internal outcomes (including whether an identifier resolved
|
|
20
22
|
* to an account), so they are for server-side capture only — never surface
|
|
@@ -26,8 +28,9 @@ import type { DeliveryHooks } from "./delivery.js";
|
|
|
26
28
|
/** Why a sign-in attempt failed. Internal detail — never shown to the user. */
|
|
27
29
|
export type SignInFailureReason = "unknown_identifier" | "no_password" | "wrong_password";
|
|
28
30
|
/**
|
|
29
|
-
* Why a sign-in code verification failed.
|
|
30
|
-
*
|
|
31
|
+
* Why a sign-in code verification failed. Internal detail — never shown to the
|
|
32
|
+
* user: `IdentityService.verifySignInCode` reports every one of these as
|
|
33
|
+
* `invalid`.
|
|
31
34
|
*/
|
|
32
35
|
export type SignInCodeFailureReason = "invalid" | "expired" | "locked" | "unknown_email";
|
|
33
36
|
/**
|
|
@@ -122,9 +125,22 @@ export type IdentityEvent = {
|
|
|
122
125
|
* `"session_revocation_failed"` — the new password was set but the
|
|
123
126
|
* configured {@link RevocableSessionService} threw before the user's other
|
|
124
127
|
* sessions could be revoked, so the reset is reported failed (and rethrown)
|
|
125
|
-
* rather than trusted
|
|
128
|
+
* rather than trusted. The reset token is already spent, so clear the
|
|
129
|
+
* still-live sessions by calling `revokeAllByUser` again from this event,
|
|
130
|
+
* or have the user complete a fresh reset link.
|
|
126
131
|
*/
|
|
127
132
|
reason: "invalid_token" | "session_revocation_failed";
|
|
133
|
+
} | {
|
|
134
|
+
type: "credential_mint.failed";
|
|
135
|
+
/**
|
|
136
|
+
* Which enumeration-safe request flow could not mint its credential
|
|
137
|
+
* because the token or code store threw. The call still resolved `void`,
|
|
138
|
+
* exactly as it does for an unknown email, so this event is the only
|
|
139
|
+
* signal that the user was never sent anything.
|
|
140
|
+
*/
|
|
141
|
+
flow: "password_reset" | "signin_link" | "signin_code";
|
|
142
|
+
/** The store's failure message. */
|
|
143
|
+
error: string;
|
|
128
144
|
} | {
|
|
129
145
|
type: "email_verification.requested";
|
|
130
146
|
userId: string;
|
package/esm/identity/events.js
CHANGED
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
* Some outcomes are *only* visible here, because the flow that produced them
|
|
14
14
|
* resolves uniformly by design: `delivery.failed` is the sole in-band signal
|
|
15
15
|
* that a message never went out (and, when `invalidated` is `false`, that a
|
|
16
|
-
* live credential the user never received is still outstanding)
|
|
17
|
-
*
|
|
16
|
+
* live credential the user never received is still outstanding), and
|
|
17
|
+
* `credential_mint.failed` the sole signal that a token or code store outage
|
|
18
|
+
* kept one from being minted at all. Treat this hook as a monitored surface,
|
|
19
|
+
* not just an audit log.
|
|
18
20
|
*
|
|
19
21
|
* Events describe internal outcomes (including whether an identifier resolved
|
|
20
22
|
* to an account), so they are for server-side capture only — never surface
|
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Not part of the public API — reach for a connector from
|
|
5
5
|
* `@udibo/oauth2/identity/external` instead, which re-exports the one type an
|
|
6
|
-
* adopter configures ({@link AzpPolicy}).
|
|
7
|
-
* reads the same way whichever connector hit it: one error renderer, one
|
|
8
|
-
* error-body reader, one provider-text sanitizer, and one id_token claim gate
|
|
9
|
-
* rather than a copy per provider. The token exchange itself is
|
|
6
|
+
* adopter configures ({@link AzpPolicy}). The token exchange is
|
|
10
7
|
* `_token-exchange.ts`.
|
|
11
8
|
*
|
|
12
9
|
* @module
|
|
@@ -75,9 +72,10 @@ export interface IdTokenClaimsInput {
|
|
|
75
72
|
leewaySeconds?: number;
|
|
76
73
|
}
|
|
77
74
|
/**
|
|
78
|
-
* Enforces
|
|
79
|
-
*
|
|
80
|
-
*
|
|
75
|
+
* Enforces these OpenID Connect Core §3.1.3.7 claim checks, in order: `iss`,
|
|
76
|
+
* `aud`, `azp`, `exp` (a missing `exp` fails), and `nonce` (only when
|
|
77
|
+
* `expectedNonce` is given). `iat`, `acr`, and `auth_time` are not checked.
|
|
78
|
+
* Callers verify the signature (or establish the token's provenance)
|
|
81
79
|
* themselves before calling this.
|
|
82
80
|
*
|
|
83
81
|
* Throws {@link ExternalAuthError} with code `nonce_mismatch` when the `nonce`
|
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Not part of the public API — reach for a connector from
|
|
5
5
|
* `@udibo/oauth2/identity/external` instead, which re-exports the one type an
|
|
6
|
-
* adopter configures ({@link AzpPolicy}).
|
|
7
|
-
* reads the same way whichever connector hit it: one error renderer, one
|
|
8
|
-
* error-body reader, one provider-text sanitizer, and one id_token claim gate
|
|
9
|
-
* rather than a copy per provider. The token exchange itself is
|
|
6
|
+
* adopter configures ({@link AzpPolicy}). The token exchange is
|
|
10
7
|
* `_token-exchange.ts`.
|
|
11
8
|
*
|
|
12
9
|
* @module
|
|
@@ -61,9 +58,10 @@ export function stripTrailingSlash(value) {
|
|
|
61
58
|
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
62
59
|
}
|
|
63
60
|
/**
|
|
64
|
-
* Enforces
|
|
65
|
-
*
|
|
66
|
-
*
|
|
61
|
+
* Enforces these OpenID Connect Core §3.1.3.7 claim checks, in order: `iss`,
|
|
62
|
+
* `aud`, `azp`, `exp` (a missing `exp` fails), and `nonce` (only when
|
|
63
|
+
* `expectedNonce` is given). `iat`, `acr`, and `auth_time` are not checked.
|
|
64
|
+
* Callers verify the signature (or establish the token's provenance)
|
|
67
65
|
* themselves before calling this.
|
|
68
66
|
*
|
|
69
67
|
* Throws {@link ExternalAuthError} with code `nonce_mismatch` when the `nonce`
|
|
@@ -3,11 +3,8 @@
|
|
|
3
3
|
* through — {@link githubProvider}, {@link oauth2Provider} (and its
|
|
4
4
|
* {@link discordProvider} preset), and {@link appleProvider}.
|
|
5
5
|
* {@link oidcProvider}, and {@link googleProvider} on top of it, exchange via
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
* Not part of the public API. Split out of `_shared.ts` so the ladder — the one
|
|
9
|
-
* place a provider's bytes become an error message or a token — sits next to
|
|
10
|
-
* the tests that pin it.
|
|
6
|
+
* `DirectClient` instead and do not reach this module. Not part of the public
|
|
7
|
+
* API.
|
|
11
8
|
*
|
|
12
9
|
* @module
|
|
13
10
|
*/
|
|
@@ -35,7 +32,7 @@ export interface TokenExchangeInput {
|
|
|
35
32
|
* `RequestInit`** — `redirect` and `signal` in particular. They are the only
|
|
36
33
|
* channel the redirect refusal and the deadline reach the transport, so a
|
|
37
34
|
* wrapper that rebuilds `init` from scratch silently reopens the credential
|
|
38
|
-
* replay hole.
|
|
35
|
+
* replay hole.
|
|
39
36
|
*/
|
|
40
37
|
fetch: typeof fetch;
|
|
41
38
|
/** Guidance appended when a failure carries no OAuth2 error code. */
|
|
@@ -3,11 +3,8 @@
|
|
|
3
3
|
* through — {@link githubProvider}, {@link oauth2Provider} (and its
|
|
4
4
|
* {@link discordProvider} preset), and {@link appleProvider}.
|
|
5
5
|
* {@link oidcProvider}, and {@link googleProvider} on top of it, exchange via
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
8
|
-
* Not part of the public API. Split out of `_shared.ts` so the ladder — the one
|
|
9
|
-
* place a provider's bytes become an error message or a token — sits next to
|
|
10
|
-
* the tests that pin it.
|
|
6
|
+
* `DirectClient` instead and do not reach this module. Not part of the public
|
|
7
|
+
* API.
|
|
11
8
|
*
|
|
12
9
|
* @module
|
|
13
10
|
*/
|
|
@@ -38,8 +38,8 @@ export interface AppleClientSecretOptions {
|
|
|
38
38
|
privateKey: string;
|
|
39
39
|
/**
|
|
40
40
|
* Lifetime of the generated secret in seconds. Defaults to
|
|
41
|
-
* {@link DEFAULT_APPLE_CLIENT_SECRET_TTL_SECONDS}; must
|
|
42
|
-
* {@link APPLE_CLIENT_SECRET_MAX_TTL_SECONDS}.
|
|
41
|
+
* {@link DEFAULT_APPLE_CLIENT_SECRET_TTL_SECONDS}; must be a finite number
|
|
42
|
+
* that does not exceed {@link APPLE_CLIENT_SECRET_MAX_TTL_SECONDS}.
|
|
43
43
|
*/
|
|
44
44
|
expiresInSeconds?: number;
|
|
45
45
|
/** Provider id used in error messages. Defaults to `"apple"`. */
|
|
@@ -54,7 +54,8 @@ export interface AppleClientSecretOptions {
|
|
|
54
54
|
*
|
|
55
55
|
* @throws {ExternalAuthError} `configuration` naming the offending input when
|
|
56
56
|
* the team id / key id / client id is empty, the `.p8` is not a valid P-256
|
|
57
|
-
* key, or the requested TTL
|
|
57
|
+
* key, or the requested TTL is not a finite number between 1 and Apple's
|
|
58
|
+
* cap (`NaN` and `Infinity` are rejected, never signed into `exp`).
|
|
58
59
|
*
|
|
59
60
|
* @example
|
|
60
61
|
* ```ts
|
|
@@ -67,15 +68,19 @@ export interface AppleClientSecretOptions {
|
|
|
67
68
|
* ```
|
|
68
69
|
*/
|
|
69
70
|
export declare function generateAppleClientSecret(options: AppleClientSecretOptions): Promise<string>;
|
|
70
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Returns the client-secret JWT to present on a token exchange; called once per
|
|
73
|
+
* exchange. {@link createAppleClientSecretFactory} builds the caching one.
|
|
74
|
+
*/
|
|
71
75
|
export type AppleClientSecretFactory = () => Promise<string>;
|
|
72
76
|
/**
|
|
73
77
|
* Build a self-renewing Apple client-secret source. It signs a secret with
|
|
74
78
|
* {@link generateAppleClientSecret} on first call, caches it, and re-signs
|
|
75
|
-
* automatically once the cached secret is within `renewBeforeSeconds`
|
|
76
|
-
* — so a long-lived {@link appleProvider} never presents
|
|
77
|
-
* The key material is validated on the first call,
|
|
78
|
-
* as a `configuration`
|
|
79
|
+
* automatically once the cached secret is within `renewBeforeSeconds` (default
|
|
80
|
+
* one hour) of expiry — so a long-lived {@link appleProvider} never presents
|
|
81
|
+
* an expired assertion. The key material is validated on the first call, not
|
|
82
|
+
* at construction, surfacing a misconfiguration as a `configuration`
|
|
83
|
+
* {@link ExternalAuthError}; a failed call caches nothing.
|
|
79
84
|
*/
|
|
80
85
|
export declare function createAppleClientSecretFactory(options: AppleClientSecretOptions & {
|
|
81
86
|
renewBeforeSeconds?: number;
|