@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.
- package/README.md +168 -1
- package/dist/contracts.d.ts +1 -1
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +2 -0
- package/dist/contracts.js.map +1 -1
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -1
- package/dist/oauth/authorize-handler.d.ts +13 -0
- package/dist/oauth/authorize-handler.d.ts.map +1 -0
- package/dist/oauth/authorize-handler.js +143 -0
- package/dist/oauth/authorize-handler.js.map +1 -0
- package/dist/oauth/consent-decision-handler.d.ts +11 -0
- package/dist/oauth/consent-decision-handler.d.ts.map +1 -0
- package/dist/oauth/consent-decision-handler.js +58 -0
- package/dist/oauth/consent-decision-handler.js.map +1 -0
- package/dist/oauth/crypto-utils.d.ts +3 -0
- package/dist/oauth/crypto-utils.d.ts.map +1 -0
- package/dist/oauth/crypto-utils.js +13 -0
- package/dist/oauth/crypto-utils.js.map +1 -0
- package/dist/oauth/dpop.d.ts +18 -0
- package/dist/oauth/dpop.d.ts.map +1 -0
- package/dist/oauth/dpop.js +54 -0
- package/dist/oauth/dpop.js.map +1 -0
- package/dist/oauth/http-utils.d.ts +6 -0
- package/dist/oauth/http-utils.d.ts.map +1 -0
- package/dist/oauth/http-utils.js +27 -0
- package/dist/oauth/http-utils.js.map +1 -0
- package/dist/oauth/introspect-handler.d.ts +8 -0
- package/dist/oauth/introspect-handler.d.ts.map +1 -0
- package/dist/oauth/introspect-handler.js +63 -0
- package/dist/oauth/introspect-handler.js.map +1 -0
- package/dist/oauth/jwks-service.d.ts +25 -0
- package/dist/oauth/jwks-service.d.ts.map +1 -0
- package/dist/oauth/jwks-service.js +61 -0
- package/dist/oauth/jwks-service.js.map +1 -0
- package/dist/oauth/revoke-handler.d.ts +8 -0
- package/dist/oauth/revoke-handler.d.ts.map +1 -0
- package/dist/oauth/revoke-handler.js +55 -0
- package/dist/oauth/revoke-handler.js.map +1 -0
- package/dist/oauth/router.d.ts +8 -0
- package/dist/oauth/router.d.ts.map +1 -0
- package/dist/oauth/router.js +30 -0
- package/dist/oauth/router.js.map +1 -0
- package/dist/oauth/service-auth-middleware.d.ts +30 -0
- package/dist/oauth/service-auth-middleware.d.ts.map +1 -0
- package/dist/oauth/service-auth-middleware.js +170 -0
- package/dist/oauth/service-auth-middleware.js.map +1 -0
- package/dist/oauth/session-resolver.d.ts +9 -0
- package/dist/oauth/session-resolver.d.ts.map +1 -0
- package/dist/oauth/session-resolver.js +28 -0
- package/dist/oauth/session-resolver.js.map +1 -0
- package/dist/oauth/state-codec.d.ts +25 -0
- package/dist/oauth/state-codec.d.ts.map +1 -0
- package/dist/oauth/state-codec.js +60 -0
- package/dist/oauth/state-codec.js.map +1 -0
- package/dist/oauth/state-store-types.d.ts +100 -0
- package/dist/oauth/state-store-types.d.ts.map +1 -0
- package/dist/oauth/state-store-types.js +2 -0
- package/dist/oauth/state-store-types.js.map +1 -0
- package/dist/oauth/token-handler.d.ts +12 -0
- package/dist/oauth/token-handler.d.ts.map +1 -0
- package/dist/oauth/token-handler.js +294 -0
- package/dist/oauth/token-handler.js.map +1 -0
- package/dist/oauth/userinfo-handler.d.ts +9 -0
- package/dist/oauth/userinfo-handler.d.ts.map +1 -0
- package/dist/oauth/userinfo-handler.js +93 -0
- package/dist/oauth/userinfo-handler.js.map +1 -0
- package/dist/oauth/wellknown-handler.d.ts +9 -0
- package/dist/oauth/wellknown-handler.d.ts.map +1 -0
- package/dist/oauth/wellknown-handler.js +37 -0
- package/dist/oauth/wellknown-handler.js.map +1 -0
- package/dist/ports.d.ts +4 -0
- package/dist/ports.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/contracts.ts +2 -0
- package/src/index.ts +16 -0
- package/src/oauth/authorize-handler.ts +201 -0
- package/src/oauth/consent-decision-handler.ts +93 -0
- package/src/oauth/crypto-utils.ts +14 -0
- package/src/oauth/dpop.ts +93 -0
- package/src/oauth/http-utils.ts +58 -0
- package/src/oauth/introspect-handler.ts +88 -0
- package/src/oauth/jwks-service.ts +103 -0
- package/src/oauth/revoke-handler.ts +70 -0
- package/src/oauth/router.ts +42 -0
- package/src/oauth/service-auth-middleware.ts +250 -0
- package/src/oauth/session-resolver.ts +48 -0
- package/src/oauth/state-codec.ts +98 -0
- package/src/oauth/state-store-types.ts +109 -0
- package/src/oauth/token-handler.ts +423 -0
- package/src/oauth/userinfo-handler.ts +129 -0
- package/src/oauth/wellknown-handler.ts +52 -0
- 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.
|
|
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.
|
package/dist/contracts.d.ts
CHANGED
|
@@ -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
|
package/dist/contracts.d.ts.map
CHANGED
|
@@ -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,
|
|
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
package/dist/contracts.js.map
CHANGED
|
@@ -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;
|
|
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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 @@
|
|
|
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"}
|