@_mustachio/openauth 0.10.0 → 0.12.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/dist/esm/adapters/d1/index.js +18 -9
- package/dist/esm/adapters/d1/migrations.js +7 -2
- package/dist/esm/adapters/d1/session-store.js +39 -0
- package/dist/esm/adapters/d1/session.js +2 -2
- package/dist/esm/adapters/durable-object/index.js +4 -0
- package/dist/esm/adapters/durable-object/session-store.js +42 -0
- package/dist/esm/adapters/dynamo/index.js +24 -7
- package/dist/esm/adapters/dynamo/session-store.js +49 -0
- package/dist/esm/adapters/kms/index.js +6 -2
- package/dist/esm/adapters/kv/index.js +6 -2
- package/dist/esm/adapters/memory/index.js +24 -7
- package/dist/esm/adapters/memory/session-store.js +45 -0
- package/dist/esm/adapters/memory/token-store.js +13 -0
- package/dist/esm/adapters/postgres/executor.js +2 -2
- package/dist/esm/adapters/postgres/index.js +33 -10
- package/dist/esm/adapters/postgres/migrations.js +8 -2
- package/dist/esm/adapters/postgres/session-store.js +42 -0
- package/dist/esm/client.js +35 -28
- package/dist/esm/domain/authorize.js +7 -2
- package/dist/esm/domain/callback.js +124 -7
- package/dist/esm/domain/client-auth.js +2 -2
- package/dist/esm/domain/crypto.js +14 -14
- package/dist/esm/domain/discovery.js +46 -4
- package/dist/esm/domain/dpop.js +122 -0
- package/dist/esm/domain/id-token.js +94 -0
- package/dist/esm/domain/introspect.js +5 -1
- package/dist/esm/domain/jwt.js +30 -2
- package/dist/esm/domain/logout.js +75 -0
- package/dist/esm/domain/method-dispatch.js +27 -1
- package/dist/esm/domain/method-route.js +83 -1
- package/dist/esm/domain/par.js +51 -0
- package/dist/esm/domain/pkce.js +2 -2
- package/dist/esm/domain/refresh.js +12 -1
- package/dist/esm/domain/register.js +74 -0
- package/dist/esm/domain/revoke.js +2 -2
- package/dist/esm/domain/state-envelope.js +2 -2
- package/dist/esm/domain/token-exchange.js +1 -0
- package/dist/esm/domain/token.js +53 -12
- package/dist/esm/domain/userinfo.js +38 -3
- package/dist/esm/error.js +8 -8
- package/dist/esm/http/cookies.js +19 -2
- package/dist/esm/http/errors.js +4 -4
- package/dist/esm/http/handlers/authorize.js +92 -21
- package/dist/esm/http/handlers/callback.js +7 -1
- package/dist/esm/http/handlers/end-session.js +64 -0
- package/dist/esm/http/handlers/metadata.js +7 -4
- package/dist/esm/http/handlers/method-route.js +47 -2
- package/dist/esm/http/handlers/par.js +53 -0
- package/dist/esm/http/handlers/register.js +46 -0
- package/dist/esm/http/handlers/revocation.js +2 -2
- package/dist/esm/http/handlers/token.js +36 -6
- package/dist/esm/http/handlers/userinfo.js +35 -29
- package/dist/esm/http/middleware/tenant.js +6 -6
- package/dist/esm/http/router.js +10 -0
- package/dist/esm/http/schemas/authorize.js +8 -2
- package/dist/esm/http/schemas/end-session.js +13 -0
- package/dist/esm/http/schemas/par.js +9 -0
- package/dist/esm/http/schemas/register.js +16 -0
- package/dist/esm/http/schemas/revocation.js +2 -2
- package/dist/esm/http/schemas/token.js +7 -7
- package/dist/esm/index.js +40 -34
- package/dist/esm/methods/oauth2-factory.js +2 -2
- package/dist/esm/methods/password-hash.js +2 -2
- package/dist/esm/methods/providers/index.js +30 -14
- package/dist/esm/methods/saml-sp/acs.js +208 -0
- package/dist/esm/methods/saml-sp/attributes.js +75 -0
- package/dist/esm/methods/saml-sp/authnrequest.js +63 -0
- package/dist/esm/methods/saml-sp/cache-provider.js +33 -0
- package/dist/esm/methods/saml-sp/cert-rotation.js +13 -0
- package/dist/esm/methods/saml-sp/factory.js +70 -0
- package/dist/esm/methods/saml-sp/index.js +7 -0
- package/dist/esm/methods/saml-sp/metadata.js +69 -0
- package/dist/esm/methods/saml-sp/method.js +29 -0
- package/dist/esm/methods/saml-sp/parse-idp-metadata.js +102 -0
- package/dist/esm/methods/saml-sp/saml-instance.js +49 -0
- package/dist/esm/methods/saml-sp/slo-initiate.js +84 -0
- package/dist/esm/methods/saml-sp/sls.js +154 -0
- package/dist/esm/methods/saml-sp/types.js +0 -0
- package/dist/esm/types/error.js +5 -0
- package/dist/esm/types/result.js +3 -3
- package/dist/esm/ui/forms.js +3 -3
- package/dist/esm/ui/index.js +8 -5
- package/dist/esm/ui/picker.js +2 -2
- package/dist/types/adapters/d1/migrations.d.ts.map +1 -1
- package/dist/types/adapters/d1/session-store.d.ts +3 -0
- package/dist/types/adapters/d1/session-store.d.ts.map +1 -1
- package/dist/types/adapters/durable-object/session-store.d.ts +3 -0
- package/dist/types/adapters/durable-object/session-store.d.ts.map +1 -1
- package/dist/types/adapters/dynamo/session-store.d.ts +3 -0
- package/dist/types/adapters/dynamo/session-store.d.ts.map +1 -1
- package/dist/types/adapters/memory/session-store.d.ts +6 -1
- package/dist/types/adapters/memory/session-store.d.ts.map +1 -1
- package/dist/types/adapters/memory/token-store.d.ts +1 -0
- package/dist/types/adapters/memory/token-store.d.ts.map +1 -1
- package/dist/types/adapters/postgres/migrations.d.ts +1 -1
- package/dist/types/adapters/postgres/migrations.d.ts.map +1 -1
- package/dist/types/adapters/postgres/session-store.d.ts +3 -0
- package/dist/types/adapters/postgres/session-store.d.ts.map +1 -1
- package/dist/types/client.d.ts +19 -0
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/domain/authorize.d.ts.map +1 -1
- package/dist/types/domain/callback.d.ts +16 -1
- package/dist/types/domain/callback.d.ts.map +1 -1
- package/dist/types/domain/client-credentials.d.ts +2 -0
- package/dist/types/domain/client-credentials.d.ts.map +1 -1
- package/dist/types/domain/discovery.d.ts +37 -0
- package/dist/types/domain/discovery.d.ts.map +1 -1
- package/dist/types/domain/dpop.d.ts +61 -0
- package/dist/types/domain/dpop.d.ts.map +1 -0
- package/dist/types/domain/id-token.d.ts +83 -0
- package/dist/types/domain/id-token.d.ts.map +1 -0
- package/dist/types/domain/introspect.d.ts +8 -0
- package/dist/types/domain/introspect.d.ts.map +1 -1
- package/dist/types/domain/jwt.d.ts +27 -1
- package/dist/types/domain/jwt.d.ts.map +1 -1
- package/dist/types/domain/logout.d.ts +69 -0
- package/dist/types/domain/logout.d.ts.map +1 -0
- package/dist/types/domain/method-dispatch.d.ts.map +1 -1
- package/dist/types/domain/method-route.d.ts +57 -1
- package/dist/types/domain/method-route.d.ts.map +1 -1
- package/dist/types/domain/par.d.ts +62 -0
- package/dist/types/domain/par.d.ts.map +1 -0
- package/dist/types/domain/refresh.d.ts +9 -0
- package/dist/types/domain/refresh.d.ts.map +1 -1
- package/dist/types/domain/register.d.ts +33 -0
- package/dist/types/domain/register.d.ts.map +1 -0
- package/dist/types/domain/token-exchange.d.ts +2 -0
- package/dist/types/domain/token-exchange.d.ts.map +1 -1
- package/dist/types/domain/token.d.ts +41 -1
- package/dist/types/domain/token.d.ts.map +1 -1
- package/dist/types/domain/userinfo.d.ts +44 -5
- package/dist/types/domain/userinfo.d.ts.map +1 -1
- package/dist/types/http/context.d.ts +6 -1
- package/dist/types/http/context.d.ts.map +1 -1
- package/dist/types/http/cookies.d.ts +9 -1
- package/dist/types/http/cookies.d.ts.map +1 -1
- package/dist/types/http/handlers/authorize.d.ts.map +1 -1
- package/dist/types/http/handlers/callback.d.ts.map +1 -1
- package/dist/types/http/handlers/end-session.d.ts +3 -0
- package/dist/types/http/handlers/end-session.d.ts.map +1 -0
- package/dist/types/http/handlers/metadata.d.ts +1 -1
- package/dist/types/http/handlers/metadata.d.ts.map +1 -1
- package/dist/types/http/handlers/method-route.d.ts.map +1 -1
- package/dist/types/http/handlers/par.d.ts +3 -0
- package/dist/types/http/handlers/par.d.ts.map +1 -0
- package/dist/types/http/handlers/register.d.ts +3 -0
- package/dist/types/http/handlers/register.d.ts.map +1 -0
- package/dist/types/http/handlers/token.d.ts.map +1 -1
- package/dist/types/http/handlers/userinfo.d.ts.map +1 -1
- package/dist/types/http/router.d.ts.map +1 -1
- package/dist/types/http/schemas/authorize.d.ts +40 -0
- package/dist/types/http/schemas/authorize.d.ts.map +1 -1
- package/dist/types/http/schemas/end-session.d.ts +31 -0
- package/dist/types/http/schemas/end-session.d.ts.map +1 -0
- package/dist/types/http/schemas/par.d.ts +20 -0
- package/dist/types/http/schemas/par.d.ts.map +1 -0
- package/dist/types/http/schemas/register.d.ts +42 -0
- package/dist/types/http/schemas/register.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/methods/saml-sp/acs.d.ts +4 -0
- package/dist/types/methods/saml-sp/acs.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/attributes.d.ts +40 -0
- package/dist/types/methods/saml-sp/attributes.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/authnrequest.d.ts +4 -0
- package/dist/types/methods/saml-sp/authnrequest.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/cache-provider.d.ts +38 -0
- package/dist/types/methods/saml-sp/cache-provider.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/cert-rotation.d.ts +23 -0
- package/dist/types/methods/saml-sp/cert-rotation.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/factory.d.ts +704 -0
- package/dist/types/methods/saml-sp/factory.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/index.d.ts +24 -0
- package/dist/types/methods/saml-sp/index.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/metadata.d.ts +42 -0
- package/dist/types/methods/saml-sp/metadata.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/method.d.ts +17 -0
- package/dist/types/methods/saml-sp/method.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/parse-idp-metadata.d.ts +4 -0
- package/dist/types/methods/saml-sp/parse-idp-metadata.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/saml-instance.d.ts +81 -0
- package/dist/types/methods/saml-sp/saml-instance.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/slo-initiate.d.ts +4 -0
- package/dist/types/methods/saml-sp/slo-initiate.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/sls.d.ts +4 -0
- package/dist/types/methods/saml-sp/sls.d.ts.map +1 -0
- package/dist/types/methods/saml-sp/types.d.ts +204 -0
- package/dist/types/methods/saml-sp/types.d.ts.map +1 -0
- package/dist/types/ports/audit-log.d.ts +40 -0
- package/dist/types/ports/audit-log.d.ts.map +1 -1
- package/dist/types/ports/session-store.d.ts +50 -0
- package/dist/types/ports/session-store.d.ts.map +1 -1
- package/dist/types/ports/token-store.d.ts +12 -0
- package/dist/types/ports/token-store.d.ts.map +1 -1
- package/dist/types/types/authorization.d.ts +23 -0
- package/dist/types/types/authorization.d.ts.map +1 -1
- package/dist/types/types/error.d.ts +9 -0
- package/dist/types/types/error.d.ts.map +1 -1
- package/dist/types/types/flow.d.ts +10 -0
- package/dist/types/types/flow.d.ts.map +1 -1
- package/dist/types/types/idp.d.ts +162 -1
- package/dist/types/types/idp.d.ts.map +1 -1
- package/dist/types/types/method.d.ts +109 -0
- package/dist/types/types/method.d.ts.map +1 -1
- package/dist/types/types/tenant.d.ts +37 -0
- package/dist/types/types/tenant.d.ts.map +1 -1
- package/dist/types/types/token.d.ts +128 -0
- package/dist/types/types/token.d.ts.map +1 -1
- package/package.json +9 -1
- package/src/adapters/d1/migrations.ts +5 -0
- package/src/adapters/d1/session-store.ts +68 -0
- package/src/adapters/durable-object/session-store.ts +61 -0
- package/src/adapters/dynamo/session-store.ts +63 -0
- package/src/adapters/memory/session-store.ts +83 -1
- package/src/adapters/memory/token-store.ts +16 -0
- package/src/adapters/postgres/migrations.ts +6 -0
- package/src/adapters/postgres/session-store.ts +69 -0
- package/src/client.ts +32 -0
- package/src/domain/authorize.ts +9 -0
- package/src/domain/callback.ts +220 -7
- package/src/domain/client-credentials.ts +2 -0
- package/src/domain/discovery.ts +80 -2
- package/src/domain/dpop.ts +293 -0
- package/src/domain/id-token.ts +213 -0
- package/src/domain/introspect.ts +10 -0
- package/src/domain/jwt.ts +80 -1
- package/src/domain/logout.ts +180 -0
- package/src/domain/method-dispatch.ts +53 -1
- package/src/domain/method-route.ts +195 -1
- package/src/domain/par.ts +141 -0
- package/src/domain/refresh.ts +41 -0
- package/src/domain/register.ts +168 -0
- package/src/domain/token-exchange.ts +11 -0
- package/src/domain/token.ts +134 -8
- package/src/domain/userinfo.ts +121 -6
- package/src/http/context.ts +10 -0
- package/src/http/cookies.ts +19 -1
- package/src/http/handlers/authorize.ts +146 -24
- package/src/http/handlers/callback.ts +9 -1
- package/src/http/handlers/end-session.ts +106 -0
- package/src/http/handlers/metadata.ts +7 -2
- package/src/http/handlers/method-route.ts +75 -2
- package/src/http/handlers/par.ts +84 -0
- package/src/http/handlers/register.ts +70 -0
- package/src/http/handlers/token.ts +56 -0
- package/src/http/handlers/userinfo.ts +62 -38
- package/src/http/router.ts +19 -0
- package/src/http/schemas/authorize.ts +27 -0
- package/src/http/schemas/end-session.ts +23 -0
- package/src/http/schemas/par.ts +23 -0
- package/src/http/schemas/register.ts +29 -0
- package/src/index.ts +21 -1
- package/src/methods/saml-sp/acs.ts +399 -0
- package/src/methods/saml-sp/attributes.ts +137 -0
- package/src/methods/saml-sp/authnrequest.ts +128 -0
- package/src/methods/saml-sp/cache-provider.ts +76 -0
- package/src/methods/saml-sp/cert-rotation.ts +34 -0
- package/src/methods/saml-sp/factory.ts +118 -0
- package/src/methods/saml-sp/index.ts +34 -0
- package/src/methods/saml-sp/metadata.ts +204 -0
- package/src/methods/saml-sp/method.ts +65 -0
- package/src/methods/saml-sp/parse-idp-metadata.ts +164 -0
- package/src/methods/saml-sp/saml-instance.ts +173 -0
- package/src/methods/saml-sp/slo-initiate.ts +177 -0
- package/src/methods/saml-sp/sls.ts +306 -0
- package/src/methods/saml-sp/types.ts +203 -0
- package/src/ports/CONSISTENCY.md +77 -21
- package/src/ports/audit-log.ts +42 -0
- package/src/ports/session-store.ts +61 -0
- package/src/ports/token-store.ts +13 -0
- package/src/types/authorization.ts +25 -0
- package/src/types/error.ts +23 -0
- package/src/types/flow.ts +10 -0
- package/src/types/idp.ts +170 -0
- package/src/types/method.ts +110 -0
- package/src/types/tenant.ts +37 -0
- package/src/types/token.ts +135 -0
|
@@ -4,13 +4,16 @@ import { startAuthorize } from "../../domain/authorize";
|
|
|
4
4
|
import { asTenantId } from "../../types/tenant";
|
|
5
5
|
import { authError } from "../../types/error";
|
|
6
6
|
import { renderPicker as renderDefaultPicker } from "../../ui/picker";
|
|
7
|
-
import { applyResponsePolicy } from "../cookies";
|
|
7
|
+
import { applyResponsePolicy, cacheControlHeader } from "../cookies";
|
|
8
8
|
import {
|
|
9
9
|
authorizeDirectErrorResponse,
|
|
10
10
|
authorizeRedirectErrorResponse,
|
|
11
11
|
isNonRecoverable
|
|
12
12
|
} from "../errors";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
authorizeQuerySchema,
|
|
15
|
+
authorizeRequestUriQuerySchema
|
|
16
|
+
} from "../schemas/authorize";
|
|
14
17
|
function makeAuthorizeHandler(deps) {
|
|
15
18
|
return async (c) => {
|
|
16
19
|
const tenant = c.get("tenant");
|
|
@@ -19,13 +22,37 @@ function makeAuthorizeHandler(deps) {
|
|
|
19
22
|
}
|
|
20
23
|
const url = new URL(c.req.url);
|
|
21
24
|
const raw = Object.fromEntries(url.searchParams.entries());
|
|
22
|
-
|
|
25
|
+
let workingRaw = raw;
|
|
26
|
+
if (raw.request_uri !== undefined) {
|
|
27
|
+
const parParsed = authorizeRequestUriQuerySchema.safeParse(raw);
|
|
28
|
+
if (!parParsed.success) {
|
|
29
|
+
return authorizeDirectErrorResponse(authError.invalidRequest(parParsed.error.issues[0]?.message ?? "invalid request", parParsed.error.issues[0]?.path[0]?.toString() ?? "request"));
|
|
30
|
+
}
|
|
31
|
+
if (!deps.sessionStore.consumePar) {
|
|
32
|
+
return authorizeDirectErrorResponse(authError.invalidRequest("session adapter does not support PAR", "request_uri"));
|
|
33
|
+
}
|
|
34
|
+
const par = await deps.sessionStore.consumePar(parParsed.data.request_uri);
|
|
35
|
+
if (isErr(par)) {
|
|
36
|
+
return authorizeDirectErrorResponse(authError.invalidRequest("unknown or expired request_uri", "request_uri"));
|
|
37
|
+
}
|
|
38
|
+
if (par.value.clientId !== parParsed.data.client_id) {
|
|
39
|
+
return authorizeDirectErrorResponse(authError.invalidRequest("request_uri belongs to a different client", "request_uri"));
|
|
40
|
+
}
|
|
41
|
+
workingRaw = par.value.params;
|
|
42
|
+
}
|
|
43
|
+
const parsed = authorizeQuerySchema.safeParse(workingRaw);
|
|
23
44
|
if (!parsed.success) {
|
|
24
45
|
const first = parsed.error.issues[0];
|
|
25
46
|
const field = first?.path[0]?.toString() ?? "request";
|
|
26
47
|
return authorizeDirectErrorResponse(authError.invalidRequest(first?.message ?? "invalid request", field));
|
|
27
48
|
}
|
|
28
49
|
const q = parsed.data;
|
|
50
|
+
if (raw.request_uri === undefined) {
|
|
51
|
+
const client = tenant.config.clients.find((c) => c.id === q.client_id);
|
|
52
|
+
if (client?.requirePushedAuthorizationRequests) {
|
|
53
|
+
return authorizeDirectErrorResponse(authError.invalidRequest("this client requires Pushed Authorization Requests (RFC 9126)", "request_uri"));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
29
56
|
if (q.response_type !== "code") {
|
|
30
57
|
return authorizeDirectErrorResponse({
|
|
31
58
|
code: "invalid_request",
|
|
@@ -33,6 +60,14 @@ function makeAuthorizeHandler(deps) {
|
|
|
33
60
|
field: "response_type"
|
|
34
61
|
});
|
|
35
62
|
}
|
|
63
|
+
let claimsRequest;
|
|
64
|
+
if (q.claims !== undefined) {
|
|
65
|
+
const parsedClaims = parseClaimsParameter(q.claims);
|
|
66
|
+
if (!parsedClaims.ok) {
|
|
67
|
+
return authorizeDirectErrorResponse(authError.invalidRequest(parsedClaims.error, "claims"));
|
|
68
|
+
}
|
|
69
|
+
claimsRequest = parsedClaims.value;
|
|
70
|
+
}
|
|
36
71
|
const request = {
|
|
37
72
|
tenantId: asTenantId(tenant.id),
|
|
38
73
|
clientId: q.client_id,
|
|
@@ -46,7 +81,8 @@ function makeAuthorizeHandler(deps) {
|
|
|
46
81
|
...q.code_challenge_method !== undefined ? { codeChallengeMethod: q.code_challenge_method } : {},
|
|
47
82
|
...q.prompt !== undefined ? { prompt: q.prompt } : {},
|
|
48
83
|
...q.ui_locales !== undefined ? { uiLocales: q.ui_locales } : {},
|
|
49
|
-
...q.nonce !== undefined ? { nonce: q.nonce } : {}
|
|
84
|
+
...q.nonce !== undefined ? { nonce: q.nonce } : {},
|
|
85
|
+
...claimsRequest !== undefined ? { claimsRequest } : {}
|
|
50
86
|
};
|
|
51
87
|
const result = await startAuthorize({
|
|
52
88
|
request,
|
|
@@ -87,7 +123,7 @@ function makeAuthorizeHandler(deps) {
|
|
|
87
123
|
return applyResponsePolicy(out.response, {
|
|
88
124
|
setCookies: [flowCookie, ...out.setCookies],
|
|
89
125
|
cookieDefaults: deps.cookieDefaults,
|
|
90
|
-
cacheControl:
|
|
126
|
+
cacheControl: cacheControlHeader(out.cache)
|
|
91
127
|
});
|
|
92
128
|
}
|
|
93
129
|
case "issue-code": {
|
|
@@ -132,6 +168,57 @@ function makeAuthorizeHandler(deps) {
|
|
|
132
168
|
}
|
|
133
169
|
};
|
|
134
170
|
}
|
|
171
|
+
function parseClaimsParameter(raw) {
|
|
172
|
+
let parsed;
|
|
173
|
+
try {
|
|
174
|
+
parsed = JSON.parse(raw);
|
|
175
|
+
} catch (e) {
|
|
176
|
+
return {
|
|
177
|
+
ok: false,
|
|
178
|
+
error: `claims parameter is not valid JSON: ${e instanceof Error ? e.message : String(e)}`
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
182
|
+
return { ok: false, error: "claims parameter must be a JSON object" };
|
|
183
|
+
}
|
|
184
|
+
const obj = parsed;
|
|
185
|
+
const result = {};
|
|
186
|
+
for (const section of ["userinfo", "id_token"]) {
|
|
187
|
+
const v = obj[section];
|
|
188
|
+
if (v === undefined)
|
|
189
|
+
continue;
|
|
190
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) {
|
|
191
|
+
return {
|
|
192
|
+
ok: false,
|
|
193
|
+
error: `claims.${section} must be a JSON object`
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const sectionMap = {};
|
|
197
|
+
for (const [name, entry] of Object.entries(v)) {
|
|
198
|
+
if (entry === null) {
|
|
199
|
+
sectionMap[name] = null;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (typeof entry !== "object" || Array.isArray(entry)) {
|
|
203
|
+
return {
|
|
204
|
+
ok: false,
|
|
205
|
+
error: `claims.${section}.${name} must be null or a JSON object`
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const e = entry;
|
|
209
|
+
const normalized = {};
|
|
210
|
+
if (typeof e.essential === "boolean")
|
|
211
|
+
normalized.essential = e.essential;
|
|
212
|
+
if ("value" in e)
|
|
213
|
+
normalized.value = e.value;
|
|
214
|
+
if (Array.isArray(e.values))
|
|
215
|
+
normalized.values = e.values;
|
|
216
|
+
sectionMap[name] = normalized;
|
|
217
|
+
}
|
|
218
|
+
result[section] = sectionMap;
|
|
219
|
+
}
|
|
220
|
+
return { ok: true, value: result };
|
|
221
|
+
}
|
|
135
222
|
function clearFlowCookie() {
|
|
136
223
|
return {
|
|
137
224
|
name: "idp.flow",
|
|
@@ -142,22 +229,6 @@ function clearFlowCookie() {
|
|
|
142
229
|
maxAge: 0
|
|
143
230
|
};
|
|
144
231
|
}
|
|
145
|
-
function cacheControlFor(cache) {
|
|
146
|
-
if (!cache)
|
|
147
|
-
return "no-store";
|
|
148
|
-
if ((cache.maxAge ?? -1) === 0)
|
|
149
|
-
return "no-store";
|
|
150
|
-
const parts = [];
|
|
151
|
-
if (cache.isPrivate)
|
|
152
|
-
parts.push("private");
|
|
153
|
-
if (cache.maxAge !== undefined)
|
|
154
|
-
parts.push(`max-age=${cache.maxAge}`);
|
|
155
|
-
if (cache.sMaxAge !== undefined)
|
|
156
|
-
parts.push(`s-maxage=${cache.sMaxAge}`);
|
|
157
|
-
if (cache.immutable)
|
|
158
|
-
parts.push("immutable");
|
|
159
|
-
return parts.length ? parts.join(", ") : "no-store";
|
|
160
|
-
}
|
|
161
232
|
export {
|
|
162
233
|
makeAuthorizeHandler
|
|
163
234
|
};
|
|
@@ -5,7 +5,13 @@ import { applyResponsePolicy } from "../cookies";
|
|
|
5
5
|
import { authorizeDirectErrorResponse } from "../errors";
|
|
6
6
|
function makeCallbackHandler(deps) {
|
|
7
7
|
return async (c) => {
|
|
8
|
-
const
|
|
8
|
+
const tenant = c.get("tenant");
|
|
9
|
+
const result = await handleCallback({
|
|
10
|
+
rawRequest: c.req.raw,
|
|
11
|
+
cookies: c.get("cookies"),
|
|
12
|
+
...tenant ? { tenant } : {},
|
|
13
|
+
issuerUrl: c.get("issuerUrl")
|
|
14
|
+
}, {
|
|
9
15
|
configStore: deps.configStore,
|
|
10
16
|
sessionStore: deps.sessionStore,
|
|
11
17
|
tokenStore: deps.tokenStore,
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// src/http/handlers/end-session.ts
|
|
2
|
+
import { endSession } from "../../domain/logout";
|
|
3
|
+
import { authError } from "../../types/error";
|
|
4
|
+
import { isErr } from "../../types/result";
|
|
5
|
+
import { applyResponsePolicy } from "../cookies";
|
|
6
|
+
import { tokenEndpointErrorResponse } from "../errors";
|
|
7
|
+
import { endSessionParamsSchema } from "../schemas/end-session";
|
|
8
|
+
function makeEndSessionHandler(deps) {
|
|
9
|
+
return async (c) => {
|
|
10
|
+
const tenant = c.get("tenant");
|
|
11
|
+
if (!tenant) {
|
|
12
|
+
return tokenEndpointErrorResponse(authError.invalidRequest("tenant unresolved"));
|
|
13
|
+
}
|
|
14
|
+
let raw;
|
|
15
|
+
const httpMethod = c.req.method.toUpperCase();
|
|
16
|
+
if (httpMethod === "GET") {
|
|
17
|
+
raw = Object.fromEntries(new URL(c.req.url).searchParams.entries());
|
|
18
|
+
} else if (httpMethod === "POST") {
|
|
19
|
+
const ct = (c.req.header("content-type") ?? "").toLowerCase();
|
|
20
|
+
if (!ct.startsWith("application/x-www-form-urlencoded")) {
|
|
21
|
+
return tokenEndpointErrorResponse(authError.invalidRequest("POST /end_session requires application/x-www-form-urlencoded body"));
|
|
22
|
+
}
|
|
23
|
+
raw = Object.fromEntries(new URLSearchParams(await c.req.raw.text()));
|
|
24
|
+
} else {
|
|
25
|
+
return tokenEndpointErrorResponse(authError.invalidRequest(`unsupported method "${httpMethod}"`));
|
|
26
|
+
}
|
|
27
|
+
const parsed = endSessionParamsSchema.safeParse(raw);
|
|
28
|
+
if (!parsed.success) {
|
|
29
|
+
return tokenEndpointErrorResponse(authError.invalidRequest(parsed.error.issues[0]?.message ?? "invalid request"));
|
|
30
|
+
}
|
|
31
|
+
const q = parsed.data;
|
|
32
|
+
const res = await endSession({
|
|
33
|
+
...q.id_token_hint !== undefined ? { idTokenHint: q.id_token_hint } : {},
|
|
34
|
+
...q.client_id !== undefined ? { clientId: q.client_id } : {},
|
|
35
|
+
...q.post_logout_redirect_uri !== undefined ? { postLogoutRedirectUri: q.post_logout_redirect_uri } : {},
|
|
36
|
+
...q.state !== undefined ? { state: q.state } : {}
|
|
37
|
+
}, tenant, {
|
|
38
|
+
configStore: deps.configStore,
|
|
39
|
+
tokenStore: deps.tokenStore,
|
|
40
|
+
keyStore: deps.keyStore,
|
|
41
|
+
...deps.auditLog ? { auditLog: deps.auditLog } : {},
|
|
42
|
+
issuerUrl: c.get("issuerUrl"),
|
|
43
|
+
clock: deps.clock
|
|
44
|
+
});
|
|
45
|
+
if (isErr(res))
|
|
46
|
+
return tokenEndpointErrorResponse(res.error);
|
|
47
|
+
if (res.value.kind === "redirect") {
|
|
48
|
+
return applyResponsePolicy(new Response(null, {
|
|
49
|
+
status: 302,
|
|
50
|
+
headers: { location: res.value.url }
|
|
51
|
+
}), { setCookies: [], cookieDefaults: deps.cookieDefaults });
|
|
52
|
+
}
|
|
53
|
+
return new Response("Logged out.", {
|
|
54
|
+
status: 200,
|
|
55
|
+
headers: {
|
|
56
|
+
"content-type": "text/plain; charset=utf-8",
|
|
57
|
+
"cache-control": "no-store"
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export {
|
|
63
|
+
makeEndSessionHandler
|
|
64
|
+
};
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
import { buildDiscoveryDocument, buildJwks } from "../../domain/discovery";
|
|
3
3
|
import { isErr } from "../../types/result";
|
|
4
4
|
import { tokenEndpointErrorResponse } from "../errors";
|
|
5
|
-
function makeDiscoveryHandler(
|
|
5
|
+
function makeDiscoveryHandler(deps) {
|
|
6
6
|
return async (c) => {
|
|
7
|
-
const doc = buildDiscoveryDocument({
|
|
7
|
+
const doc = buildDiscoveryDocument({
|
|
8
|
+
issuerUrl: c.get("issuerUrl"),
|
|
9
|
+
...deps.customScopeClaims !== undefined ? { customScopeClaims: deps.customScopeClaims } : {}
|
|
10
|
+
});
|
|
8
11
|
return new Response(JSON.stringify(doc), {
|
|
9
12
|
status: 200,
|
|
10
13
|
headers: {
|
|
@@ -29,6 +32,6 @@ function makeJwksHandler(deps) {
|
|
|
29
32
|
};
|
|
30
33
|
}
|
|
31
34
|
export {
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
makeDiscoveryHandler,
|
|
36
|
+
makeJwksHandler
|
|
34
37
|
};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// src/http/handlers/method-route.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
handleMethodRoute,
|
|
4
|
+
handlePublicMethodRoute
|
|
5
|
+
} from "../../domain/method-route";
|
|
3
6
|
import { authError } from "../../types/error";
|
|
4
7
|
import { isErr } from "../../types/result";
|
|
5
|
-
import { applyResponsePolicy } from "../cookies";
|
|
8
|
+
import { applyResponsePolicy, cacheControlHeader } from "../cookies";
|
|
6
9
|
import { authorizeDirectErrorResponse } from "../errors";
|
|
7
10
|
function makeMethodRouteHandler(deps) {
|
|
8
11
|
return async (c) => {
|
|
@@ -10,6 +13,48 @@ function makeMethodRouteHandler(deps) {
|
|
|
10
13
|
if (!tenant) {
|
|
11
14
|
return authorizeDirectErrorResponse(authError.invalidRequest("tenant unresolved"));
|
|
12
15
|
}
|
|
16
|
+
{
|
|
17
|
+
const u = new URL(c.req.url);
|
|
18
|
+
const segs = u.pathname.split("/").filter(Boolean);
|
|
19
|
+
if (segs.length >= 3 && segs[0] === "m") {
|
|
20
|
+
const methodId = segs[1];
|
|
21
|
+
const subPath = "/" + segs.slice(2).join("/");
|
|
22
|
+
const httpMethod = c.req.method.toUpperCase() === "POST" ? "POST" : "GET";
|
|
23
|
+
const routeKey = `${httpMethod} ${subPath}`;
|
|
24
|
+
const resolved = await deps.methodCache.resolve(tenant.config, methodId);
|
|
25
|
+
if (!isErr(resolved) && resolved.value.publicRoutes?.includes(routeKey)) {
|
|
26
|
+
const issuerUrl = c.get("issuerUrl");
|
|
27
|
+
const callbackHost = deps.callbackHostFor?.(tenant.id) ?? new URL(issuerUrl).host;
|
|
28
|
+
const callbackUrl = `${new URL(issuerUrl).protocol}//${callbackHost}/cb/${methodId}`;
|
|
29
|
+
const pub = await handlePublicMethodRoute({
|
|
30
|
+
rawRequest: c.req.raw,
|
|
31
|
+
tenant,
|
|
32
|
+
method: resolved.value,
|
|
33
|
+
route: routeKey,
|
|
34
|
+
subPath,
|
|
35
|
+
dispatch: { state: "", callbackUrl, issuerUrl }
|
|
36
|
+
}, {
|
|
37
|
+
sessionStore: deps.sessionStore,
|
|
38
|
+
tokenStore: deps.tokenStore,
|
|
39
|
+
clock: deps.clock,
|
|
40
|
+
...deps.auditLog ? { auditLog: deps.auditLog } : {},
|
|
41
|
+
...deps.onLogout ? { onLogout: deps.onLogout } : {}
|
|
42
|
+
});
|
|
43
|
+
if (isErr(pub))
|
|
44
|
+
return authorizeDirectErrorResponse(pub.error);
|
|
45
|
+
if (pub.value.kind === "denied") {
|
|
46
|
+
return applyResponsePolicy(new Response(`access_denied: ${pub.value.reason}`, {
|
|
47
|
+
status: 403,
|
|
48
|
+
headers: { "content-type": "text/plain" }
|
|
49
|
+
}), { cookieDefaults: deps.cookieDefaults });
|
|
50
|
+
}
|
|
51
|
+
return applyResponsePolicy(pub.value.response, {
|
|
52
|
+
cacheControl: cacheControlHeader(pub.value.cache),
|
|
53
|
+
cookieDefaults: deps.cookieDefaults
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
13
58
|
const cookies = c.get("cookies");
|
|
14
59
|
const flowId = cookies.get("idp.flow");
|
|
15
60
|
if (!flowId) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// src/http/handlers/par.ts
|
|
2
|
+
import { resolveClientCredentials } from "../../domain/client-auth";
|
|
3
|
+
import { pushAuthorizationRequest } from "../../domain/par";
|
|
4
|
+
import { authError } from "../../types/error";
|
|
5
|
+
import { isErr } from "../../types/result";
|
|
6
|
+
import { tokenEndpointErrorResponse } from "../errors";
|
|
7
|
+
import { parBodySchema } from "../schemas/par";
|
|
8
|
+
function makeParHandler(deps) {
|
|
9
|
+
return async (c) => {
|
|
10
|
+
const tenant = c.get("tenant");
|
|
11
|
+
if (!tenant) {
|
|
12
|
+
return tokenEndpointErrorResponse(authError.invalidRequest("tenant unresolved"));
|
|
13
|
+
}
|
|
14
|
+
const ct = (c.req.header("content-type") ?? "").toLowerCase();
|
|
15
|
+
if (!ct.startsWith("application/x-www-form-urlencoded")) {
|
|
16
|
+
return tokenEndpointErrorResponse(authError.invalidRequest("body must be application/x-www-form-urlencoded"));
|
|
17
|
+
}
|
|
18
|
+
const raw = Object.fromEntries(new URLSearchParams(await c.req.raw.text()).entries());
|
|
19
|
+
const parsed = parBodySchema.safeParse(raw);
|
|
20
|
+
if (!parsed.success) {
|
|
21
|
+
return tokenEndpointErrorResponse(authError.invalidRequest(parsed.error.issues[0]?.message ?? "invalid request"));
|
|
22
|
+
}
|
|
23
|
+
const creds = resolveClientCredentials({
|
|
24
|
+
authorizationHeader: c.req.header("authorization") ?? null,
|
|
25
|
+
bodyClientId: parsed.data.client_id,
|
|
26
|
+
bodyClientSecret: parsed.data.client_secret
|
|
27
|
+
});
|
|
28
|
+
const clientId = creds?.clientId ?? parsed.data.client_id;
|
|
29
|
+
const { client_secret: _strip, ...paramsForAuthorize } = parsed.data;
|
|
30
|
+
const res = await pushAuthorizationRequest({
|
|
31
|
+
clientId,
|
|
32
|
+
...creds?.clientSecret !== undefined ? { clientSecret: creds.clientSecret } : {},
|
|
33
|
+
params: paramsForAuthorize
|
|
34
|
+
}, tenant, {
|
|
35
|
+
configStore: deps.configStore,
|
|
36
|
+
sessionStore: deps.sessionStore,
|
|
37
|
+
...deps.auditLog ? { auditLog: deps.auditLog } : {},
|
|
38
|
+
clock: deps.clock
|
|
39
|
+
});
|
|
40
|
+
if (isErr(res))
|
|
41
|
+
return tokenEndpointErrorResponse(res.error);
|
|
42
|
+
return new Response(JSON.stringify(res.value), {
|
|
43
|
+
status: 201,
|
|
44
|
+
headers: {
|
|
45
|
+
"content-type": "application/json",
|
|
46
|
+
"cache-control": "no-store"
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
makeParHandler
|
|
53
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/http/handlers/register.ts
|
|
2
|
+
import { registerNewClient } from "../../domain/register";
|
|
3
|
+
import { authError } from "../../types/error";
|
|
4
|
+
import { isErr } from "../../types/result";
|
|
5
|
+
import { tokenEndpointErrorResponse } from "../errors";
|
|
6
|
+
import { registerBodySchema } from "../schemas/register";
|
|
7
|
+
function makeRegisterHandler(deps) {
|
|
8
|
+
return async (c) => {
|
|
9
|
+
const tenant = c.get("tenant");
|
|
10
|
+
if (!tenant) {
|
|
11
|
+
return tokenEndpointErrorResponse(authError.invalidRequest("tenant unresolved"));
|
|
12
|
+
}
|
|
13
|
+
const ct = (c.req.header("content-type") ?? "").toLowerCase();
|
|
14
|
+
if (!ct.startsWith("application/json")) {
|
|
15
|
+
return tokenEndpointErrorResponse(authError.invalidRequest("body must be application/json"));
|
|
16
|
+
}
|
|
17
|
+
let raw;
|
|
18
|
+
try {
|
|
19
|
+
raw = await c.req.raw.json();
|
|
20
|
+
} catch (e) {
|
|
21
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
22
|
+
return tokenEndpointErrorResponse(authError.invalidRequest(`malformed JSON: ${reason}`));
|
|
23
|
+
}
|
|
24
|
+
const parsed = registerBodySchema.safeParse(raw);
|
|
25
|
+
if (!parsed.success) {
|
|
26
|
+
return tokenEndpointErrorResponse(authError.invalidRequest(parsed.error.issues[0]?.message ?? "invalid request"));
|
|
27
|
+
}
|
|
28
|
+
const request = parsed.data;
|
|
29
|
+
const res = await registerNewClient(request, tenant, {
|
|
30
|
+
...deps.registerClient ? { registerClient: deps.registerClient } : {},
|
|
31
|
+
clock: deps.clock
|
|
32
|
+
});
|
|
33
|
+
if (isErr(res))
|
|
34
|
+
return tokenEndpointErrorResponse(res.error);
|
|
35
|
+
return new Response(JSON.stringify(res.value), {
|
|
36
|
+
status: 201,
|
|
37
|
+
headers: {
|
|
38
|
+
"content-type": "application/json",
|
|
39
|
+
"cache-control": "no-store"
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
makeRegisterHandler
|
|
46
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/http/handlers/token.ts
|
|
2
2
|
import { clientCredentialsGrant } from "../../domain/client-credentials";
|
|
3
|
+
import { canonicalHtu, verifyDpopProof } from "../../domain/dpop";
|
|
3
4
|
import { exchangeCode } from "../../domain/token";
|
|
4
5
|
import { exchangeToken } from "../../domain/token-exchange";
|
|
5
6
|
import { refreshTokens } from "../../domain/refresh";
|
|
@@ -19,6 +20,29 @@ function makeTokenHandler(deps) {
|
|
|
19
20
|
body.set("client_id", basic.id);
|
|
20
21
|
body.set("client_secret", basic.secret);
|
|
21
22
|
}
|
|
23
|
+
const dpopHeader = c.req.header("dpop") ?? null;
|
|
24
|
+
let dpopJkt;
|
|
25
|
+
if (dpopHeader) {
|
|
26
|
+
const dpopRes = await verifyDpopProof({
|
|
27
|
+
proofJwt: dpopHeader,
|
|
28
|
+
htu: canonicalHtu(c.req.url),
|
|
29
|
+
htm: "POST",
|
|
30
|
+
nowSec: Math.floor(deps.clock() / 1000)
|
|
31
|
+
}, { tokenStore: deps.tokenStore });
|
|
32
|
+
if (isErr(dpopRes)) {
|
|
33
|
+
const errVal = dpopRes.error;
|
|
34
|
+
if (errVal.code === "invalid_dpop_proof" && errVal.replaySignal && deps.auditLog) {
|
|
35
|
+
await deps.auditLog.log({
|
|
36
|
+
kind: "dpop_replay_detected",
|
|
37
|
+
tenantId: null,
|
|
38
|
+
jtiPrefix: errVal.replaySignal.jti.slice(0, 16),
|
|
39
|
+
timestamp: deps.clock()
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return tokenEndpointErrorResponse(errVal);
|
|
43
|
+
}
|
|
44
|
+
dpopJkt = dpopRes.value.jkt;
|
|
45
|
+
}
|
|
22
46
|
const parsed = tokenRequestSchema.safeParse(Object.fromEntries(body.entries()));
|
|
23
47
|
if (!parsed.success) {
|
|
24
48
|
const first = parsed.error.issues[0];
|
|
@@ -35,7 +59,8 @@ function makeTokenHandler(deps) {
|
|
|
35
59
|
redirectUri: req.redirect_uri,
|
|
36
60
|
clientId: req.client_id,
|
|
37
61
|
...req.client_secret !== undefined ? { clientSecret: req.client_secret } : {},
|
|
38
|
-
...req.code_verifier !== undefined ? { codeVerifier: req.code_verifier } : {}
|
|
62
|
+
...req.code_verifier !== undefined ? { codeVerifier: req.code_verifier } : {},
|
|
63
|
+
...dpopJkt !== undefined ? { dpopJkt } : {}
|
|
39
64
|
}, {
|
|
40
65
|
configStore: deps.configStore,
|
|
41
66
|
tokenStore: deps.tokenStore,
|
|
@@ -44,7 +69,8 @@ function makeTokenHandler(deps) {
|
|
|
44
69
|
success: deps.success,
|
|
45
70
|
...deps.persistUpstreamTokens ? { persistUpstreamTokens: deps.persistUpstreamTokens } : {},
|
|
46
71
|
issuerUrl: c.get("issuerUrl"),
|
|
47
|
-
clock: deps.clock
|
|
72
|
+
clock: deps.clock,
|
|
73
|
+
...deps.customScopeClaims !== undefined ? { customScopeClaims: deps.customScopeClaims } : {}
|
|
48
74
|
});
|
|
49
75
|
if (isErr(result))
|
|
50
76
|
return tokenEndpointErrorResponse(result.error);
|
|
@@ -75,7 +101,8 @@ function makeTokenHandler(deps) {
|
|
|
75
101
|
success: deps.success,
|
|
76
102
|
...deps.persistUpstreamTokens ? { persistUpstreamTokens: deps.persistUpstreamTokens } : {},
|
|
77
103
|
issuerUrl: c.get("issuerUrl"),
|
|
78
|
-
clock: deps.clock
|
|
104
|
+
clock: deps.clock,
|
|
105
|
+
...deps.customScopeClaims !== undefined ? { customScopeClaims: deps.customScopeClaims } : {}
|
|
79
106
|
}, tenantRes.value);
|
|
80
107
|
if (isErr(result))
|
|
81
108
|
return tokenEndpointErrorResponse(result.error);
|
|
@@ -87,14 +114,16 @@ function makeTokenHandler(deps) {
|
|
|
87
114
|
refreshToken: req.refresh_token,
|
|
88
115
|
...req.scope !== undefined ? { scope: req.scope } : {},
|
|
89
116
|
...req.client_id !== undefined ? { clientId: req.client_id } : {},
|
|
90
|
-
...req.client_secret !== undefined ? { clientSecret: req.client_secret } : {}
|
|
117
|
+
...req.client_secret !== undefined ? { clientSecret: req.client_secret } : {},
|
|
118
|
+
...dpopJkt !== undefined ? { dpopJkt } : {}
|
|
91
119
|
}, {
|
|
92
120
|
configStore: deps.configStore,
|
|
93
121
|
tokenStore: deps.tokenStore,
|
|
94
122
|
keyStore: deps.keyStore,
|
|
95
123
|
...deps.auditLog ? { auditLog: deps.auditLog } : {},
|
|
96
124
|
issuerUrl: c.get("issuerUrl"),
|
|
97
|
-
clock: deps.clock
|
|
125
|
+
clock: deps.clock,
|
|
126
|
+
...deps.customScopeClaims !== undefined ? { customScopeClaims: deps.customScopeClaims } : {}
|
|
98
127
|
});
|
|
99
128
|
if (isErr(refreshResult))
|
|
100
129
|
return tokenEndpointErrorResponse(refreshResult.error);
|
|
@@ -117,7 +146,8 @@ function makeTokenHandler(deps) {
|
|
|
117
146
|
...deps.auditLog ? { auditLog: deps.auditLog } : {},
|
|
118
147
|
...deps.exchangeAudience ? { exchangeAudience: deps.exchangeAudience } : {},
|
|
119
148
|
issuerUrl: c.get("issuerUrl"),
|
|
120
|
-
clock: deps.clock
|
|
149
|
+
clock: deps.clock,
|
|
150
|
+
...deps.customScopeClaims !== undefined ? { customScopeClaims: deps.customScopeClaims } : {}
|
|
121
151
|
});
|
|
122
152
|
if (isErr(exchangeResult)) {
|
|
123
153
|
return tokenEndpointErrorResponse(exchangeResult.error);
|
|
@@ -1,38 +1,32 @@
|
|
|
1
1
|
// src/http/handlers/userinfo.ts
|
|
2
|
+
import { canonicalHtu } from "../../domain/dpop";
|
|
2
3
|
import { userinfo } from "../../domain/userinfo";
|
|
3
4
|
import { isErr } from "../../types/result";
|
|
4
5
|
function makeUserinfoHandler(deps) {
|
|
5
6
|
return async (c) => {
|
|
6
|
-
const
|
|
7
|
-
if (!
|
|
8
|
-
|
|
9
|
-
error: "invalid_token",
|
|
10
|
-
error_description: "missing bearer token"
|
|
11
|
-
}), {
|
|
12
|
-
status: 401,
|
|
13
|
-
headers: {
|
|
14
|
-
"content-type": "application/json",
|
|
15
|
-
"www-authenticate": 'Bearer realm="userinfo"'
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
return r;
|
|
7
|
+
const auth = extractAuthorization(c.req.header("authorization") ?? null);
|
|
8
|
+
if (!auth) {
|
|
9
|
+
return wwwAuthenticateResponse("Bearer", "invalid_token", "missing bearer / DPoP token", 401);
|
|
19
10
|
}
|
|
20
|
-
const
|
|
11
|
+
const dpopProof = c.req.header("dpop") ?? undefined;
|
|
12
|
+
const res = await userinfo({
|
|
13
|
+
accessToken: auth.token,
|
|
14
|
+
scheme: auth.scheme,
|
|
15
|
+
...dpopProof !== undefined ? { dpopProof } : {},
|
|
16
|
+
htu: canonicalHtu(c.req.url),
|
|
17
|
+
htm: c.req.method.toUpperCase(),
|
|
18
|
+
nowSec: Math.floor(deps.clock() / 1000)
|
|
19
|
+
}, {
|
|
21
20
|
keyStore: deps.keyStore,
|
|
22
|
-
|
|
21
|
+
tokenStore: deps.tokenStore,
|
|
22
|
+
issuerUrl: c.get("issuerUrl"),
|
|
23
|
+
...deps.customScopeClaims !== undefined ? { customScopeClaims: deps.customScopeClaims } : {}
|
|
23
24
|
});
|
|
24
25
|
if (isErr(res)) {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}), {
|
|
30
|
-
status: 401,
|
|
31
|
-
headers: {
|
|
32
|
-
"content-type": "application/json",
|
|
33
|
-
"www-authenticate": `Bearer error="invalid_token"`
|
|
34
|
-
}
|
|
35
|
-
});
|
|
26
|
+
const error = res.error;
|
|
27
|
+
const scheme = auth.scheme;
|
|
28
|
+
const wireError = error.code === "invalid_dpop_proof" ? "invalid_dpop_proof" : "invalid_token";
|
|
29
|
+
return wwwAuthenticateResponse(scheme, wireError, error.description, 401);
|
|
36
30
|
}
|
|
37
31
|
return new Response(JSON.stringify(res.value), {
|
|
38
32
|
status: 200,
|
|
@@ -43,11 +37,23 @@ function makeUserinfoHandler(deps) {
|
|
|
43
37
|
});
|
|
44
38
|
};
|
|
45
39
|
}
|
|
46
|
-
function
|
|
40
|
+
function extractAuthorization(header) {
|
|
47
41
|
if (!header)
|
|
48
42
|
return null;
|
|
49
|
-
const m = /^Bearer\s+(.+)$/i.exec(header);
|
|
50
|
-
|
|
43
|
+
const m = /^(Bearer|DPoP)\s+(.+)$/i.exec(header);
|
|
44
|
+
if (!m || !m[1] || !m[2])
|
|
45
|
+
return null;
|
|
46
|
+
const scheme = m[1].toLowerCase() === "dpop" ? "DPoP" : "Bearer";
|
|
47
|
+
return { scheme, token: m[2] };
|
|
48
|
+
}
|
|
49
|
+
function wwwAuthenticateResponse(scheme, error, description, status) {
|
|
50
|
+
return new Response(JSON.stringify({ error, error_description: description }), {
|
|
51
|
+
status,
|
|
52
|
+
headers: {
|
|
53
|
+
"content-type": "application/json",
|
|
54
|
+
"www-authenticate": `${scheme} error="${error}"`
|
|
55
|
+
}
|
|
56
|
+
});
|
|
51
57
|
}
|
|
52
58
|
export {
|
|
53
59
|
makeUserinfoHandler
|
|
@@ -27,12 +27,12 @@ function tenantMiddleware(deps) {
|
|
|
27
27
|
const recovery = await runCallbackRecovery(c.req.raw, deps);
|
|
28
28
|
c.set("recovery", recovery);
|
|
29
29
|
if (recovery.kind !== "fresh-request") {
|
|
30
|
-
const
|
|
31
|
-
if (isErr(
|
|
32
|
-
return tokenEndpointErrorResponse(
|
|
30
|
+
const cfg = await deps.configStore.getTenantConfig(recovery.tenantId);
|
|
31
|
+
if (isErr(cfg)) {
|
|
32
|
+
return tokenEndpointErrorResponse(cfg.error);
|
|
33
33
|
}
|
|
34
34
|
const customCb = deps.buildCustomContext ? await deps.buildCustomContext(c.req.raw) : {};
|
|
35
|
-
c.set("tenant", buildTenantContext(c.req.raw, recovery.tenantId,
|
|
35
|
+
c.set("tenant", buildTenantContext(c.req.raw, recovery.tenantId, cfg.value, customCb));
|
|
36
36
|
return next();
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -97,6 +97,6 @@ async function extractStateParam(req) {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
export {
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
bootstrapMiddleware,
|
|
101
|
+
tenantMiddleware
|
|
102
102
|
};
|