@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
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { report } from '@rudderjs/core';
|
|
2
|
+
import { exchangeAuthCode, clientCredentialsGrant, refreshTokenGrant, pollDeviceCode, OAuthError, } from '../grants/index.js';
|
|
3
|
+
import { resolveClientCredentials } from './helpers.js';
|
|
4
|
+
/**
|
|
5
|
+
* Register `POST /oauth/token` — the OAuth 2 token endpoint.
|
|
6
|
+
*
|
|
7
|
+
* Dispatches to one of the four supported grants:
|
|
8
|
+
* - `authorization_code` — exchanges an auth code for tokens
|
|
9
|
+
* - `client_credentials` — machine-to-machine, confidential clients only
|
|
10
|
+
* - `refresh_token` — rotates an access+refresh pair
|
|
11
|
+
* - `urn:ietf:params:oauth:grant-type:device_code` — polls device flow
|
|
12
|
+
*
|
|
13
|
+
* `mw` runs ahead of the handler. The token endpoint is the canonical
|
|
14
|
+
* brute-force target for client_secret guessing — every production app
|
|
15
|
+
* SHOULD pass a per-route rate limiter here. See
|
|
16
|
+
* `PassportRouteOptions.tokenMiddleware` jsdoc for the recommended config.
|
|
17
|
+
*
|
|
18
|
+
* RFC 6749 §5.2 — client-auth failures (HTTP 401) are signalled with a
|
|
19
|
+
* `WWW-Authenticate: Basic` header alongside the body. RFC 8628 §3.5 —
|
|
20
|
+
* device-flow polling errors (`authorization_pending`, `slow_down`,
|
|
21
|
+
* `expired_token`, `access_denied`) return HTTP 400; 429 is for transport-
|
|
22
|
+
* level rate-limiting, not the OAuth `slow_down` signal.
|
|
23
|
+
*/
|
|
24
|
+
export function registerTokenRoute(router, prefix, mw) {
|
|
25
|
+
router.post(`${prefix}/token`, async (req, res) => {
|
|
26
|
+
try {
|
|
27
|
+
const body = req.body ?? {};
|
|
28
|
+
const grantType = body['grant_type'];
|
|
29
|
+
// RFC 6749 §2.3.1 — confidential clients MUST be able to
|
|
30
|
+
// authenticate via HTTP Basic; body params are an alternative.
|
|
31
|
+
// §2.3 forbids using both at once. Resolve credentials once for
|
|
32
|
+
// all grants instead of repeating the parsing in each branch.
|
|
33
|
+
const credentials = resolveClientCredentials(req, body);
|
|
34
|
+
let result;
|
|
35
|
+
switch (grantType) {
|
|
36
|
+
case 'authorization_code':
|
|
37
|
+
result = await exchangeAuthCode({
|
|
38
|
+
grantType,
|
|
39
|
+
code: body['code'],
|
|
40
|
+
...credentials,
|
|
41
|
+
redirectUri: body['redirect_uri'],
|
|
42
|
+
codeVerifier: body['code_verifier'],
|
|
43
|
+
});
|
|
44
|
+
break;
|
|
45
|
+
case 'client_credentials':
|
|
46
|
+
// ClientCredentialsRequest requires clientSecret (the grant
|
|
47
|
+
// is confidential-only by spec). Surface the missing-secret
|
|
48
|
+
// case as invalid_request rather than letting it surface
|
|
49
|
+
// downstream as "Invalid client secret."
|
|
50
|
+
if (credentials.clientSecret === undefined) {
|
|
51
|
+
throw new OAuthError('invalid_request', 'client_secret is required for the client_credentials grant.', 401);
|
|
52
|
+
}
|
|
53
|
+
result = await clientCredentialsGrant({
|
|
54
|
+
grantType,
|
|
55
|
+
clientId: credentials.clientId,
|
|
56
|
+
clientSecret: credentials.clientSecret,
|
|
57
|
+
scope: body['scope'],
|
|
58
|
+
});
|
|
59
|
+
break;
|
|
60
|
+
case 'refresh_token':
|
|
61
|
+
result = await refreshTokenGrant({
|
|
62
|
+
grantType,
|
|
63
|
+
refreshToken: body['refresh_token'],
|
|
64
|
+
...credentials,
|
|
65
|
+
scope: body['scope'],
|
|
66
|
+
});
|
|
67
|
+
break;
|
|
68
|
+
case 'urn:ietf:params:oauth:grant-type:device_code': {
|
|
69
|
+
const pollResult = await pollDeviceCode({
|
|
70
|
+
grantType,
|
|
71
|
+
deviceCode: body['device_code'],
|
|
72
|
+
clientId: credentials.clientId,
|
|
73
|
+
});
|
|
74
|
+
if (pollResult.status === 'authorized') {
|
|
75
|
+
result = pollResult.tokens;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// RFC 8628 §3.5 — device-flow polling errors (including
|
|
79
|
+
// slow_down) are §5.2-shaped errors and MUST return HTTP
|
|
80
|
+
// 400. 429 is for transport-level rate-limiting, not the
|
|
81
|
+
// OAuth `slow_down` signal.
|
|
82
|
+
//
|
|
83
|
+
// On slow_down, forward the escalated `interval` so a
|
|
84
|
+
// well-behaved client uses the new value instead of having
|
|
85
|
+
// to add 5 itself. Other variants don't need it.
|
|
86
|
+
if (pollResult.status === 'slow_down') {
|
|
87
|
+
res.status(400).json({ error: 'slow_down', interval: pollResult.interval });
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
res.status(400).json({ error: pollResult.status });
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
default:
|
|
97
|
+
res.status(400).json({
|
|
98
|
+
error: 'unsupported_grant_type',
|
|
99
|
+
error_description: `Grant type "${grantType}" is not supported.`,
|
|
100
|
+
});
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
res.json(result);
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
if (e instanceof OAuthError) {
|
|
107
|
+
// RFC 6749 §5.2 — client-auth failures at the token endpoint
|
|
108
|
+
// are signalled with WWW-Authenticate alongside the 401 status.
|
|
109
|
+
if (e.statusCode === 401 && typeof res.header === 'function') {
|
|
110
|
+
res.header('WWW-Authenticate', 'Basic realm="oauth"');
|
|
111
|
+
}
|
|
112
|
+
res.status(e.statusCode).json(e.toJSON());
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
report(e);
|
|
116
|
+
res.status(500).json({ error: 'server_error', error_description: 'Internal server error.' });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}, mw);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/routes/token.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from '@rudderjs/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Minimal handler signature. Kept `any`-typed because passport routes are
|
|
4
|
+
* mounted on arbitrary Router implementations (Hono adapter, express-style,
|
|
5
|
+
* test fakes) — pinning a concrete request/response type here would force
|
|
6
|
+
* downstream casts everywhere a handler is implemented.
|
|
7
|
+
*/
|
|
8
|
+
export type RouteHandler = (req: any, res: any) => Promise<any> | any;
|
|
9
|
+
/**
|
|
10
|
+
* Minimal Router contract passport's `register*Routes()` functions accept.
|
|
11
|
+
* The `@rudderjs/router` instance satisfies this, as do simple test fakes —
|
|
12
|
+
* we don't import the concrete class here to keep this package usable from
|
|
13
|
+
* apps that bring their own router.
|
|
14
|
+
*/
|
|
15
|
+
export interface Router {
|
|
16
|
+
get(path: string, handler: RouteHandler, ...middleware: any[]): void;
|
|
17
|
+
post(path: string, handler: RouteHandler, ...middleware: any[]): void;
|
|
18
|
+
delete(path: string, handler: RouteHandler, ...middleware: any[]): void;
|
|
19
|
+
}
|
|
20
|
+
/** Groups of routes that can be selectively excluded. */
|
|
21
|
+
export type PassportRouteGroup = 'authorize' | 'token' | 'revoke' | 'scopes' | 'device';
|
|
22
|
+
export interface PassportRouteOptions {
|
|
23
|
+
/** Base path for OAuth routes (default: '/oauth') */
|
|
24
|
+
prefix?: string;
|
|
25
|
+
/** Verification URI for device auth (default: '{origin}/oauth/device') */
|
|
26
|
+
verificationUri?: string;
|
|
27
|
+
/** Route groups to skip when registering. */
|
|
28
|
+
except?: PassportRouteGroup[];
|
|
29
|
+
/**
|
|
30
|
+
* Middleware applied to `POST /oauth/token`. The token endpoint is the
|
|
31
|
+
* canonical brute-force target for client_secret guessing — every
|
|
32
|
+
* production app SHOULD mount a per-route rate limiter here.
|
|
33
|
+
*
|
|
34
|
+
* Recommended setup:
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { RateLimit } from '@rudderjs/middleware'
|
|
38
|
+
* import { registerPassportRoutes } from '@rudderjs/passport'
|
|
39
|
+
*
|
|
40
|
+
* registerPassportRoutes(router, {
|
|
41
|
+
* tokenMiddleware: [
|
|
42
|
+
* RateLimit.perMinute(10).by((req) => `${req.ip}:${req.body?.client_id}`),
|
|
43
|
+
* ],
|
|
44
|
+
* })
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* The composite key (`ip + client_id`) prevents one noisy client from
|
|
48
|
+
* exhausting the budget for legitimate co-tenants behind a shared NAT,
|
|
49
|
+
* and prevents a single IP from churning through every client_id in the
|
|
50
|
+
* registry. RateLimit also requires a cache provider to be registered —
|
|
51
|
+
* see `@rudderjs/cache`. Without one the middleware silently passes
|
|
52
|
+
* through.
|
|
53
|
+
*
|
|
54
|
+
* Accepts a single handler or an array. Empty / omitted means no
|
|
55
|
+
* additional middleware is applied (the same as before this option
|
|
56
|
+
* existed).
|
|
57
|
+
*/
|
|
58
|
+
tokenMiddleware?: MiddlewareHandler | MiddlewareHandler[];
|
|
59
|
+
/**
|
|
60
|
+
* Middleware applied to the consent endpoints — `GET/POST/DELETE
|
|
61
|
+
* /oauth/authorize` and `DELETE /oauth/tokens/:id`. POST /oauth/authorize
|
|
62
|
+
* is the canonical CSRF target (an attacker page that auto-submits a
|
|
63
|
+
* hidden form would mint authorization codes for the victim's logged-in
|
|
64
|
+
* session).
|
|
65
|
+
*
|
|
66
|
+
* Most apps should NOT use this option. The recommended pattern is to
|
|
67
|
+
* mount CSRF on the entire web group from `bootstrap/app.ts`:
|
|
68
|
+
*
|
|
69
|
+
* ```ts
|
|
70
|
+
* .withMiddleware((m) => m.web(CsrfMiddleware()))
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* which automatically covers `/oauth/authorize` along with every other
|
|
74
|
+
* state-changing web route. `authorizeMiddleware` is the per-route
|
|
75
|
+
* fallback for apps that do NOT mount CSRF at the group level:
|
|
76
|
+
*
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { CsrfMiddleware } from '@rudderjs/middleware'
|
|
79
|
+
* import { registerPassportWebRoutes } from '@rudderjs/passport'
|
|
80
|
+
*
|
|
81
|
+
* registerPassportWebRoutes(router, {
|
|
82
|
+
* authorizeMiddleware: [CsrfMiddleware()],
|
|
83
|
+
* })
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* Don't do both — CsrfMiddleware running twice on the same request
|
|
87
|
+
* emits duplicate `Set-Cookie`s on GETs and runs validation twice on
|
|
88
|
+
* POSTs.
|
|
89
|
+
*
|
|
90
|
+
* Accepts a single handler or an array. Empty / omitted means no
|
|
91
|
+
* additional middleware is applied — the typical case for apps that
|
|
92
|
+
* already CSRF-guard at the group level.
|
|
93
|
+
*/
|
|
94
|
+
authorizeMiddleware?: MiddlewareHandler | MiddlewareHandler[];
|
|
95
|
+
/**
|
|
96
|
+
* Middleware applied to the device-flow endpoints — `POST /oauth/device/code`
|
|
97
|
+
* and `POST /oauth/device/approve`. RFC 8628 §5.2 calls for brute-force
|
|
98
|
+
* protection on the user_code surface (8-char alphabet → 32^8 ≈ 1.1×10^12
|
|
99
|
+
* keyspace; per-IP throttling makes exhaustion infeasible).
|
|
100
|
+
*
|
|
101
|
+
* Most apps should NOT need this option. The recommended pattern is to
|
|
102
|
+
* mount a rate limiter on the entire api group from `bootstrap/app.ts`
|
|
103
|
+
* (`withMiddleware((m) => m.api(RateLimit.perMinute(60)))`) — that single
|
|
104
|
+
* hook covers the device endpoints alongside every other api route, and
|
|
105
|
+
* 60/min per-IP is already enough that exhausting the user_code keyspace
|
|
106
|
+
* would take tens of thousands of years.
|
|
107
|
+
*
|
|
108
|
+
* `deviceMiddleware` is the per-route fallback for apps that want a
|
|
109
|
+
* tighter device-specific limit (e.g. `RateLimit.perMinute(5)`) on top of
|
|
110
|
+
* — or in place of — the group default:
|
|
111
|
+
*
|
|
112
|
+
* ```ts
|
|
113
|
+
* import { RateLimit } from '@rudderjs/middleware'
|
|
114
|
+
* import { registerPassportApiRoutes } from '@rudderjs/passport'
|
|
115
|
+
*
|
|
116
|
+
* registerPassportApiRoutes(router, {
|
|
117
|
+
* deviceMiddleware: [RateLimit.perMinute(5).by((req) => req.ip)],
|
|
118
|
+
* })
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* Layered limits compose in sequence — group + per-route both run, with
|
|
122
|
+
* the tightest budget winning. Locking individual user_codes after N
|
|
123
|
+
* misses (the stateful half of the original RFC 8628 §5.2 guidance)
|
|
124
|
+
* isn't covered by RateLimit; if you need it, wrap your own middleware.
|
|
125
|
+
*
|
|
126
|
+
* Accepts a single handler or an array. Empty / omitted means no
|
|
127
|
+
* additional middleware is applied — the typical case for apps that
|
|
128
|
+
* already throttle the api group.
|
|
129
|
+
*/
|
|
130
|
+
deviceMiddleware?: MiddlewareHandler | MiddlewareHandler[];
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/routes/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAE5D;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;AAErE;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,UAAU,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;IACpE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,UAAU,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;IACrE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,UAAU,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;CACxE;AAED,yDAAyD;AACzD,MAAM,MAAM,kBAAkB,GAC1B,WAAW,GACX,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,QAAQ,CAAA;AAEZ,MAAM,WAAW,oBAAoB;IACnC,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0EAA0E;IAC1E,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,kBAAkB,EAAE,CAAA;IAC7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,eAAe,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,EAAE,CAAA;IACzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACH,mBAAmB,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,EAAE,CAAA;IAC7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACH,gBAAgB,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,EAAE,CAAA;CAC3D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/routes/types.ts"],"names":[],"mappings":""}
|
package/dist/routes.d.ts
CHANGED
|
@@ -1,122 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
type
|
|
3
|
-
interface Router {
|
|
4
|
-
get(path: string, handler: RouteHandler, ...middleware: any[]): void;
|
|
5
|
-
post(path: string, handler: RouteHandler, ...middleware: any[]): void;
|
|
6
|
-
delete(path: string, handler: RouteHandler, ...middleware: any[]): void;
|
|
7
|
-
}
|
|
8
|
-
/** Groups of routes that can be selectively excluded. */
|
|
9
|
-
export type PassportRouteGroup = 'authorize' | 'token' | 'revoke' | 'scopes' | 'device';
|
|
10
|
-
export interface PassportRouteOptions {
|
|
11
|
-
/** Base path for OAuth routes (default: '/oauth') */
|
|
12
|
-
prefix?: string;
|
|
13
|
-
/** Verification URI for device auth (default: '{origin}/oauth/device') */
|
|
14
|
-
verificationUri?: string;
|
|
15
|
-
/** Route groups to skip when registering. */
|
|
16
|
-
except?: PassportRouteGroup[];
|
|
17
|
-
/**
|
|
18
|
-
* Middleware applied to `POST /oauth/token`. The token endpoint is the
|
|
19
|
-
* canonical brute-force target for client_secret guessing — every
|
|
20
|
-
* production app SHOULD mount a per-route rate limiter here.
|
|
21
|
-
*
|
|
22
|
-
* Recommended setup:
|
|
23
|
-
*
|
|
24
|
-
* ```ts
|
|
25
|
-
* import { RateLimit } from '@rudderjs/middleware'
|
|
26
|
-
* import { registerPassportRoutes } from '@rudderjs/passport'
|
|
27
|
-
*
|
|
28
|
-
* registerPassportRoutes(router, {
|
|
29
|
-
* tokenMiddleware: [
|
|
30
|
-
* RateLimit.perMinute(10).by((req) => `${req.ip}:${req.body?.client_id}`),
|
|
31
|
-
* ],
|
|
32
|
-
* })
|
|
33
|
-
* ```
|
|
34
|
-
*
|
|
35
|
-
* The composite key (`ip + client_id`) prevents one noisy client from
|
|
36
|
-
* exhausting the budget for legitimate co-tenants behind a shared NAT,
|
|
37
|
-
* and prevents a single IP from churning through every client_id in the
|
|
38
|
-
* registry. RateLimit also requires a cache provider to be registered —
|
|
39
|
-
* see `@rudderjs/cache`. Without one the middleware silently passes
|
|
40
|
-
* through.
|
|
41
|
-
*
|
|
42
|
-
* Accepts a single handler or an array. Empty / omitted means no
|
|
43
|
-
* additional middleware is applied (the same as before this option
|
|
44
|
-
* existed).
|
|
45
|
-
*/
|
|
46
|
-
tokenMiddleware?: MiddlewareHandler | MiddlewareHandler[];
|
|
47
|
-
/**
|
|
48
|
-
* Middleware applied to the consent endpoints — `GET/POST/DELETE
|
|
49
|
-
* /oauth/authorize` and `DELETE /oauth/tokens/:id`. POST /oauth/authorize
|
|
50
|
-
* is the canonical CSRF target (an attacker page that auto-submits a
|
|
51
|
-
* hidden form would mint authorization codes for the victim's logged-in
|
|
52
|
-
* session).
|
|
53
|
-
*
|
|
54
|
-
* Most apps should NOT use this option. The recommended pattern is to
|
|
55
|
-
* mount CSRF on the entire web group from `bootstrap/app.ts`:
|
|
56
|
-
*
|
|
57
|
-
* ```ts
|
|
58
|
-
* .withMiddleware((m) => m.web(CsrfMiddleware()))
|
|
59
|
-
* ```
|
|
60
|
-
*
|
|
61
|
-
* which automatically covers `/oauth/authorize` along with every other
|
|
62
|
-
* state-changing web route. `authorizeMiddleware` is the per-route
|
|
63
|
-
* fallback for apps that do NOT mount CSRF at the group level:
|
|
64
|
-
*
|
|
65
|
-
* ```ts
|
|
66
|
-
* import { CsrfMiddleware } from '@rudderjs/middleware'
|
|
67
|
-
* import { registerPassportWebRoutes } from '@rudderjs/passport'
|
|
68
|
-
*
|
|
69
|
-
* registerPassportWebRoutes(router, {
|
|
70
|
-
* authorizeMiddleware: [CsrfMiddleware()],
|
|
71
|
-
* })
|
|
72
|
-
* ```
|
|
73
|
-
*
|
|
74
|
-
* Don't do both — CsrfMiddleware running twice on the same request
|
|
75
|
-
* emits duplicate `Set-Cookie`s on GETs and runs validation twice on
|
|
76
|
-
* POSTs.
|
|
77
|
-
*
|
|
78
|
-
* Accepts a single handler or an array. Empty / omitted means no
|
|
79
|
-
* additional middleware is applied — the typical case for apps that
|
|
80
|
-
* already CSRF-guard at the group level.
|
|
81
|
-
*/
|
|
82
|
-
authorizeMiddleware?: MiddlewareHandler | MiddlewareHandler[];
|
|
83
|
-
/**
|
|
84
|
-
* Middleware applied to the device-flow endpoints — `POST /oauth/device/code`
|
|
85
|
-
* and `POST /oauth/device/approve`. RFC 8628 §5.2 calls for brute-force
|
|
86
|
-
* protection on the user_code surface (8-char alphabet → 32^8 ≈ 1.1×10^12
|
|
87
|
-
* keyspace; per-IP throttling makes exhaustion infeasible).
|
|
88
|
-
*
|
|
89
|
-
* Most apps should NOT need this option. The recommended pattern is to
|
|
90
|
-
* mount a rate limiter on the entire api group from `bootstrap/app.ts`
|
|
91
|
-
* (`withMiddleware((m) => m.api(RateLimit.perMinute(60)))`) — that single
|
|
92
|
-
* hook covers the device endpoints alongside every other api route, and
|
|
93
|
-
* 60/min per-IP is already enough that exhausting the user_code keyspace
|
|
94
|
-
* would take tens of thousands of years.
|
|
95
|
-
*
|
|
96
|
-
* `deviceMiddleware` is the per-route fallback for apps that want a
|
|
97
|
-
* tighter device-specific limit (e.g. `RateLimit.perMinute(5)`) on top of
|
|
98
|
-
* — or in place of — the group default:
|
|
99
|
-
*
|
|
100
|
-
* ```ts
|
|
101
|
-
* import { RateLimit } from '@rudderjs/middleware'
|
|
102
|
-
* import { registerPassportApiRoutes } from '@rudderjs/passport'
|
|
103
|
-
*
|
|
104
|
-
* registerPassportApiRoutes(router, {
|
|
105
|
-
* deviceMiddleware: [RateLimit.perMinute(5).by((req) => req.ip)],
|
|
106
|
-
* })
|
|
107
|
-
* ```
|
|
108
|
-
*
|
|
109
|
-
* Layered limits compose in sequence — group + per-route both run, with
|
|
110
|
-
* the tightest budget winning. Locking individual user_codes after N
|
|
111
|
-
* misses (the stateful half of the original RFC 8628 §5.2 guidance)
|
|
112
|
-
* isn't covered by RateLimit; if you need it, wrap your own middleware.
|
|
113
|
-
*
|
|
114
|
-
* Accepts a single handler or an array. Empty / omitted means no
|
|
115
|
-
* additional middleware is applied — the typical case for apps that
|
|
116
|
-
* already throttle the api group.
|
|
117
|
-
*/
|
|
118
|
-
deviceMiddleware?: MiddlewareHandler | MiddlewareHandler[];
|
|
119
|
-
}
|
|
1
|
+
import type { PassportRouteOptions, Router } from './routes/types.js';
|
|
2
|
+
export type { PassportRouteGroup, PassportRouteOptions } from './routes/types.js';
|
|
120
3
|
/**
|
|
121
4
|
* Register all Passport OAuth routes on the given router.
|
|
122
5
|
*
|
|
@@ -178,5 +61,4 @@ export declare function registerPassportWebRoutes(router: Router, opts?: Passpor
|
|
|
178
61
|
* stateful half on the web group.
|
|
179
62
|
*/
|
|
180
63
|
export declare function registerPassportApiRoutes(router: Router, opts?: PassportRouteOptions): void;
|
|
181
|
-
export {};
|
|
182
64
|
//# sourceMappingURL=routes.d.ts.map
|
package/dist/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAsB,oBAAoB,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAQzF,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAEjF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,oBAAyB,GAAG,IAAI,CAc5F;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,oBAAyB,GAAG,IAAI,CAG/F;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,oBAAyB,GAAG,IAAI,CAG/F"}
|