@rudderjs/passport 1.1.0 → 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/boost/guidelines.md +190 -0
- 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 +7 -6
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
|
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# @rudderjs/passport
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
OAuth 2 server package — issues JWT access tokens, refresh tokens, and personal access tokens. Ships four grants (authorization code + PKCE, client credentials, refresh token, device code), a `HasApiTokens` mixin for user models, and `RequireBearer` + `scope` middleware for protecting API routes. JWTs are RS256-signed, so third parties can verify them without calling the server.
|
|
6
|
+
|
|
7
|
+
## When to Use Passport vs Auth
|
|
8
|
+
|
|
9
|
+
`@rudderjs/auth` covers **session-based web auth** — login forms, cookies, password reset, email verification. `@rudderjs/passport` covers **token-based API auth** — OAuth flows for third-party integrations, M2M service auth, personal access tokens.
|
|
10
|
+
|
|
11
|
+
Most apps need both:
|
|
12
|
+
|
|
13
|
+
- **Web routes** (`m.web` group): `AuthMiddleware` runs automatically — read `req.user` directly.
|
|
14
|
+
- **API routes** (`m.api` group): stateless by default. Opt in per-route with `RequireBearer()` + `scope(...)`, or mount `AuthMiddleware('api')` + `RequireAuth('api')` with a token guard.
|
|
15
|
+
|
|
16
|
+
**Don't** mount `AuthMiddleware` globally via `m.use(...)`. API routes must stay stateless so they don't depend on session ALS context.
|
|
17
|
+
|
|
18
|
+
## Key Patterns
|
|
19
|
+
|
|
20
|
+
### Protecting API Routes
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { RequireBearer, scope } from '@rudderjs/passport'
|
|
24
|
+
|
|
25
|
+
router.get('/api/user', [RequireBearer()], (req) => req.user)
|
|
26
|
+
router.get('/api/posts', [RequireBearer(), scope('read')], listPosts)
|
|
27
|
+
router.post('/api/posts', [RequireBearer(), scope('write')], createPost)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`RequireBearer()` validates the JWT signature, checks expiration, and confirms the token hasn't been revoked in the DB. A valid token attaches the user to `req.user` (same shape as session-based routes).
|
|
31
|
+
|
|
32
|
+
`scope(...)` must run **after** `RequireBearer()` — it reads token scopes from request state set by the bearer middleware. Wildcard `*` grants everything.
|
|
33
|
+
|
|
34
|
+
### Personal Access Tokens (HasApiTokens)
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { Model } from '@rudderjs/orm'
|
|
38
|
+
import { HasApiTokens } from '@rudderjs/passport'
|
|
39
|
+
|
|
40
|
+
export class User extends HasApiTokens(Model) {
|
|
41
|
+
static table = 'user'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Issue — plain-text JWT is shown ONCE
|
|
45
|
+
const { plainTextToken, token } = await user.createToken('my-cli', ['read', 'write'])
|
|
46
|
+
|
|
47
|
+
// Manage
|
|
48
|
+
await user.tokens() // all tokens for this user
|
|
49
|
+
await user.revokeAllTokens() // revokes all, returns count
|
|
50
|
+
user.tokenCan('admin') // checks current-request token's scope (inside RequireBearer route)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Personal access tokens are issued against an internal `__personal_access__` OAuth client that Passport auto-creates on first use.
|
|
54
|
+
|
|
55
|
+
### Route Registration
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
// routes/api.ts
|
|
59
|
+
import { registerPassportRoutes } from '@rudderjs/passport'
|
|
60
|
+
|
|
61
|
+
export default (router) => {
|
|
62
|
+
registerPassportRoutes(router) // mounts /oauth/* endpoints
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Or selectively skip groups:
|
|
66
|
+
registerPassportRoutes(router, {
|
|
67
|
+
except: ['authorize', 'scopes'], // mount custom consent + scopes endpoints
|
|
68
|
+
prefix: '/api/oauth', // default is '/oauth'
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Available groups: `authorize`, `token`, `revoke`, `scopes`, `device`.
|
|
73
|
+
|
|
74
|
+
### Customization Hooks
|
|
75
|
+
|
|
76
|
+
All hooks live on the `Passport` static singleton. Call them from a provider's `boot()` method, before routes register:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { Passport, OAuthClient } from '@rudderjs/passport'
|
|
80
|
+
import { view } from '@rudderjs/view'
|
|
81
|
+
|
|
82
|
+
// Custom consent screen (default returns JSON)
|
|
83
|
+
Passport.authorizationView((ctx) => {
|
|
84
|
+
return view('oauth.authorize', {
|
|
85
|
+
client: ctx.client,
|
|
86
|
+
scopes: ctx.scopes,
|
|
87
|
+
redirectUri: ctx.redirectUri,
|
|
88
|
+
state: ctx.state,
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// Swap any model (add columns, override behavior)
|
|
93
|
+
class CustomOAuthClient extends OAuthClient { /* ... */ }
|
|
94
|
+
Passport.useClientModel(CustomOAuthClient)
|
|
95
|
+
// Also: useTokenModel, useRefreshTokenModel, useAuthCodeModel, useDeviceCodeModel
|
|
96
|
+
|
|
97
|
+
// Disable automatic route registration entirely
|
|
98
|
+
Passport.ignoreRoutes() // registerPassportRoutes() becomes a no-op
|
|
99
|
+
|
|
100
|
+
// Scopes can also be defined here instead of config
|
|
101
|
+
Passport.tokensCan({ read: 'Read access', write: 'Write access' })
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Config Shape
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
// config/passport.ts
|
|
108
|
+
import type { PassportConfig } from '@rudderjs/passport'
|
|
109
|
+
|
|
110
|
+
export default {
|
|
111
|
+
scopes: { read: 'Read', write: 'Write', admin: 'Admin' },
|
|
112
|
+
|
|
113
|
+
// Keys — prefer env vars in production
|
|
114
|
+
privateKey: process.env.PASSPORT_PRIVATE_KEY,
|
|
115
|
+
publicKey: process.env.PASSPORT_PUBLIC_KEY,
|
|
116
|
+
// OR filesystem:
|
|
117
|
+
keyPath: 'storage', // reads storage/oauth-{private,public}.key
|
|
118
|
+
|
|
119
|
+
// Lifetimes (ms)
|
|
120
|
+
tokensExpireIn: 15 * 24 * 60 * 60 * 1000,
|
|
121
|
+
refreshTokensExpireIn: 30 * 24 * 60 * 60 * 1000,
|
|
122
|
+
personalAccessTokensExpireIn: 6 * 30 * 24 * 60 * 60 * 1000,
|
|
123
|
+
} satisfies PassportConfig
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### CLI Commands
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pnpm rudder passport:keys [--force] # generate RSA keypair
|
|
130
|
+
pnpm rudder passport:client "App Name" [--public|--client-credentials|--device|--personal]
|
|
131
|
+
pnpm rudder passport:purge # remove expired/revoked records
|
|
132
|
+
pnpm rudder make:passport-client # scaffold a client seeder
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Common Pitfalls
|
|
136
|
+
|
|
137
|
+
- **Missing RSA keys** — run `pnpm rudder passport:keys` before issuing tokens, or set `PASSPORT_PRIVATE_KEY`/`PASSPORT_PUBLIC_KEY` env vars. Without keys, `passport.token()` throws.
|
|
138
|
+
- **Prisma schema not copied** — `@rudderjs/passport` ships 5 Prisma models in `schema/passport.prisma`. Copy that file into the app's multi-file Prisma schema directory and run `prisma db push`. The provider does not migrate for you.
|
|
139
|
+
- **Mounting `AuthMiddleware` globally breaks API routes** — `@rudderjs/auth`'s `AuthMiddleware` auto-installs on the `web` group only. API routes stay stateless; opt into auth per-route with `RequireBearer()`. Never call `m.use(AuthMiddleware())` — it reintroduces the old global-install problem.
|
|
140
|
+
- **Scope middleware before bearer** — `scope('read')` must come after `RequireBearer()` in the middleware array; it reads the token scopes the bearer middleware attaches to the request.
|
|
141
|
+
- **PKCE required for public clients** — public clients (created with `--public`) must send `code_challenge` + `code_challenge_method=S256`. Missing PKCE → `invalid_request`.
|
|
142
|
+
- **Refresh token reuse** — rotation revokes the old refresh token atomically. Retrying with the old one returns `invalid_grant`.
|
|
143
|
+
- **ORM returns records, not Model instances** — `AccessToken.where(...).first()` returns a plain data object. Prototype methods don't work on query results. Use `@rudderjs/passport`'s `models/helpers.ts` helpers (e.g. `accessTokenHelpers.can(token, scope)`) rather than calling methods on the record.
|
|
144
|
+
- **Custom model `static table`** — use the Prisma delegate name (camelCase, e.g. `oauthClient`), NOT the `@@map`'d SQL name (`oauth_clients`). Wrong table name → `[RudderJS ORM] Prisma has no delegate for table "oauth_clients"`.
|
|
145
|
+
- **Consent screen needs session** — `POST /oauth/authorize` and `POST /oauth/device/approve` both require `req.user`. If you mount OAuth routes on the `api` group, these two routes will 401. Either keep consent + device-approve on the `web` group, or mount `SessionMiddleware()` + `AuthMiddleware()` per-route.
|
|
146
|
+
- **Personal access client cache** — `_personalClientId` is cached module-level. `resetPersonalAccessClient()` is test-only; don't call it in production code.
|
|
147
|
+
- **Don't store plain-text JWTs** — `user.createToken()` returns `plainTextToken` once. The DB stores only the record (used for revocation lookup via `jti`). Show the JWT to the user; they must save it themselves.
|
|
148
|
+
|
|
149
|
+
## Key Imports
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
// Middleware
|
|
153
|
+
import { RequireBearer, BearerMiddleware, scope } from '@rudderjs/passport'
|
|
154
|
+
|
|
155
|
+
// Personal access tokens (user model mixin)
|
|
156
|
+
import { HasApiTokens } from '@rudderjs/passport'
|
|
157
|
+
|
|
158
|
+
// Customization
|
|
159
|
+
import { Passport } from '@rudderjs/passport'
|
|
160
|
+
|
|
161
|
+
// Route registration
|
|
162
|
+
import { registerPassportRoutes } from '@rudderjs/passport'
|
|
163
|
+
import type { PassportRouteOptions, PassportRouteGroup } from '@rudderjs/passport'
|
|
164
|
+
|
|
165
|
+
// Grant primitives (for custom route handlers)
|
|
166
|
+
import {
|
|
167
|
+
validateAuthorizationRequest,
|
|
168
|
+
issueAuthCode,
|
|
169
|
+
exchangeAuthCode,
|
|
170
|
+
clientCredentialsGrant,
|
|
171
|
+
refreshTokenGrant,
|
|
172
|
+
requestDeviceCode,
|
|
173
|
+
pollDeviceCode,
|
|
174
|
+
approveDeviceCode,
|
|
175
|
+
OAuthError,
|
|
176
|
+
} from '@rudderjs/passport'
|
|
177
|
+
|
|
178
|
+
// Models
|
|
179
|
+
import { OAuthClient, AccessToken, RefreshToken, AuthCode, DeviceCode } from '@rudderjs/passport'
|
|
180
|
+
|
|
181
|
+
// JWT primitives
|
|
182
|
+
import { createToken, verifyToken, unsafeDecodeToken } from '@rudderjs/passport'
|
|
183
|
+
// `decodeToken` is kept as a deprecated alias for `unsafeDecodeToken`. The
|
|
184
|
+
// `unsafe` prefix is intentional — the function does NOT verify the
|
|
185
|
+
// signature, so its output cannot be trusted for auth decisions. Use
|
|
186
|
+
// `verifyToken` whenever you need an authenticated payload.
|
|
187
|
+
|
|
188
|
+
// Types
|
|
189
|
+
import type { PassportConfig, PassportScope, NewPersonalAccessToken } from '@rudderjs/passport'
|
|
190
|
+
```
|
|
@@ -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":"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,eAAe,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACpE,OAAO,EAAE,WAAW,EAAqB,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAA;AAuBlE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,MAA4B;IAC7E,IAAI,MAAM,CAAC,YAAY,KAAK,MAAM,EAAE,CAAC;QACnC,MAAM,IAAI,UAAU,CAAC,2BAA2B,EAAE,uCAAuC,CAAC,CAAA;IAC5F,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,CAAC,CAAA;IAC7D,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,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,UAAU,CAAC,iBAAiB,EAAE,uBAAuB,CAAC,CAAA;IAClE,CAAC;IAED,kBAAkB;IAClB,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,CAAC,mBAAmB,IAAI,MAAM,CAAA;QACnD,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YAC5C,MAAM,IAAI,UAAU,CAAC,iBAAiB,EAAE,uDAAuD,CAAC,CAAA;QAClG,CAAC;QACD,0EAA0E;QAC1E,2EAA2E;QAC3E,wEAAwE;QACxE,mEAAmE;QACnE,4DAA4D;QAC5D,IAAI,MAAM,KAAK,OAAO,IAAI,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACzD,MAAM,IAAI,UAAU,CAAC,iBAAiB,EAAE,qDAAqD,CAAC,CAAA;QAChG,CAAC;IACH,CAAC;SAAM,IAAI,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1C,+BAA+B;QAC/B,MAAM,IAAI,UAAU,CAAC,iBAAiB,EAAE,yDAAyD,CAAC,CAAA;IACpG,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACxC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAE9B,MAAM,MAAM,GAAyB;QACnC,MAAM;QACN,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,MAAM;KACP,CAAA;IACD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;QAAE,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAA;IAC3D,IAAI,MAAM,CAAC,aAAa,KAAK,SAAS;QAAE,MAAM,CAAC,aAAa,GAAG,MAAM,CAAC,aAAa,CAAA;IACnF,MAAM,MAAM,GAAG,MAAM,CAAC,mBAAmB,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IACxF,IAAI,MAAM,KAAK,SAAS;QAAE,MAAM,CAAC,mBAAmB,GAAG,MAAM,CAAA;IAE7D,OAAO,MAAM,CAAA;AACf,CAAC;AAED,6DAA6D;AAE7D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAOnC;IACC,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA,CAAC,aAAa;IAErE,yEAAyE;IACzE,yEAAyE;IACzE,yEAAyE;IACzE,qEAAqE;IACrE,MAAM,aAAa,GAAG,MAAM,cAAc,EAAE,CAAA;IAC5C,MAAM,QAAQ,GAAQ,MAAM,eAAe,CAAC,aAAa,CAAC,CAAA;IAE1D,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,aAAa,EAAE,CAAA;IAClD,MAAM,WAAW,CAAC,MAAM,CAAC;QACvB,MAAM,EAAe,IAAI,CAAC,MAAM;QAChC,QAAQ,EAAa,IAAI,CAAC,QAAQ;QAClC,SAAS,EAAY,QAAQ;QAC7B,MAAM,EAAe,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;QAChD,OAAO,EAAc,KAAK;QAC1B,SAAS;QACT,WAAW,EAAU,IAAI,CAAC,WAAW;QACrC,aAAa,EAAQ,IAAI,CAAC,aAAa,IAAI,IAAI;QAC/C,mBAAmB,EAAE,IAAI,CAAC,mBAAmB,IAAI,IAAI;KAC3B,CAAC,CAAA;IAE7B,OAAO,aAAa,CAAA;AACtB,CAAC;AAaD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAA4B;IACjE,IAAI,MAAM,CAAC,SAAS,KAAK,oBAAoB,EAAE,CAAC;QAC9C,MAAM,IAAI,UAAU,CAAC,wBAAwB,EAAE,yCAAyC,CAAC,CAAA;IAC3F,CAAC;IAED,MAAM,SAAS,GAAK,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAA;IAChD,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,aAAa,EAAE,CAAA;IAElD,qEAAqE;IACrE,oEAAoE;IACpE,gEAAgE;IAChE,sEAAsE;IACtE,4CAA4C;IAC5C,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,MAAM,6BAA6B,CAAC,MAAM,EAAE,MAAM,CAAC,YAAY,CAAC,CAAA;IAEhE,wEAAwE;IACxE,0EAA0E;IAC1E,0EAA0E;IAC1E,wEAAwE;IACxE,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IACnD,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,KAAK,EAAqB,CAAA;IAC1F,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE,+BAA+B,CAAC,CAAA;IACxE,CAAC;IACD,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;QACrB,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE,sCAAsC,CAAC,CAAA;IAC/E,CAAC;IACD,IAAI,eAAe,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE,iCAAiC,CAAC,CAAA;IAC1E,CAAC;IACD,IAAI,QAAQ,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC1C,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE,mDAAmD,CAAC,CAAA;IAC5F,CAAC;IAED,0EAA0E;IAC1E,sEAAsE;IACtE,wEAAwE;IACxE,2EAA2E;IAC3E,yEAAyE;IACzE,+DAA+D;IAC/D,IAAI,QAAQ,CAAC,WAAW,KAAK,IAAI,IAAI,QAAQ,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACxE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACxB,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE,uDAAuD,CAAC,CAAA;QAChG,CAAC;QACD,IAAI,QAAQ,CAAC,WAAW,KAAK,MAAM,CAAC,WAAW,EAAE,CAAC;YAChD,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE,mEAAmE,CAAC,CAAA;QAC5G,CAAC;IACH,CAAC;IAED,oBAAoB;IACpB,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YACzB,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE,8BAA8B,CAAC,CAAA;QACvE,CAAC;QAED,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAA;QAClD,IAAI,QAAgB,CAAA;QAEpB,IAAI,QAAQ,CAAC,mBAAmB,KAAK,MAAM,EAAE,CAAC;YAC5C,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC;iBAC5B,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;iBAC3B,MAAM,CAAC,WAAW,CAAC,CAAA;QACxB,CAAC;aAAM,CAAC;YACN,QAAQ;YACR,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAA;QAChC,CAAC;QAED,uEAAuE;QACvE,sEAAsE;QACtE,oEAAoE;QACpE,iCAAiC;QACjC,IAAI,CAAC,CAAC,MAAM,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC;YAC3D,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE,oCAAoC,CAAC,CAAA;QAC7E,CAAC;IACH,CAAC;IAED,kEAAkE;IAClE,iEAAiE;IACjE,sEAAsE;IACtE,kEAAkE;IAClE,wEAAwE;IACxE,kEAAkE;IAClE,oEAAoE;IACpE,wEAAwE;IACxE,wEAAwE;IACxE,mEAAmE;IACnE,qEAAqE;IACrE,mEAAmE;IACnE,MAAM,QAAQ,GAAG,MAAM,WAAW;SAC/B,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC;SACxB,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC;SACvB,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAA6B,CAAC,CAAA;IAC1D,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,UAAU,CAAC,eAAe,EAAE,2CAA2C,CAAC,CAAA;IACpF,CAAC;IAED,eAAe;IACf,OAAO,WAAW,CAAC;QACjB,MAAM,EAAI,QAAQ,CAAC,MAAM;QACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,MAAM,EAAI,eAAe,CAAC,SAAS,CAAC,QAAQ,CAAC;QAC7C,cAAc,EAAE,IAAI;KACrB,CAAC,CAAA;AACJ,CAAC;AAED,6DAA6D;AAE7D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,cAAc,CAAC,MAAmB,EAAE,SAAmB;IACrE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAElC,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAA;IACpC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;QACnD,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QACpE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,UAAU,CAClB,eAAe,EACf,0DAA0D,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAC/E,CAAA;QACH,CAAC;IACH,CAAC;IAED,MAAM,YAAY,GAAG,aAAa,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;IACpD,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,CAAA;QACnC,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QAChE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,UAAU,CAClB,eAAe,EACf,0DAA0D,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAC9E,CAAA;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,6DAA6D;AAE7D,MAAM,OAAO,UAAW,SAAQ,KAAK;IAEjB;IACA;IACA;IAHlB,YACkB,KAAa,EACb,gBAAwB,EACxB,aAAqB,GAAG;QAExC,KAAK,CAAC,gBAAgB,CAAC,CAAA;QAJP,UAAK,GAAL,KAAK,CAAQ;QACb,qBAAgB,GAAhB,gBAAgB,CAAQ;QACxB,eAAU,GAAV,UAAU,CAAc;QAGxC,IAAI,CAAC,IAAI,GAAG,YAAY,CAAA;IAC1B,CAAC;IAED,MAAM;QACJ,OAAO;YACL,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,iBAAiB,EAAE,IAAI,CAAC,gBAAgB;SACzC,CAAA;IACH,CAAC;CACF"}
|
|
@@ -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"}
|