@_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
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Per AD9, access tokens are JWTs (ES256 by default, Ed25519 optional per
|
|
7
7
|
* AD11) and refresh tokens are opaque server-side records.
|
|
8
8
|
*/
|
|
9
|
+
import type { ClaimsRequest } from "./authorization";
|
|
9
10
|
import type { SubjectClaim } from "./subject";
|
|
10
11
|
import type { TenantId } from "./tenant";
|
|
11
12
|
/**
|
|
@@ -39,9 +40,98 @@ export type CodePayload = {
|
|
|
39
40
|
providerSubject: string;
|
|
40
41
|
/** Typed-per-method properties from `MethodResult.success`. */
|
|
41
42
|
properties: unknown;
|
|
43
|
+
/**
|
|
44
|
+
* Relying party's OIDC `nonce` (OIDC Core §3.1.2.1). Snapshotted from
|
|
45
|
+
* `FlowRecord.appNonce`. Echoed into the issued `id_token` when present.
|
|
46
|
+
*/
|
|
47
|
+
appNonce?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Seconds-since-epoch when end-user authentication completed
|
|
50
|
+
* (OIDC Core §2 `auth_time`). Stamped at the moment the method's
|
|
51
|
+
* `MethodResult.success` fires. Carried into the `id_token` and forward
|
|
52
|
+
* across refresh-token rotations.
|
|
53
|
+
*/
|
|
54
|
+
authTime: number;
|
|
55
|
+
/** OIDC Core §5.5 — RP-requested claims; honored at id_token + /userinfo. */
|
|
56
|
+
claimsRequest?: ClaimsRequest;
|
|
42
57
|
/** Auth-code TTL is 60 s — framework refuses anything longer. */
|
|
43
58
|
expiresAt: number;
|
|
44
59
|
};
|
|
60
|
+
/**
|
|
61
|
+
* OIDC Core §5.1 `address` claim. Structured object value rather than a
|
|
62
|
+
* scalar — distinct from the rest of the profile fields.
|
|
63
|
+
*/
|
|
64
|
+
export type AddressClaim = {
|
|
65
|
+
formatted?: string;
|
|
66
|
+
street_address?: string;
|
|
67
|
+
locality?: string;
|
|
68
|
+
region?: string;
|
|
69
|
+
postal_code?: string;
|
|
70
|
+
country?: string;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* OIDC Core §5.1 profile / email / phone / address claims, gated by the
|
|
74
|
+
* `profile` / `email` / `phone` / `address` scopes via §5.4. Reused by
|
|
75
|
+
* `IdTokenClaims` and the `/userinfo` response so the two surfaces share
|
|
76
|
+
* exactly one source of truth for scope→claim mapping.
|
|
77
|
+
*
|
|
78
|
+
* Every field is optional: presence depends on (a) which scopes were
|
|
79
|
+
* granted and (b) whether the host populated the matching key in
|
|
80
|
+
* `SubjectClaim.properties`.
|
|
81
|
+
*/
|
|
82
|
+
export type ScopedProfileClaims = {
|
|
83
|
+
name?: string;
|
|
84
|
+
given_name?: string;
|
|
85
|
+
family_name?: string;
|
|
86
|
+
middle_name?: string;
|
|
87
|
+
nickname?: string;
|
|
88
|
+
preferred_username?: string;
|
|
89
|
+
profile?: string;
|
|
90
|
+
picture?: string;
|
|
91
|
+
website?: string;
|
|
92
|
+
gender?: string;
|
|
93
|
+
birthdate?: string;
|
|
94
|
+
zoneinfo?: string;
|
|
95
|
+
locale?: string;
|
|
96
|
+
updated_at?: number;
|
|
97
|
+
email?: string;
|
|
98
|
+
email_verified?: boolean;
|
|
99
|
+
phone_number?: string;
|
|
100
|
+
phone_number_verified?: boolean;
|
|
101
|
+
address?: AddressClaim;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* OIDC `id_token` JWT claims this IdP issues at `/token` when `openid`
|
|
105
|
+
* scope is granted (OIDC Core §2). Distinct from access-token claims:
|
|
106
|
+
*
|
|
107
|
+
* - `aud` is the relying-party `client_id` (not the API audience).
|
|
108
|
+
* - `nonce` echoes the RP's `/authorize` `nonce` parameter when present.
|
|
109
|
+
* - `auth_time` is stable across refresh-token rotations (OIDC Core §12).
|
|
110
|
+
* - `at_hash` is recommended whenever an id_token and access_token are
|
|
111
|
+
* returned in the same response (§3.1.3.6).
|
|
112
|
+
*
|
|
113
|
+
* Standard OIDC claim names use snake_case on the wire; TypeScript field
|
|
114
|
+
* names match to keep the marshalling 1:1.
|
|
115
|
+
*/
|
|
116
|
+
export type IdTokenClaims = ScopedProfileClaims & {
|
|
117
|
+
iss: string;
|
|
118
|
+
sub: string;
|
|
119
|
+
aud: string;
|
|
120
|
+
exp: number;
|
|
121
|
+
iat: number;
|
|
122
|
+
/** Seconds-since-epoch when end-user auth completed. */
|
|
123
|
+
auth_time?: number;
|
|
124
|
+
/** RP-supplied OIDC nonce, when present. MUST equal the `/authorize` value. */
|
|
125
|
+
nonce?: string;
|
|
126
|
+
/** Authentication Methods References (RFC 8176). */
|
|
127
|
+
amr?: string[];
|
|
128
|
+
/** Authentication Context Class Reference. */
|
|
129
|
+
acr?: string;
|
|
130
|
+
/** Authorized party — the client_id of the party the id_token is for. */
|
|
131
|
+
azp?: string;
|
|
132
|
+
/** Access-token hash (OIDC Core §3.1.3.6). */
|
|
133
|
+
at_hash?: string;
|
|
134
|
+
};
|
|
45
135
|
/** Access-token JWT claims this IdP issues. */
|
|
46
136
|
export type AccessTokenClaims = {
|
|
47
137
|
iss: string;
|
|
@@ -56,10 +146,30 @@ export type AccessTokenClaims = {
|
|
|
56
146
|
/** Factory kind that originated this token. */
|
|
57
147
|
mkind?: string;
|
|
58
148
|
scope?: string;
|
|
149
|
+
/**
|
|
150
|
+
* Seconds-since-epoch when the end-user originally authenticated
|
|
151
|
+
* (OIDC Core §2). Stable across refresh-token rotation and across
|
|
152
|
+
* RFC 8693 token-exchange (§12 — exchanging an audience does NOT
|
|
153
|
+
* re-authenticate the user). Absent on `client_credentials` grants
|
|
154
|
+
* where there is no end-user.
|
|
155
|
+
*
|
|
156
|
+
* Carried on the access token so RFC 8693 `subject_token` consumers
|
|
157
|
+
* can propagate it without needing the original id_token. Resource
|
|
158
|
+
* servers typically ignore it; it's there for the IdP's own
|
|
159
|
+
* downstream issuance.
|
|
160
|
+
*/
|
|
161
|
+
auth_time?: number;
|
|
59
162
|
/** DPoP confirmation claim (Phase 8). */
|
|
60
163
|
cnf?: {
|
|
61
164
|
jkt: string;
|
|
62
165
|
};
|
|
166
|
+
/**
|
|
167
|
+
* OIDC Core §5.5 — list of claim names the RP requested for the
|
|
168
|
+
* `/userinfo` response via the `claims` parameter. The resource server
|
|
169
|
+
* surface uses this to bypass scope-gating per §5.5; it is empty /
|
|
170
|
+
* absent when the RP did not use the `claims` parameter.
|
|
171
|
+
*/
|
|
172
|
+
uic?: string[];
|
|
63
173
|
/** The structured subject (matches `SubjectClaim`). Inlined for resource servers. */
|
|
64
174
|
claim: SubjectClaim;
|
|
65
175
|
};
|
|
@@ -87,6 +197,24 @@ export type RefreshTokenPayload = {
|
|
|
87
197
|
methodId: string;
|
|
88
198
|
/** Factory kind (`MethodConfig.kind`) that originated the chain. */
|
|
89
199
|
methodKind: string;
|
|
200
|
+
/**
|
|
201
|
+
* Seconds-since-epoch when end-user authentication originally completed
|
|
202
|
+
* (OIDC Core §2 `auth_time`). Stable across refresh-token rotations —
|
|
203
|
+
* `auth_time` does **not** advance on refresh, only on re-authentication.
|
|
204
|
+
*/
|
|
205
|
+
authTime: number;
|
|
206
|
+
/**
|
|
207
|
+
* RFC 7638 JWK thumbprint of the client's DPoP key (RFC 9449 §6.1).
|
|
208
|
+
* Present when the original token grant was DPoP-bound. Refresh-grant
|
|
209
|
+
* rotation REQUIRES a fresh DPoP proof whose thumbprint matches; absent
|
|
210
|
+
* = the token is plain Bearer and Bearer is acceptable on refresh.
|
|
211
|
+
*/
|
|
212
|
+
dpopJkt?: string;
|
|
213
|
+
/**
|
|
214
|
+
* OIDC Core §12 — the original RP-requested claims, preserved so
|
|
215
|
+
* refresh-grant id_token + /userinfo responses keep returning them.
|
|
216
|
+
*/
|
|
217
|
+
claimsRequest?: ClaimsRequest;
|
|
90
218
|
/** Wall-clock issuance and absolute-expiry timestamps. */
|
|
91
219
|
issuedAt: number;
|
|
92
220
|
expiresAt: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../../src/types/token.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExC;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,EAAE,QAAQ,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gFAAgF;IAChF,UAAU,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAClD,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAA;IAChB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAA;IAClB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IACxC,yEAAyE;IACzE,eAAe,EAAE,MAAM,CAAA;IACvB,+DAA+D;IAC/D,UAAU,EAAE,OAAO,CAAA;IACnB,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,+CAA+C;AAC/C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,iBAAiB;IACjB,GAAG,EAAE,QAAQ,CAAA;IACb,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,yCAAyC;IACzC,GAAG,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IACrB,qFAAqF;IACrF,KAAK,EAAE,YAAY,CAAA;CACpB,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,QAAQ,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,YAAY,CAAA;IACnB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAA;IACd;;;;;OAKG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB,oEAAoE;IACpE,UAAU,EAAE,MAAM,CAAA;IAClB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,iDAAiD;AACjD,MAAM,MAAM,aAAa,GAAG;IAC1B,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,QAAQ,GAAG,MAAM,CAAA;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA"}
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../../src/types/token.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@_mustachio/openauth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,6 +37,12 @@
|
|
|
37
37
|
"import": "./dist/esm/adapters/*/index.js",
|
|
38
38
|
"types": "./dist/types/adapters/*/index.d.ts"
|
|
39
39
|
},
|
|
40
|
+
"./methods/saml-sp": {
|
|
41
|
+
"node": {
|
|
42
|
+
"import": "./dist/esm/methods/saml-sp/index.js",
|
|
43
|
+
"types": "./dist/types/methods/saml-sp/index.d.ts"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
40
46
|
"./*": {
|
|
41
47
|
"import": "./dist/esm/*.js",
|
|
42
48
|
"types": "./dist/types/*.d.ts"
|
|
@@ -73,8 +79,10 @@
|
|
|
73
79
|
},
|
|
74
80
|
"dependencies": {
|
|
75
81
|
"@noble/hashes": "2.2.0",
|
|
82
|
+
"@node-saml/node-saml": "5.1.0",
|
|
76
83
|
"@simplewebauthn/server": "13.1.1",
|
|
77
84
|
"@standard-schema/spec": "1.0.0-beta.3",
|
|
85
|
+
"@xmldom/xmldom": "0.8.13",
|
|
78
86
|
"aws4fetch": "1.0.20",
|
|
79
87
|
"jose": "5.9.6",
|
|
80
88
|
"oauth4webapi": "3.8.6",
|
|
@@ -67,6 +67,11 @@ export const INITIAL_SCHEMA_SQL = [
|
|
|
67
67
|
payload TEXT NOT NULL,
|
|
68
68
|
expires_at INTEGER NOT NULL
|
|
69
69
|
)`,
|
|
70
|
+
`CREATE TABLE IF NOT EXISTS openauth_scratch (
|
|
71
|
+
scratch_key TEXT PRIMARY KEY,
|
|
72
|
+
value TEXT NOT NULL,
|
|
73
|
+
expires_at INTEGER NOT NULL
|
|
74
|
+
)`,
|
|
70
75
|
`CREATE TABLE IF NOT EXISTS openauth_audit_events (
|
|
71
76
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
72
77
|
ts INTEGER NOT NULL,
|
|
@@ -227,4 +227,72 @@ export class D1SessionStore implements SessionStore {
|
|
|
227
227
|
}
|
|
228
228
|
return ok(undefined)
|
|
229
229
|
}
|
|
230
|
+
|
|
231
|
+
async saveScratch(
|
|
232
|
+
key: string,
|
|
233
|
+
value: string,
|
|
234
|
+
ttlMs: number,
|
|
235
|
+
): Promise<Result<void>> {
|
|
236
|
+
if (ttlMs <= 0) {
|
|
237
|
+
return err(
|
|
238
|
+
authError.internalError(
|
|
239
|
+
`saveScratch: ttlMs must be positive, got ${ttlMs}`,
|
|
240
|
+
),
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
const expiresAt = this.#clock() + ttlMs
|
|
244
|
+
try {
|
|
245
|
+
// Pinned to the primary — scratch backs InResponseTo replay
|
|
246
|
+
// protection, which is read-after-write sensitive at the ACS.
|
|
247
|
+
await primarySession(this.#db)
|
|
248
|
+
.prepare(
|
|
249
|
+
`INSERT INTO openauth_scratch (scratch_key, value, expires_at) VALUES (?1, ?2, ?3)
|
|
250
|
+
ON CONFLICT(scratch_key) DO UPDATE SET value = excluded.value, expires_at = excluded.expires_at`,
|
|
251
|
+
)
|
|
252
|
+
.bind(key, value, expiresAt)
|
|
253
|
+
.run()
|
|
254
|
+
} catch (e) {
|
|
255
|
+
return err(authError.internalError("saveScratch: upsert failed", e))
|
|
256
|
+
}
|
|
257
|
+
return ok(undefined)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async readScratch(key: string): Promise<Result<string>> {
|
|
261
|
+
let row: { value: string; expires_at: number } | null
|
|
262
|
+
try {
|
|
263
|
+
row = await primarySession(this.#db)
|
|
264
|
+
.prepare(
|
|
265
|
+
`SELECT value, expires_at FROM openauth_scratch WHERE scratch_key = ?1`,
|
|
266
|
+
)
|
|
267
|
+
.bind(key)
|
|
268
|
+
.first<{ value: string; expires_at: number }>()
|
|
269
|
+
} catch (e) {
|
|
270
|
+
return err(authError.internalError("readScratch: query failed", e))
|
|
271
|
+
}
|
|
272
|
+
if (!row) {
|
|
273
|
+
return err(authError.unknownState(`scratch "${key}" unknown`))
|
|
274
|
+
}
|
|
275
|
+
if (this.#clock() >= Number(row.expires_at)) {
|
|
276
|
+
try {
|
|
277
|
+
await primarySession(this.#db)
|
|
278
|
+
.prepare(`DELETE FROM openauth_scratch WHERE scratch_key = ?1`)
|
|
279
|
+
.bind(key)
|
|
280
|
+
.run()
|
|
281
|
+
} catch {}
|
|
282
|
+
return err(authError.unknownState(`scratch "${key}" expired`))
|
|
283
|
+
}
|
|
284
|
+
return ok(row.value)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async deleteScratch(key: string): Promise<Result<void>> {
|
|
288
|
+
try {
|
|
289
|
+
await primarySession(this.#db)
|
|
290
|
+
.prepare(`DELETE FROM openauth_scratch WHERE scratch_key = ?1`)
|
|
291
|
+
.bind(key)
|
|
292
|
+
.run()
|
|
293
|
+
} catch (e) {
|
|
294
|
+
return err(authError.internalError("deleteScratch: delete failed", e))
|
|
295
|
+
}
|
|
296
|
+
return ok(undefined)
|
|
297
|
+
}
|
|
230
298
|
}
|
|
@@ -51,6 +51,11 @@ type StoredSession = {
|
|
|
51
51
|
expiresAt: number
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
type StoredScratch = {
|
|
55
|
+
value: string
|
|
56
|
+
expiresAt: number
|
|
57
|
+
}
|
|
58
|
+
|
|
54
59
|
export type DurableObjectSessionStoreOptions = {
|
|
55
60
|
storage: DurableObjectStorageLike
|
|
56
61
|
clock?: () => number
|
|
@@ -218,10 +223,66 @@ export class DurableObjectSessionStore implements SessionStore {
|
|
|
218
223
|
}
|
|
219
224
|
return ok(undefined)
|
|
220
225
|
}
|
|
226
|
+
|
|
227
|
+
async saveScratch(
|
|
228
|
+
key: string,
|
|
229
|
+
value: string,
|
|
230
|
+
ttlMs: number,
|
|
231
|
+
): Promise<Result<void>> {
|
|
232
|
+
if (ttlMs <= 0) {
|
|
233
|
+
return err(
|
|
234
|
+
authError.internalError(
|
|
235
|
+
`saveScratch: ttlMs must be positive, got ${ttlMs}`,
|
|
236
|
+
),
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
const stored: StoredScratch = {
|
|
240
|
+
value,
|
|
241
|
+
expiresAt: this.#clock() + ttlMs,
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
// Plain put — scratch overwrites any prior value for the key. No
|
|
245
|
+
// transaction needed: it is a single write, and the DO serializes
|
|
246
|
+
// all operations on this instance.
|
|
247
|
+
await this.#storage.put(scratchKey(key), stored)
|
|
248
|
+
} catch (e) {
|
|
249
|
+
return err(authError.internalError("saveScratch: put failed", e))
|
|
250
|
+
}
|
|
251
|
+
return ok(undefined)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async readScratch(key: string): Promise<Result<string>> {
|
|
255
|
+
let stored: StoredScratch | undefined
|
|
256
|
+
try {
|
|
257
|
+
stored = await this.#storage.get<StoredScratch>(scratchKey(key))
|
|
258
|
+
} catch (e) {
|
|
259
|
+
return err(authError.internalError("readScratch: get failed", e))
|
|
260
|
+
}
|
|
261
|
+
if (!stored) {
|
|
262
|
+
return err(authError.unknownState(`scratch "${key}" unknown`))
|
|
263
|
+
}
|
|
264
|
+
if (this.#clock() >= stored.expiresAt) {
|
|
265
|
+
try {
|
|
266
|
+
await this.#storage.delete(scratchKey(key))
|
|
267
|
+
} catch {}
|
|
268
|
+
return err(authError.unknownState(`scratch "${key}" expired`))
|
|
269
|
+
}
|
|
270
|
+
return ok(stored.value)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async deleteScratch(key: string): Promise<Result<void>> {
|
|
274
|
+
try {
|
|
275
|
+
await this.#storage.delete(scratchKey(key))
|
|
276
|
+
} catch (e) {
|
|
277
|
+
return err(authError.internalError("deleteScratch: delete failed", e))
|
|
278
|
+
}
|
|
279
|
+
return ok(undefined)
|
|
280
|
+
}
|
|
221
281
|
}
|
|
222
282
|
|
|
223
283
|
const flowKey = (flowId: string) => `flow:${flowId}`
|
|
224
284
|
const sessionKey = (sessionId: string) => `session:${sessionId}`
|
|
285
|
+
const scratchKey = (key: string) => `scratch:${key}`
|
|
225
286
|
|
|
226
287
|
// Re-exported for adapter users wiring `storage.transaction` callbacks.
|
|
227
288
|
export type { DurableObjectStorageLike, DurableObjectTransactionLike }
|
|
@@ -211,6 +211,69 @@ export class DynamoSessionStore implements SessionStore {
|
|
|
211
211
|
}
|
|
212
212
|
return ok(undefined)
|
|
213
213
|
}
|
|
214
|
+
|
|
215
|
+
async saveScratch(
|
|
216
|
+
key: string,
|
|
217
|
+
value: string,
|
|
218
|
+
ttlMs: number,
|
|
219
|
+
): Promise<Result<void>> {
|
|
220
|
+
if (ttlMs <= 0) {
|
|
221
|
+
return err(
|
|
222
|
+
authError.internalError(
|
|
223
|
+
`saveScratch: ttlMs must be positive, got ${ttlMs}`,
|
|
224
|
+
),
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
const expiresAtMs = this.#clock() + ttlMs
|
|
228
|
+
try {
|
|
229
|
+
// Unconditional put — scratch overwrites any prior value for the key.
|
|
230
|
+
await this.#exec.put({
|
|
231
|
+
item: {
|
|
232
|
+
pk: "scratch",
|
|
233
|
+
sk: key,
|
|
234
|
+
value,
|
|
235
|
+
expires_at: expiresAtMs,
|
|
236
|
+
ttl: Math.floor(expiresAtMs / 1000),
|
|
237
|
+
},
|
|
238
|
+
})
|
|
239
|
+
} catch (e) {
|
|
240
|
+
return err(authError.internalError("saveScratch: put failed", e))
|
|
241
|
+
}
|
|
242
|
+
return ok(undefined)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async readScratch(key: string): Promise<Result<string>> {
|
|
246
|
+
let row: Record<string, unknown> | undefined
|
|
247
|
+
try {
|
|
248
|
+
row = await this.#exec.get({
|
|
249
|
+
key: { pk: "scratch", sk: key },
|
|
250
|
+
consistentRead: true,
|
|
251
|
+
})
|
|
252
|
+
} catch (e) {
|
|
253
|
+
return err(authError.internalError("readScratch: get failed", e))
|
|
254
|
+
}
|
|
255
|
+
if (!row) {
|
|
256
|
+
return err(authError.unknownState(`scratch "${key}" unknown`))
|
|
257
|
+
}
|
|
258
|
+
// Native DynamoDB TTL eviction is best-effort (can lag up to ~48h), so
|
|
259
|
+
// filter on the adapter clock for correctness.
|
|
260
|
+
if (this.#clock() >= Number(row.expires_at)) {
|
|
261
|
+
try {
|
|
262
|
+
await this.#exec.delete({ key: { pk: "scratch", sk: key } })
|
|
263
|
+
} catch {}
|
|
264
|
+
return err(authError.unknownState(`scratch "${key}" expired`))
|
|
265
|
+
}
|
|
266
|
+
return ok(String(row.value))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async deleteScratch(key: string): Promise<Result<void>> {
|
|
270
|
+
try {
|
|
271
|
+
await this.#exec.delete({ key: { pk: "scratch", sk: key } })
|
|
272
|
+
} catch (e) {
|
|
273
|
+
return err(authError.internalError("deleteScratch: delete failed", e))
|
|
274
|
+
}
|
|
275
|
+
return ok(undefined)
|
|
276
|
+
}
|
|
214
277
|
}
|
|
215
278
|
|
|
216
279
|
function parseFlowPayload(raw: unknown): FlowRecord {
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { authError } from "../../types/error"
|
|
10
10
|
import type { FlowRecord } from "../../types/flow"
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
ParRecord,
|
|
13
|
+
SessionRecord,
|
|
14
|
+
SessionStore,
|
|
15
|
+
} from "../../ports/session-store"
|
|
12
16
|
import type { Result } from "../../types/result"
|
|
13
17
|
import { err, ok } from "../../types/result"
|
|
14
18
|
import type { Clock } from "./clock"
|
|
@@ -19,6 +23,16 @@ type StoredFlow = {
|
|
|
19
23
|
expiresAt: number
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
type StoredPar = {
|
|
27
|
+
record: ParRecord
|
|
28
|
+
expiresAt: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type StoredScratch = {
|
|
32
|
+
value: string
|
|
33
|
+
expiresAt: number
|
|
34
|
+
}
|
|
35
|
+
|
|
22
36
|
export type MemorySessionStoreOptions = {
|
|
23
37
|
clock?: Clock
|
|
24
38
|
}
|
|
@@ -27,6 +41,8 @@ export class MemorySessionStore implements SessionStore {
|
|
|
27
41
|
#clock: Clock
|
|
28
42
|
#flows = new Map<string, StoredFlow>()
|
|
29
43
|
#sessions = new Map<string, SessionRecord>()
|
|
44
|
+
#par = new Map<string, StoredPar>()
|
|
45
|
+
#scratch = new Map<string, StoredScratch>()
|
|
30
46
|
|
|
31
47
|
constructor(opts: MemorySessionStoreOptions = {}) {
|
|
32
48
|
this.#clock = opts.clock ?? realClock
|
|
@@ -125,4 +141,70 @@ export class MemorySessionStore implements SessionStore {
|
|
|
125
141
|
this.#sessions.delete(sessionId)
|
|
126
142
|
return ok(undefined)
|
|
127
143
|
}
|
|
144
|
+
|
|
145
|
+
async savePar(
|
|
146
|
+
requestUri: string,
|
|
147
|
+
payload: ParRecord,
|
|
148
|
+
ttl: number,
|
|
149
|
+
): Promise<Result<void>> {
|
|
150
|
+
if (ttl <= 0) {
|
|
151
|
+
return err(
|
|
152
|
+
authError.internalError(`savePar: ttl must be positive, got ${ttl}`),
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
this.#par.set(requestUri, {
|
|
156
|
+
record: payload,
|
|
157
|
+
expiresAt: this.#clock() + ttl,
|
|
158
|
+
})
|
|
159
|
+
return ok(undefined)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async consumePar(requestUri: string): Promise<Result<ParRecord>> {
|
|
163
|
+
const stored = this.#par.get(requestUri)
|
|
164
|
+
if (!stored) {
|
|
165
|
+
return err(
|
|
166
|
+
authError.unknownState(
|
|
167
|
+
`par "${requestUri}" unknown or already consumed`,
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
this.#par.delete(requestUri)
|
|
172
|
+
if (this.#clock() >= stored.expiresAt) {
|
|
173
|
+
return err(authError.unknownState(`par "${requestUri}" expired`))
|
|
174
|
+
}
|
|
175
|
+
return ok(stored.record)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async saveScratch(
|
|
179
|
+
key: string,
|
|
180
|
+
value: string,
|
|
181
|
+
ttlMs: number,
|
|
182
|
+
): Promise<Result<void>> {
|
|
183
|
+
if (ttlMs <= 0) {
|
|
184
|
+
return err(
|
|
185
|
+
authError.internalError(
|
|
186
|
+
`saveScratch: ttlMs must be positive, got ${ttlMs}`,
|
|
187
|
+
),
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
this.#scratch.set(key, { value, expiresAt: this.#clock() + ttlMs })
|
|
191
|
+
return ok(undefined)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async readScratch(key: string): Promise<Result<string>> {
|
|
195
|
+
const stored = this.#scratch.get(key)
|
|
196
|
+
if (!stored) {
|
|
197
|
+
return err(authError.unknownState(`scratch "${key}" unknown`))
|
|
198
|
+
}
|
|
199
|
+
if (this.#clock() >= stored.expiresAt) {
|
|
200
|
+
this.#scratch.delete(key)
|
|
201
|
+
return err(authError.unknownState(`scratch "${key}" expired`))
|
|
202
|
+
}
|
|
203
|
+
return ok(stored.value)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async deleteScratch(key: string): Promise<Result<void>> {
|
|
207
|
+
this.#scratch.delete(key)
|
|
208
|
+
return ok(undefined)
|
|
209
|
+
}
|
|
128
210
|
}
|
|
@@ -43,6 +43,8 @@ export class MemoryTokenStore implements TokenStore {
|
|
|
43
43
|
#clock: Clock
|
|
44
44
|
#codes = new Map<string, StoredCode>()
|
|
45
45
|
#refresh = new Map<string, StoredRefresh>()
|
|
46
|
+
/** DPoP proof `jti` replay-protection map. Value is expiry timestamp. */
|
|
47
|
+
#dpopJtis = new Map<string, number>()
|
|
46
48
|
|
|
47
49
|
constructor(opts: MemoryTokenStoreOptions = {}) {
|
|
48
50
|
this.#clock = opts.clock ?? realClock
|
|
@@ -162,4 +164,18 @@ export class MemoryTokenStore implements TokenStore {
|
|
|
162
164
|
}
|
|
163
165
|
return ok(undefined)
|
|
164
166
|
}
|
|
167
|
+
|
|
168
|
+
async recordDpopJti(jti: string, ttlMs: number): Promise<Result<void>> {
|
|
169
|
+
const now = this.#clock()
|
|
170
|
+
// Sweep expired entries opportunistically — keeps the map bounded
|
|
171
|
+
// under sustained load without a separate GC pass.
|
|
172
|
+
for (const [k, exp] of this.#dpopJtis) {
|
|
173
|
+
if (exp <= now) this.#dpopJtis.delete(k)
|
|
174
|
+
}
|
|
175
|
+
if (this.#dpopJtis.has(jti)) {
|
|
176
|
+
return err(authError.invalidGrant(`dpop jti "${jti}" replayed`))
|
|
177
|
+
}
|
|
178
|
+
this.#dpopJtis.set(jti, now + ttlMs)
|
|
179
|
+
return ok(undefined)
|
|
180
|
+
}
|
|
165
181
|
}
|
|
@@ -89,6 +89,12 @@ CREATE TABLE IF NOT EXISTS openauth_sessions (
|
|
|
89
89
|
expires_at bigint NOT NULL
|
|
90
90
|
);
|
|
91
91
|
|
|
92
|
+
CREATE TABLE IF NOT EXISTS openauth_scratch (
|
|
93
|
+
scratch_key text PRIMARY KEY,
|
|
94
|
+
value text NOT NULL,
|
|
95
|
+
expires_at bigint NOT NULL
|
|
96
|
+
);
|
|
97
|
+
|
|
92
98
|
CREATE TABLE IF NOT EXISTS openauth_audit_events (
|
|
93
99
|
id bigserial PRIMARY KEY,
|
|
94
100
|
ts bigint NOT NULL,
|
|
@@ -203,6 +203,75 @@ export class PostgresSessionStore implements SessionStore {
|
|
|
203
203
|
}
|
|
204
204
|
return ok(undefined)
|
|
205
205
|
}
|
|
206
|
+
|
|
207
|
+
async saveScratch(
|
|
208
|
+
key: string,
|
|
209
|
+
value: string,
|
|
210
|
+
ttlMs: number,
|
|
211
|
+
): Promise<Result<void>> {
|
|
212
|
+
if (ttlMs <= 0) {
|
|
213
|
+
return err(
|
|
214
|
+
authError.internalError(
|
|
215
|
+
`saveScratch: ttlMs must be positive, got ${ttlMs}`,
|
|
216
|
+
),
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
const expiresAt = this.#clock() + ttlMs
|
|
220
|
+
try {
|
|
221
|
+
await this.#exec.query(
|
|
222
|
+
`INSERT INTO openauth_scratch (scratch_key, value, expires_at)
|
|
223
|
+
VALUES ($1, $2, $3)
|
|
224
|
+
ON CONFLICT (scratch_key)
|
|
225
|
+
DO UPDATE SET value = EXCLUDED.value, expires_at = EXCLUDED.expires_at`,
|
|
226
|
+
[key, value, expiresAt],
|
|
227
|
+
)
|
|
228
|
+
} catch (e) {
|
|
229
|
+
return err(authError.internalError("saveScratch: upsert failed", e))
|
|
230
|
+
}
|
|
231
|
+
return ok(undefined)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async readScratch(key: string): Promise<Result<string>> {
|
|
235
|
+
let row: { value: string; expires_at: string | number } | undefined
|
|
236
|
+
try {
|
|
237
|
+
const result = await this.#exec.query<{
|
|
238
|
+
value: string
|
|
239
|
+
expires_at: string | number
|
|
240
|
+
}>(
|
|
241
|
+
`SELECT value, expires_at FROM openauth_scratch WHERE scratch_key = $1`,
|
|
242
|
+
[key],
|
|
243
|
+
)
|
|
244
|
+
row = result.rows[0]
|
|
245
|
+
} catch (e) {
|
|
246
|
+
return err(authError.internalError("readScratch: query failed", e))
|
|
247
|
+
}
|
|
248
|
+
if (!row) {
|
|
249
|
+
return err(authError.unknownState(`scratch "${key}" unknown`))
|
|
250
|
+
}
|
|
251
|
+
if (this.#clock() >= Number(row.expires_at)) {
|
|
252
|
+
// Lazy GC — best-effort, don't fail the read if the delete races.
|
|
253
|
+
try {
|
|
254
|
+
await this.#exec.query(
|
|
255
|
+
`DELETE FROM openauth_scratch WHERE scratch_key = $1`,
|
|
256
|
+
[key],
|
|
257
|
+
)
|
|
258
|
+
} catch {}
|
|
259
|
+
return err(authError.unknownState(`scratch "${key}" expired`))
|
|
260
|
+
}
|
|
261
|
+
return ok(row.value)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async deleteScratch(key: string): Promise<Result<void>> {
|
|
265
|
+
try {
|
|
266
|
+
await this.#exec.query(
|
|
267
|
+
`DELETE FROM openauth_scratch WHERE scratch_key = $1`,
|
|
268
|
+
[key],
|
|
269
|
+
)
|
|
270
|
+
} catch (e) {
|
|
271
|
+
return err(authError.internalError("deleteScratch: delete failed", e))
|
|
272
|
+
}
|
|
273
|
+
return ok(undefined)
|
|
274
|
+
}
|
|
206
275
|
}
|
|
207
276
|
|
|
208
277
|
function parseFlowRecord(raw: unknown): FlowRecord {
|