@sentropic/auth-hono 0.2.1 → 0.4.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.
Files changed (95) hide show
  1. package/README.md +168 -1
  2. package/dist/contracts.d.ts +1 -1
  3. package/dist/contracts.d.ts.map +1 -1
  4. package/dist/contracts.js +2 -0
  5. package/dist/contracts.js.map +1 -1
  6. package/dist/index.d.ts +16 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +16 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/oauth/authorize-handler.d.ts +13 -0
  11. package/dist/oauth/authorize-handler.d.ts.map +1 -0
  12. package/dist/oauth/authorize-handler.js +143 -0
  13. package/dist/oauth/authorize-handler.js.map +1 -0
  14. package/dist/oauth/consent-decision-handler.d.ts +11 -0
  15. package/dist/oauth/consent-decision-handler.d.ts.map +1 -0
  16. package/dist/oauth/consent-decision-handler.js +58 -0
  17. package/dist/oauth/consent-decision-handler.js.map +1 -0
  18. package/dist/oauth/crypto-utils.d.ts +3 -0
  19. package/dist/oauth/crypto-utils.d.ts.map +1 -0
  20. package/dist/oauth/crypto-utils.js +13 -0
  21. package/dist/oauth/crypto-utils.js.map +1 -0
  22. package/dist/oauth/dpop.d.ts +18 -0
  23. package/dist/oauth/dpop.d.ts.map +1 -0
  24. package/dist/oauth/dpop.js +54 -0
  25. package/dist/oauth/dpop.js.map +1 -0
  26. package/dist/oauth/http-utils.d.ts +6 -0
  27. package/dist/oauth/http-utils.d.ts.map +1 -0
  28. package/dist/oauth/http-utils.js +27 -0
  29. package/dist/oauth/http-utils.js.map +1 -0
  30. package/dist/oauth/introspect-handler.d.ts +8 -0
  31. package/dist/oauth/introspect-handler.d.ts.map +1 -0
  32. package/dist/oauth/introspect-handler.js +63 -0
  33. package/dist/oauth/introspect-handler.js.map +1 -0
  34. package/dist/oauth/jwks-service.d.ts +25 -0
  35. package/dist/oauth/jwks-service.d.ts.map +1 -0
  36. package/dist/oauth/jwks-service.js +61 -0
  37. package/dist/oauth/jwks-service.js.map +1 -0
  38. package/dist/oauth/revoke-handler.d.ts +8 -0
  39. package/dist/oauth/revoke-handler.d.ts.map +1 -0
  40. package/dist/oauth/revoke-handler.js +55 -0
  41. package/dist/oauth/revoke-handler.js.map +1 -0
  42. package/dist/oauth/router.d.ts +8 -0
  43. package/dist/oauth/router.d.ts.map +1 -0
  44. package/dist/oauth/router.js +30 -0
  45. package/dist/oauth/router.js.map +1 -0
  46. package/dist/oauth/service-auth-middleware.d.ts +30 -0
  47. package/dist/oauth/service-auth-middleware.d.ts.map +1 -0
  48. package/dist/oauth/service-auth-middleware.js +170 -0
  49. package/dist/oauth/service-auth-middleware.js.map +1 -0
  50. package/dist/oauth/session-resolver.d.ts +9 -0
  51. package/dist/oauth/session-resolver.d.ts.map +1 -0
  52. package/dist/oauth/session-resolver.js +28 -0
  53. package/dist/oauth/session-resolver.js.map +1 -0
  54. package/dist/oauth/state-codec.d.ts +25 -0
  55. package/dist/oauth/state-codec.d.ts.map +1 -0
  56. package/dist/oauth/state-codec.js +60 -0
  57. package/dist/oauth/state-codec.js.map +1 -0
  58. package/dist/oauth/state-store-types.d.ts +100 -0
  59. package/dist/oauth/state-store-types.d.ts.map +1 -0
  60. package/dist/oauth/state-store-types.js +2 -0
  61. package/dist/oauth/state-store-types.js.map +1 -0
  62. package/dist/oauth/token-handler.d.ts +12 -0
  63. package/dist/oauth/token-handler.d.ts.map +1 -0
  64. package/dist/oauth/token-handler.js +294 -0
  65. package/dist/oauth/token-handler.js.map +1 -0
  66. package/dist/oauth/userinfo-handler.d.ts +9 -0
  67. package/dist/oauth/userinfo-handler.d.ts.map +1 -0
  68. package/dist/oauth/userinfo-handler.js +93 -0
  69. package/dist/oauth/userinfo-handler.js.map +1 -0
  70. package/dist/oauth/wellknown-handler.d.ts +9 -0
  71. package/dist/oauth/wellknown-handler.d.ts.map +1 -0
  72. package/dist/oauth/wellknown-handler.js +37 -0
  73. package/dist/oauth/wellknown-handler.js.map +1 -0
  74. package/dist/ports.d.ts +4 -0
  75. package/dist/ports.d.ts.map +1 -1
  76. package/package.json +1 -1
  77. package/src/contracts.ts +2 -0
  78. package/src/index.ts +16 -0
  79. package/src/oauth/authorize-handler.ts +201 -0
  80. package/src/oauth/consent-decision-handler.ts +93 -0
  81. package/src/oauth/crypto-utils.ts +14 -0
  82. package/src/oauth/dpop.ts +93 -0
  83. package/src/oauth/http-utils.ts +58 -0
  84. package/src/oauth/introspect-handler.ts +88 -0
  85. package/src/oauth/jwks-service.ts +103 -0
  86. package/src/oauth/revoke-handler.ts +70 -0
  87. package/src/oauth/router.ts +42 -0
  88. package/src/oauth/service-auth-middleware.ts +250 -0
  89. package/src/oauth/session-resolver.ts +48 -0
  90. package/src/oauth/state-codec.ts +98 -0
  91. package/src/oauth/state-store-types.ts +109 -0
  92. package/src/oauth/token-handler.ts +423 -0
  93. package/src/oauth/userinfo-handler.ts +129 -0
  94. package/src/oauth/wellknown-handler.ts +52 -0
  95. package/src/ports.ts +17 -0
package/README.md CHANGED
@@ -68,13 +68,180 @@ All package handlers emit structured responses to keep contracts predictable acr
68
68
  - **Sentropic-style app** with a Drizzle/Postgres backend, WebAuthn-only login, and workspace-scoped sessions: implement `AuthHonoCredentialPort`, an `AuthHonoSessionService` adapter (or the package's `createAuthSessionService` when the full `AuthHonoPorts` bundle is wired), an `AuthHonoWebAuthnRegistrationService`/`AuthHonoWebAuthnAuthenticationService` adapter, and provide `prepareRegistrationOptions`/`resolveRegistrationUser` for first-admin + account-status policy and `finalizeRegistration`/`finalizeAuthentication` for session creation and cookie issuance. Sentropic's `api/src/services/auth/*-adapter.ts` modules show this end-to-end.
69
69
  - **DB-less admin flow** (e.g. `spa-transpose-cv` mounting at `/admin/auth/*`): use a file- or memory-backed implementation of `AuthHonoCredentialPort` and the relevant ports, skip workspace bootstrap entirely, and either rely on the default verify response or supply a minimal `finalizeAuthentication` that issues a host-managed session cookie. The package never touches workspace state, so no DB schema is required.
70
70
 
71
+ ## OAuth2 / OpenID Connect Identity Provider (since 0.3.0)
72
+
73
+ `@sentropic/auth-hono` ships a complete OAuth2 Authorization Server + OIDC Identity Provider built on the same ports-and-adapters model as the existing WebAuthn routes.
74
+
75
+ ### Endpoints
76
+
77
+ | Endpoint | Description |
78
+ | --- | --- |
79
+ | `GET /oauth/authorize` | Authorization Code + PKCE entry point (S256 required) |
80
+ | `POST /oauth/token` | Token issuance (`grant_type=authorization_code` and `client_credentials` since 0.4.0) |
81
+ | `GET\|POST /oauth/userinfo` | Returns claims for a valid access token |
82
+ | `POST /oauth/revoke` | Revokes an access token (RFC 7009) |
83
+ | `POST /oauth/introspect` | Token introspection (RFC 7662, client-auth required) |
84
+ | `POST /oauth/consent/decision` | Host-private consent approval/denial — NOT in discovery doc |
85
+ | `GET /.well-known/openid-configuration` | OIDC discovery document |
86
+ | `GET /.well-known/jwks.json` | Public Ed25519 signing keys (RFC 7517) |
87
+
88
+ ### Wiring the OAuth router
89
+
90
+ ```ts
91
+ import { createOAuthRouter, createWellKnownRouter } from '@sentropic/auth-hono';
92
+ import { Hono } from 'hono';
93
+
94
+ // Router mounted under /api/v1/auth
95
+ const authRouter = new Hono();
96
+ authRouter.route('/oauth', createOAuthRouter({
97
+ ports, // AuthHonoPorts — includes oauthStateStore + jwks
98
+ issuer, // e.g. 'https://api.example.com'
99
+ loginUrl, // e.g. 'https://app.example.com/auth/login'
100
+ consentUrl, // e.g. 'https://app.example.com/auth/oauth/consent'
101
+ }));
102
+
103
+ // Well-known router mounted at root level
104
+ const app = new Hono();
105
+ app.route('/.well-known', createWellKnownRouter({ ports, issuer }));
106
+ app.route('/api/v1/auth', authRouter);
107
+ ```
108
+
109
+ ### JwksService and signing
110
+
111
+ `createJwksService({ jwksPort, clock })` returns `{ signJwt, verifyJwt, getPublicJwks }`:
112
+
113
+ ```ts
114
+ import { createJwksService } from '@sentropic/auth-hono';
115
+
116
+ const jwksService = createJwksService({ jwksPort: hostJwksPort, clock: Date.now });
117
+
118
+ // Sign an access token
119
+ const accessToken = await jwksService.signJwt(
120
+ { sub: userId, scope: 'openid profile', iss: issuer, aud: `${issuer}/userinfo` },
121
+ { expiresIn: 3600 }
122
+ );
123
+
124
+ // Verify any token (looks up kid from JWKS, accepts active and rotated keys)
125
+ const payload = await jwksService.verifyJwt(incomingJwt);
126
+ ```
127
+
128
+ Signing algorithm: EdDSA (Ed25519) only. No RS256 fallback. JWKS response includes the active key and all rotated keys for at least `access_token TTL + JWKS cache TTL` (≥ 65 minutes). Discovery response sends `Cache-Control: public, max-age=300` on JWKS.
129
+
130
+ ### Key rotation policy
131
+
132
+ 1. The active signing key is created once via the host's `make exec-api CMD="npm run oauth:init-keys"` (or `make oauth-init-keys`).
133
+ 2. Rotate via `make oauth-rotate-keys` (calls `JwksService.rotateKey()` through `api/src/scripts/oauth-rotate-keys.ts`).
134
+ 3. Rotated keys remain in JWKS for ≥ 65 minutes so in-flight tokens stay verifiable.
135
+ 4. Rotate the KEK (`OAUTH_SIGNING_KEK`) separately, every 90 days; re-encrypt stored private keys.
136
+
137
+ ### OauthStateStorePort
138
+
139
+ `AuthHonoPorts.oauthStateStore` must implement:
140
+
141
+ ```ts
142
+ interface OauthStateStorePort {
143
+ findClient(clientId: string): Promise<OauthClientRecord | null>;
144
+ saveAuthCode(code: string, payload: AuthCodePayload, ttlSec: number): Promise<void>;
145
+ consumeAuthCode(code: string): Promise<AuthCodePayload | null>; // atomic single-use
146
+ saveTokenMeta(jti: string, meta: TokenMeta, ttlSec: number): Promise<void>;
147
+ findTokenMeta(jti: string): Promise<TokenMeta | null>;
148
+ revokeToken(jti: string): Promise<void>;
149
+ isTokenRevoked(jti: string): Promise<boolean>;
150
+ recordDpopJti(jti: string, expiresAt: Date): Promise<boolean>; // false = duplicate
151
+ purgeExpired(): Promise<number>;
152
+ }
153
+ ```
154
+
155
+ The package never imports Postgres or any persistence library. Sentropic supplies `api/src/services/auth/oauth-state-adapter.ts` (Drizzle/Postgres). Package tests use the in-memory fixture at `packages/auth-hono/tests/__fixtures__/memory-oauth-state-store.ts`.
156
+
157
+ ### DPoP opt-in (RFC 9449)
158
+
159
+ Set `dpop_bound_access_tokens: true` on the OAuth client record. Bound clients must send a `DPoP: <proof-jwt>` header on `/token`, `/userinfo`, and `/revoke`. The IdP verifies `htm`, `htu`, `iat` skew, unique proof `jti`, and `ath` on resource calls. Access and ID tokens include `cnf.jkt`.
160
+
161
+ ### Service-to-service auth — `client_credentials` (since 0.4.0)
162
+
163
+ Backend services mint scoped, audience-bound, **stateless** access tokens without
164
+ a human via the `client_credentials` grant.
165
+
166
+ - **Service clients** are a separate record type, `ServiceClientRecord`, looked up
167
+ through an **optional** `findServiceClient?(clientId)` method on
168
+ `OauthStateStorePort`. Existing implementors of the `0.3.0` contract keep
169
+ compiling; if the method is absent, `client_credentials` returns
170
+ `unsupported_grant_type`.
171
+ - **Auth methods**: `client_secret_basic` and `client_secret_post`, verified via
172
+ `ports.tokens.hashSecret`.
173
+ - **Scopes**: empty/absent `scope` grants the client's full `allowed_scopes`;
174
+ otherwise the request must be a subset (else `invalid_scope`).
175
+ - **RFC 8707 resource indicators**: the issued token `aud` is the resolved
176
+ `resource`, which must be in the client's `resource_indicators`. Resolution: 1
177
+ indicator + no `resource` ⇒ use it; >1 + no `resource` ⇒ `invalid_target`; 0
178
+ indicators ⇒ `resource` required else `invalid_target`; unknown `resource` ⇒
179
+ `invalid_target`.
180
+ - **Stateless** (no `saveTokenMeta`, no `oauth_tokens` row): security relies on a
181
+ short TTL (`serviceAccessTokenTtlSeconds`, default `900`) + secret rotation.
182
+ Service-token revocation/introspection are deferred to BR-39h.
183
+ - **DPoP** is opt-in via `dpop_bound_access_tokens` and strongly recommended for
184
+ production S2S.
185
+
186
+ Resource servers verify these tokens with `createRequireServiceAuth`:
187
+
188
+ ```ts
189
+ import { createRequireServiceAuth, type ServiceAuthPorts } from '@sentropic/auth-hono';
190
+
191
+ const ports: ServiceAuthPorts = {
192
+ clock, // AuthHonoClockPort
193
+ jwks, // JwksPort
194
+ dpopReplay: { recordDpopJti }, // optional, required to enforce DPoP replay
195
+ };
196
+
197
+ app.get(
198
+ '/internal/ping',
199
+ createRequireServiceAuth({ issuer, resource, requiredScopes: ['service:ping'], ports }),
200
+ (c) => c.json({ ok: true, client: c.get('serviceClient') }),
201
+ );
202
+ ```
203
+
204
+ `ServiceAuthPorts` is a **narrow** port (`Pick<AuthHonoPorts,'jwks'|'clock'> & { dpopReplay? }`):
205
+ resource servers do not construct user/credential/session/email ports just to
206
+ verify a token. The middleware validates `iss`, `aud === resource`, `exp`, and
207
+ `scope ⊇ requiredScopes`; for `cnf.jkt`-bound tokens it requires a DPoP proof,
208
+ enforces `ath` (RFC 9449 §4.3), and records the proof `jti` for replay defense.
209
+ On failure it returns 401/403 with a `WWW-Authenticate` header.
210
+
211
+ ### Claims and ACR levels
212
+
213
+ | Claim | Source | Notes |
214
+ | --- | --- | --- |
215
+ | `acr` | Session type | `urn:sentropic:loa:passkey-fresh` (passkey session), `urn:sentropic:loa:bearer` (magic-link session) |
216
+ | `auth_time` | `session.createdAt` | Strong-auth timestamp tracking lands in BR-39j |
217
+ | `tenant_id` | `oauth_clients.tenant_id` | Forward-compat column; real tenants in BR-39e |
218
+
219
+ ### Required environment variables
220
+
221
+ | Variable | Description | Default |
222
+ | --- | --- | --- |
223
+ | `OAUTH_SIGNING_KEK` | Passphrase for Postgres `pgp_sym_encrypt` of private key | **Required in production** — see `docs/secrets.md` |
224
+ | `OAUTH_ISSUER_URL` | Override issuer claim | Derived from `AUTH_CALLBACK_BASE_URL` |
225
+ | `OAUTH_ACCESS_TOKEN_TTL_SEC` | Access token lifetime | `3600` |
226
+ | `OAUTH_ID_TOKEN_TTL_SEC` | ID token lifetime | `3600` |
227
+ | `OAUTH_AUTHORIZATION_CODE_TTL_SEC` | Authorization code TTL | `60` |
228
+ | `OAUTH_DPOP_IAT_SKEW_SEC` | DPoP proof `iat` tolerance | `60` |
229
+ | `OAUTH_SERVICE_ACCESS_TOKEN_TTL_SEC` | Stateless service token TTL (`client_credentials`) | `900` |
230
+ | `OAUTH_SERVICE_RESOURCE_URI` | Service token `aud` this API accepts/advertises | Derived from issuer |
231
+
232
+ ### End-to-end example
233
+
234
+ See `packages/auth-hono/tests/example-oauth-rp.test.ts` for a complete in-process test that walks the full flow: authorize → consent → callback → token → userinfo → revoke → userinfo 401.
235
+
71
236
  ## First Publish
72
237
 
73
238
  This is a brand-new public package. First publish requires the one-shot bootstrap flow from `rules/workflow.md`: trigger `ci.yml` with `bootstrap_publish_target=auth-hono`, handle any npm token or 2FA requirement with the npm owner, then attach the npm OIDC trusted publisher for `rhanka/sentropic` workflow `ci.yml`. Steady-state publishes should use trusted publishing and skip if the version already exists.
74
239
 
75
240
  ## Versioning
76
241
 
77
- This branch ships `0.2.1`:
242
+ This branch ships `0.4.0`:
78
243
 
79
244
  - `0.2.0` adds `AuthHonoRouteHandlerError` short-circuit on WebAuthn prepare/resolve hooks and the `finalizeRegistration`/`finalizeAuthentication` post-verify hooks. Additive; existing handler signatures stay valid.
80
245
  - `0.2.1` patches `extractChallenge` (both WebAuthn handlers) to handle `credential.response === null` defensively (returns 400 `invalid_credential` instead of throwing 500).
246
+ - `0.3.0` adds the OAuth2/OIDC IdP surface: `createOAuthRouter`, `createWellKnownRouter`, `createJwksService`, `OauthStateStorePort`, `JwksPort`, Ed25519 signing, DPoP opt-in, and all six OAuth endpoints. Additive; existing WebAuthn/session handler signatures unchanged.
247
+ - `0.4.0` adds the S2S `client_credentials` grant (stateless service tokens), `createRequireServiceAuth` + `ServiceAuthPorts`, the optional `findServiceClient?` on `OauthStateStorePort`, `ServiceClientRecord`, and RFC 8707 resource indicators. Discovery now advertises `client_credentials` and `client_secret_post`. Additive and non-breaking — existing `0.3.0` implementors keep compiling.
@@ -59,6 +59,6 @@ export declare const AUTH_HONO_ROUTE_MAP: {
59
59
  readonly path: "/credentials/:id";
60
60
  };
61
61
  };
62
- export declare const AUTH_HONO_REQUIRED_PORTS: readonly ["users", "credentials", "challenges", "sessions", "emailVerification", "magicLinks", "emailDelivery", "cookies", "tokens", "auditLog", "clock", "random", "accountPolicy"];
62
+ export declare const AUTH_HONO_REQUIRED_PORTS: readonly ["users", "credentials", "challenges", "sessions", "emailVerification", "magicLinks", "emailDelivery", "cookies", "tokens", "auditLog", "clock", "random", "accountPolicy", "oauthStateStore", "jwks"];
63
63
  export type AuthHonoRequiredPort = (typeof AUTH_HONO_REQUIRED_PORTS)[number];
64
64
  //# sourceMappingURL=contracts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,yBAAyB,sTAc5B,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9E,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEnE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDwC,CAAC;AAEzE,eAAO,MAAM,wBAAwB,sLAcgB,CAAC;AAEtD,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC"}
1
+ {"version":3,"file":"contracts.d.ts","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,yBAAyB,sTAc5B,CAAC;AAEX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9E,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEnE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,kBAAkB,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqDwC,CAAC;AAEzE,eAAO,MAAM,wBAAwB,iNAgBgB,CAAC;AAEtD,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC"}
package/dist/contracts.js CHANGED
@@ -81,5 +81,7 @@ export const AUTH_HONO_REQUIRED_PORTS = [
81
81
  'clock',
82
82
  'random',
83
83
  'accountPolicy',
84
+ 'oauthStateStore',
85
+ 'jwks',
84
86
  ];
85
87
  //# sourceMappingURL=contracts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,kBAAkB;IAClB,iBAAiB;IACjB,kBAAkB;IAClB,iBAAiB;IACjB,kCAAkC;IAClC,2BAA2B;IAC3B,oCAAoC;IACpC,6BAA6B;IAC7B,gBAAgB;IAChB,QAAQ;IACR,iBAAiB;IACjB,kBAAkB;IAClB,kBAAkB;CACV,CAAC;AAWX,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,gBAAgB,EAAE;QAChB,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,uBAAuB;KAC9B;IACD,eAAe,EAAE;QACf,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,oBAAoB;KAC3B;IACD,gBAAgB,EAAE;QAChB,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,qBAAqB;KAC5B;IACD,eAAe,EAAE;QACf,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,oBAAoB;KAC3B;IACD,gCAAgC,EAAE;QAChC,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,mBAAmB;KAC1B;IACD,yBAAyB,EAAE;QACzB,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,kBAAkB;KACzB;IACD,kCAAkC,EAAE;QAClC,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,gBAAgB;KACvB;IACD,2BAA2B,EAAE;QAC3B,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,eAAe;KACtB;IACD,cAAc,EAAE;QACd,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,kBAAkB;KACzB;IACD,MAAM,EAAE;QACN,MAAM,EAAE,QAAQ;QAChB,IAAI,EAAE,UAAU;KACjB;IACD,eAAe,EAAE;QACf,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,cAAc;KACrB;IACD,gBAAgB,EAAE;QAChB,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,kBAAkB;KACzB;IACD,gBAAgB,EAAE;QAChB,MAAM,EAAE,QAAQ;QAChB,IAAI,EAAE,kBAAkB;KACzB;CACqE,CAAC;AAEzE,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,OAAO;IACP,aAAa;IACb,YAAY;IACZ,UAAU;IACV,mBAAmB;IACnB,YAAY;IACZ,eAAe;IACf,SAAS;IACT,QAAQ;IACR,UAAU;IACV,OAAO;IACP,QAAQ;IACR,eAAe;CACoC,CAAC"}
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../src/contracts.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,kBAAkB;IAClB,iBAAiB;IACjB,kBAAkB;IAClB,iBAAiB;IACjB,kCAAkC;IAClC,2BAA2B;IAC3B,oCAAoC;IACpC,6BAA6B;IAC7B,gBAAgB;IAChB,QAAQ;IACR,iBAAiB;IACjB,kBAAkB;IAClB,kBAAkB;CACV,CAAC;AAWX,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,gBAAgB,EAAE;QAChB,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,uBAAuB;KAC9B;IACD,eAAe,EAAE;QACf,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,oBAAoB;KAC3B;IACD,gBAAgB,EAAE;QAChB,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,qBAAqB;KAC5B;IACD,eAAe,EAAE;QACf,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,oBAAoB;KAC3B;IACD,gCAAgC,EAAE;QAChC,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,mBAAmB;KAC1B;IACD,yBAAyB,EAAE;QACzB,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,kBAAkB;KACzB;IACD,kCAAkC,EAAE;QAClC,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,gBAAgB;KACvB;IACD,2BAA2B,EAAE;QAC3B,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,eAAe;KACtB;IACD,cAAc,EAAE;QACd,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,kBAAkB;KACzB;IACD,MAAM,EAAE;QACN,MAAM,EAAE,QAAQ;QAChB,IAAI,EAAE,UAAU;KACjB;IACD,eAAe,EAAE;QACf,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,cAAc;KACrB;IACD,gBAAgB,EAAE;QAChB,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,kBAAkB;KACzB;IACD,gBAAgB,EAAE;QAChB,MAAM,EAAE,QAAQ;QAChB,IAAI,EAAE,kBAAkB;KACzB;CACqE,CAAC;AAEzE,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,OAAO;IACP,aAAa;IACb,YAAY;IACZ,UAAU;IACV,mBAAmB;IACnB,YAAY;IACZ,eAAe;IACf,SAAS;IACT,QAAQ;IACR,UAAU;IACV,OAAO;IACP,QAAQ;IACR,eAAe;IACf,iBAAiB;IACjB,MAAM;CAC6C,CAAC"}
package/dist/index.d.ts CHANGED
@@ -3,6 +3,22 @@ export * from './credential-route-handlers.js';
3
3
  export * from './email-verification.js';
4
4
  export * from './magic-link.js';
5
5
  export * from './middleware.js';
6
+ export * from './oauth/authorize-handler.js';
7
+ export * from './oauth/consent-decision-handler.js';
8
+ export * from './oauth/crypto-utils.js';
9
+ export * from './oauth/dpop.js';
10
+ export * from './oauth/http-utils.js';
11
+ export * from './oauth/introspect-handler.js';
12
+ export * from './oauth/jwks-service.js';
13
+ export * from './oauth/router.js';
14
+ export * from './oauth/revoke-handler.js';
15
+ export * from './oauth/service-auth-middleware.js';
16
+ export * from './oauth/session-resolver.js';
17
+ export * from './oauth/state-store-types.js';
18
+ export * from './oauth/state-codec.js';
19
+ export * from './oauth/token-handler.js';
20
+ export * from './oauth/userinfo-handler.js';
21
+ export * from './oauth/wellknown-handler.js';
6
22
  export * from './ports.js';
7
23
  export * from './route-handlers.js';
8
24
  export * from './router.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2CAA2C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qCAAqC,CAAC;AACpD,cAAc,yBAAyB,CAAC;AACxC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oCAAoC,CAAC;AACnD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2CAA2C,CAAC"}
package/dist/index.js CHANGED
@@ -3,6 +3,22 @@ export * from './credential-route-handlers.js';
3
3
  export * from './email-verification.js';
4
4
  export * from './magic-link.js';
5
5
  export * from './middleware.js';
6
+ export * from './oauth/authorize-handler.js';
7
+ export * from './oauth/consent-decision-handler.js';
8
+ export * from './oauth/crypto-utils.js';
9
+ export * from './oauth/dpop.js';
10
+ export * from './oauth/http-utils.js';
11
+ export * from './oauth/introspect-handler.js';
12
+ export * from './oauth/jwks-service.js';
13
+ export * from './oauth/router.js';
14
+ export * from './oauth/revoke-handler.js';
15
+ export * from './oauth/service-auth-middleware.js';
16
+ export * from './oauth/session-resolver.js';
17
+ export * from './oauth/state-store-types.js';
18
+ export * from './oauth/state-codec.js';
19
+ export * from './oauth/token-handler.js';
20
+ export * from './oauth/userinfo-handler.js';
21
+ export * from './oauth/wellknown-handler.js';
6
22
  export * from './ports.js';
7
23
  export * from './route-handlers.js';
8
24
  export * from './router.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2CAA2C,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gCAAgC,CAAC;AAC/C,cAAc,yBAAyB,CAAC;AACxC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,qCAAqC,CAAC;AACpD,cAAc,yBAAyB,CAAC;AACxC,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,yBAAyB,CAAC;AACxC,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,oCAAoC,CAAC;AACnD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6CAA6C,CAAC;AAC5D,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2CAA2C,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { Context } from 'hono';
2
+ import type { AuthHonoPorts } from '../ports.js';
3
+ import type { OAuthContinuationCodec } from './state-codec.js';
4
+ export interface OAuthAuthorizeHandlerOptions {
5
+ consentUrl: string;
6
+ issuer: string;
7
+ loginUrl: string;
8
+ ports: AuthHonoPorts;
9
+ stateCodec: OAuthContinuationCodec;
10
+ stateTtlSeconds?: number;
11
+ }
12
+ export declare const createOAuthAuthorizeHandler: (options: OAuthAuthorizeHandlerOptions) => (c: Context) => Promise<Response>;
13
+ //# sourceMappingURL=authorize-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize-handler.d.ts","sourceRoot":"","sources":["../../src/oauth/authorize-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,KAAK,EAAE,sBAAsB,EAA0B,MAAM,kBAAkB,CAAC;AAIvF,MAAM,WAAW,4BAA4B;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,aAAa,CAAC;IACrB,UAAU,EAAE,sBAAsB,CAAC;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAYD,eAAO,MAAM,2BAA2B,YAC5B,4BAA4B,SAC5B,OAAO,KAAG,QAAQ,QAAQ,CAgCnC,CAAC"}
@@ -0,0 +1,143 @@
1
+ import { appendParams, oauthJsonError, redirectWithOAuthError } from './http-utils.js';
2
+ import { resolveOAuthAcr, resolveOAuthSession } from './session-resolver.js';
3
+ export const createOAuthAuthorizeHandler = (options) => async (c) => {
4
+ const continuation = c.req.query('continue');
5
+ if (continuation) {
6
+ return resumeLoginContinuation(c, options, continuation);
7
+ }
8
+ const validation = await validateAuthorizeRequest(c, options.ports);
9
+ if (validation instanceof Response)
10
+ return validation;
11
+ const prompt = c.req.query('prompt') ?? '';
12
+ const session = await resolveOAuthSession(c.req.raw, options.ports);
13
+ if (!session || prompt === 'login') {
14
+ if (prompt === 'none') {
15
+ return redirectWithOAuthError(validation.redirectUri, 'login_required', validation.state, c.req.url);
16
+ }
17
+ const continuation = await sealContinuation(c, options, validation);
18
+ return c.redirect(appendParams(options.loginUrl, { continue: continuation }, c.req.url), 302);
19
+ }
20
+ if (prompt === 'none') {
21
+ return redirectWithOAuthError(validation.redirectUri, 'consent_required', validation.state, c.req.url);
22
+ }
23
+ const sealedState = await sealContinuation(c, options, validation, {
24
+ acr: resolveOAuthAcr(session.sessionRecord),
25
+ authTime: session.sessionRecord.createdAt.toISOString(),
26
+ userId: session.user.id,
27
+ });
28
+ return c.redirect(appendParams(options.consentUrl, { state: sealedState }, c.req.url), 302);
29
+ };
30
+ const resumeLoginContinuation = async (c, options, continuation) => {
31
+ const payload = await options.stateCodec.unseal(continuation);
32
+ const now = options.ports.clock.now();
33
+ if (!payload || payload.userId || payload.codeChallengeMethod !== 'S256' || new Date(payload.expiresAt) <= now) {
34
+ return oauthJsonError(c, 400, 'invalid_request', 'OAuth continuation is invalid or expired.');
35
+ }
36
+ const client = await options.ports.oauthStateStore.findClient(payload.clientId);
37
+ if (!client)
38
+ return oauthJsonError(c, 400, 'invalid_request', 'Unknown OAuth client.');
39
+ const redirectError = validateRedirectUri(client, payload.redirectUri);
40
+ if (redirectError)
41
+ return oauthJsonError(c, 400, 'invalid_request', redirectError);
42
+ const scopeResult = validateScope(payload.scope, client, payload.redirectUri, payload.state, c.req.url);
43
+ if (scopeResult instanceof Response)
44
+ return scopeResult;
45
+ const session = await resolveOAuthSession(c.req.raw, options.ports);
46
+ if (!session) {
47
+ return c.redirect(appendParams(options.loginUrl, { continue: continuation }, c.req.url), 302);
48
+ }
49
+ const expiresAt = options.ports.clock.addSeconds(now, options.stateTtlSeconds ?? 10 * 60);
50
+ const sealedState = await options.stateCodec.seal({
51
+ ...payload,
52
+ acr: resolveOAuthAcr(session.sessionRecord),
53
+ authTime: session.sessionRecord.createdAt.toISOString(),
54
+ createdAt: now.toISOString(),
55
+ expiresAt: expiresAt.toISOString(),
56
+ scope: scopeResult,
57
+ userId: session.user.id,
58
+ });
59
+ return c.redirect(appendParams(options.consentUrl, { state: sealedState }, c.req.url), 302);
60
+ };
61
+ const validateAuthorizeRequest = async (c, ports) => {
62
+ const clientId = c.req.query('client_id');
63
+ const client = clientId ? await ports.oauthStateStore.findClient(clientId) : null;
64
+ if (!client) {
65
+ return oauthJsonError(c, 400, 'invalid_request', 'Unknown OAuth client.');
66
+ }
67
+ const redirectUri = c.req.query('redirect_uri') ?? '';
68
+ const redirectError = validateRedirectUri(client, redirectUri);
69
+ if (redirectError) {
70
+ return oauthJsonError(c, 400, 'invalid_request', redirectError);
71
+ }
72
+ const state = c.req.query('state') ?? null;
73
+ if (c.req.query('response_type') !== 'code') {
74
+ return redirectWithOAuthError(redirectUri, 'unsupported_response_type', state, c.req.url);
75
+ }
76
+ const codeChallenge = c.req.query('code_challenge') ?? '';
77
+ if (!codeChallenge || c.req.query('code_challenge_method') !== 'S256') {
78
+ return redirectWithOAuthError(redirectUri, 'invalid_request', state, c.req.url);
79
+ }
80
+ const scopeResult = validateScope(c.req.query('scope') ?? '', client, redirectUri, state, c.req.url);
81
+ if (scopeResult instanceof Response)
82
+ return scopeResult;
83
+ return {
84
+ client,
85
+ codeChallenge,
86
+ dpopJkt: c.req.query('dpop_jkt') ?? null,
87
+ nonce: c.req.query('nonce') ?? null,
88
+ redirectUri,
89
+ scope: scopeResult,
90
+ state,
91
+ };
92
+ };
93
+ const validateRedirectUri = (client, redirectUri) => {
94
+ if (!client.redirectUris.includes(redirectUri))
95
+ return 'redirect_uri is not registered for this client.';
96
+ let parsed;
97
+ try {
98
+ parsed = new URL(redirectUri);
99
+ }
100
+ catch {
101
+ return 'redirect_uri must be an absolute URI.';
102
+ }
103
+ if (parsed.hash)
104
+ return 'redirect_uri must not contain a fragment.';
105
+ if (parsed.username || parsed.password)
106
+ return 'redirect_uri must not contain credentials.';
107
+ if (parsed.protocol === 'https:')
108
+ return null;
109
+ if (parsed.protocol === 'http:' && ['localhost', '127.0.0.1'].includes(parsed.hostname))
110
+ return null;
111
+ return 'redirect_uri must use https except for localhost development callbacks.';
112
+ };
113
+ const validateScope = (scope, client, redirectUri, state, baseUrl) => {
114
+ const requestedScopes = scope.split(/\s+/).filter(Boolean);
115
+ if (requestedScopes.includes('offline_access')) {
116
+ return redirectWithOAuthError(redirectUri, 'invalid_scope', state, baseUrl);
117
+ }
118
+ if (requestedScopes.some((requestedScope) => !client.allowedScopes.includes(requestedScope))) {
119
+ return redirectWithOAuthError(redirectUri, 'invalid_scope', state, baseUrl);
120
+ }
121
+ return requestedScopes.join(' ');
122
+ };
123
+ const sealContinuation = async (c, options, request, session) => {
124
+ const now = options.ports.clock.now();
125
+ const expiresAt = options.ports.clock.addSeconds(now, options.stateTtlSeconds ?? 10 * 60);
126
+ return options.stateCodec.seal({
127
+ acr: session?.acr,
128
+ authTime: session?.authTime,
129
+ clientId: request.client.clientId,
130
+ codeChallenge: request.codeChallenge,
131
+ codeChallengeMethod: 'S256',
132
+ createdAt: now.toISOString(),
133
+ dpopJkt: request.dpopJkt,
134
+ expiresAt: expiresAt.toISOString(),
135
+ nonce: request.nonce,
136
+ redirectUri: request.redirectUri,
137
+ scope: request.scope,
138
+ state: request.state,
139
+ tenantId: request.client.tenantId,
140
+ userId: session?.userId,
141
+ });
142
+ };
143
+ //# sourceMappingURL=authorize-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorize-handler.js","sourceRoot":"","sources":["../../src/oauth/authorize-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Context } from 'hono';
2
+ import type { AuthHonoPorts } from '../ports.js';
3
+ import type { OAuthContinuationCodec } from './state-codec.js';
4
+ export interface OAuthConsentHandlerOptions {
5
+ authorizationCodeTtlSeconds?: number;
6
+ ports: AuthHonoPorts;
7
+ stateCodec: OAuthContinuationCodec;
8
+ }
9
+ export declare const createOAuthConsentDetailsHandler: (options: OAuthConsentHandlerOptions) => (c: Context) => Promise<Response>;
10
+ export declare const createOAuthConsentDecisionHandler: (options: OAuthConsentHandlerOptions) => (c: Context) => Promise<Response>;
11
+ //# sourceMappingURL=consent-decision-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent-decision-handler.d.ts","sourceRoot":"","sources":["../../src/oauth/consent-decision-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,KAAK,EAAE,sBAAsB,EAA0B,MAAM,kBAAkB,CAAC;AAGvF,MAAM,WAAW,0BAA0B;IACzC,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,KAAK,EAAE,aAAa,CAAC;IACrB,UAAU,EAAE,sBAAsB,CAAC;CACpC;AAED,eAAO,MAAM,gCAAgC,YACjC,0BAA0B,SAC1B,OAAO,KAAG,QAAQ,QAAQ,CAanC,CAAC;AAEJ,eAAO,MAAM,iCAAiC,YAClC,0BAA0B,SAC1B,OAAO,KAAG,QAAQ,QAAQ,CA0CnC,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { appendParams, oauthJsonError, redirectOrJson } from './http-utils.js';
2
+ import { resolveOAuthSession } from './session-resolver.js';
3
+ export const createOAuthConsentDetailsHandler = (options) => async (c) => {
4
+ const state = c.req.query('state') ?? '';
5
+ const payload = await validateConsentState(c, options, state);
6
+ if (payload instanceof Response)
7
+ return payload;
8
+ const client = await options.ports.oauthStateStore.findClient(payload.clientId);
9
+ if (!client)
10
+ return oauthJsonError(c, 400, 'invalid_request', 'Unknown OAuth client.');
11
+ return c.json({
12
+ clientName: client.name,
13
+ redirectUri: payload.redirectUri,
14
+ scopes: payload.scope.split(/\s+/).filter(Boolean),
15
+ });
16
+ };
17
+ export const createOAuthConsentDecisionHandler = (options) => async (c) => {
18
+ const body = await c.req.json().catch(() => null);
19
+ if (!body?.state || !['approve', 'deny'].includes(body.decision ?? '')) {
20
+ return oauthJsonError(c, 400, 'invalid_request', 'Consent decision and state are required.');
21
+ }
22
+ const payload = await validateConsentState(c, options, body.state);
23
+ if (payload instanceof Response)
24
+ return payload;
25
+ if (body.decision === 'deny') {
26
+ return redirectOrJson(c, appendParams(payload.redirectUri, { error: 'access_denied', state: payload.state }, c.req.url));
27
+ }
28
+ const code = options.ports.random.token(32);
29
+ const now = options.ports.clock.now();
30
+ await options.ports.oauthStateStore.saveAuthCode(code, {
31
+ acr: payload.acr ?? 'urn:sentropic:loa:bearer',
32
+ authTime: new Date(payload.authTime ?? now.toISOString()),
33
+ clientId: payload.clientId,
34
+ codeChallenge: payload.codeChallenge,
35
+ codeChallengeMethod: 'S256',
36
+ createdAt: now,
37
+ dpopJkt: payload.dpopJkt,
38
+ expiresAt: options.ports.clock.addSeconds(now, options.authorizationCodeTtlSeconds ?? 60),
39
+ nonce: payload.nonce,
40
+ redirectUri: payload.redirectUri,
41
+ scope: payload.scope,
42
+ tenantId: payload.tenantId,
43
+ userId: payload.userId ?? '',
44
+ }, options.authorizationCodeTtlSeconds ?? 60);
45
+ return redirectOrJson(c, appendParams(payload.redirectUri, { code, state: payload.state }, c.req.url));
46
+ };
47
+ const validateConsentState = async (c, options, sealedState) => {
48
+ const payload = await options.stateCodec.unseal(sealedState);
49
+ if (!payload || !payload.userId || new Date(payload.expiresAt) <= options.ports.clock.now()) {
50
+ return oauthJsonError(c, 400, 'invalid_request', 'OAuth consent state is invalid or expired.');
51
+ }
52
+ const session = await resolveOAuthSession(c.req.raw, options.ports);
53
+ if (!session || session.user.id !== payload.userId) {
54
+ return oauthJsonError(c, 401, 'login_required', 'A valid user session is required.');
55
+ }
56
+ return payload;
57
+ };
58
+ //# sourceMappingURL=consent-decision-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent-decision-handler.js","sourceRoot":"","sources":["../../src/oauth/consent-decision-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare const sha256Base64url: (value: string) => Promise<string>;
2
+ export declare const base64urlEncode: (bytes: Uint8Array) => string;
3
+ //# sourceMappingURL=crypto-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto-utils.d.ts","sourceRoot":"","sources":["../../src/oauth/crypto-utils.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe,UAAiB,MAAM,KAAG,QAAQ,MAAM,CAGnE,CAAC;AAEF,eAAO,MAAM,eAAe,UAAW,UAAU,KAAG,MAMnD,CAAC"}
@@ -0,0 +1,13 @@
1
+ const textEncoder = new TextEncoder();
2
+ export const sha256Base64url = async (value) => {
3
+ const digest = await crypto.subtle.digest('SHA-256', textEncoder.encode(value));
4
+ return base64urlEncode(new Uint8Array(digest));
5
+ };
6
+ export const base64urlEncode = (bytes) => {
7
+ let binary = '';
8
+ for (const byte of bytes) {
9
+ binary += String.fromCharCode(byte);
10
+ }
11
+ return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/u, '');
12
+ };
13
+ //# sourceMappingURL=crypto-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto-utils.js","sourceRoot":"","sources":["../../src/oauth/crypto-utils.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;AAEtC,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,KAAa,EAAmB,EAAE;IACtE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAChF,OAAO,eAAe,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;AACjD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAiB,EAAU,EAAE;IAC3D,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACpF,CAAC,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { AuthHonoPorts } from '../ports.js';
2
+ export interface VerifyDpopProofOptions {
3
+ accessToken?: string;
4
+ htm: string;
5
+ htu: string;
6
+ iatSkewSeconds?: number;
7
+ ports: AuthHonoPorts;
8
+ proof: string;
9
+ }
10
+ export interface VerifiedDpopProof {
11
+ jkt: string;
12
+ jti: string;
13
+ }
14
+ export declare class OAuthDpopProofError extends Error {
15
+ constructor(message: string);
16
+ }
17
+ export declare const verifyOAuthDpopProof: (options: VerifyDpopProofOptions) => Promise<VerifiedDpopProof>;
18
+ //# sourceMappingURL=dpop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dpop.d.ts","sourceRoot":"","sources":["../../src/oauth/dpop.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,aAAa,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED,eAAO,MAAM,oBAAoB,YACtB,sBAAsB,KAC9B,QAAQ,iBAAiB,CAwB3B,CAAC"}