@udibo/oauth2 0.4.1 → 0.5.1
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/contract/tenant.d.ts +1 -1
- package/esm/testing/contract/tenant.js +2 -2
- package/esm/testing/mod.d.ts +6 -4
- package/esm/testing/mod.js +6 -4
- package/esm/testing/services.d.ts +1 -1
- package/esm/testing/services.js +1 -1
- package/esm/testing/tenant.d.ts +14 -5
- 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/README.md
CHANGED
|
@@ -27,7 +27,7 @@ without a hosted account.
|
|
|
27
27
|
Requires Deno 2 for the development workflow:
|
|
28
28
|
|
|
29
29
|
```sh
|
|
30
|
-
deno add jsr:@udibo/oauth2
|
|
30
|
+
deno add jsr:@udibo/oauth2
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
Import the part you use; there is no root barrel or default export:
|
|
@@ -38,8 +38,8 @@ import { HonoBff } from "@udibo/oauth2/hono/bff";
|
|
|
38
38
|
import { HonoResourceServer } from "@udibo/oauth2/hono/resource-server";
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
The package is pre-1.0. Minor releases may contain breaking changes; review the
|
|
42
|
+
[stability policy](docs/stability.md) before upgrading.
|
|
43
43
|
|
|
44
44
|
## How the pieces fit
|
|
45
45
|
|
package/SECURITY.md
CHANGED
|
@@ -4,10 +4,6 @@
|
|
|
4
4
|
OpenID Connect protocol surface and password-based identity flows. We take
|
|
5
5
|
reports seriously and value the time of security researchers.
|
|
6
6
|
|
|
7
|
-
> **Status note:** the package is not yet published to a public registry. Until
|
|
8
|
-
> it is, the disclosure channel below is drafted and monitored, but the package
|
|
9
|
-
> has no external users; the process activates fully at publish.
|
|
10
|
-
|
|
11
7
|
## Supported versions
|
|
12
8
|
|
|
13
9
|
| Version | Supported |
|
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
* Internal helpers shared by the Hono adapters.
|
|
3
3
|
*
|
|
4
4
|
* Not part of the public API — use {@link HonoResourceServer} or
|
|
5
|
-
* {@link HonoAuthorizationServer} instead.
|
|
6
|
-
*
|
|
7
|
-
* adapter classes stay as direct subclasses of the server type they
|
|
8
|
-
* specialize (no composition / companion objects) without duplicating the
|
|
9
|
-
* Hono-specific plumbing.
|
|
5
|
+
* {@link HonoAuthorizationServer} instead. Only {@link OAUTH2_CONTEXT_KEY}
|
|
6
|
+
* and {@link HonoResourceServerVariables} are re-exported publicly.
|
|
10
7
|
*
|
|
11
8
|
* @module
|
|
12
9
|
*/
|
|
@@ -37,7 +34,7 @@ export interface HonoResourceServerVariables<Client extends ClientInterface, Use
|
|
|
37
34
|
export declare function createProtectMiddleware<Client extends ClientInterface, User, S extends AbstractScope>(server: ResourceServer<Client, User, S>, requiredScope?: S | string): MiddlewareHandler;
|
|
38
35
|
/**
|
|
39
36
|
* Builds a Hono middleware that asserts the **already-authenticated** request
|
|
40
|
-
* (whose context a prior
|
|
37
|
+
* (whose context a prior adapter or BFF `protect` set) carries
|
|
41
38
|
* `requiredScope`, without re-validating the token. Shared by both adapter
|
|
42
39
|
* classes' `requireScope` so the check and error stay identical to `protect`'s
|
|
43
40
|
* scope enforcement. `adapterName` names the class in the "context missing"
|
|
@@ -52,15 +49,15 @@ export declare function createRequireScopeMiddleware<Client extends ClientInterf
|
|
|
52
49
|
*/
|
|
53
50
|
export declare function createRequireMiddleware<Client extends ClientInterface, User, S extends AbstractScope>(server: ResourceServer<Client, User, S>, adapterName: string, conditions: RequireConditions): MiddlewareHandler;
|
|
54
51
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* Context-accepting override
|
|
58
|
-
* `authenticate` overrides end up calling.
|
|
52
|
+
* Authenticates a Hono {@link Context} or raw {@link Request} with the base
|
|
53
|
+
* {@link ResourceServer.authenticate}, bypassing the adapters'
|
|
54
|
+
* Context-accepting override.
|
|
59
55
|
*/
|
|
60
56
|
export declare function authenticateHonoRequest<Client extends ClientInterface, User, S extends AbstractScope>(server: ResourceServer<Client, User, S>, c: Context | Request, requiredScope?: S | string): Promise<AuthenticatedContext<Client, User, S>>;
|
|
61
57
|
/**
|
|
62
|
-
* Reads the authenticated OAuth2 context off the Hono context.
|
|
63
|
-
*
|
|
64
|
-
*
|
|
58
|
+
* Reads the authenticated OAuth2 context off the Hono context.
|
|
59
|
+
*
|
|
60
|
+
* @throws {Error} If no `protect` (the adapter's or the BFF's) middleware set
|
|
61
|
+
* it first; the message names `adapterName`.
|
|
65
62
|
*/
|
|
66
63
|
export declare function readContext<Client extends ClientInterface, User, S extends AbstractScope>(c: Context, adapterName: string): AuthenticatedContext<Client, User, S>;
|
|
@@ -2,11 +2,8 @@
|
|
|
2
2
|
* Internal helpers shared by the Hono adapters.
|
|
3
3
|
*
|
|
4
4
|
* Not part of the public API — use {@link HonoResourceServer} or
|
|
5
|
-
* {@link HonoAuthorizationServer} instead.
|
|
6
|
-
*
|
|
7
|
-
* adapter classes stay as direct subclasses of the server type they
|
|
8
|
-
* specialize (no composition / companion objects) without duplicating the
|
|
9
|
-
* Hono-specific plumbing.
|
|
5
|
+
* {@link HonoAuthorizationServer} instead. Only {@link OAUTH2_CONTEXT_KEY}
|
|
6
|
+
* and {@link HonoResourceServerVariables} are re-exported publicly.
|
|
10
7
|
*
|
|
11
8
|
* @module
|
|
12
9
|
*/
|
|
@@ -35,7 +32,7 @@ export function createProtectMiddleware(server, requiredScope) {
|
|
|
35
32
|
}
|
|
36
33
|
/**
|
|
37
34
|
* Builds a Hono middleware that asserts the **already-authenticated** request
|
|
38
|
-
* (whose context a prior
|
|
35
|
+
* (whose context a prior adapter or BFF `protect` set) carries
|
|
39
36
|
* `requiredScope`, without re-validating the token. Shared by both adapter
|
|
40
37
|
* classes' `requireScope` so the check and error stay identical to `protect`'s
|
|
41
38
|
* scope enforcement. `adapterName` names the class in the "context missing"
|
|
@@ -72,31 +69,25 @@ export function createRequireMiddleware(server, adapterName, conditions) {
|
|
|
72
69
|
};
|
|
73
70
|
}
|
|
74
71
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* Context-accepting override
|
|
78
|
-
* `authenticate` overrides end up calling.
|
|
72
|
+
* Authenticates a Hono {@link Context} or raw {@link Request} with the base
|
|
73
|
+
* {@link ResourceServer.authenticate}, bypassing the adapters'
|
|
74
|
+
* Context-accepting override.
|
|
79
75
|
*/
|
|
80
76
|
export function authenticateHonoRequest(server, c, requiredScope) {
|
|
81
77
|
const request = c instanceof Request ? c : c.req.raw;
|
|
82
78
|
return baseAuthenticate(server, request, requiredScope);
|
|
83
79
|
}
|
|
84
|
-
|
|
85
|
-
* Invokes the base {@link ResourceServer.authenticate} directly, bypassing
|
|
86
|
-
* any subclass override. Necessary because {@link HonoResourceServer}
|
|
87
|
-
* overrides `authenticate` to accept `Context | Request`; if this module
|
|
88
|
-
* called `server.authenticate(...)` the dispatch would land back on that
|
|
89
|
-
* override and recurse forever.
|
|
90
|
-
*/
|
|
80
|
+
// Base method on purpose: the Hono subclasses' override would recurse.
|
|
91
81
|
function baseAuthenticate(server, request, requiredScope) {
|
|
92
82
|
// deno-lint-ignore no-explicit-any
|
|
93
83
|
const base = ResourceServer.prototype;
|
|
94
84
|
return base.authenticate.call(server, request, requiredScope);
|
|
95
85
|
}
|
|
96
86
|
/**
|
|
97
|
-
* Reads the authenticated OAuth2 context off the Hono context.
|
|
98
|
-
*
|
|
99
|
-
*
|
|
87
|
+
* Reads the authenticated OAuth2 context off the Hono context.
|
|
88
|
+
*
|
|
89
|
+
* @throws {Error} If no `protect` (the adapter's or the BFF's) middleware set
|
|
90
|
+
* it first; the message names `adapterName`.
|
|
100
91
|
*/
|
|
101
92
|
export function readContext(c, adapterName) {
|
|
102
93
|
const ctx = c.get(OAUTH2_CONTEXT_KEY);
|
|
@@ -69,8 +69,9 @@ export type HonoAuthenticateUserFn<User> = (c: Context) => Promise<{
|
|
|
69
69
|
* Receives the Hono {@link Context} so the callback can render a consent
|
|
70
70
|
* page, read form input, etc. Return one of:
|
|
71
71
|
*
|
|
72
|
-
* - `{ approved: true, scope? }` — consent granted
|
|
73
|
-
* scope
|
|
72
|
+
* - `{ approved: true, scope? }` — consent granted. `scope`, when set,
|
|
73
|
+
* replaces the granted scope (RFC 6749 §3.3) and is not checked against the
|
|
74
|
+
* request, so return a subset.
|
|
74
75
|
* - `{ approved: false }` — consent denied; the framework redirects to
|
|
75
76
|
* `redirect_uri` with `error=access_denied`.
|
|
76
77
|
* - A `Response` (typically `c.html(...)` rendering a consent page) —
|
|
@@ -93,8 +94,9 @@ export interface HonoRoutesOptions<Client extends ClientInterface, User, S exten
|
|
|
93
94
|
export type HonoAuthorizeHandlerOptions<Client extends ClientInterface, User, S extends AbstractScope> = HonoRoutesOptions<Client, User, S>;
|
|
94
95
|
/**
|
|
95
96
|
* Hono-specific wrapper around {@link AuthorizationServer}. Adds endpoint
|
|
96
|
-
* handler factories and the
|
|
97
|
-
*
|
|
97
|
+
* handler factories and the resource-server-shaped helpers (`protect`,
|
|
98
|
+
* `requireScope`, `require`, `authenticate`, `getContext`) against this same
|
|
99
|
+
* instance.
|
|
98
100
|
*
|
|
99
101
|
* Resource-server behavior (realm, errorFormat, throwOnError, token lookup)
|
|
100
102
|
* reads directly off the auth-server instance, so mutating those fields
|
|
@@ -107,8 +109,9 @@ export declare class HonoAuthorizationServer<Client extends ClientInterface, Use
|
|
|
107
109
|
/**
|
|
108
110
|
* Returns a Hono middleware that validates the bearer token against this
|
|
109
111
|
* server's token service. On success, sets the authenticated context at
|
|
110
|
-
* `c.get("@udibo/oauth2")`; on failure returns an
|
|
111
|
-
*
|
|
112
|
+
* `c.get("@udibo/oauth2")`; on failure returns an error response, with a
|
|
113
|
+
* `WWW-Authenticate` challenge (RFC 6750 Section 3) on a 401 or 403, or
|
|
114
|
+
* rethrows when `throwOnError` is set.
|
|
112
115
|
*/
|
|
113
116
|
protect(requiredScope?: S | string): MiddlewareHandler;
|
|
114
117
|
/**
|
|
@@ -198,14 +201,17 @@ export declare class HonoAuthorizationServer<Client extends ClientInterface, Use
|
|
|
198
201
|
* - `GET /.well-known/openid-configuration`
|
|
199
202
|
* - `GET /jwks` and `GET`/`POST /userinfo` (live when OIDC issuance is
|
|
200
203
|
* configured via `signingKeys`)
|
|
204
|
+
* - `GET`/`POST /end_session` (live when the server has an `endSession`
|
|
205
|
+
* callback)
|
|
201
206
|
*
|
|
202
207
|
* Mount with `app.route("/oauth2", server.routes({...}))`.
|
|
203
208
|
*
|
|
204
209
|
* Every path and verb above comes from
|
|
205
|
-
* {@link ENDPOINT_PATHS}/{@link ENDPOINT_METHODS},
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
210
|
+
* {@link ENDPOINT_PATHS}/{@link ENDPOINT_METHODS}, the same paths the server
|
|
211
|
+
* derives from an `issuer` alone. The advertised URLs match the mount only
|
|
212
|
+
* when the issuer (or each explicit `*Endpoint` in the resolved context)
|
|
213
|
+
* points at where you mounted it. Users who want custom paths or only a
|
|
214
|
+
* subset of endpoints should mount the individual handler factories instead.
|
|
209
215
|
*/
|
|
210
216
|
routes(options: HonoRoutesOptions<Client, User, S>): Hono;
|
|
211
217
|
}
|
|
@@ -38,8 +38,9 @@ import { AuthorizationServer, ENDPOINT_METHODS, ENDPOINT_PATHS, } from "../../se
|
|
|
38
38
|
import { authenticateHonoRequest, createProtectMiddleware, createRequireMiddleware, createRequireScopeMiddleware, readContext, } from "./_common.js";
|
|
39
39
|
/**
|
|
40
40
|
* Hono-specific wrapper around {@link AuthorizationServer}. Adds endpoint
|
|
41
|
-
* handler factories and the
|
|
42
|
-
*
|
|
41
|
+
* handler factories and the resource-server-shaped helpers (`protect`,
|
|
42
|
+
* `requireScope`, `require`, `authenticate`, `getContext`) against this same
|
|
43
|
+
* instance.
|
|
43
44
|
*
|
|
44
45
|
* Resource-server behavior (realm, errorFormat, throwOnError, token lookup)
|
|
45
46
|
* reads directly off the auth-server instance, so mutating those fields
|
|
@@ -54,8 +55,9 @@ export class HonoAuthorizationServer extends AuthorizationServer {
|
|
|
54
55
|
/**
|
|
55
56
|
* Returns a Hono middleware that validates the bearer token against this
|
|
56
57
|
* server's token service. On success, sets the authenticated context at
|
|
57
|
-
* `c.get("@udibo/oauth2")`; on failure returns an
|
|
58
|
-
*
|
|
58
|
+
* `c.get("@udibo/oauth2")`; on failure returns an error response, with a
|
|
59
|
+
* `WWW-Authenticate` challenge (RFC 6750 Section 3) on a 401 or 403, or
|
|
60
|
+
* rethrows when `throwOnError` is set.
|
|
59
61
|
*/
|
|
60
62
|
protect(requiredScope) {
|
|
61
63
|
return createProtectMiddleware(this, requiredScope);
|
|
@@ -182,14 +184,17 @@ export class HonoAuthorizationServer extends AuthorizationServer {
|
|
|
182
184
|
* - `GET /.well-known/openid-configuration`
|
|
183
185
|
* - `GET /jwks` and `GET`/`POST /userinfo` (live when OIDC issuance is
|
|
184
186
|
* configured via `signingKeys`)
|
|
187
|
+
* - `GET`/`POST /end_session` (live when the server has an `endSession`
|
|
188
|
+
* callback)
|
|
185
189
|
*
|
|
186
190
|
* Mount with `app.route("/oauth2", server.routes({...}))`.
|
|
187
191
|
*
|
|
188
192
|
* Every path and verb above comes from
|
|
189
|
-
* {@link ENDPOINT_PATHS}/{@link ENDPOINT_METHODS},
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
+
* {@link ENDPOINT_PATHS}/{@link ENDPOINT_METHODS}, the same paths the server
|
|
194
|
+
* derives from an `issuer` alone. The advertised URLs match the mount only
|
|
195
|
+
* when the issuer (or each explicit `*Endpoint` in the resolved context)
|
|
196
|
+
* points at where you mounted it. Users who want custom paths or only a
|
|
197
|
+
* subset of endpoints should mount the individual handler factories instead.
|
|
193
198
|
*/
|
|
194
199
|
routes(options) {
|
|
195
200
|
const handlers = {
|
|
@@ -123,6 +123,10 @@ export interface AuthRequestStorageFactory {
|
|
|
123
123
|
* `unknown state parameter`. The AES key is derived on first use and shared by
|
|
124
124
|
* every later request.
|
|
125
125
|
*
|
|
126
|
+
* It has no `take`: the record travels with the browser and cannot be claimed
|
|
127
|
+
* atomically, so a replayed callback is bounded by the authorization server's
|
|
128
|
+
* single-use code and PKCE.
|
|
129
|
+
*
|
|
126
130
|
* @example
|
|
127
131
|
* ```ts
|
|
128
132
|
* const bff = new HonoBff({
|
|
@@ -53,6 +53,10 @@ function cookieAttributes(options, maxAgeSeconds) {
|
|
|
53
53
|
* `unknown state parameter`. The AES key is derived on first use and shared by
|
|
54
54
|
* every later request.
|
|
55
55
|
*
|
|
56
|
+
* It has no `take`: the record travels with the browser and cannot be claimed
|
|
57
|
+
* atomically, so a replayed callback is bounded by the authorization server's
|
|
58
|
+
* single-use code and PKCE.
|
|
59
|
+
*
|
|
56
60
|
* @example
|
|
57
61
|
* ```ts
|
|
58
62
|
* const bff = new HonoBff({
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Wraps a {@link DirectClient} configured with a client secret
|
|
5
5
|
* and exposes the session endpoints a browser SPA talks to:
|
|
6
6
|
*
|
|
7
|
-
* - `POST /auth/login` — redirects the browser to the authorize endpoint,
|
|
7
|
+
* - `GET|POST /auth/login` — redirects the browser to the authorize endpoint,
|
|
8
8
|
* persisting a per-request state/verifier record and binding its `state` to
|
|
9
9
|
* this browser with a short-lived cookie.
|
|
10
10
|
* - `GET /auth/callback` — requires the browser to present that binding
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
* the refresh token upstream. Registered for both methods because the
|
|
16
16
|
* shipped React client navigates the browser here (a GET).
|
|
17
17
|
* - `GET /auth/session` — returns `{ isAuthenticated, user }` from the
|
|
18
|
-
* session
|
|
18
|
+
* session, plus `sessionExpiresIn` and `logoutUrl` when signed in.
|
|
19
|
+
* **Never** exposes tokens to the browser.
|
|
19
20
|
* - `POST /auth/backchannel` — OIDC Back-Channel Logout receiver, mounted only
|
|
20
21
|
* when `backchannelLogout` is configured. A server-to-server call from the OP
|
|
21
22
|
* that ends the matching server-side session(s).
|
|
@@ -62,12 +63,12 @@ import { type HonoBffProxyOptions } from "./proxy.js";
|
|
|
62
63
|
*
|
|
63
64
|
* Accepted in two shapes so both deployment topologies stay symmetric:
|
|
64
65
|
*
|
|
65
|
-
* - **Own-issuer
|
|
66
|
+
* - **Own-issuer**: pass a {@link HonoAuthorizationServer}.
|
|
66
67
|
* `protect()` validates against the same in-process token service the
|
|
67
68
|
* issuer uses — no introspection round-trip.
|
|
68
69
|
* - **Third-party-issuer**: pass a {@link HonoResourceServer} configured
|
|
69
|
-
* with `IntrospectionTokenReader
|
|
70
|
-
*
|
|
70
|
+
* with `IntrospectionTokenReader` (RFC 7662 against the upstream) or
|
|
71
|
+
* `JwksTokenReader` (signed JWT access tokens, verified locally).
|
|
71
72
|
*/
|
|
72
73
|
export type HonoBffResourceServer<Client extends ClientInterface = any, User = any, S extends AbstractScope = BasicScope> = HonoResourceServer<Client, User, S> | HonoAuthorizationServer<Client, User, S>;
|
|
73
74
|
/**
|
|
@@ -122,9 +123,11 @@ export interface HonoBffCookieOptions {
|
|
|
122
123
|
*/
|
|
123
124
|
domain?: string;
|
|
124
125
|
/**
|
|
125
|
-
* `Max-Age` in seconds, for the **cookie only**.
|
|
126
|
+
* `Max-Age` in seconds, for the **cookie only**. In the default
|
|
127
|
+
* `sessionMode: "own"` it defaults to
|
|
126
128
|
* {@linkcode HonoBffOptions.sessionMaxAgeMs}, so the browser keeps the
|
|
127
|
-
* cookie exactly as long as the session behind it is honored
|
|
129
|
+
* cookie exactly as long as the session behind it is honored; in
|
|
130
|
+
* `sessionMode: "shared"` it has no default and is not cross-checked.
|
|
128
131
|
*
|
|
129
132
|
* Set it to lengthen the cookie past the session (harmless: the session ends
|
|
130
133
|
* first and the stale cookie is cleared). Setting it **shorter** than the
|
|
@@ -143,8 +146,7 @@ export interface HonoBffCookieOptions {
|
|
|
143
146
|
*
|
|
144
147
|
* The BFF's session cookie is an ambient credential the browser attaches
|
|
145
148
|
* automatically, so a cross-site page can trigger credentialed requests
|
|
146
|
-
* (classic CSRF). The defense
|
|
147
|
-
* `token-handler-version` headers) requires a **custom request header** on the
|
|
149
|
+
* (classic CSRF). The defense requires a **custom request header** on the
|
|
148
150
|
* fetch-driven surface: a custom header makes the request "non-simple", forcing
|
|
149
151
|
* a CORS preflight that an untrusted origin cannot satisfy, so the browser never
|
|
150
152
|
* attaches the cookie. The shipped `BffClient` (and therefore the React
|
|
@@ -269,12 +271,13 @@ export interface BffCallbackError {
|
|
|
269
271
|
* OAuth2-style error code: the IDP's `error` (e.g. `access_denied`),
|
|
270
272
|
* `invalid_request` (missing `code`/`state`, or a `state` this browser never
|
|
271
273
|
* started — see {@link HonoBffOptions.loginStateTtlMs}), or `invalid_grant`
|
|
272
|
-
* (the code exchange failed — expired/replayed code, unknown `state`
|
|
274
|
+
* (the code exchange failed — expired/replayed code, unknown `state` — or
|
|
275
|
+
* a later step threw: `resolveUser` or the session store).
|
|
273
276
|
*/
|
|
274
277
|
error: string;
|
|
275
278
|
/** Human-readable description; never contains secrets. */
|
|
276
279
|
error_description: string;
|
|
277
|
-
/** The thrown cause for the `invalid_grant`
|
|
280
|
+
/** The thrown cause for the `invalid_grant` case; otherwise `undefined`. */
|
|
278
281
|
cause?: unknown;
|
|
279
282
|
}
|
|
280
283
|
/** Options accepted by the {@link HonoBff} constructor. */
|
|
@@ -394,7 +397,7 @@ export interface HonoBffOptions {
|
|
|
394
397
|
* an end-session endpoint (`endpoints.endSession`, or via discovery),
|
|
395
398
|
* `/auth/logout` — after destroying the local session — additionally
|
|
396
399
|
* redirects the browser to the OP's `end_session_endpoint` with
|
|
397
|
-
* `id_token_hint` (the session's id_token, if any) and a
|
|
400
|
+
* `id_token_hint` (the session's id_token, if any), `client_id`, and a
|
|
398
401
|
* `post_logout_redirect_uri` built from the resolved origin + the
|
|
399
402
|
* open-redirect-guarded `return_to`. This ends the upstream IDP SSO session,
|
|
400
403
|
* not just the local BFF session.
|
|
@@ -557,7 +560,8 @@ export interface HonoBffOptions {
|
|
|
557
560
|
* Pass a {@link HonoAuthorizationServer} for own-issuer co-located
|
|
558
561
|
* deployments (validation happens against the in-process token service)
|
|
559
562
|
* or a {@link HonoResourceServer} for third-party-issuer deployments
|
|
560
|
-
* (validation goes through
|
|
563
|
+
* (validation goes through its token reader, such as
|
|
564
|
+
* `IntrospectionTokenReader` or `JwksTokenReader`).
|
|
561
565
|
*/
|
|
562
566
|
resourceServer?: HonoBffResourceServer;
|
|
563
567
|
/**
|
|
@@ -604,7 +608,8 @@ export declare class HonoBff {
|
|
|
604
608
|
* {@linkcode HonoBffPaths} entry falls outside a non-empty
|
|
605
609
|
* {@linkcode HonoBffPaths.basePath}, when the session cookie would die
|
|
606
610
|
* before the session behind it (see
|
|
607
|
-
* {@linkcode HonoBffOptions.sessionMaxAgeMs}), or
|
|
611
|
+
* {@linkcode HonoBffOptions.sessionMaxAgeMs}), when `sessionMaxAgeMs` or a
|
|
612
|
+
* numeric `cookie.maxAge` is not positive or exceeds 400 days, or when
|
|
608
613
|
* {@linkcode HonoBffOptions.extraParams} or
|
|
609
614
|
* {@linkcode HonoBffOptions.forwardedParams} names a reserved authorize
|
|
610
615
|
* parameter, gives one a name that is empty or not equal to its trimmed
|
|
@@ -701,7 +706,11 @@ export declare class HonoBff {
|
|
|
701
706
|
callbackHandler(): Handler;
|
|
702
707
|
/** Logout handler — revokes the refresh token and clears the session cookie. */
|
|
703
708
|
logoutHandler(): Handler;
|
|
704
|
-
/**
|
|
709
|
+
/**
|
|
710
|
+
* Session probe — returns `{ isAuthenticated, user }`, plus
|
|
711
|
+
* `sessionExpiresIn` (seconds until the access token expires, or `null`) and
|
|
712
|
+
* `logoutUrl` when signed in. Never returns tokens.
|
|
713
|
+
*/
|
|
705
714
|
sessionHandler(): Handler;
|
|
706
715
|
/**
|
|
707
716
|
* OIDC Back-Channel Logout receiver. A server-to-server `POST` from the OP
|
|
@@ -825,9 +834,11 @@ export declare class HonoBff {
|
|
|
825
834
|
* - **Headers.** Only {@link DEFAULT_PROXY_FORWARD_HEADERS} (or your
|
|
826
835
|
* `forwardHeaders`) travel upstream; the session cookie, inbound
|
|
827
836
|
* `Authorization`, and hop-by-hop headers never do. Coming back, upstream
|
|
828
|
-
* `Set-Cookie` and hop-by-hop headers are dropped
|
|
829
|
-
*
|
|
830
|
-
*
|
|
837
|
+
* `Set-Cookie` and hop-by-hop headers are dropped; the status, body
|
|
838
|
+
* (problem-details errors included), and remaining headers pass through,
|
|
839
|
+
* with `Location`/`Content-Location` rewritten into the mount, and the
|
|
840
|
+
* response marked `Cache-Control: private`, `Vary: Cookie`, and
|
|
841
|
+
* `X-Content-Type-Options: nosniff`.
|
|
831
842
|
* - **Bodies stream** in both directions; nothing is buffered. Upstream
|
|
832
843
|
* redirects are returned to the browser rather than followed, so the token
|
|
833
844
|
* is never replayed to a `Location` the BFF did not vet.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Wraps a {@link DirectClient} configured with a client secret
|
|
5
5
|
* and exposes the session endpoints a browser SPA talks to:
|
|
6
6
|
*
|
|
7
|
-
* - `POST /auth/login` — redirects the browser to the authorize endpoint,
|
|
7
|
+
* - `GET|POST /auth/login` — redirects the browser to the authorize endpoint,
|
|
8
8
|
* persisting a per-request state/verifier record and binding its `state` to
|
|
9
9
|
* this browser with a short-lived cookie.
|
|
10
10
|
* - `GET /auth/callback` — requires the browser to present that binding
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
* the refresh token upstream. Registered for both methods because the
|
|
16
16
|
* shipped React client navigates the browser here (a GET).
|
|
17
17
|
* - `GET /auth/session` — returns `{ isAuthenticated, user }` from the
|
|
18
|
-
* session
|
|
18
|
+
* session, plus `sessionExpiresIn` and `logoutUrl` when signed in.
|
|
19
|
+
* **Never** exposes tokens to the browser.
|
|
19
20
|
* - `POST /auth/backchannel` — OIDC Back-Channel Logout receiver, mounted only
|
|
20
21
|
* when `backchannelLogout` is configured. A server-to-server call from the OP
|
|
21
22
|
* that ends the matching server-side session(s).
|
|
@@ -56,10 +57,6 @@ import { loginContinuation as computeLoginContinuation, safeReturnTo, } from "..
|
|
|
56
57
|
import { resolveCookieName, resolvePrefixConstrainedAttributes, } from "./cookie-prefix.js";
|
|
57
58
|
import { DEFAULT_SESSION_MAX_AGE_MS, MemorySessionStore, sessionStoreMaxAgeMs, supportsBackchannelLogout, } from "./session-store.js";
|
|
58
59
|
import { buildDownstreamHeaders, buildUpstreamHeaders, isInvalidTokenChallenge, resolveForwardHeaders, resolveProxyUrl, } from "./proxy.js";
|
|
59
|
-
/**
|
|
60
|
-
* Whether the authorization server definitively rejected the grant, as opposed
|
|
61
|
-
* to failing to answer. Only these end the session.
|
|
62
|
-
*/
|
|
63
60
|
function isRevokedGrant(error) {
|
|
64
61
|
if (!isOAuth2Error(error))
|
|
65
62
|
return false;
|
|
@@ -305,7 +302,8 @@ export class HonoBff {
|
|
|
305
302
|
* {@linkcode HonoBffPaths} entry falls outside a non-empty
|
|
306
303
|
* {@linkcode HonoBffPaths.basePath}, when the session cookie would die
|
|
307
304
|
* before the session behind it (see
|
|
308
|
-
* {@linkcode HonoBffOptions.sessionMaxAgeMs}), or
|
|
305
|
+
* {@linkcode HonoBffOptions.sessionMaxAgeMs}), when `sessionMaxAgeMs` or a
|
|
306
|
+
* numeric `cookie.maxAge` is not positive or exceeds 400 days, or when
|
|
309
307
|
* {@linkcode HonoBffOptions.extraParams} or
|
|
310
308
|
* {@linkcode HonoBffOptions.forwardedParams} names a reserved authorize
|
|
311
309
|
* parameter, gives one a name that is empty or not equal to its trimmed
|
|
@@ -742,7 +740,11 @@ export class HonoBff {
|
|
|
742
740
|
"silent. Give the client an `issuer` so discovery can find the " +
|
|
743
741
|
"endpoint, or add `endpoints.endSession` explicitly.");
|
|
744
742
|
}
|
|
745
|
-
/**
|
|
743
|
+
/**
|
|
744
|
+
* Session probe — returns `{ isAuthenticated, user }`, plus
|
|
745
|
+
* `sessionExpiresIn` (seconds until the access token expires, or `null`) and
|
|
746
|
+
* `logoutUrl` when signed in. Never returns tokens.
|
|
747
|
+
*/
|
|
746
748
|
sessionHandler() {
|
|
747
749
|
return async (c) => {
|
|
748
750
|
this.#noStore(c);
|
|
@@ -1084,9 +1086,11 @@ export class HonoBff {
|
|
|
1084
1086
|
* - **Headers.** Only {@link DEFAULT_PROXY_FORWARD_HEADERS} (or your
|
|
1085
1087
|
* `forwardHeaders`) travel upstream; the session cookie, inbound
|
|
1086
1088
|
* `Authorization`, and hop-by-hop headers never do. Coming back, upstream
|
|
1087
|
-
* `Set-Cookie` and hop-by-hop headers are dropped
|
|
1088
|
-
*
|
|
1089
|
-
*
|
|
1089
|
+
* `Set-Cookie` and hop-by-hop headers are dropped; the status, body
|
|
1090
|
+
* (problem-details errors included), and remaining headers pass through,
|
|
1091
|
+
* with `Location`/`Content-Location` rewritten into the mount, and the
|
|
1092
|
+
* response marked `Cache-Control: private`, `Vary: Cookie`, and
|
|
1093
|
+
* `X-Content-Type-Options: nosniff`.
|
|
1090
1094
|
* - **Bodies stream** in both directions; nothing is buffered. Upstream
|
|
1091
1095
|
* redirects are returned to the browser rather than followed, so the token
|
|
1092
1096
|
* is never replayed to a `Location` the BFF did not vet.
|
|
@@ -10,10 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module
|
|
12
12
|
*/
|
|
13
|
-
|
|
14
|
-
* Hop-by-hop headers (RFC 9110 §7.6.1). They describe a single transport
|
|
15
|
-
* connection, so a proxy must not pass them through in either direction.
|
|
16
|
-
*/
|
|
13
|
+
// RFC 9110 §7.6.1: connection-specific, never proxied in either direction.
|
|
17
14
|
const HOP_BY_HOP_HEADERS = [
|
|
18
15
|
"connection",
|
|
19
16
|
"keep-alive",
|
|
@@ -46,14 +43,7 @@ export const DEFAULT_PROXY_FORWARD_HEADERS = [
|
|
|
46
43
|
"tracestate",
|
|
47
44
|
"x-request-id",
|
|
48
45
|
];
|
|
49
|
-
|
|
50
|
-
* Never forwarded upstream, even when named in
|
|
51
|
-
* {@link HonoBffProxyOptions.forwardHeaders}. `cookie` and `authorization` are
|
|
52
|
-
* the security-critical entries: the browser's session cookie is the BFF's
|
|
53
|
-
* credential (not the API's), and an inbound `Authorization` must never be able
|
|
54
|
-
* to override the token the BFF attaches. `accept-encoding` and `content-length`
|
|
55
|
-
* are dropped because `fetch` owns content coding and body framing.
|
|
56
|
-
*/
|
|
46
|
+
// The session cookie is the BFF's credential; the BFF's bearer token must win.
|
|
57
47
|
const NEVER_FORWARDED_REQUEST_HEADERS = [
|
|
58
48
|
...HOP_BY_HOP_HEADERS,
|
|
59
49
|
"accept-encoding",
|
|
@@ -63,26 +53,16 @@ const NEVER_FORWARDED_REQUEST_HEADERS = [
|
|
|
63
53
|
"cookie2",
|
|
64
54
|
"host",
|
|
65
55
|
];
|
|
66
|
-
|
|
67
|
-
* Never returned to the browser. `set-cookie` is dropped so an upstream API
|
|
68
|
-
* cannot plant cookies on the BFF's origin, where they would ride along with
|
|
69
|
-
* the session cookie.
|
|
70
|
-
*/
|
|
56
|
+
// An upstream Set-Cookie would plant cookies on the BFF's origin.
|
|
71
57
|
const NEVER_RETURNED_RESPONSE_HEADERS = [
|
|
72
58
|
...HOP_BY_HOP_HEADERS,
|
|
73
59
|
"set-cookie",
|
|
74
60
|
];
|
|
75
|
-
|
|
76
|
-
* Dropped only when a body is actually streamed back: `fetch` has already
|
|
77
|
-
* decoded it, so the upstream's content coding and length no longer describe
|
|
78
|
-
* what the browser receives. A body-less response (`HEAD`, `204`, `304`) was
|
|
79
|
-
* never decoded, and there `Content-Length` *is* the answer.
|
|
80
|
-
*/
|
|
61
|
+
// fetch decodes a streamed body; a body-less response keeps its Content-Length.
|
|
81
62
|
const DECODED_BODY_RESPONSE_HEADERS = [
|
|
82
63
|
"content-encoding",
|
|
83
64
|
"content-length",
|
|
84
65
|
];
|
|
85
|
-
/** Case-insensitive header name set. */
|
|
86
66
|
function headerSet(names) {
|
|
87
67
|
return new Set(names.map((name) => name.toLowerCase()));
|
|
88
68
|
}
|
|
@@ -115,29 +95,17 @@ export function buildUpstreamHeaders(inbound, allowed, accessToken) {
|
|
|
115
95
|
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
116
96
|
return headers;
|
|
117
97
|
}
|
|
118
|
-
|
|
119
|
-
* The header names an upstream `Connection` field lists. RFC 9110 §7.6.1 makes
|
|
120
|
-
* those connection-specific too, so a proxy must drop them alongside the
|
|
121
|
-
* well-known hop-by-hop set.
|
|
122
|
-
*/
|
|
98
|
+
// RFC 9110 §7.6.1: headers named in `Connection` are hop-by-hop too.
|
|
123
99
|
function connectionNamedHeaders(headers) {
|
|
124
100
|
return (headers.get("connection") ?? "")
|
|
125
101
|
.split(",")
|
|
126
102
|
.map((name) => name.trim().toLowerCase())
|
|
127
103
|
.filter((name) => name.length > 0);
|
|
128
104
|
}
|
|
129
|
-
/** Headers whose value is a URL into the upstream's namespace. */
|
|
130
105
|
const URL_VALUED_RESPONSE_HEADERS = [
|
|
131
106
|
"location",
|
|
132
107
|
"content-location",
|
|
133
108
|
];
|
|
134
|
-
/**
|
|
135
|
-
* Rewrites an upstream URL into the mount's namespace, so a `Location` naming
|
|
136
|
-
* the resource server neither leaks internal topology to the browser nor points
|
|
137
|
-
* it at a host it cannot reach. A relative value resolves against the upstream
|
|
138
|
-
* request first. Anything outside the configured base is left alone — it is a
|
|
139
|
-
* genuinely external destination, not part of what this mount proxies.
|
|
140
|
-
*/
|
|
141
109
|
function rewriteUpstreamUrl(value, upstreamUrl, base, mountPrefix) {
|
|
142
110
|
let resolved;
|
|
143
111
|
try {
|
|
@@ -159,7 +127,6 @@ function rewriteUpstreamUrl(value, upstreamUrl, base, mountPrefix) {
|
|
|
159
127
|
const suffix = resolved.pathname.slice(basePath.length) || "/";
|
|
160
128
|
return `${mountPrefix}${suffix}${resolved.search}${resolved.hash}`;
|
|
161
129
|
}
|
|
162
|
-
/** Adds `name` to `Vary` without disturbing what the upstream already varies on. */
|
|
163
130
|
function appendVary(headers, name) {
|
|
164
131
|
const existing = headers.get("vary");
|
|
165
132
|
if (!existing) {
|
|
@@ -174,11 +141,7 @@ function appendVary(headers, name) {
|
|
|
174
141
|
if (!present)
|
|
175
142
|
headers.set("vary", `${existing}, ${name}`);
|
|
176
143
|
}
|
|
177
|
-
|
|
178
|
-
* Forces the response private. Upstream sees an `Authorization`-protected
|
|
179
|
-
* request, which RFC 9111 §3.5 already keeps out of shared caches; downstream
|
|
180
|
-
* the same response is authenticated by a cookie, which carries no such rule.
|
|
181
|
-
*/
|
|
144
|
+
// RFC 9111 §3.5 covers the upstream Authorization, not the downstream cookie.
|
|
182
145
|
function forcePrivateCache(headers) {
|
|
183
146
|
const existing = headers.get("cache-control");
|
|
184
147
|
if (!existing) {
|
|
@@ -227,18 +190,7 @@ export function buildDownstreamHeaders(upstream, context) {
|
|
|
227
190
|
headers.set("X-Content-Type-Options", "nosniff");
|
|
228
191
|
return headers;
|
|
229
192
|
}
|
|
230
|
-
|
|
231
|
-
* Rejects a path segment that would become a dot segment once decoded.
|
|
232
|
-
*
|
|
233
|
-
* Fails closed on a malformed escape: `decodeURIComponent` throws on overlong
|
|
234
|
-
* or invalid UTF-8 (`%C0%AF`, the classic `/` smuggle), and a lenient upstream
|
|
235
|
-
* decoder may still reconstruct the separator, so the segment cannot be
|
|
236
|
-
* forwarded unexamined. Encoded separators themselves are allowed through —
|
|
237
|
-
* `a%2Fb` is a legitimate resource id — it is only `..` between them that is
|
|
238
|
-
* refused.
|
|
239
|
-
*
|
|
240
|
-
* @throws {TypeError} If the segment has a malformed escape or hides `..`.
|
|
241
|
-
*/
|
|
193
|
+
// A lenient upstream decoder can rebuild `/` from `%C0%AF`: fail closed.
|
|
242
194
|
function assertSafeSegment(segment) {
|
|
243
195
|
let decoded;
|
|
244
196
|
try {
|
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
*
|
|
18
18
|
* A raw "token in cookie" store is deliberately **not** shipped: a leaked
|
|
19
19
|
* log line containing the cookie would yield a working bearer token. The
|
|
20
|
-
* AES-GCM encryption in the stateless store
|
|
21
|
-
*
|
|
20
|
+
* AES-GCM encryption in the stateless store keeps the tokens unreadable, but
|
|
21
|
+
* a leaked encrypted cookie still works as a session cookie against the BFF
|
|
22
|
+
* until it expires.
|
|
22
23
|
*
|
|
23
24
|
* @module
|
|
24
25
|
*/
|
|
@@ -31,8 +32,8 @@ export interface SessionData {
|
|
|
31
32
|
refreshToken?: string;
|
|
32
33
|
/**
|
|
33
34
|
* Cached user claims surfaced by `GET /auth/session` without exposing
|
|
34
|
-
* tokens to the browser. Populated
|
|
35
|
-
*
|
|
35
|
+
* tokens to the browser. Populated at callback time from the id_token's
|
|
36
|
+
* claims, as replaced by `HonoBffOptions.resolveUser` when configured.
|
|
36
37
|
*/
|
|
37
38
|
user?: UserInfoClaims | null;
|
|
38
39
|
/**
|
|
@@ -160,8 +161,8 @@ export interface EncryptedCookieSessionStoreOptions {
|
|
|
160
161
|
* older secret keeps unsealing. Rotate by deploying `[newSecret, oldSecret]`,
|
|
161
162
|
* then drop `oldSecret` once the grace window (at least {@linkcode maxAgeMs},
|
|
162
163
|
* or your longest session lifetime) has elapsed. This is the session-store
|
|
163
|
-
* analog of a multi-key JWKS grace window.
|
|
164
|
-
*
|
|
164
|
+
* analog of a multi-key JWKS grace window. With a single secret, rotating it
|
|
165
|
+
* signs everyone out.
|
|
165
166
|
*/
|
|
166
167
|
secret: string | Uint8Array | Array<string | Uint8Array>;
|
|
167
168
|
/**
|
|
@@ -181,9 +182,8 @@ export interface EncryptedCookieSessionStoreOptions {
|
|
|
181
182
|
* milliseconds.
|
|
182
183
|
*
|
|
183
184
|
* The store stamps each cookie with a seal time at `create`/`update`; a
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
* grandfathered as unbounded.
|
|
185
|
+
* cookie without that stamp is aged from its `updatedAt` instead, so it is
|
|
186
|
+
* bounded too.
|
|
187
187
|
*/
|
|
188
188
|
maxAgeMs?: number;
|
|
189
189
|
}
|