@rudderjs/passport 1.1.1 → 1.1.2
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 +96 -15
- package/dist/grants/authorization-code.d.ts.map +1 -1
- package/dist/grants/authorization-code.js +4 -17
- package/dist/grants/authorization-code.js.map +1 -1
- package/dist/grants/client-credentials.d.ts.map +1 -1
- package/dist/grants/client-credentials.js +4 -17
- package/dist/grants/client-credentials.js.map +1 -1
- package/dist/grants/device-code.d.ts.map +1 -1
- package/dist/grants/device-code.js +2 -1
- package/dist/grants/device-code.js.map +1 -1
- package/dist/grants/parse-scopes.d.ts +15 -0
- package/dist/grants/parse-scopes.d.ts.map +1 -0
- package/dist/grants/parse-scopes.js +17 -0
- package/dist/grants/parse-scopes.js.map +1 -0
- package/dist/grants/refresh-token.d.ts.map +1 -1
- package/dist/grants/refresh-token.js +5 -18
- package/dist/grants/refresh-token.js.map +1 -1
- package/dist/grants/verify-client.d.ts +29 -0
- package/dist/grants/verify-client.d.ts.map +1 -0
- package/dist/grants/verify-client.js +43 -0
- package/dist/grants/verify-client.js.map +1 -0
- package/dist/middleware/bearer.d.ts.map +1 -1
- package/dist/middleware/bearer.js +98 -103
- package/dist/middleware/bearer.js.map +1 -1
- package/dist/models/AccessToken.d.ts +3 -3
- package/dist/models/AuthCode.d.ts +3 -3
- package/dist/models/DeviceCode.d.ts +3 -3
- package/dist/models/RefreshToken.d.ts +3 -3
- package/dist/models/helpers.d.ts +27 -9
- package/dist/models/helpers.d.ts.map +1 -1
- package/dist/models/helpers.js +12 -6
- package/dist/models/helpers.js.map +1 -1
- package/dist/personal-access-tokens.d.ts.map +1 -1
- package/dist/personal-access-tokens.js.map +1 -1
- package/dist/routes/authorize.d.ts +17 -0
- package/dist/routes/authorize.d.ts.map +1 -0
- package/dist/routes/authorize.js +107 -0
- package/dist/routes/authorize.js.map +1 -0
- package/dist/routes/device.d.ts +23 -0
- package/dist/routes/device.d.ts.map +1 -0
- package/dist/routes/device.js +69 -0
- package/dist/routes/device.js.map +1 -0
- package/dist/routes/helpers.d.ts +64 -0
- package/dist/routes/helpers.d.ts.map +1 -0
- package/dist/routes/helpers.js +154 -0
- package/dist/routes/helpers.js.map +1 -0
- package/dist/routes/revoke.d.ts +16 -0
- package/dist/routes/revoke.d.ts.map +1 -0
- package/dist/routes/revoke.js +33 -0
- package/dist/routes/revoke.js.map +1 -0
- package/dist/routes/scopes.d.ts +9 -0
- package/dist/routes/scopes.d.ts.map +1 -0
- package/dist/routes/scopes.js +13 -0
- package/dist/routes/scopes.js.map +1 -0
- package/dist/routes/token.d.ts +24 -0
- package/dist/routes/token.d.ts.map +1 -0
- package/dist/routes/token.js +121 -0
- package/dist/routes/token.js.map +1 -0
- package/dist/routes/types.d.ts +132 -0
- package/dist/routes/types.d.ts.map +1 -0
- package/dist/routes/types.js +2 -0
- package/dist/routes/types.js.map +1 -0
- package/dist/routes.d.ts +2 -120
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +16 -411
- package/dist/routes.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -6,10 +6,11 @@ OAuth 2 server for RudderJS — the Laravel Passport equivalent. Turns your app
|
|
|
6
6
|
|
|
7
7
|
- **Four OAuth 2 grants** — authorization code (with PKCE), client credentials, refresh token, device code
|
|
8
8
|
- **Personal access tokens** — Laravel-style `user.createToken()` via the `HasApiTokens` mixin
|
|
9
|
-
- **JWT with RS256
|
|
10
|
-
- **Auto-registered routes** — `/oauth/authorize`, `/oauth/token`, `/oauth/scopes`, `/oauth/device/*`, plus token revocation
|
|
11
|
-
- **Bearer middleware** — `RequireBearer()` + `scope('read', 'write')` for per-route API auth
|
|
12
|
-
- **
|
|
9
|
+
- **JWT with RS256 + JWKS-style key rotation** — third parties verify without calling your server; rotating keys keeps a previous-key verification window
|
|
10
|
+
- **Auto-registered routes** — `/oauth/authorize`, `/oauth/token`, `/oauth/scopes`, `/oauth/device/*`, plus token revocation; web/api split available so consent lives on the `web` group and stateless endpoints on `api`
|
|
11
|
+
- **Bearer middleware** — `RequireBearer()` + `scope('read', 'write')` (AND) or `scopeAny(...)` (OR) for per-route API auth
|
|
12
|
+
- **Issuer & device-flow knobs** — opt-in `iss` claim, configurable device-code polling cap
|
|
13
|
+
- **Customization hooks** — swap any model, wire a custom consent screen, mount per-endpoint middleware (CSRF / rate limit), disable routes selectively
|
|
13
14
|
|
|
14
15
|
## Installation
|
|
15
16
|
|
|
@@ -69,28 +70,40 @@ export default [
|
|
|
69
70
|
]
|
|
70
71
|
```
|
|
71
72
|
|
|
72
|
-
Register the OAuth routes.
|
|
73
|
+
Register the OAuth routes. The recommended layout splits the routes across the web and api groups so the consent flow gets session + CSRF, and the stateless token/device/scope endpoints stay on api:
|
|
73
74
|
|
|
74
75
|
```ts
|
|
75
|
-
// routes/
|
|
76
|
-
import {
|
|
76
|
+
// routes/web.ts — consent flow (needs session + signed-in user + CSRF)
|
|
77
|
+
import { registerPassportWebRoutes } from '@rudderjs/passport'
|
|
77
78
|
|
|
78
79
|
export default (router) => {
|
|
79
|
-
|
|
80
|
+
registerPassportWebRoutes(router) // GET/POST/DELETE /oauth/authorize + DELETE /oauth/tokens/:id
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// routes/api.ts — stateless token endpoints
|
|
84
|
+
import { registerPassportApiRoutes } from '@rudderjs/passport'
|
|
85
|
+
|
|
86
|
+
export default (router) => {
|
|
87
|
+
registerPassportApiRoutes(router) // /oauth/token, /oauth/device/*, /oauth/scopes
|
|
80
88
|
}
|
|
81
89
|
```
|
|
82
90
|
|
|
91
|
+
Or use the legacy single-mount form `registerPassportRoutes(router)` to register everything onto one router — kept for back-compat / single-group apps.
|
|
92
|
+
|
|
93
|
+
> **POST `/oauth/authorize` is CSRF-protected.** Mount `CsrfMiddleware()` on the entire `web` group (`m.web(CsrfMiddleware())` in `withMiddleware`) — that covers it along with every other state-changing web route. Don't double-mount `CsrfMiddleware` via `authorizeMiddleware` as well; it emits duplicate `Set-Cookie`s on GETs.
|
|
94
|
+
|
|
83
95
|
## Protecting API Routes
|
|
84
96
|
|
|
85
|
-
`RequireBearer()` validates the JWT signature, checks expiration, and confirms the token hasn't been revoked. `scope(...)`
|
|
97
|
+
`RequireBearer()` validates the JWT signature, checks expiration, and confirms the token hasn't been revoked. Pair it with either `scope(...)` (AND — must have **every** listed scope) or `scopeAny(...)` (OR — must have **at least one**):
|
|
86
98
|
|
|
87
99
|
```ts
|
|
88
|
-
import { RequireBearer, scope } from '@rudderjs/passport'
|
|
100
|
+
import { RequireBearer, scope, scopeAny } from '@rudderjs/passport'
|
|
89
101
|
|
|
90
|
-
router.get('/api/user', [RequireBearer()],
|
|
91
|
-
router.get('/api/posts', [RequireBearer(), scope('read')],
|
|
92
|
-
router.post('/api/posts', [RequireBearer(), scope('write')],
|
|
93
|
-
router.post('/api/admin', [RequireBearer(), scope('admin')],
|
|
102
|
+
router.get('/api/user', [RequireBearer()], (req) => req.user)
|
|
103
|
+
router.get('/api/posts', [RequireBearer(), scope('read')], listPosts)
|
|
104
|
+
router.post('/api/posts', [RequireBearer(), scope('write')], createPost)
|
|
105
|
+
router.post('/api/admin', [RequireBearer(), scope('admin')], adminAction)
|
|
106
|
+
router.get('/api/feed', [RequireBearer(), scopeAny('read', 'admin')], showFeed) // either scope unlocks it
|
|
94
107
|
```
|
|
95
108
|
|
|
96
109
|
A valid request attaches the resolved user to `req.user`, so handlers read it the same way they would under session auth.
|
|
@@ -289,6 +302,38 @@ Available groups: `authorize`, `token`, `revoke`, `scopes`, `device`.
|
|
|
289
302
|
|
|
290
303
|
To disable route registration entirely, call `Passport.ignoreRoutes()` before the provider boots. `registerPassportRoutes()` becomes a no-op.
|
|
291
304
|
|
|
305
|
+
### Per-endpoint middleware
|
|
306
|
+
|
|
307
|
+
`registerPassportRoutes()` (and the web/api variants) accept per-endpoint middleware so you can layer rate limits or CSRF onto exactly the endpoints that need them:
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import { RateLimit, CsrfMiddleware } from '@rudderjs/middleware'
|
|
311
|
+
|
|
312
|
+
registerPassportRoutes(router, {
|
|
313
|
+
// POST /oauth/token — the canonical brute-force target. Composite key
|
|
314
|
+
// (ip + client_id) so one noisy client behind shared NAT can't exhaust
|
|
315
|
+
// the budget for legitimate co-tenants, AND a single IP can't churn
|
|
316
|
+
// through every client_id in the registry.
|
|
317
|
+
tokenMiddleware: [
|
|
318
|
+
RateLimit.perMinute(10).by((req) => `${req.ip}:${req.body?.client_id}`),
|
|
319
|
+
],
|
|
320
|
+
|
|
321
|
+
// POST /oauth/device/code + /oauth/device/approve + /oauth/device/token
|
|
322
|
+
// — opt-in tighter per-IP limit on top of the api-group rate limit.
|
|
323
|
+
deviceMiddleware: [
|
|
324
|
+
RateLimit.perMinute(5).by((req) => req.ip),
|
|
325
|
+
],
|
|
326
|
+
|
|
327
|
+
// GET/POST /oauth/authorize — opt-in per-route CSRF when you're NOT
|
|
328
|
+
// running CsrfMiddleware on the whole web group. Don't do both.
|
|
329
|
+
authorizeMiddleware: [
|
|
330
|
+
CsrfMiddleware(),
|
|
331
|
+
],
|
|
332
|
+
})
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
> `RateLimit` requires `@rudderjs/cache` registered before middleware runs — without a cache provider the limiter silently passes through.
|
|
336
|
+
|
|
292
337
|
## Configuration
|
|
293
338
|
|
|
294
339
|
### Key Management
|
|
@@ -322,6 +367,36 @@ All in milliseconds:
|
|
|
322
367
|
| `refreshTokensExpireIn` | 30 days | Refresh token lifetime |
|
|
323
368
|
| `personalAccessTokensExpireIn` | ~6 months | Personal access token lifetime |
|
|
324
369
|
|
|
370
|
+
### JWT issuer (opt-in)
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
// config/passport.ts
|
|
374
|
+
export default {
|
|
375
|
+
issuer: 'https://app.example.com', // or call Passport.useIssuer(url) at boot
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
When set, every new JWT carries this URL as the `iss` claim and `BearerMiddleware` / `RequireBearer` reject tokens whose `iss` doesn't match. Tokens minted before the issuer was configured carry no `iss` claim and stay verifiable during the migration window. Single-issuer deployments don't need this; turn it on once you have more than one possible signer (multi-tenant, staging+prod sharing keys) — RFC 8725 §3.10.
|
|
380
|
+
|
|
381
|
+
> **Rotating the issuer URL invalidates every live token.** Plan changes as a forced sign-out window — same blast radius as rotating the RSA keypair.
|
|
382
|
+
|
|
383
|
+
### Device-flow polling cap
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
// config/passport.ts
|
|
387
|
+
export default {
|
|
388
|
+
deviceMaxInterval: 60, // seconds; default 60, floor 5 (clamped), call `Passport.deviceMaxInterval()` at boot for the same effect
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Device-code polling starts at 5 seconds and escalates by 5s per `slow_down` response per RFC 8628 §3.5. `deviceMaxInterval` is the cap that escalation will never exceed. Raise it for machine-only / no-human-in-the-loop device flows where misbehaving clients warrant aggressive back-off. Values below 5 are clamped to the 5s floor — escalation always needs to be able to take effect.
|
|
393
|
+
|
|
394
|
+
### Key rotation grace window
|
|
395
|
+
|
|
396
|
+
`pnpm rudder passport:keys --force` rotates the RSA keypair and writes timestamped audit backups (`*.bak.<ISO-timestamp>`) plus a rolling `storage/oauth-previous-public.key`. Every JWT carries a `kid` header equal to the SHA-256 fingerprint of the public key that signed it; `verifyToken()` walks `[currentPublicKey, ...previousPublicKeys]` and accepts a match against any retained key, so **tokens minted before the rotation keep verifying until they expire naturally** — no global sign-out at rotation time.
|
|
397
|
+
|
|
398
|
+
One previous-slot is retained by design. Drop `oauth-previous-public.key` (or call `Passport.setPreviousPublicKey(null)`) once the old tokens have expired to close the window. Operators needing a longer history should stage rotations to land outside the configured access-token lifetime.
|
|
399
|
+
|
|
325
400
|
## CLI Commands
|
|
326
401
|
|
|
327
402
|
```bash
|
|
@@ -366,7 +441,13 @@ pnpm rudder passport:purge
|
|
|
366
441
|
- **Refresh token replay** — reusing an old refresh token returns `invalid_grant`; the rotation already revoked it.
|
|
367
442
|
- **Stale personal-access client cache** — `resetPersonalAccessClient()` is test-only. Don't call it at runtime.
|
|
368
443
|
- **Prisma delegate vs `@@map`** — if you override a model, `static table` must be the Prisma delegate name (camelCase), not the `@@map`'d SQL name. `oauthClient`, not `oauth_clients`.
|
|
369
|
-
- **Scope middleware ordering** — `scope(...)` must run after `RequireBearer()` or `BearerMiddleware()`.
|
|
444
|
+
- **Scope middleware ordering** — `scope(...)` / `scopeAny(...)` must run after `RequireBearer()` or `BearerMiddleware()`. They read token scopes from the request state set by the bearer middleware.
|
|
445
|
+
- **`APP_KEY` rotation invalidates every peppered client secret.** When `APP_KEY` is set, `passport:client` stores client secrets as `peppered:<HMAC-SHA256(secret, APP_KEY)>`. Replace `APP_KEY` and the HMAC no longer reproduces — every confidential client fails token-endpoint authentication until you re-issue secrets via `passport:client`. Plan rotations as a coordinated re-issuance window with third-party integrations. Legacy plain-SHA-256 rows (minted before `APP_KEY` was set) are unaffected.
|
|
446
|
+
- **Don't trust `Host` / `X-Forwarded-Host` for OAuth URLs.** The device flow falls back to `${req.protocol}://${req.hostname}${prefix}/device` when `verificationUri` isn't configured — an attacker-controlled `Host` header steers users to a phishing origin. Always pass an explicit `verificationUri` (or derive OAuth URLs from `config('app.url')`) when registering passport routes behind a reverse proxy.
|
|
447
|
+
|
|
448
|
+
## Reaping expired tokens
|
|
449
|
+
|
|
450
|
+
`AuthCode`, `DeviceCode`, `AccessToken`, and `RefreshToken` are all `MassPrunable`, so `pnpm rudder model:prune` reaps expired/revoked rows automatically — no need to schedule `passport:purge` separately. `PassportProvider.boot()` eagerly registers the four classes with `ModelRegistry` so the prune walker sees them on day-1 fresh apps before any oauth flow has fired. `passport:purge` remains available for one-off cleanups.
|
|
370
451
|
|
|
371
452
|
## Related
|
|
372
453
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorization-code.d.ts","sourceRoot":"","sources":["../../src/grants/authorization-code.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;
|
|
1
|
+
{"version":3,"file":"authorization-code.d.ts","sourceRoot":"","sources":["../../src/grants/authorization-code.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAK3D,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAMlE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAO,MAAM,CAAA;IACrB,WAAW,EAAI,MAAM,CAAA;IACrB,YAAY,EAAG,MAAM,CAAA;IACrB,KAAK,EAAU,MAAM,CAAA;IACrB,KAAK,CAAC,EAAS,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAS,WAAW,CAAA;IAC1B,WAAW,EAAI,MAAM,CAAA;IACrB,MAAM,EAAS,MAAM,EAAE,CAAA;IACvB,KAAK,CAAC,EAAS,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAoD9G;AAID;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,MAAM,EAAK,MAAM,CAAA;IACjB,QAAQ,EAAG,MAAM,CAAA;IACjB,MAAM,EAAK,MAAM,EAAE,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBlB;AAID,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAK,MAAM,CAAA;IACpB,IAAI,EAAU,MAAM,CAAA;IACpB,QAAQ,EAAM,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,EAAG,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC,CA4G1F;AAID;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CA0B7E;AAID,qBAAa,UAAW,SAAQ,KAAK;aAEjB,KAAK,EAAE,MAAM;aACb,gBAAgB,EAAE,MAAM;aACxB,UAAU,EAAE,MAAM;gBAFlB,KAAK,EAAE,MAAM,EACb,gBAAgB,EAAE,MAAM,EACxB,UAAU,GAAE,MAAY;IAM1C,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAMjC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Passport } from '../Passport.js';
|
|
2
2
|
import { clientHelpers, authCodeHelpers } from '../models/helpers.js';
|
|
3
3
|
import { safeCompare } from './safe-compare.js';
|
|
4
|
-
import { verifyClientSecret } from '../client-secret.js';
|
|
5
4
|
import { hashOpaqueToken, newOpaqueToken } from '../opaque-token.js';
|
|
6
5
|
import { issueTokens } from './issue-tokens.js';
|
|
6
|
+
import { parseScopes } from './parse-scopes.js';
|
|
7
|
+
import { verifyConfidentialCredentials } from './verify-client.js';
|
|
7
8
|
/**
|
|
8
9
|
* Validate an authorization request (GET /oauth/authorize).
|
|
9
10
|
* Returns the validated request or throws with an error message.
|
|
@@ -42,7 +43,7 @@ export async function validateAuthorizationRequest(params) {
|
|
|
42
43
|
// Public clients MUST use PKCE
|
|
43
44
|
throw new OAuthError('invalid_request', 'Public clients must use PKCE (code_challenge required).');
|
|
44
45
|
}
|
|
45
|
-
const scopes = params.scope
|
|
46
|
+
const scopes = parseScopes(params.scope);
|
|
46
47
|
validateScopes(client, scopes);
|
|
47
48
|
const result = {
|
|
48
49
|
client,
|
|
@@ -103,21 +104,7 @@ export async function exchangeAuthCode(params) {
|
|
|
103
104
|
if (!client || client.revoked) {
|
|
104
105
|
throw new OAuthError('invalid_client', 'Client not found.', 401);
|
|
105
106
|
}
|
|
106
|
-
|
|
107
|
-
if (client.confidential) {
|
|
108
|
-
if (!params.clientSecret) {
|
|
109
|
-
throw new OAuthError('invalid_client', 'Client secret required.', 401);
|
|
110
|
-
}
|
|
111
|
-
// Schema allows `client.secret` to be null; explicit guard so a future
|
|
112
|
-
// refactor can't mask `secret = null` as authenticating. See
|
|
113
|
-
// `client-credentials.ts` for the longer-form rationale.
|
|
114
|
-
if (client.secret == null) {
|
|
115
|
-
throw new OAuthError('invalid_client', 'Confidential client has no secret on file.', 401);
|
|
116
|
-
}
|
|
117
|
-
if (!(await verifyClientSecret(params.clientSecret, client.secret))) {
|
|
118
|
-
throw new OAuthError('invalid_client', 'Invalid client secret.', 401);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
107
|
+
await verifyConfidentialCredentials(client, params.clientSecret);
|
|
121
108
|
// Validate auth code by hashed plaintext (M5/P6) — the row's `id` is no
|
|
122
109
|
// longer the bearer secret. Pre-migration codes won't match because their
|
|
123
110
|
// hashed form was never persisted; affected exchanges fall through to the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorization-code.js","sourceRoot":"","sources":["../../src/grants/authorization-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAGzC,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"authorization-code.js","sourceRoot":"","sources":["../../src/grants/authorization-code.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-credentials.d.ts","sourceRoot":"","sources":["../../src/grants/client-credentials.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client-credentials.d.ts","sourceRoot":"","sources":["../../src/grants/client-credentials.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAKlE,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAK,MAAM,CAAA;IACpB,QAAQ,EAAM,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAQ,MAAM,CAAA;CACrB;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,YAAY,CAAC,CA0BpG"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Passport } from '../Passport.js';
|
|
2
2
|
import { clientHelpers } from '../models/helpers.js';
|
|
3
|
-
import { verifyClientSecret } from '../client-secret.js';
|
|
4
3
|
import { issueTokens } from './issue-tokens.js';
|
|
5
4
|
import { OAuthError, validateScopes } from './authorization-code.js';
|
|
5
|
+
import { parseScopes } from './parse-scopes.js';
|
|
6
|
+
import { verifyConfidentialCredentials } from './verify-client.js';
|
|
6
7
|
/**
|
|
7
8
|
* Client credentials grant — machine-to-machine, no user context.
|
|
8
9
|
* Issues an access token (no refresh token).
|
|
@@ -19,22 +20,8 @@ export async function clientCredentialsGrant(params) {
|
|
|
19
20
|
if (!clientHelpers.hasGrantType(client, 'client_credentials')) {
|
|
20
21
|
throw new OAuthError('unauthorized_client', 'Client is not authorized for client_credentials grant.');
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
// Schema allows `client.secret` to be null (public clients), but reaching
|
|
26
|
-
// this branch with a confidential client should always have a hashed secret
|
|
27
|
-
// on file. Catching the null case explicitly prevents a future refactor
|
|
28
|
-
// from masking `secret = null` as authenticating against
|
|
29
|
-
// `verifyClientSecret(_, null)` (which fails closed today, but the guard
|
|
30
|
-
// makes the contract obvious to readers and hardens against drift).
|
|
31
|
-
if (client.secret == null) {
|
|
32
|
-
throw new OAuthError('invalid_client', 'Confidential client has no secret on file.', 401);
|
|
33
|
-
}
|
|
34
|
-
if (!(await verifyClientSecret(params.clientSecret, client.secret))) {
|
|
35
|
-
throw new OAuthError('invalid_client', 'Invalid client secret.', 401);
|
|
36
|
-
}
|
|
37
|
-
const scopes = params.scope ? params.scope.split(' ').filter(Boolean) : [];
|
|
23
|
+
await verifyConfidentialCredentials(client, params.clientSecret, { requireConfidential: true });
|
|
24
|
+
const scopes = parseScopes(params.scope);
|
|
38
25
|
validateScopes(client, scopes);
|
|
39
26
|
return issueTokens({
|
|
40
27
|
userId: null, // no user context
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-credentials.js","sourceRoot":"","sources":["../../src/grants/client-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"client-credentials.js","sourceRoot":"","sources":["../../src/grants/client-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,WAAW,EAAqB,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAA;AASlE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,MAAgC;IAC3E,IAAI,MAAM,CAAC,SAAS,KAAK,oBAAoB,EAAE,CAAC;QAC9C,MAAM,IAAI,UAAU,CAAC,wBAAwB,EAAE,yCAAyC,CAAC,CAAA;IAC3F,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAA;IAC9C,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAwB,CAAA;IACzF,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QAC9B,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,GAAG,CAAC,CAAA;IAClE,CAAC;IAED,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,MAAM,EAAE,oBAAoB,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,UAAU,CAAC,qBAAqB,EAAE,wDAAwD,CAAC,CAAA;IACvG,CAAC;IAED,MAAM,6BAA6B,CAAC,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE,EAAE,mBAAmB,EAAE,IAAI,EAAE,CAAC,CAAA;IAE/F,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACxC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAE9B,OAAO,WAAW,CAAC;QACjB,MAAM,EAAU,IAAI,EAAE,kBAAkB;QACxC,QAAQ,EAAQ,MAAM,CAAC,QAAQ;QAC/B,MAAM;QACN,cAAc,EAAE,KAAK,EAAE,8CAA8C;KACtE,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-code.d.ts","sourceRoot":"","sources":["../../src/grants/device-code.ts"],"names":[],"mappings":"AAKA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"device-code.d.ts","sourceRoot":"","sources":["../../src/grants/device-code.ts"],"names":[],"mappings":"AAKA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAgBlE,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAiB,MAAM,CAAA;IAClC,SAAS,EAAmB,MAAM,CAAA;IAClC,gBAAgB,EAAY,MAAM,CAAA;IAClC,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,UAAU,EAAkB,MAAM,CAAA;IAClC,QAAQ,EAAoB,MAAM,CAAA;CACnC;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAI,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAiDvC;AAID;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB1G;AAID,MAAM,MAAM,gBAAgB,GACxB;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAC9C;IAAE,MAAM,EAAE,uBAAuB,CAAA;CAAE;AACrC;;;;;;GAMG;GACD;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,GAC3B;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,CAAA;AAE/B;;GAEG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE;IAC3C,SAAS,EAAG,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAI,MAAM,CAAA;CACnB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA0D5B"}
|
|
@@ -3,6 +3,7 @@ import { clientHelpers, deviceCodeHelpers } from '../models/helpers.js';
|
|
|
3
3
|
import { hashDeviceSecret } from '../device-code-secret.js';
|
|
4
4
|
import { issueTokens } from './issue-tokens.js';
|
|
5
5
|
import { OAuthError, validateScopes } from './authorization-code.js';
|
|
6
|
+
import { parseScopes } from './parse-scopes.js';
|
|
6
7
|
/**
|
|
7
8
|
* Initial polling interval for new device-code rows (RFC 8628 §3.5).
|
|
8
9
|
* Server escalates by 5s on each `slow_down` response, capped at
|
|
@@ -26,7 +27,7 @@ export async function requestDeviceCode(params) {
|
|
|
26
27
|
if (!clientHelpers.hasGrantType(client, 'urn:ietf:params:oauth:grant-type:device_code')) {
|
|
27
28
|
throw new OAuthError('unauthorized_client', 'Client is not authorized for device authorization grant.');
|
|
28
29
|
}
|
|
29
|
-
const scopes = params.scope
|
|
30
|
+
const scopes = parseScopes(params.scope);
|
|
30
31
|
validateScopes(client, scopes);
|
|
31
32
|
const { randomBytes } = await import('node:crypto');
|
|
32
33
|
const deviceCode = randomBytes(32).toString('hex');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-code.js","sourceRoot":"","sources":["../../src/grants/device-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAGzC,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAqB,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"device-code.js","sourceRoot":"","sources":["../../src/grants/device-code.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse the space-separated `scope` parameter from a token-endpoint request
|
|
3
|
+
* into a deduplicated-by-position string array.
|
|
4
|
+
*
|
|
5
|
+
* RFC 6749 §3.3 defines `scope` as a space-delimited list. `.filter(Boolean)`
|
|
6
|
+
* drops the leading/trailing/double-space tokens that some SDKs emit, since
|
|
7
|
+
* a stray empty string would fail the scope-registry / per-client-allowlist
|
|
8
|
+
* checks downstream with a confusing "unknown scope: " message.
|
|
9
|
+
*
|
|
10
|
+
* Returns `[]` when the parameter is missing — the same shape every grant
|
|
11
|
+
* needs ("no scope requested"), so callers don't have to ternary every
|
|
12
|
+
* read.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseScopes(scope: string | undefined): string[];
|
|
15
|
+
//# sourceMappingURL=parse-scopes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-scopes.d.ts","sourceRoot":"","sources":["../../src/grants/parse-scopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAE/D"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse the space-separated `scope` parameter from a token-endpoint request
|
|
3
|
+
* into a deduplicated-by-position string array.
|
|
4
|
+
*
|
|
5
|
+
* RFC 6749 §3.3 defines `scope` as a space-delimited list. `.filter(Boolean)`
|
|
6
|
+
* drops the leading/trailing/double-space tokens that some SDKs emit, since
|
|
7
|
+
* a stray empty string would fail the scope-registry / per-client-allowlist
|
|
8
|
+
* checks downstream with a confusing "unknown scope: " message.
|
|
9
|
+
*
|
|
10
|
+
* Returns `[]` when the parameter is missing — the same shape every grant
|
|
11
|
+
* needs ("no scope requested"), so callers don't have to ternary every
|
|
12
|
+
* read.
|
|
13
|
+
*/
|
|
14
|
+
export function parseScopes(scope) {
|
|
15
|
+
return scope ? scope.split(' ').filter(Boolean) : [];
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=parse-scopes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-scopes.js","sourceRoot":"","sources":["../../src/grants/parse-scopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,KAAyB;IACnD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;AACtD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../src/grants/refresh-token.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../src/grants/refresh-token.ts"],"names":[],"mappings":"AAMA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAKlE,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAK,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAM,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAQ,MAAM,CAAA;CACrB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,CAAC,CA+E1F"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Passport } from '../Passport.js';
|
|
2
2
|
import { accessTokenHelpers, refreshTokenHelpers } from '../models/helpers.js';
|
|
3
|
-
import { verifyClientSecret } from '../client-secret.js';
|
|
4
3
|
import { hashOpaqueToken } from '../opaque-token.js';
|
|
5
4
|
import { issueTokens } from './issue-tokens.js';
|
|
6
5
|
import { OAuthError } from './authorization-code.js';
|
|
6
|
+
import { parseScopes } from './parse-scopes.js';
|
|
7
|
+
import { verifyConfidentialCredentials } from './verify-client.js';
|
|
7
8
|
/**
|
|
8
9
|
* Refresh token grant — exchange a refresh token for a new access + refresh token pair.
|
|
9
10
|
* The old refresh token is revoked.
|
|
@@ -20,21 +21,7 @@ export async function refreshTokenGrant(params) {
|
|
|
20
21
|
if (!client || client.revoked) {
|
|
21
22
|
throw new OAuthError('invalid_client', 'Client not found.', 401);
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
if (client.confidential) {
|
|
25
|
-
if (!params.clientSecret) {
|
|
26
|
-
throw new OAuthError('invalid_client', 'Client secret required.', 401);
|
|
27
|
-
}
|
|
28
|
-
// Schema allows `client.secret` to be null; explicit guard so a future
|
|
29
|
-
// refactor can't mask `secret = null` as authenticating. See
|
|
30
|
-
// `client-credentials.ts` for the longer-form rationale.
|
|
31
|
-
if (client.secret == null) {
|
|
32
|
-
throw new OAuthError('invalid_client', 'Confidential client has no secret on file.', 401);
|
|
33
|
-
}
|
|
34
|
-
if (!(await verifyClientSecret(params.clientSecret, client.secret))) {
|
|
35
|
-
throw new OAuthError('invalid_client', 'Invalid client secret.', 401);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
24
|
+
await verifyConfidentialCredentials(client, params.clientSecret);
|
|
38
25
|
// Find refresh token by hashed plaintext (M5/P6) — the row's `id` is no
|
|
39
26
|
// longer the bearer secret, so a DB read leak doesn't yield usable tokens.
|
|
40
27
|
// Pre-migration tokens (which used `id` as the plaintext) won't match
|
|
@@ -70,8 +57,8 @@ export async function refreshTokenGrant(params) {
|
|
|
70
57
|
// Determine scopes — can only narrow, not widen
|
|
71
58
|
const originalScopes = accessTokenHelpers.getScopes(accessToken);
|
|
72
59
|
let scopes = originalScopes;
|
|
73
|
-
|
|
74
|
-
|
|
60
|
+
const requested = parseScopes(params.scope);
|
|
61
|
+
if (requested.length > 0) {
|
|
75
62
|
const invalid = requested.filter(s => !originalScopes.includes(s) && !originalScopes.includes('*'));
|
|
76
63
|
if (invalid.length > 0) {
|
|
77
64
|
throw new OAuthError('invalid_scope', `Cannot request scopes not in original token: ${invalid.join(', ')}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"refresh-token.js","sourceRoot":"","sources":["../../src/grants/refresh-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAIzC,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC9E,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"refresh-token.js","sourceRoot":"","sources":["../../src/grants/refresh-token.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { OAuthClient } from '../models/OAuthClient.js';
|
|
2
|
+
/**
|
|
3
|
+
* Verify client credentials at the token endpoint per RFC 6749 §2.3 / §5.2.
|
|
4
|
+
*
|
|
5
|
+
* Three failure modes return `invalid_client` 401 (with `WWW-Authenticate`
|
|
6
|
+
* set by the route handler on 401 responses):
|
|
7
|
+
*
|
|
8
|
+
* 1. **Missing secret** for a confidential client. The token endpoint
|
|
9
|
+
* requires a credential pair from confidential clients regardless of
|
|
10
|
+
* grant flow.
|
|
11
|
+
* 2. **`client.secret == null` on a confidential client.** The schema
|
|
12
|
+
* allows the column to be null (public clients legitimately have no
|
|
13
|
+
* secret). Hitting this branch on a confidential client is a data
|
|
14
|
+
* anomaly — explicit guard so a future refactor can't mask
|
|
15
|
+
* `secret = null` as authenticating against `verifyClientSecret(_, null)`
|
|
16
|
+
* (which fail-closes today, but the contract should be obvious).
|
|
17
|
+
* 3. **Hash mismatch.** Constant-time compare inside `verifyClientSecret`.
|
|
18
|
+
*
|
|
19
|
+
* `opts.requireConfidential` rejects non-confidential clients up front
|
|
20
|
+
* (`client_credentials` grant — RFC 6749 §4.4). Auth-code and refresh-token
|
|
21
|
+
* grants accept either kind; the credential pair is only checked when
|
|
22
|
+
* `client.confidential === true`. Public clients with no secret skip
|
|
23
|
+
* verification entirely and rely on PKCE / refresh-token-rotation for
|
|
24
|
+
* binding.
|
|
25
|
+
*/
|
|
26
|
+
export declare function verifyConfidentialCredentials(client: OAuthClient, clientSecret: string | undefined, opts?: {
|
|
27
|
+
requireConfidential?: boolean;
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=verify-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-client.d.ts","sourceRoot":"","sources":["../../src/grants/verify-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAI3D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,WAAW,EACnB,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,IAAI,GAAE;IAAE,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3C,OAAO,CAAC,IAAI,CAAC,CAgBf"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { verifyClientSecret } from '../client-secret.js';
|
|
2
|
+
import { OAuthError } from './authorization-code.js';
|
|
3
|
+
/**
|
|
4
|
+
* Verify client credentials at the token endpoint per RFC 6749 §2.3 / §5.2.
|
|
5
|
+
*
|
|
6
|
+
* Three failure modes return `invalid_client` 401 (with `WWW-Authenticate`
|
|
7
|
+
* set by the route handler on 401 responses):
|
|
8
|
+
*
|
|
9
|
+
* 1. **Missing secret** for a confidential client. The token endpoint
|
|
10
|
+
* requires a credential pair from confidential clients regardless of
|
|
11
|
+
* grant flow.
|
|
12
|
+
* 2. **`client.secret == null` on a confidential client.** The schema
|
|
13
|
+
* allows the column to be null (public clients legitimately have no
|
|
14
|
+
* secret). Hitting this branch on a confidential client is a data
|
|
15
|
+
* anomaly — explicit guard so a future refactor can't mask
|
|
16
|
+
* `secret = null` as authenticating against `verifyClientSecret(_, null)`
|
|
17
|
+
* (which fail-closes today, but the contract should be obvious).
|
|
18
|
+
* 3. **Hash mismatch.** Constant-time compare inside `verifyClientSecret`.
|
|
19
|
+
*
|
|
20
|
+
* `opts.requireConfidential` rejects non-confidential clients up front
|
|
21
|
+
* (`client_credentials` grant — RFC 6749 §4.4). Auth-code and refresh-token
|
|
22
|
+
* grants accept either kind; the credential pair is only checked when
|
|
23
|
+
* `client.confidential === true`. Public clients with no secret skip
|
|
24
|
+
* verification entirely and rely on PKCE / refresh-token-rotation for
|
|
25
|
+
* binding.
|
|
26
|
+
*/
|
|
27
|
+
export async function verifyConfidentialCredentials(client, clientSecret, opts = {}) {
|
|
28
|
+
if (opts.requireConfidential && !client.confidential) {
|
|
29
|
+
throw new OAuthError('invalid_client', 'Client credentials grant requires a confidential client.');
|
|
30
|
+
}
|
|
31
|
+
if (!client.confidential)
|
|
32
|
+
return;
|
|
33
|
+
if (!clientSecret) {
|
|
34
|
+
throw new OAuthError('invalid_client', 'Client secret required.', 401);
|
|
35
|
+
}
|
|
36
|
+
if (client.secret == null) {
|
|
37
|
+
throw new OAuthError('invalid_client', 'Confidential client has no secret on file.', 401);
|
|
38
|
+
}
|
|
39
|
+
if (!(await verifyClientSecret(clientSecret, client.secret))) {
|
|
40
|
+
throw new OAuthError('invalid_client', 'Invalid client secret.', 401);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=verify-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-client.js","sourceRoot":"","sources":["../../src/grants/verify-client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,MAAmB,EACnB,YAAgC,EAChC,OAA0C,EAAE;IAE5C,IAAI,IAAI,CAAC,mBAAmB,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;QACrD,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,0DAA0D,CAAC,CAAA;IACpG,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,YAAY;QAAE,OAAM;IAEhC,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,yBAAyB,EAAE,GAAG,CAAC,CAAA;IACxE,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC;QAC1B,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,4CAA4C,EAAE,GAAG,CAAC,CAAA;IAC3F,CAAC;IACD,IAAI,CAAC,CAAC,MAAM,kBAAkB,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,wBAAwB,EAAE,GAAG,CAAC,CAAA;IACvE,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bearer.d.ts","sourceRoot":"","sources":["../../src/middleware/bearer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"bearer.d.ts","sourceRoot":"","sources":["../../src/middleware/bearer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAc,MAAM,qBAAqB,CAAA;AAsIxE;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,iBAAiB,CAKpD;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,iBAAiB,CAkBjD"}
|