@udibo/oauth2 0.4.0 → 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/public-suffix/rules.d.ts +4 -4
- package/esm/server/public-suffix/rules.js +4 -4
- 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
|
@@ -149,15 +149,12 @@ export declare function checkRedirectUriPattern(pattern: string, isPublicSuffix?
|
|
|
149
149
|
* - A host label may never be empty, and a single trailing dot is treated as
|
|
150
150
|
* the same host, so `example.com.` cannot smuggle an extra label past the
|
|
151
151
|
* parent-domain rule.
|
|
152
|
-
* - A requested URI containing `*` never matches
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
* comparison no longer matches at all. Such a registration cannot be created
|
|
159
|
-
* any more — a `*` now makes the value a pattern, which must satisfy the rules
|
|
160
|
-
* above — so this only affects rows written before patterns existed.
|
|
152
|
+
* - A requested URI containing a literal `*` never matches any registration,
|
|
153
|
+
* so a pattern can never be replayed back as a redirect target. A requested
|
|
154
|
+
* host that percent-decodes to one containing `*` never matches a pattern.
|
|
155
|
+
*
|
|
156
|
+
* Any registration containing `*` is a pattern, so a stored literal URI with a
|
|
157
|
+
* `*` in its host matches only if it also satisfies the pattern rules.
|
|
161
158
|
*
|
|
162
159
|
* @param registered The client's registered redirect URIs.
|
|
163
160
|
* @param redirectUri The `redirect_uri` from the authorization request.
|
|
@@ -38,27 +38,8 @@
|
|
|
38
38
|
*
|
|
39
39
|
* @module
|
|
40
40
|
*/
|
|
41
|
-
|
|
42
|
-
* A label used to ask whether the *children* of a parent domain are public
|
|
43
|
-
* suffixes, which is a different question from whether the parent is one.
|
|
44
|
-
*
|
|
45
|
-
* `r.appspot.com` is a registrable domain by the list's reckoning, but the rule
|
|
46
|
-
* `*.r.appspot.com` makes every one of its subdomains a public suffix belonging
|
|
47
|
-
* to a different registrant — so a wildcard over it hands codes to strangers
|
|
48
|
-
* even though the parent itself passes. Probing with a label no registry has
|
|
49
|
-
* ever issued answers that in one lookup.
|
|
50
|
-
*/
|
|
41
|
+
// A label no registry issues; a PSL hit on it catches `*.r.appspot.com` rules.
|
|
51
42
|
const CHILD_PROBE_LABEL = "udibo-public-suffix-probe";
|
|
52
|
-
/**
|
|
53
|
-
* Split a hostname into labels, or `null` when it is not usable for matching.
|
|
54
|
-
*
|
|
55
|
-
* A single trailing dot is the DNS root and is dropped, so `example.com.` and
|
|
56
|
-
* `example.com` are the same host — without this, the extra empty label would
|
|
57
|
-
* shift every label the public-suffix check looks at and let `*.deno.net.`
|
|
58
|
-
* through. Any *other* empty label (`a..b`, a leading `.`) is refused outright
|
|
59
|
-
* rather than normalized: it cannot resolve, and an empty leftmost label would
|
|
60
|
-
* otherwise satisfy any prefix/suffix test.
|
|
61
|
-
*/
|
|
62
43
|
function hostLabels(hostname) {
|
|
63
44
|
const normalized = hostname.endsWith(".") ? hostname.slice(0, -1) : hostname;
|
|
64
45
|
if (normalized.length === 0)
|
|
@@ -175,12 +156,7 @@ export function checkRedirectUriPattern(pattern, isPublicSuffix) {
|
|
|
175
156
|
}
|
|
176
157
|
return null;
|
|
177
158
|
}
|
|
178
|
-
|
|
179
|
-
* The loopback IP literals RFC 8252 §7.3 covers, in the form
|
|
180
|
-
* `URL.hostname` normalizes them to. `localhost` is deliberately absent: RFC
|
|
181
|
-
* 8252 §8.3 advises against it because its resolution depends on a host file
|
|
182
|
-
* the authorization server does not control.
|
|
183
|
-
*/
|
|
159
|
+
// RFC 8252 §7.3 IP literals only; `localhost` is excluded per RFC 8252 §8.3.
|
|
184
160
|
const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "[::1]"]);
|
|
185
161
|
function matchesLoopback(registration, redirectUri) {
|
|
186
162
|
if (isRedirectUriPattern(registration))
|
|
@@ -275,15 +251,12 @@ function matchesPattern(pattern, redirectUri, isPublicSuffix) {
|
|
|
275
251
|
* - A host label may never be empty, and a single trailing dot is treated as
|
|
276
252
|
* the same host, so `example.com.` cannot smuggle an extra label past the
|
|
277
253
|
* parent-domain rule.
|
|
278
|
-
* - A requested URI containing `*` never matches
|
|
279
|
-
*
|
|
280
|
-
*
|
|
254
|
+
* - A requested URI containing a literal `*` never matches any registration,
|
|
255
|
+
* so a pattern can never be replayed back as a redirect target. A requested
|
|
256
|
+
* host that percent-decodes to one containing `*` never matches a pattern.
|
|
281
257
|
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
* comparison no longer matches at all. Such a registration cannot be created
|
|
285
|
-
* any more — a `*` now makes the value a pattern, which must satisfy the rules
|
|
286
|
-
* above — so this only affects rows written before patterns existed.
|
|
258
|
+
* Any registration containing `*` is a pattern, so a stored literal URI with a
|
|
259
|
+
* `*` in its host matches only if it also satisfies the pattern rules.
|
|
287
260
|
*
|
|
288
261
|
* @param registered The client's registered redirect URIs.
|
|
289
262
|
* @param redirectUri The `redirect_uri` from the authorization request.
|
|
@@ -231,10 +231,12 @@ export declare class ResourceServer<Client extends ClientInterface, User, S exte
|
|
|
231
231
|
*
|
|
232
232
|
* The token's expiry is compared against now with {@linkcode
|
|
233
233
|
* clockSkewSeconds} of leeway, so the same tolerance applies whichever
|
|
234
|
-
* token reader produced the token.
|
|
234
|
+
* token reader produced the token. A token without `accessTokenExpiresAt`
|
|
235
|
+
* is never refused as expired here. Revocation is the reader's to report,
|
|
236
|
+
* by resolving no token.
|
|
235
237
|
*
|
|
236
|
-
* @throws {InvalidTokenError} If the
|
|
237
|
-
* configured leeway
|
|
238
|
+
* @throws {InvalidTokenError} If the reader resolves no token, or the token
|
|
239
|
+
* has expired beyond the configured leeway
|
|
238
240
|
* @see https://datatracker.ietf.org/doc/html/rfc6750#section-3.1
|
|
239
241
|
*/
|
|
240
242
|
getToken(accessToken: string, services: ResourceServerServices<Client, User, S>): Promise<Token<Client, User, S>>;
|
|
@@ -258,10 +258,12 @@ export class ResourceServer {
|
|
|
258
258
|
*
|
|
259
259
|
* The token's expiry is compared against now with {@linkcode
|
|
260
260
|
* clockSkewSeconds} of leeway, so the same tolerance applies whichever
|
|
261
|
-
* token reader produced the token.
|
|
261
|
+
* token reader produced the token. A token without `accessTokenExpiresAt`
|
|
262
|
+
* is never refused as expired here. Revocation is the reader's to report,
|
|
263
|
+
* by resolving no token.
|
|
262
264
|
*
|
|
263
|
-
* @throws {InvalidTokenError} If the
|
|
264
|
-
* configured leeway
|
|
265
|
+
* @throws {InvalidTokenError} If the reader resolves no token, or the token
|
|
266
|
+
* has expired beyond the configured leeway
|
|
265
267
|
* @see https://datatracker.ietf.org/doc/html/rfc6750#section-3.1
|
|
266
268
|
*/
|
|
267
269
|
async getToken(accessToken, services) {
|
|
@@ -30,6 +30,10 @@ export interface ClientServiceInterface<Client extends ClientInterface, User> {
|
|
|
30
30
|
* carried none.
|
|
31
31
|
*/
|
|
32
32
|
getAuthenticated(id: string, secret?: string): Promise<Client | undefined>;
|
|
33
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Resolves the user a client-credentials token acts for. Resolving
|
|
35
|
+
* `undefined` is the conformant case and yields a user-less machine token;
|
|
36
|
+
* see `ClientCredentialsGrant` before mapping a client to a human user.
|
|
37
|
+
*/
|
|
34
38
|
getUser(client: Client | string): Promise<User | undefined>;
|
|
35
39
|
}
|
|
@@ -34,15 +34,15 @@ import type { AbstractScope } from "../../models/scope.js";
|
|
|
34
34
|
*/
|
|
35
35
|
export interface DeviceAuthorizationServiceInterface<Client extends ClientInterface, User, Scope extends AbstractScope> {
|
|
36
36
|
/**
|
|
37
|
-
* Lifetime of device codes in seconds.
|
|
38
|
-
*
|
|
39
|
-
* @default 1800 (30 minutes)
|
|
37
|
+
* Lifetime of device codes in seconds. RFC 8628 sets no default; 15–30
|
|
38
|
+
* minutes is common.
|
|
40
39
|
*/
|
|
41
40
|
lifetime: number;
|
|
42
41
|
/**
|
|
43
|
-
* Minimum polling interval in seconds
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* Minimum polling interval in seconds, copied onto each new authorization
|
|
43
|
+
* and returned to the device as `interval`. The grant answers a faster poll
|
|
44
|
+
* with `slow_down` and adds 5 seconds to that authorization's interval
|
|
45
|
+
* (RFC 8628 §3.5).
|
|
46
46
|
*/
|
|
47
47
|
interval: number;
|
|
48
48
|
/**
|
|
@@ -24,9 +24,10 @@ export interface TokenReaderInterface<Client extends ClientInterface, User, Scop
|
|
|
24
24
|
* its grant types.
|
|
25
25
|
*
|
|
26
26
|
* **A token need not have a user.** The client credentials grant (RFC 6749
|
|
27
|
-
* §4.4) has no resource owner, so
|
|
28
|
-
*
|
|
29
|
-
* saves a {@linkcode Token}
|
|
27
|
+
* §4.4) has no resource owner, so when `ClientServiceInterface.getUser`
|
|
28
|
+
* resolves none it calls `acceptedScope`, `generateAccessToken`, and
|
|
29
|
+
* `accessTokenExpiresAt` with `user: undefined` and saves a {@linkcode Token}
|
|
30
|
+
* whose `user` is unset. An implementation that
|
|
30
31
|
* dereferences `user` without checking will throw on those requests, and a
|
|
31
32
|
* store with a non-nullable user column cannot persist the result — decide
|
|
32
33
|
* scope and lifetime from the client alone when it is absent.
|
|
@@ -53,8 +54,13 @@ export interface TokenServiceInterface<Client extends ClientInterface, User, Sco
|
|
|
53
54
|
* generator names the client as the subject then, per RFC 9068 §2.2.
|
|
54
55
|
* Forward `authenticationContext` to a signed-token generator so it uses
|
|
55
56
|
* the credential's event, including the original event on refresh.
|
|
57
|
+
* `expiresAt` is the expiry the grant will store for this token — already
|
|
58
|
+
* clamped to the refresh family's cap when one applies. Forward it too, so
|
|
59
|
+
* a signed token's `exp` never outlives what the server itself honors; the
|
|
60
|
+
* grants always pass it, and a wrapper that drops it leaves offline
|
|
61
|
+
* verifiers accepting the token past its stored expiry.
|
|
56
62
|
*/
|
|
57
|
-
generateAccessToken(client: Client, user: User | undefined, scope?: Scope | null, authenticationContext?: AuthenticationContext): Promise<string>;
|
|
63
|
+
generateAccessToken(client: Client, user: User | undefined, scope?: Scope | null, authenticationContext?: AuthenticationContext, expiresAt?: Date): Promise<string>;
|
|
58
64
|
/** Generates a refresh token string. */
|
|
59
65
|
generateRefreshToken(client: Client, user: User, scope?: Scope | null): Promise<string | undefined>;
|
|
60
66
|
/**
|
|
@@ -71,7 +77,8 @@ export interface TokenServiceInterface<Client extends ClientInterface, User, Sco
|
|
|
71
77
|
*
|
|
72
78
|
* **This method is the whole cap.** The grants clamp a rotation's access and
|
|
73
79
|
* refresh expiries to the date it returns and answer `invalid_grant` once
|
|
74
|
-
* that date has passed
|
|
80
|
+
* that date has passed; a refresh-token record without `familyCreatedAt` is
|
|
81
|
+
* never capped. Return `undefined` — or leave the method off
|
|
75
82
|
* entirely, which is the default — and the family is never capped, however
|
|
76
83
|
* the service is otherwise configured. Implement it to cap per client (see
|
|
77
84
|
* {@linkcode AbstractTokenService.refreshTokenMaxLifetime} for the
|
|
@@ -151,8 +158,8 @@ export interface AbstractTokenServiceOptions<Client extends ClientInterface, Use
|
|
|
151
158
|
/**
|
|
152
159
|
* Absolute lifetime of a refresh-token family in seconds, read by this
|
|
153
160
|
* class's {@linkcode AbstractTokenService.refreshTokenFamilyExpiresAt}. Omit
|
|
154
|
-
*
|
|
155
|
-
* `refreshTokenLifetime`.
|
|
161
|
+
* to leave families uncapped, so rotation extends a family indefinitely.
|
|
162
|
+
* Must be at least `refreshTokenLifetime`.
|
|
156
163
|
*/
|
|
157
164
|
refreshTokenMaxLifetime?: number;
|
|
158
165
|
/** Client service for hydrating tokens. */
|
|
@@ -202,7 +209,7 @@ export declare abstract class AbstractTokenService<Client extends ClientInterfac
|
|
|
202
209
|
*/
|
|
203
210
|
acceptedScope(_client: Client, _user: User | undefined, scope?: Scope | null): Promise<Scope | null | undefined | false>;
|
|
204
211
|
/** Generates an opaque access token string. Override to issue a different format. */
|
|
205
|
-
generateAccessToken(_client: Client, _user: User | undefined, _scope?: Scope | null, _authenticationContext?: AuthenticationContext): Promise<string>;
|
|
212
|
+
generateAccessToken(_client: Client, _user: User | undefined, _scope?: Scope | null, _authenticationContext?: AuthenticationContext, _expiresAt?: Date): Promise<string>;
|
|
206
213
|
/** Generates an opaque refresh token string. Override to issue a different format. */
|
|
207
214
|
generateRefreshToken(_client: Client, _user: User, _scope?: Scope | null): Promise<string | undefined>;
|
|
208
215
|
/** Computes the access token expiry as now plus {@linkcode accessTokenLifetime}. */
|
|
@@ -52,7 +52,7 @@ export class AbstractTokenService {
|
|
|
52
52
|
return Promise.resolve(scope);
|
|
53
53
|
}
|
|
54
54
|
/** Generates an opaque access token string. Override to issue a different format. */
|
|
55
|
-
generateAccessToken(_client, _user, _scope, _authenticationContext) {
|
|
55
|
+
generateAccessToken(_client, _user, _scope, _authenticationContext, _expiresAt) {
|
|
56
56
|
return Promise.resolve(crypto.randomUUID());
|
|
57
57
|
}
|
|
58
58
|
/** Generates an opaque refresh token string. Override to issue a different format. */
|
|
@@ -115,7 +115,11 @@ export declare function generateSigningKey(): Promise<SigningKey>;
|
|
|
115
115
|
export declare function exportSigningKeyJwk(key: SigningKey): Promise<JsonWebKey & {
|
|
116
116
|
kid: string;
|
|
117
117
|
}>;
|
|
118
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Import a signing key previously exported with {@link exportSigningKeyJwk}.
|
|
120
|
+
* A JWK without a `kid` is given a random one, so every instance importing it
|
|
121
|
+
* would publish and stamp a different `kid` — persist the exported `kid`.
|
|
122
|
+
*/
|
|
119
123
|
export declare function importSigningKeyJwk(jwk: JsonWebKey & {
|
|
120
124
|
kid?: string;
|
|
121
125
|
}): Promise<SigningKey>;
|
|
@@ -139,10 +143,12 @@ export interface VerifyJwtOptions {
|
|
|
139
143
|
}
|
|
140
144
|
/**
|
|
141
145
|
* Verify a compact JWS against a public JWK and return its payload, or
|
|
142
|
-
* `undefined` when the signature (or shape) is invalid or
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
+
* `undefined` when the signature (or shape) is invalid or a numeric `exp` is
|
|
147
|
+
* not in the future. It verifies ES256 regardless of the header's `alg` and
|
|
148
|
+
* checks nothing else: a payload with no `exp` passes, and `iss`, `aud`, and
|
|
149
|
+
* `nbf` are the caller's to check. Exported for tests and lightweight
|
|
150
|
+
* in-process verification; production resource servers should verify against
|
|
151
|
+
* the JWKS endpoint with their JOSE library of choice.
|
|
146
152
|
*/
|
|
147
153
|
export declare function verifyJwt(jwt: string, publicJwk: JsonWebKey, options?: VerifyJwtOptions): Promise<Record<string, unknown> | undefined>;
|
|
148
154
|
/**
|
|
@@ -159,8 +165,10 @@ export declare function verifyJwt(jwt: string, publicJwk: JsonWebKey, options?:
|
|
|
159
165
|
* }
|
|
160
166
|
* ```
|
|
161
167
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
168
|
+
* Your token store persists the JWT string like any access token, so
|
|
169
|
+
* revocation and introspection work as with opaque tokens for readers that
|
|
170
|
+
* consult the store. A resource server that verifies the JWT offline against
|
|
171
|
+
* JWKS does not see a revocation and accepts the token until its `exp`.
|
|
164
172
|
*/
|
|
165
173
|
export declare function createJwtAccessTokenGenerator(options: {
|
|
166
174
|
/** Keys the tokens are signed with (also served from JWKS). */
|
|
@@ -173,7 +181,14 @@ export declare function createJwtAccessTokenGenerator(options: {
|
|
|
173
181
|
* the client id, which single-resource validators must then accept.
|
|
174
182
|
*/
|
|
175
183
|
audience?: string;
|
|
176
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* Upper bound in seconds on the `exp` claim. Defaults to 3600. The claim
|
|
186
|
+
* is the earlier of this and the expiry the grant passes as the fifth
|
|
187
|
+
* argument — the token service's `accessTokenExpiresAt`, already clamped to
|
|
188
|
+
* any refresh-family cap — so an offline verifier never accepts the token
|
|
189
|
+
* past the server's own expiry. A wrapper that drops that argument falls
|
|
190
|
+
* back to this lifetime alone.
|
|
191
|
+
*/
|
|
177
192
|
lifetimeSeconds?: number;
|
|
178
193
|
/** Maps a user to the `sub` claim. Defaults to the user's `id` property. */
|
|
179
194
|
subjectOf?: (user: unknown) => string;
|
|
@@ -198,4 +213,4 @@ export declare function createJwtAccessTokenGenerator(options: {
|
|
|
198
213
|
id: string;
|
|
199
214
|
}, user: unknown, scope?: {
|
|
200
215
|
toString(): string;
|
|
201
|
-
} | null, authenticationContext?: AuthenticationContext) => Promise<string>;
|
|
216
|
+
} | null, authenticationContext?: AuthenticationContext, expiresAt?: Date) => Promise<string>;
|
|
@@ -128,7 +128,11 @@ export async function exportSigningKeyJwk(key) {
|
|
|
128
128
|
const jwk = await crypto.subtle.exportKey("jwk", key.privateKey);
|
|
129
129
|
return { ...jwk, kid: key.kid, alg: key.alg };
|
|
130
130
|
}
|
|
131
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* Import a signing key previously exported with {@link exportSigningKeyJwk}.
|
|
133
|
+
* A JWK without a `kid` is given a random one, so every instance importing it
|
|
134
|
+
* would publish and stamp a different `kid` — persist the exported `kid`.
|
|
135
|
+
*/
|
|
132
136
|
export async function importSigningKeyJwk(jwk) {
|
|
133
137
|
const privateKey = await crypto.subtle.importKey("jwk", jwk, ES256_PARAMS, true, ["sign"]);
|
|
134
138
|
const kid = jwk.kid ?? crypto.randomUUID();
|
|
@@ -150,10 +154,12 @@ export async function signJwt(key, payload, header = {}) {
|
|
|
150
154
|
}
|
|
151
155
|
/**
|
|
152
156
|
* Verify a compact JWS against a public JWK and return its payload, or
|
|
153
|
-
* `undefined` when the signature (or shape) is invalid or
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
+
* `undefined` when the signature (or shape) is invalid or a numeric `exp` is
|
|
158
|
+
* not in the future. It verifies ES256 regardless of the header's `alg` and
|
|
159
|
+
* checks nothing else: a payload with no `exp` passes, and `iss`, `aud`, and
|
|
160
|
+
* `nbf` are the caller's to check. Exported for tests and lightweight
|
|
161
|
+
* in-process verification; production resource servers should verify against
|
|
162
|
+
* the JWKS endpoint with their JOSE library of choice.
|
|
157
163
|
*/
|
|
158
164
|
export async function verifyJwt(jwt, publicJwk, options = {}) {
|
|
159
165
|
const parts = jwt.split(".");
|
|
@@ -190,13 +196,15 @@ export async function verifyJwt(jwt, publicJwk, options = {}) {
|
|
|
190
196
|
* }
|
|
191
197
|
* ```
|
|
192
198
|
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
199
|
+
* Your token store persists the JWT string like any access token, so
|
|
200
|
+
* revocation and introspection work as with opaque tokens for readers that
|
|
201
|
+
* consult the store. A resource server that verifies the JWT offline against
|
|
202
|
+
* JWKS does not see a revocation and accepts the token until its `exp`.
|
|
195
203
|
*/
|
|
196
204
|
export function createJwtAccessTokenGenerator(options) {
|
|
197
205
|
const lifetime = options.lifetimeSeconds ?? 3600;
|
|
198
206
|
const subjectOf = options.subjectOf ?? defaultSubjectOf;
|
|
199
|
-
return async (client, user, scope, authenticationContext) => {
|
|
207
|
+
return async (client, user, scope, authenticationContext, expiresAt) => {
|
|
200
208
|
const event = user
|
|
201
209
|
? snapshotAuthenticationContext(authenticationContext)
|
|
202
210
|
: undefined;
|
|
@@ -205,6 +213,9 @@ export function createJwtAccessTokenGenerator(options) {
|
|
|
205
213
|
const extraClaims = user && options.userClaims
|
|
206
214
|
? await options.userClaims(user, scope, client, event)
|
|
207
215
|
: undefined;
|
|
216
|
+
const storedExp = expiresAt
|
|
217
|
+
? Math.floor(expiresAt.getTime() / 1000)
|
|
218
|
+
: now + lifetime;
|
|
208
219
|
const payload = {
|
|
209
220
|
...extraClaims,
|
|
210
221
|
iss: options.issuer,
|
|
@@ -212,7 +223,7 @@ export function createJwtAccessTokenGenerator(options) {
|
|
|
212
223
|
client_id: client.id,
|
|
213
224
|
aud: options.audience ?? client.id,
|
|
214
225
|
iat: now,
|
|
215
|
-
exp: now + lifetime,
|
|
226
|
+
exp: Math.min(now + lifetime, storedExp),
|
|
216
227
|
jti: crypto.randomUUID(),
|
|
217
228
|
};
|
|
218
229
|
const scopeText = scope?.toString();
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
* sequential case and fails the concurrent one, which is the whole
|
|
20
20
|
* reason the suites exist.
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
* methods aren't part of the public service
|
|
24
|
-
* tells the runner how to populate test data.
|
|
22
|
+
* The service runners take a factory plus seed helpers because `add` /
|
|
23
|
+
* `register` methods aren't part of the public service interfaces — the
|
|
24
|
+
* consumer tells the runner how to populate test data.
|
|
25
25
|
*
|
|
26
26
|
* @module
|
|
27
27
|
*/
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
* sequential case and fails the concurrent one, which is the whole
|
|
20
20
|
* reason the suites exist.
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
* methods aren't part of the public service
|
|
24
|
-
* tells the runner how to populate test data.
|
|
22
|
+
* The service runners take a factory plus seed helpers because `add` /
|
|
23
|
+
* `register` methods aren't part of the public service interfaces — the
|
|
24
|
+
* consumer tells the runner how to populate test data.
|
|
25
25
|
*
|
|
26
26
|
* @module
|
|
27
27
|
*/
|
package/esm/testing/mod.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Testing helpers for apps that use `@udibo/oauth2`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Three layers ship:
|
|
5
5
|
*
|
|
6
6
|
* - **Testing surface for the OAuth2 protocol.** The recommended pattern
|
|
7
7
|
* for testing apps that use the resource server is to expose your
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
* - **In-memory service implementations.** `MemoryUserService`,
|
|
21
21
|
* `MemoryClientService`, `MemoryTokenService`,
|
|
22
22
|
* `MemoryAuthorizationCodeService`, and `MemoryDeviceAuthorizationService`
|
|
23
|
-
*
|
|
23
|
+
* implement the authorization server's user, client, token,
|
|
24
|
+
* authorization-code and device-authorization service interfaces, generic
|
|
25
|
+
* over the consumer's
|
|
24
26
|
* user / client / scope types. Suitable for tests, examples, and
|
|
25
27
|
* prototypes — never for production.
|
|
26
28
|
*
|
package/esm/testing/mod.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Testing helpers for apps that use `@udibo/oauth2`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Three layers ship:
|
|
5
5
|
*
|
|
6
6
|
* - **Testing surface for the OAuth2 protocol.** The recommended pattern
|
|
7
7
|
* for testing apps that use the resource server is to expose your
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
* - **In-memory service implementations.** `MemoryUserService`,
|
|
21
21
|
* `MemoryClientService`, `MemoryTokenService`,
|
|
22
22
|
* `MemoryAuthorizationCodeService`, and `MemoryDeviceAuthorizationService`
|
|
23
|
-
*
|
|
23
|
+
* implement the authorization server's user, client, token,
|
|
24
|
+
* authorization-code and device-authorization service interfaces, generic
|
|
25
|
+
* over the consumer's
|
|
24
26
|
* user / client / scope types. Suitable for tests, examples, and
|
|
25
27
|
* prototypes — never for production.
|
|
26
28
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* In-memory implementations of
|
|
2
|
+
* In-memory implementations of the authorization server's service interfaces.
|
|
3
3
|
*
|
|
4
4
|
* The `Memory*Service` family is the public, generic version of the fixtures
|
|
5
5
|
* the library uses for its own tests. They satisfy the
|
package/esm/testing/services.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* In-memory implementations of
|
|
2
|
+
* In-memory implementations of the authorization server's service interfaces.
|
|
3
3
|
*
|
|
4
4
|
* The `Memory*Service` family is the public, generic version of the fixtures
|
|
5
5
|
* the library uses for its own tests. They satisfy the
|
package/esm/testing/tenant.d.ts
CHANGED
|
@@ -124,9 +124,17 @@ export interface FakeTenant {
|
|
|
124
124
|
addUser(user: FakeTenantUser): Promise<void>;
|
|
125
125
|
/** Adds an organization. Throws on a duplicate id. */
|
|
126
126
|
addOrganization(organization: FakeTenantOrganization): void;
|
|
127
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Makes a person a member of an organization, replacing any earlier
|
|
129
|
+
* membership. Throws when the organization does not exist.
|
|
130
|
+
*/
|
|
128
131
|
addMember(organizationId: string, userId: string, membership?: FakeTenantMembership): void;
|
|
129
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Ends a membership. From then on introspection and `/api/check` stop
|
|
134
|
+
* answering for that organization on credentials issued in it, and UserInfo
|
|
135
|
+
* drops its `org_*` claims; a JWT access token or id_token already minted
|
|
136
|
+
* keeps its claims until it expires.
|
|
137
|
+
*/
|
|
130
138
|
removeMember(organizationId: string, userId: string): void;
|
|
131
139
|
/** Registers a resource type, so `/api/check` accepts it. */
|
|
132
140
|
registerResourceType(type: string): void;
|
|
@@ -134,12 +142,13 @@ export interface FakeTenant {
|
|
|
134
142
|
grant(grant: FakeTenantGrant): void;
|
|
135
143
|
/**
|
|
136
144
|
* Chooses who the next authorization requests authenticate as, until
|
|
137
|
-
* called again. With nobody chosen, authorize answers `
|
|
145
|
+
* called again. With nobody chosen, authorize answers `access_denied`.
|
|
138
146
|
*/
|
|
139
147
|
signInAs(userId: string | null, signIn?: FakeTenantSignIn): void;
|
|
140
148
|
/**
|
|
141
149
|
* Mints an access token without the browser flow, for testing an API
|
|
142
|
-
* directly. JWT or opaque as the client is registered.
|
|
150
|
+
* directly. JWT or opaque as the client is registered. Throws when the
|
|
151
|
+
* client or user does not exist.
|
|
143
152
|
*/
|
|
144
153
|
issueAccessToken(request: FakeTenantTokenRequest): Promise<string>;
|
|
145
154
|
}
|
package/esm/utils/_buffer.d.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Copies the viewed bytes into a standalone `ArrayBuffer`, for passing a
|
|
8
|
+
* `Uint8Array<ArrayBufferLike>` where Web Crypto's `BufferSource` type does not
|
|
9
|
+
* accept it.
|
|
9
10
|
*/
|
|
10
11
|
export declare function toArrayBuffer(bytes: Uint8Array): ArrayBuffer;
|
package/esm/utils/_buffer.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Copies the viewed bytes into a standalone `ArrayBuffer`, for passing a
|
|
8
|
+
* `Uint8Array<ArrayBufferLike>` where Web Crypto's `BufferSource` type does not
|
|
9
|
+
* accept it.
|
|
9
10
|
*/
|
|
10
11
|
export function toArrayBuffer(bytes) {
|
|
11
12
|
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Internal
|
|
2
|
+
* Internal timing-safe matching for secret candidates.
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* Compares `target` against every candidate with no early exit, so the scan's
|
|
8
|
+
* duration does not reveal where a match occurred. Each comparison is
|
|
9
|
+
* {@link timingSafeEqualString}, which returns early on a byte-length mismatch,
|
|
10
|
+
* so candidates of a different length than `target` are distinguishable by
|
|
11
|
+
* timing. Returns the index of the *last* matching candidate, or `undefined`
|
|
12
|
+
* when none match — order candidates least- to most-preferred so a duplicate
|
|
13
|
+
* resolves to the preferred entry.
|
|
12
14
|
*/
|
|
13
15
|
export declare function timingSafeMatchIndex(candidates: string[], target: string): number | undefined;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Internal
|
|
2
|
+
* Internal timing-safe matching for secret candidates.
|
|
3
3
|
*
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
import { timingSafeEqualString } from "./crypto.js";
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* Compares `target` against every candidate with no early exit, so the scan's
|
|
9
|
+
* duration does not reveal where a match occurred. Each comparison is
|
|
10
|
+
* {@link timingSafeEqualString}, which returns early on a byte-length mismatch,
|
|
11
|
+
* so candidates of a different length than `target` are distinguishable by
|
|
12
|
+
* timing. Returns the index of the *last* matching candidate, or `undefined`
|
|
13
|
+
* when none match — order candidates least- to most-preferred so a duplicate
|
|
14
|
+
* resolves to the preferred entry.
|
|
13
15
|
*/
|
|
14
16
|
export function timingSafeMatchIndex(candidates, target) {
|
|
15
17
|
let index;
|
|
@@ -18,9 +18,8 @@ export interface BasicAuth {
|
|
|
18
18
|
* trip. A segment that is not valid percent-encoding is taken verbatim, so a
|
|
19
19
|
* client that sends such characters un-encoded still authenticates.
|
|
20
20
|
*
|
|
21
|
-
* @
|
|
22
|
-
*
|
|
23
|
-
* @throws {InvalidClientError} If the header is missing or malformed
|
|
21
|
+
* @throws {InvalidClientError} If the header is missing, not `Basic`, not
|
|
22
|
+
* valid base64, or does not decode to `name:pass` with a non-empty name.
|
|
24
23
|
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
|
|
25
24
|
*/
|
|
26
25
|
export declare function parseBasicAuth(authorization: string | null): BasicAuth;
|
|
@@ -33,18 +32,12 @@ export declare function parseBasicAuth(authorization: string | null): BasicAuth;
|
|
|
33
32
|
* only of `A-Z`, `a-z`, `0-9`, `-`, `_`, `.` and `*` encode byte-identically to
|
|
34
33
|
* their raw form; every other character, `~` included, is percent-encoded.
|
|
35
34
|
*
|
|
36
|
-
* @
|
|
37
|
-
* @param clientSecret The client secret
|
|
38
|
-
* @returns The encoded Authorization header value
|
|
35
|
+
* @returns The `Basic …` Authorization header value.
|
|
39
36
|
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1
|
|
40
37
|
*/
|
|
41
38
|
export declare function encodeBasicAuth(clientId: string, clientSecret: string): string;
|
|
42
39
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* segments the same way {@link parseBasicAuth} does.
|
|
46
|
-
*
|
|
47
|
-
* @param authorization The Authorization header value
|
|
48
|
-
* @returns The parsed credentials or undefined
|
|
40
|
+
* Non-throwing {@link parseBasicAuth}: returns `undefined` wherever that would
|
|
41
|
+
* throw — a missing, non-`Basic`, badly encoded or malformed header.
|
|
49
42
|
*/
|
|
50
43
|
export declare function tryParseBasicAuth(authorization: string | null): BasicAuth | undefined;
|