@rudderjs/passport 1.0.0 → 1.1.1
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/boost/guidelines.md +190 -0
- package/dist/Passport.d.ts +93 -0
- package/dist/Passport.d.ts.map +1 -1
- package/dist/Passport.js +147 -0
- package/dist/Passport.js.map +1 -1
- package/dist/client-secret.d.ts +12 -0
- package/dist/client-secret.d.ts.map +1 -0
- package/dist/client-secret.js +63 -0
- package/dist/client-secret.js.map +1 -0
- package/dist/commands/client.d.ts +21 -0
- package/dist/commands/client.d.ts.map +1 -1
- package/dist/commands/client.js +27 -2
- package/dist/commands/client.js.map +1 -1
- package/dist/commands/keys.d.ts +28 -4
- package/dist/commands/keys.d.ts.map +1 -1
- package/dist/commands/keys.js +34 -4
- package/dist/commands/keys.js.map +1 -1
- package/dist/commands/purge.d.ts +6 -1
- package/dist/commands/purge.d.ts.map +1 -1
- package/dist/commands/purge.js +15 -31
- package/dist/commands/purge.js.map +1 -1
- package/dist/device-code-secret.d.ts +28 -0
- package/dist/device-code-secret.d.ts.map +1 -0
- package/dist/device-code-secret.js +31 -0
- package/dist/device-code-secret.js.map +1 -0
- package/dist/grants/authorization-code.d.ts +23 -0
- package/dist/grants/authorization-code.d.ts.map +1 -1
- package/dist/grants/authorization-code.js +126 -15
- 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 +13 -5
- package/dist/grants/client-credentials.js.map +1 -1
- package/dist/grants/device-code.d.ts +10 -1
- package/dist/grants/device-code.d.ts.map +1 -1
- package/dist/grants/device-code.js +41 -10
- package/dist/grants/device-code.js.map +1 -1
- package/dist/grants/index.d.ts +1 -1
- package/dist/grants/index.d.ts.map +1 -1
- package/dist/grants/index.js +1 -1
- package/dist/grants/index.js.map +1 -1
- package/dist/grants/issue-tokens.d.ts +9 -0
- package/dist/grants/issue-tokens.d.ts.map +1 -1
- package/dist/grants/issue-tokens.js +39 -5
- package/dist/grants/issue-tokens.js.map +1 -1
- package/dist/grants/refresh-token.d.ts.map +1 -1
- package/dist/grants/refresh-token.js +64 -9
- package/dist/grants/refresh-token.js.map +1 -1
- package/dist/grants/safe-compare.d.ts +19 -0
- package/dist/grants/safe-compare.d.ts.map +1 -0
- package/dist/grants/safe-compare.js +28 -0
- package/dist/grants/safe-compare.js.map +1 -0
- package/dist/index.d.ts +27 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +122 -67
- package/dist/index.js.map +1 -1
- package/dist/middleware/bearer.d.ts.map +1 -1
- package/dist/middleware/bearer.js +36 -6
- package/dist/middleware/bearer.js.map +1 -1
- package/dist/middleware/scope.d.ts +12 -2
- package/dist/middleware/scope.d.ts.map +1 -1
- package/dist/middleware/scope.js +46 -2
- package/dist/middleware/scope.js.map +1 -1
- package/dist/models/AccessToken.d.ts +32 -0
- package/dist/models/AccessToken.d.ts.map +1 -1
- package/dist/models/AccessToken.js +63 -3
- package/dist/models/AccessToken.js.map +1 -1
- package/dist/models/AuthCode.d.ts +16 -0
- package/dist/models/AuthCode.d.ts.map +1 -1
- package/dist/models/AuthCode.js +17 -1
- package/dist/models/AuthCode.js.map +1 -1
- package/dist/models/DeviceCode.d.ts +12 -2
- package/dist/models/DeviceCode.d.ts.map +1 -1
- package/dist/models/DeviceCode.js +7 -1
- package/dist/models/DeviceCode.js.map +1 -1
- package/dist/models/OAuthClient.d.ts +4 -0
- package/dist/models/OAuthClient.d.ts.map +1 -1
- package/dist/models/OAuthClient.js +13 -1
- package/dist/models/OAuthClient.js.map +1 -1
- package/dist/models/RefreshToken.d.ts +11 -0
- package/dist/models/RefreshToken.d.ts.map +1 -1
- package/dist/models/RefreshToken.js +12 -2
- package/dist/models/RefreshToken.js.map +1 -1
- package/dist/models/helpers.d.ts +6 -0
- package/dist/models/helpers.d.ts.map +1 -1
- package/dist/models/helpers.js +15 -2
- package/dist/models/helpers.js.map +1 -1
- package/dist/opaque-token.d.ts +32 -0
- package/dist/opaque-token.d.ts.map +1 -0
- package/dist/opaque-token.js +38 -0
- package/dist/opaque-token.js.map +1 -0
- package/dist/personal-access-tokens.d.ts.map +1 -1
- package/dist/personal-access-tokens.js +48 -10
- package/dist/personal-access-tokens.js.map +1 -1
- package/dist/routes.d.ts +149 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +279 -41
- package/dist/routes.js.map +1 -1
- package/dist/token.d.ts +80 -4
- package/dist/token.d.ts.map +1 -1
- package/dist/token.js +97 -13
- package/dist/token.js.map +1 -1
- package/package.json +7 -6
- package/schema/passport.prisma +29 -9
|
@@ -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
|
+
```
|
package/dist/Passport.d.ts
CHANGED
|
@@ -28,6 +28,16 @@ export declare class Passport {
|
|
|
28
28
|
private static _keyPath;
|
|
29
29
|
private static _privateKey;
|
|
30
30
|
private static _publicKey;
|
|
31
|
+
/**
|
|
32
|
+
* Previous public key, retained for verification only after a `passport:keys
|
|
33
|
+
* --force` rotation. Tokens minted before the rotation keep verifying via
|
|
34
|
+
* this slot during the grace window; new tokens are signed by the current
|
|
35
|
+
* private key. Single-slot — one rotation deep — by design (operators who
|
|
36
|
+
* need a longer history should stage rotations to land outside the
|
|
37
|
+
* configured access-token lifetime). See JWKS verifier design notes in
|
|
38
|
+
* `verificationKeys()` below.
|
|
39
|
+
*/
|
|
40
|
+
private static _previousPublicKey;
|
|
31
41
|
private static _clientModel;
|
|
32
42
|
private static _tokenModel;
|
|
33
43
|
private static _refreshTokenModel;
|
|
@@ -35,6 +45,16 @@ export declare class Passport {
|
|
|
35
45
|
private static _deviceCodeModel;
|
|
36
46
|
private static _authorizationView;
|
|
37
47
|
private static _routesIgnored;
|
|
48
|
+
private static _issuer;
|
|
49
|
+
/**
|
|
50
|
+
* Maximum value (in seconds) the per-row `oauth_device_codes.interval` is
|
|
51
|
+
* allowed to grow to via repeated `slow_down` escalations (RFC 8628 §3.5
|
|
52
|
+
* doesn't specify a cap; we add one to keep degenerate clients from
|
|
53
|
+
* pushing the interval to absurd values). 60 seconds is the default — long
|
|
54
|
+
* enough to make a misbehaving client back off meaningfully, short enough
|
|
55
|
+
* that a legitimate user typing the user_code never hits it.
|
|
56
|
+
*/
|
|
57
|
+
private static _deviceMaxInterval;
|
|
38
58
|
/** Define available OAuth scopes. */
|
|
39
59
|
static tokensCan(scopes: Record<string, string>): void;
|
|
40
60
|
/** Check if a scope is defined. */
|
|
@@ -55,11 +75,53 @@ export declare class Passport {
|
|
|
55
75
|
static keyPath(): string;
|
|
56
76
|
/** Set keys directly (from environment variables). */
|
|
57
77
|
static setKeys(privateKey: string, publicKey: string): void;
|
|
78
|
+
/**
|
|
79
|
+
* Stamp the previous public key for verification grace after a key
|
|
80
|
+
* rotation. Tokens carrying `kid` for this key — or any token whose
|
|
81
|
+
* signature happens to verify against it — keep working until they
|
|
82
|
+
* naturally expire. Pair with the env var `PASSPORT_PREVIOUS_PUBLIC_KEY`
|
|
83
|
+
* if you don't want to rely on the on-disk `oauth-previous-public.key`
|
|
84
|
+
* convention. Pass `null` to clear.
|
|
85
|
+
*/
|
|
86
|
+
static setPreviousPublicKey(publicKey: string | null): void;
|
|
87
|
+
/**
|
|
88
|
+
* Probe whether an RSA keypair is reachable — either explicitly set via
|
|
89
|
+
* `setKeys()` (env vars) or readable on disk under the configured key path.
|
|
90
|
+
* Used by `PassportProvider.boot()` to surface a startup warning when keys
|
|
91
|
+
* are missing, before the first `/oauth/*` request fails with a confusing
|
|
92
|
+
* file-not-found error.
|
|
93
|
+
*
|
|
94
|
+
* Does NOT load or cache the keys; it only stats the files.
|
|
95
|
+
*/
|
|
96
|
+
static keysAvailable(): Promise<boolean>;
|
|
58
97
|
/** Load keys from files or env. Returns { privateKey, publicKey }. */
|
|
59
98
|
static keys(): Promise<{
|
|
60
99
|
privateKey: string;
|
|
61
100
|
publicKey: string;
|
|
62
101
|
}>;
|
|
102
|
+
/**
|
|
103
|
+
* All public keys that should be considered for JWT signature verification,
|
|
104
|
+
* ordered current-first. After `passport:keys --force` rotates the
|
|
105
|
+
* keypair, the previous public key lingers in this list (loaded from
|
|
106
|
+
* `oauth-previous-public.key` on disk or set via `setPreviousPublicKey()`)
|
|
107
|
+
* so JWTs signed before the rotation keep verifying during their natural
|
|
108
|
+
* lifetime — `verifyToken()` walks the list and accepts a match against
|
|
109
|
+
* any entry. Without this, every rotation forced an immediate global
|
|
110
|
+
* sign-out.
|
|
111
|
+
*
|
|
112
|
+
* Tokens minted by recent versions also carry a `kid` JWT header equal to
|
|
113
|
+
* the SHA-256 of the public key that signed them, so the verifier can
|
|
114
|
+
* pick the right key directly without trial-and-error. Legacy tokens with
|
|
115
|
+
* no `kid` fall through to "try each in order".
|
|
116
|
+
*
|
|
117
|
+
* Single previous-slot is intentional: one rotation deep. Operators who
|
|
118
|
+
* need a multi-step grace should stage rotations to land outside the
|
|
119
|
+
* configured access-token lifetime — at that point old tokens have
|
|
120
|
+
* expired anyway and a longer history buys nothing.
|
|
121
|
+
*/
|
|
122
|
+
static verificationKeys(): Promise<string[]>;
|
|
123
|
+
/** Get the configured previous public key, if any. */
|
|
124
|
+
static previousPublicKey(): string | null;
|
|
63
125
|
static useClientModel(cls: typeof OAuthClient): void;
|
|
64
126
|
static useTokenModel(cls: typeof AccessToken): void;
|
|
65
127
|
static useRefreshTokenModel(cls: typeof RefreshToken): void;
|
|
@@ -83,6 +145,37 @@ export declare class Passport {
|
|
|
83
145
|
*/
|
|
84
146
|
static ignoreRoutes(): void;
|
|
85
147
|
static routesIgnored(): boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Configure the JWT `iss` claim that `createToken()` stamps on every new
|
|
150
|
+
* access token, and that `verifyToken()` validates when called with
|
|
151
|
+
* `expectedIssuer: Passport.issuer()` (BearerMiddleware does this
|
|
152
|
+
* automatically). Typically set to the canonical app URL — e.g.
|
|
153
|
+
* `Passport.useIssuer('https://app.example.com')`.
|
|
154
|
+
*
|
|
155
|
+
* RFC 8725 §3.10 recommends issuer validation once a deployment has more
|
|
156
|
+
* than one possible signer (multi-tenant, staging+prod sharing keys, etc.).
|
|
157
|
+
* Tokens minted before this is configured carry no `iss` claim and are
|
|
158
|
+
* exempt during the migration window — same pattern as redirect_uri (P1)
|
|
159
|
+
* and familyId (P4).
|
|
160
|
+
*/
|
|
161
|
+
static useIssuer(url: string): void;
|
|
162
|
+
static issuer(): string | null;
|
|
163
|
+
/**
|
|
164
|
+
* Configure the maximum value (in seconds) the per-row device-code
|
|
165
|
+
* polling interval is allowed to grow to via repeated `slow_down`
|
|
166
|
+
* escalations. Must be >= 5 (the initial interval) and >= the increment
|
|
167
|
+
* step (5s) to make sense; values smaller than that disable escalation
|
|
168
|
+
* entirely and are clamped at 5.
|
|
169
|
+
*
|
|
170
|
+
* Defaults to 60 seconds. Raise it for niche flows where a misbehaving
|
|
171
|
+
* client should be backed off more aggressively (e.g. a daemon polling
|
|
172
|
+
* with no human in the loop). Lower it if your device-flow consumers can
|
|
173
|
+
* tolerate a quicker authorization handshake — but lowering below ~30
|
|
174
|
+
* gives legitimate users a small window to enter the user_code.
|
|
175
|
+
*/
|
|
176
|
+
static deviceMaxInterval(seconds: number): void;
|
|
177
|
+
/** Current cap on `oauth_device_codes.interval` (seconds). */
|
|
178
|
+
static deviceMaxIntervalSeconds(): number;
|
|
86
179
|
/** @internal */
|
|
87
180
|
static reset(): void;
|
|
88
181
|
}
|
package/dist/Passport.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Passport.d.ts","sourceRoot":"","sources":["../src/Passport.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAO,yBAAyB,CAAA;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAO,yBAAyB,CAAA;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAU,sBAAsB,CAAA;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAQ,wBAAwB,CAAA;AAI1D,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAW,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACpC,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,GAAG,EAAE,wBAAwB,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;AAE/F,qBAAa,QAAQ;IACnB,OAAO,CAAC,MAAM,CAAC,OAAO,CAA4B;IAClD,OAAO,CAAC,MAAM,CAAC,cAAc,CAAiC;IAC9D,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAA2B;IAC/D,OAAO,CAAC,MAAM,CAAC,sBAAsB,CAA+B;IACpE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAY;IACnC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAsB;IAChD,OAAO,CAAC,MAAM,CAAC,UAAU,CAAsB;
|
|
1
|
+
{"version":3,"file":"Passport.d.ts","sourceRoot":"","sources":["../src/Passport.ts"],"names":[],"mappings":"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"}
|
package/dist/Passport.js
CHANGED
|
@@ -6,6 +6,16 @@ export class Passport {
|
|
|
6
6
|
static _keyPath = 'storage';
|
|
7
7
|
static _privateKey = null;
|
|
8
8
|
static _publicKey = null;
|
|
9
|
+
/**
|
|
10
|
+
* Previous public key, retained for verification only after a `passport:keys
|
|
11
|
+
* --force` rotation. Tokens minted before the rotation keep verifying via
|
|
12
|
+
* this slot during the grace window; new tokens are signed by the current
|
|
13
|
+
* private key. Single-slot — one rotation deep — by design (operators who
|
|
14
|
+
* need a longer history should stage rotations to land outside the
|
|
15
|
+
* configured access-token lifetime). See JWKS verifier design notes in
|
|
16
|
+
* `verificationKeys()` below.
|
|
17
|
+
*/
|
|
18
|
+
static _previousPublicKey = null;
|
|
9
19
|
// Custom model overrides (lazy — resolved at use-site so the defaults aren't eagerly loaded).
|
|
10
20
|
static _clientModel = null;
|
|
11
21
|
static _tokenModel = null;
|
|
@@ -16,6 +26,20 @@ export class Passport {
|
|
|
16
26
|
static _authorizationView = null;
|
|
17
27
|
// Route auto-registration toggle
|
|
18
28
|
static _routesIgnored = false;
|
|
29
|
+
// JWT issuer URL — when set, createToken stamps `iss` on the payload and
|
|
30
|
+
// verifyToken can validate it via `expectedIssuer`. Optional per RFC 7519
|
|
31
|
+
// but recommended by RFC 8725 §3.10 once the deployment has more than one
|
|
32
|
+
// possible issuer (e.g. multi-tenant or staging vs prod sharing keys).
|
|
33
|
+
static _issuer = null;
|
|
34
|
+
/**
|
|
35
|
+
* Maximum value (in seconds) the per-row `oauth_device_codes.interval` is
|
|
36
|
+
* allowed to grow to via repeated `slow_down` escalations (RFC 8628 §3.5
|
|
37
|
+
* doesn't specify a cap; we add one to keep degenerate clients from
|
|
38
|
+
* pushing the interval to absurd values). 60 seconds is the default — long
|
|
39
|
+
* enough to make a misbehaving client back off meaningfully, short enough
|
|
40
|
+
* that a legitimate user typing the user_code never hits it.
|
|
41
|
+
*/
|
|
42
|
+
static _deviceMaxInterval = 60;
|
|
19
43
|
// ── Scopes ──────────────────────────────────────────────
|
|
20
44
|
/** Define available OAuth scopes. */
|
|
21
45
|
static tokensCan(scopes) {
|
|
@@ -52,6 +76,39 @@ export class Passport {
|
|
|
52
76
|
this._privateKey = privateKey;
|
|
53
77
|
this._publicKey = publicKey;
|
|
54
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Stamp the previous public key for verification grace after a key
|
|
81
|
+
* rotation. Tokens carrying `kid` for this key — or any token whose
|
|
82
|
+
* signature happens to verify against it — keep working until they
|
|
83
|
+
* naturally expire. Pair with the env var `PASSPORT_PREVIOUS_PUBLIC_KEY`
|
|
84
|
+
* if you don't want to rely on the on-disk `oauth-previous-public.key`
|
|
85
|
+
* convention. Pass `null` to clear.
|
|
86
|
+
*/
|
|
87
|
+
static setPreviousPublicKey(publicKey) {
|
|
88
|
+
this._previousPublicKey = publicKey && publicKey.length > 0 ? publicKey : null;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Probe whether an RSA keypair is reachable — either explicitly set via
|
|
92
|
+
* `setKeys()` (env vars) or readable on disk under the configured key path.
|
|
93
|
+
* Used by `PassportProvider.boot()` to surface a startup warning when keys
|
|
94
|
+
* are missing, before the first `/oauth/*` request fails with a confusing
|
|
95
|
+
* file-not-found error.
|
|
96
|
+
*
|
|
97
|
+
* Does NOT load or cache the keys; it only stats the files.
|
|
98
|
+
*/
|
|
99
|
+
static async keysAvailable() {
|
|
100
|
+
if (this._privateKey && this._publicKey)
|
|
101
|
+
return true;
|
|
102
|
+
const { stat } = await import('node:fs/promises');
|
|
103
|
+
const { join } = await import('node:path');
|
|
104
|
+
const privatePath = join(process.cwd(), this._keyPath, 'oauth-private.key');
|
|
105
|
+
const publicPath = join(process.cwd(), this._keyPath, 'oauth-public.key');
|
|
106
|
+
const [priv, pub] = await Promise.all([
|
|
107
|
+
stat(privatePath).then(() => true, () => false),
|
|
108
|
+
stat(publicPath).then(() => true, () => false),
|
|
109
|
+
]);
|
|
110
|
+
return priv && pub;
|
|
111
|
+
}
|
|
55
112
|
/** Load keys from files or env. Returns { privateKey, publicKey }. */
|
|
56
113
|
static async keys() {
|
|
57
114
|
// Prefer explicitly set keys (from env vars)
|
|
@@ -71,6 +128,54 @@ export class Passport {
|
|
|
71
128
|
this._publicKey = publicKey;
|
|
72
129
|
return { privateKey, publicKey };
|
|
73
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* All public keys that should be considered for JWT signature verification,
|
|
133
|
+
* ordered current-first. After `passport:keys --force` rotates the
|
|
134
|
+
* keypair, the previous public key lingers in this list (loaded from
|
|
135
|
+
* `oauth-previous-public.key` on disk or set via `setPreviousPublicKey()`)
|
|
136
|
+
* so JWTs signed before the rotation keep verifying during their natural
|
|
137
|
+
* lifetime — `verifyToken()` walks the list and accepts a match against
|
|
138
|
+
* any entry. Without this, every rotation forced an immediate global
|
|
139
|
+
* sign-out.
|
|
140
|
+
*
|
|
141
|
+
* Tokens minted by recent versions also carry a `kid` JWT header equal to
|
|
142
|
+
* the SHA-256 of the public key that signed them, so the verifier can
|
|
143
|
+
* pick the right key directly without trial-and-error. Legacy tokens with
|
|
144
|
+
* no `kid` fall through to "try each in order".
|
|
145
|
+
*
|
|
146
|
+
* Single previous-slot is intentional: one rotation deep. Operators who
|
|
147
|
+
* need a multi-step grace should stage rotations to land outside the
|
|
148
|
+
* configured access-token lifetime — at that point old tokens have
|
|
149
|
+
* expired anyway and a longer history buys nothing.
|
|
150
|
+
*/
|
|
151
|
+
static async verificationKeys() {
|
|
152
|
+
const { publicKey } = await this.keys();
|
|
153
|
+
const keys = [publicKey];
|
|
154
|
+
if (this._previousPublicKey) {
|
|
155
|
+
keys.push(this._previousPublicKey);
|
|
156
|
+
return keys;
|
|
157
|
+
}
|
|
158
|
+
// Filesystem fallback — `oauth-previous-public.key` is written by
|
|
159
|
+
// `generateKeys({ force: true })` during a rotation, alongside the
|
|
160
|
+
// timestamped audit backup.
|
|
161
|
+
const { readFile } = await import('node:fs/promises');
|
|
162
|
+
const { join } = await import('node:path');
|
|
163
|
+
const previousPath = join(process.cwd(), this._keyPath, 'oauth-previous-public.key');
|
|
164
|
+
try {
|
|
165
|
+
const previous = await readFile(previousPath, 'utf8');
|
|
166
|
+
this._previousPublicKey = previous;
|
|
167
|
+
keys.push(previous);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// No previous key on disk — first rotation hasn't happened yet, or
|
|
171
|
+
// the operator deleted the file to drop the grace window.
|
|
172
|
+
}
|
|
173
|
+
return keys;
|
|
174
|
+
}
|
|
175
|
+
/** Get the configured previous public key, if any. */
|
|
176
|
+
static previousPublicKey() {
|
|
177
|
+
return this._previousPublicKey;
|
|
178
|
+
}
|
|
74
179
|
// ── Custom Models ───────────────────────────────────────
|
|
75
180
|
static useClientModel(cls) { this._clientModel = cls; }
|
|
76
181
|
static useTokenModel(cls) { this._tokenModel = cls; }
|
|
@@ -125,6 +230,45 @@ export class Passport {
|
|
|
125
230
|
static routesIgnored() {
|
|
126
231
|
return this._routesIgnored;
|
|
127
232
|
}
|
|
233
|
+
// ── JWT issuer ──────────────────────────────────────────
|
|
234
|
+
/**
|
|
235
|
+
* Configure the JWT `iss` claim that `createToken()` stamps on every new
|
|
236
|
+
* access token, and that `verifyToken()` validates when called with
|
|
237
|
+
* `expectedIssuer: Passport.issuer()` (BearerMiddleware does this
|
|
238
|
+
* automatically). Typically set to the canonical app URL — e.g.
|
|
239
|
+
* `Passport.useIssuer('https://app.example.com')`.
|
|
240
|
+
*
|
|
241
|
+
* RFC 8725 §3.10 recommends issuer validation once a deployment has more
|
|
242
|
+
* than one possible signer (multi-tenant, staging+prod sharing keys, etc.).
|
|
243
|
+
* Tokens minted before this is configured carry no `iss` claim and are
|
|
244
|
+
* exempt during the migration window — same pattern as redirect_uri (P1)
|
|
245
|
+
* and familyId (P4).
|
|
246
|
+
*/
|
|
247
|
+
static useIssuer(url) { this._issuer = url || null; }
|
|
248
|
+
static issuer() { return this._issuer; }
|
|
249
|
+
// ── Device flow polling cap (RFC 8628 §3.5) ─────────────
|
|
250
|
+
/**
|
|
251
|
+
* Configure the maximum value (in seconds) the per-row device-code
|
|
252
|
+
* polling interval is allowed to grow to via repeated `slow_down`
|
|
253
|
+
* escalations. Must be >= 5 (the initial interval) and >= the increment
|
|
254
|
+
* step (5s) to make sense; values smaller than that disable escalation
|
|
255
|
+
* entirely and are clamped at 5.
|
|
256
|
+
*
|
|
257
|
+
* Defaults to 60 seconds. Raise it for niche flows where a misbehaving
|
|
258
|
+
* client should be backed off more aggressively (e.g. a daemon polling
|
|
259
|
+
* with no human in the loop). Lower it if your device-flow consumers can
|
|
260
|
+
* tolerate a quicker authorization handshake — but lowering below ~30
|
|
261
|
+
* gives legitimate users a small window to enter the user_code.
|
|
262
|
+
*/
|
|
263
|
+
static deviceMaxInterval(seconds) {
|
|
264
|
+
// Never below the floor — escalation is by 5s, so a cap below 5
|
|
265
|
+
// would prevent any escalation from ever taking effect.
|
|
266
|
+
this._deviceMaxInterval = Math.max(5, Math.floor(seconds));
|
|
267
|
+
}
|
|
268
|
+
/** Current cap on `oauth_device_codes.interval` (seconds). */
|
|
269
|
+
static deviceMaxIntervalSeconds() {
|
|
270
|
+
return this._deviceMaxInterval;
|
|
271
|
+
}
|
|
128
272
|
// ── Reset (testing) ─────────────────────────────────────
|
|
129
273
|
/** @internal */
|
|
130
274
|
static reset() {
|
|
@@ -135,6 +279,7 @@ export class Passport {
|
|
|
135
279
|
this._keyPath = 'storage';
|
|
136
280
|
this._privateKey = null;
|
|
137
281
|
this._publicKey = null;
|
|
282
|
+
this._previousPublicKey = null;
|
|
138
283
|
this._clientModel = null;
|
|
139
284
|
this._tokenModel = null;
|
|
140
285
|
this._refreshTokenModel = null;
|
|
@@ -142,6 +287,8 @@ export class Passport {
|
|
|
142
287
|
this._deviceCodeModel = null;
|
|
143
288
|
this._authorizationView = null;
|
|
144
289
|
this._routesIgnored = false;
|
|
290
|
+
this._issuer = null;
|
|
291
|
+
this._deviceMaxInterval = 60;
|
|
145
292
|
}
|
|
146
293
|
}
|
|
147
294
|
//# sourceMappingURL=Passport.js.map
|
package/dist/Passport.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Passport.js","sourceRoot":"","sources":["../src/Passport.ts"],"names":[],"mappings":"AAyBA,MAAM,OAAO,QAAQ;IACX,MAAM,CAAC,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC1C,MAAM,CAAC,cAAc,GAAS,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA,CAAG,UAAU;IACnE,MAAM,CAAC,qBAAqB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA,CAAE,UAAU;IACnE,MAAM,CAAC,sBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA,CAAC,YAAY;IACzE,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAA;IAC3B,MAAM,CAAC,WAAW,GAAkB,IAAI,CAAA;IACxC,MAAM,CAAC,UAAU,GAAkB,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"Passport.js","sourceRoot":"","sources":["../src/Passport.ts"],"names":[],"mappings":"AAyBA,MAAM,OAAO,QAAQ;IACX,MAAM,CAAC,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC1C,MAAM,CAAC,cAAc,GAAS,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA,CAAG,UAAU;IACnE,MAAM,CAAC,qBAAqB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA,CAAE,UAAU;IACnE,MAAM,CAAC,sBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA,CAAC,YAAY;IACzE,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAA;IAC3B,MAAM,CAAC,WAAW,GAAkB,IAAI,CAAA;IACxC,MAAM,CAAC,UAAU,GAAkB,IAAI,CAAA;IAC/C;;;;;;;;OAQG;IACK,MAAM,CAAC,kBAAkB,GAAkB,IAAI,CAAA;IAEvD,8FAA8F;IACtF,MAAM,CAAC,YAAY,GAAqC,IAAI,CAAA;IAC5D,MAAM,CAAC,WAAW,GAAsC,IAAI,CAAA;IAC5D,MAAM,CAAC,kBAAkB,GAA+B,IAAI,CAAA;IAC5D,MAAM,CAAC,cAAc,GAAmC,IAAI,CAAA;IAC5D,MAAM,CAAC,gBAAgB,GAAiC,IAAI,CAAA;IAEpE,sBAAsB;IACd,MAAM,CAAC,kBAAkB,GAA+B,IAAI,CAAA;IAEpE,iCAAiC;IACzB,MAAM,CAAC,cAAc,GAAG,KAAK,CAAA;IAErC,yEAAyE;IACzE,0EAA0E;IAC1E,0EAA0E;IAC1E,uEAAuE;IAC/D,MAAM,CAAC,OAAO,GAAkB,IAAI,CAAA;IAE5C;;;;;;;OAOG;IACK,MAAM,CAAC,kBAAkB,GAAG,EAAE,CAAA;IAEtC,2DAA2D;IAE3D,qCAAqC;IACrC,MAAM,CAAC,SAAS,CAAC,MAA8B;QAC7C,KAAK,MAAM,CAAC,EAAE,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACvD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,WAAW,CAAC,CAAA;QACnC,CAAC;IACH,CAAC;IAED,mCAAmC;IACnC,MAAM,CAAC,QAAQ,CAAC,EAAU;QACxB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IAC7B,CAAC;IAED,8BAA8B;IAC9B,MAAM,CAAC,MAAM;QACX,OAAO,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,CAAA;IACtF,CAAC;IAED,+DAA+D;IAC/D,MAAM,CAAC,WAAW,CAAC,SAAmB;QACpC,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAA;IAChE,CAAC;IAED,2DAA2D;IAE3D,MAAM,CAAC,cAAc,CAAC,EAAU,IAAU,IAAI,CAAC,cAAc,GAAG,EAAE,CAAA,CAAC,CAAC;IACpE,MAAM,CAAC,qBAAqB,CAAC,EAAU,IAAU,IAAI,CAAC,qBAAqB,GAAG,EAAE,CAAA,CAAC,CAAC;IAClF,MAAM,CAAC,4BAA4B,CAAC,EAAU,IAAU,IAAI,CAAC,sBAAsB,GAAG,EAAE,CAAA,CAAC,CAAC;IAE1F,MAAM,CAAC,aAAa,KAAa,OAAO,IAAI,CAAC,cAAc,CAAA,CAAC,CAAC;IAC7D,MAAM,CAAC,oBAAoB,KAAa,OAAO,IAAI,CAAC,qBAAqB,CAAA,CAAC,CAAC;IAC3E,MAAM,CAAC,qBAAqB,KAAa,OAAO,IAAI,CAAC,sBAAsB,CAAA,CAAC,CAAC;IAE7E,2DAA2D;IAE3D,mDAAmD;IACnD,MAAM,CAAC,YAAY,CAAC,IAAY,IAAU,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA,CAAC,CAAC;IAEhE,mCAAmC;IACnC,MAAM,CAAC,OAAO,KAAa,OAAO,IAAI,CAAC,QAAQ,CAAA,CAAC,CAAC;IAEjD,sDAAsD;IACtD,MAAM,CAAC,OAAO,CAAC,UAAkB,EAAE,SAAiB;QAClD,IAAI,CAAC,WAAW,GAAG,UAAU,CAAA;QAC7B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;IAC7B,CAAC;IAED;;;;;;;OAOG;IACH,MAAM,CAAC,oBAAoB,CAAC,SAAwB;QAClD,IAAI,CAAC,kBAAkB,GAAG,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA;IAChF,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,CAAC,aAAa;QACxB,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAA;QAEpD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;QACjD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAA;QAE1C,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAA;QAC3E,MAAM,UAAU,GAAI,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAA;QAE1E,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACpC,IAAI,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC;YAC/C,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC;SAC/C,CAAC,CAAA;QACF,OAAO,IAAI,IAAI,GAAG,CAAA;IACpB,CAAC;IAED,sEAAsE;IACtE,MAAM,CAAC,KAAK,CAAC,IAAI;QACf,6CAA6C;QAC7C,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACxC,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,CAAA;QACrE,CAAC;QAED,uBAAuB;QACvB,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;QACrD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAA;QAE1C,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAA;QAC3E,MAAM,UAAU,GAAI,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAA;QAE1E,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAChD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;YAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;SAC7B,CAAC,CAAA;QAEF,IAAI,CAAC,WAAW,GAAG,UAAU,CAAA;QAC7B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAA;QAE3B,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,CAAA;IAClC,CAAC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,CAAC,KAAK,CAAC,gBAAgB;QAC3B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAA;QACvC,MAAM,IAAI,GAAa,CAAC,SAAS,CAAC,CAAA;QAElC,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC5B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;YAClC,OAAO,IAAI,CAAA;QACb,CAAC;QAED,kEAAkE;QAClE,mEAAmE;QACnE,4BAA4B;QAC5B,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;QACrD,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAA;QAC1C,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,2BAA2B,CAAC,CAAA;QACpF,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAA;YACrD,IAAI,CAAC,kBAAkB,GAAG,QAAQ,CAAA;YAClC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACrB,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;YACnE,0DAA0D;QAC5D,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC;IAED,sDAAsD;IACtD,MAAM,CAAC,iBAAiB;QACtB,OAAO,IAAI,CAAC,kBAAkB,CAAA;IAChC,CAAC;IAED,2DAA2D;IAE3D,MAAM,CAAC,cAAc,CAAC,GAAuB,IAAiB,IAAI,CAAC,YAAY,GAAG,GAAG,CAAA,CAAC,CAAC;IACvF,MAAM,CAAC,aAAa,CAAC,GAAuB,IAAkB,IAAI,CAAC,WAAW,GAAG,GAAG,CAAA,CAAC,CAAC;IACtF,MAAM,CAAC,oBAAoB,CAAC,GAAwB,IAAU,IAAI,CAAC,kBAAkB,GAAG,GAAG,CAAA,CAAC,CAAC;IAC7F,MAAM,CAAC,gBAAgB,CAAC,GAAoB,IAAkB,IAAI,CAAC,cAAc,GAAG,GAAG,CAAA,CAAC,CAAC;IACzF,MAAM,CAAC,kBAAkB,CAAC,GAAsB,IAAc,IAAI,CAAC,gBAAgB,GAAG,GAAG,CAAA,CAAC,CAAC;IAE3F,MAAM,CAAC,KAAK,CAAC,WAAW;QACtB,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,CAAC,YAAY,CAAA;QAC/C,OAAO,CAAC,MAAM,MAAM,CAAC,yBAAyB,CAAC,CAAC,CAAC,WAAW,CAAA;IAC9D,CAAC;IACD,MAAM,CAAC,KAAK,CAAC,UAAU;QACrB,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC,WAAW,CAAA;QAC7C,OAAO,CAAC,MAAM,MAAM,CAAC,yBAAyB,CAAC,CAAC,CAAC,WAAW,CAAA;IAC9D,CAAC;IACD,MAAM,CAAC,KAAK,CAAC,iBAAiB;QAC5B,IAAI,IAAI,CAAC,kBAAkB;YAAE,OAAO,IAAI,CAAC,kBAAkB,CAAA;QAC3D,OAAO,CAAC,MAAM,MAAM,CAAC,0BAA0B,CAAC,CAAC,CAAC,YAAY,CAAA;IAChE,CAAC;IACD,MAAM,CAAC,KAAK,CAAC,aAAa;QACxB,IAAI,IAAI,CAAC,cAAc;YAAE,OAAO,IAAI,CAAC,cAAc,CAAA;QACnD,OAAO,CAAC,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC,CAAC,QAAQ,CAAA;IACxD,CAAC;IACD,MAAM,CAAC,KAAK,CAAC,eAAe;QAC1B,IAAI,IAAI,CAAC,gBAAgB;YAAE,OAAO,IAAI,CAAC,gBAAgB,CAAA;QACvD,OAAO,CAAC,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC,CAAC,UAAU,CAAA;IAC5D,CAAC;IAED,2DAA2D;IAE3D;;;;OAIG;IACH,MAAM,CAAC,iBAAiB,CAAC,EAAuB;QAC9C,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAA;IAC9B,CAAC;IAED,MAAM,CAAC,mBAAmB;QACxB,OAAO,IAAI,CAAC,kBAAkB,CAAA;IAChC,CAAC;IAED,2DAA2D;IAE3D;;;OAGG;IACH,MAAM,CAAC,YAAY;QACjB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;IAC5B,CAAC;IAED,MAAM,CAAC,aAAa;QAClB,OAAO,IAAI,CAAC,cAAc,CAAA;IAC5B,CAAC;IAED,2DAA2D;IAE3D;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,SAAS,CAAC,GAAW,IAAU,IAAI,CAAC,OAAO,GAAG,GAAG,IAAI,IAAI,CAAA,CAAC,CAAC;IAClE,MAAM,CAAC,MAAM,KAAoB,OAAO,IAAI,CAAC,OAAO,CAAA,CAAC,CAAC;IAEtD,2DAA2D;IAE3D;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,iBAAiB,CAAC,OAAe;QACtC,gEAAgE;QAChE,wDAAwD;QACxD,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;IAC5D,CAAC;IAED,8DAA8D;IAC9D,MAAM,CAAC,wBAAwB;QAC7B,OAAO,IAAI,CAAC,kBAAkB,CAAA;IAChC,CAAC;IAED,2DAA2D;IAE3D,gBAAgB;IAChB,MAAM,CAAC,KAAK;QACV,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;QACpB,IAAI,CAAC,cAAc,GAAW,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;QACtD,IAAI,CAAC,qBAAqB,GAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;QACtD,IAAI,CAAC,sBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;QAC1D,IAAI,CAAC,QAAQ,GAAM,SAAS,CAAA;QAC5B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAA;QACvB,IAAI,CAAC,UAAU,GAAI,IAAI,CAAA;QACvB,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAA;QAC9B,IAAI,CAAC,YAAY,GAAS,IAAI,CAAA;QAC9B,IAAI,CAAC,WAAW,GAAU,IAAI,CAAA;QAC9B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAA;QAC9B,IAAI,CAAC,cAAc,GAAO,IAAI,CAAA;QAC9B,IAAI,CAAC,gBAAgB,GAAK,IAAI,CAAA;QAC9B,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAA;QAC9B,IAAI,CAAC,cAAc,GAAO,KAAK,CAAA;QAC/B,IAAI,CAAC,OAAO,GAAc,IAAI,CAAA;QAC9B,IAAI,CAAC,kBAAkB,GAAG,EAAE,CAAA;IAC9B,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hash a plain-text client secret for storage. Returns a peppered HMAC if
|
|
3
|
+
* `APP_KEY` is set, otherwise a plain SHA-256 hex digest.
|
|
4
|
+
*/
|
|
5
|
+
export declare function hashClientSecret(plainSecret: string): Promise<string>;
|
|
6
|
+
/**
|
|
7
|
+
* Verify a plain-text client secret against a stored hash. Constant-time;
|
|
8
|
+
* format is auto-detected from the stored value's prefix so legacy plain
|
|
9
|
+
* SHA-256 rows continue to verify after `APP_KEY` is configured.
|
|
10
|
+
*/
|
|
11
|
+
export declare function verifyClientSecret(plainSecret: string, stored: string | null | undefined): Promise<boolean>;
|
|
12
|
+
//# sourceMappingURL=client-secret.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-secret.d.ts","sourceRoot":"","sources":["../src/client-secret.ts"],"names":[],"mappings":"AAkCA;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ3E;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAAC,OAAO,CAAC,CAclB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { safeCompare } from './grants/safe-compare.js';
|
|
2
|
+
/**
|
|
3
|
+
* Hashing + verification of OAuth client secrets at rest.
|
|
4
|
+
*
|
|
5
|
+
* Two storage formats are supported:
|
|
6
|
+
*
|
|
7
|
+
* - **Peppered** — `peppered:<HMAC-SHA256(secret, APP_KEY)>` (hex). Used
|
|
8
|
+
* when `APP_KEY` is set at the time the client is created. A leaked DB
|
|
9
|
+
* dump alone cannot be used to verify candidate secrets without also
|
|
10
|
+
* knowing `APP_KEY`.
|
|
11
|
+
* - **Plain SHA-256** — bare 64-char hex digest. Used when `APP_KEY` is
|
|
12
|
+
* unset. This matches the historical (pre-2026-05) format and is what
|
|
13
|
+
* Laravel Passport stores; client secrets are 256-bit CSPRNG so the
|
|
14
|
+
* hash adds little cryptographic protection beyond defense-in-depth.
|
|
15
|
+
*
|
|
16
|
+
* The format is self-describing via the `peppered:` prefix, so existing
|
|
17
|
+
* rows minted before this change keep verifying against their plain
|
|
18
|
+
* SHA-256 hashes — there is no migration step. New rows use whichever
|
|
19
|
+
* format is available at creation time.
|
|
20
|
+
*
|
|
21
|
+
* Rotating `APP_KEY` invalidates every peppered client secret, the same
|
|
22
|
+
* way rotating the RSA keypair invalidates every live access token. The
|
|
23
|
+
* fallback path is to re-issue secrets via `passport:client` after the
|
|
24
|
+
* rotation; legacy plain-SHA-256 rows are unaffected.
|
|
25
|
+
*/
|
|
26
|
+
const PEPPERED_PREFIX = 'peppered:';
|
|
27
|
+
function appKey() {
|
|
28
|
+
const key = process.env['APP_KEY'];
|
|
29
|
+
return key && key.length > 0 ? key : null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Hash a plain-text client secret for storage. Returns a peppered HMAC if
|
|
33
|
+
* `APP_KEY` is set, otherwise a plain SHA-256 hex digest.
|
|
34
|
+
*/
|
|
35
|
+
export async function hashClientSecret(plainSecret) {
|
|
36
|
+
const { createHash, createHmac } = await import('node:crypto');
|
|
37
|
+
const pepper = appKey();
|
|
38
|
+
if (pepper) {
|
|
39
|
+
const mac = createHmac('sha256', pepper).update(plainSecret).digest('hex');
|
|
40
|
+
return `${PEPPERED_PREFIX}${mac}`;
|
|
41
|
+
}
|
|
42
|
+
return createHash('sha256').update(plainSecret).digest('hex');
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Verify a plain-text client secret against a stored hash. Constant-time;
|
|
46
|
+
* format is auto-detected from the stored value's prefix so legacy plain
|
|
47
|
+
* SHA-256 rows continue to verify after `APP_KEY` is configured.
|
|
48
|
+
*/
|
|
49
|
+
export async function verifyClientSecret(plainSecret, stored) {
|
|
50
|
+
if (!stored)
|
|
51
|
+
return false;
|
|
52
|
+
const { createHash, createHmac } = await import('node:crypto');
|
|
53
|
+
if (stored.startsWith(PEPPERED_PREFIX)) {
|
|
54
|
+
const pepper = appKey();
|
|
55
|
+
if (!pepper)
|
|
56
|
+
return false;
|
|
57
|
+
const mac = createHmac('sha256', pepper).update(plainSecret).digest('hex');
|
|
58
|
+
return safeCompare(mac, stored.slice(PEPPERED_PREFIX.length));
|
|
59
|
+
}
|
|
60
|
+
const hashed = createHash('sha256').update(plainSecret).digest('hex');
|
|
61
|
+
return safeCompare(hashed, stored);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=client-secret.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-secret.js","sourceRoot":"","sources":["../src/client-secret.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAEtD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,eAAe,GAAG,WAAW,CAAA;AAEnC,SAAS,MAAM;IACb,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IAClC,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;AAC3C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,WAAmB;IACxD,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAA;IAC9D,MAAM,MAAM,GAAG,MAAM,EAAE,CAAA;IACvB,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC1E,OAAO,GAAG,eAAe,GAAG,GAAG,EAAE,CAAA;IACnC,CAAC;IACD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;AAC/D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,WAAmB,EACnB,MAAiC;IAEjC,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAEzB,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAA;IAE9D,IAAI,MAAM,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,MAAM,EAAE,CAAA;QACvB,IAAI,CAAC,MAAM;YAAE,OAAO,KAAK,CAAA;QACzB,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC1E,OAAO,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAA;IAC/D,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IACrE,OAAO,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AACpC,CAAC"}
|