@rudderjs/router 1.2.0 → 1.2.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/README.md +18 -0
- package/dist/binding-middleware.d.ts +54 -0
- package/dist/binding-middleware.d.ts.map +1 -0
- package/dist/binding-middleware.js +101 -0
- package/dist/binding-middleware.js.map +1 -0
- package/dist/index.d.ts +37 -139
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -313
- package/dist/index.js.map +1 -1
- package/dist/resource.d.ts +79 -0
- package/dist/resource.d.ts.map +1 -0
- package/dist/resource.js +104 -0
- package/dist/resource.js.map +1 -0
- package/dist/url-signing.d.ts +46 -0
- package/dist/url-signing.d.ts.map +1 -0
- package/dist/url-signing.js +133 -0
- package/dist/url-signing.js.map +1 -0
- package/package.json +5 -2
package/dist/resource.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** @internal — verb table for `resource()` / `apiResource()`. */
|
|
2
|
+
export const RESOURCE_VERBS = [
|
|
3
|
+
{ verb: 'index', method: 'GET', path: (n) => `/${n}`, nameSuffix: 'index' },
|
|
4
|
+
{ verb: 'create', method: 'GET', path: (n) => `/${n}/create`, nameSuffix: 'create' },
|
|
5
|
+
{ verb: 'store', method: 'POST', path: (n) => `/${n}`, nameSuffix: 'store' },
|
|
6
|
+
{ verb: 'show', method: 'GET', path: (n, p) => `/${n}/:${p}`, nameSuffix: 'show' },
|
|
7
|
+
{ verb: 'edit', method: 'GET', path: (n, p) => `/${n}/:${p}/edit`, nameSuffix: 'edit' },
|
|
8
|
+
{ verb: 'update', method: 'PUT', path: (n, p) => `/${n}/:${p}`, nameSuffix: 'update' },
|
|
9
|
+
{ verb: 'destroy', method: 'DELETE', path: (n, p) => `/${n}/:${p}`, nameSuffix: 'destroy' },
|
|
10
|
+
];
|
|
11
|
+
/** @internal — verb table for `singleton()`. */
|
|
12
|
+
export const SINGLETON_VERBS = [
|
|
13
|
+
{ verb: 'show', method: 'GET', path: (n) => `/${n}`, nameSuffix: 'show' },
|
|
14
|
+
{ verb: 'edit', method: 'GET', path: (n) => `/${n}/edit`, nameSuffix: 'edit' },
|
|
15
|
+
{ verb: 'update', method: 'PUT', path: (n) => `/${n}`, nameSuffix: 'update' },
|
|
16
|
+
];
|
|
17
|
+
/** @internal — `.creatable()` opt-in for singletons. */
|
|
18
|
+
export const SINGLETON_CREATE_VERBS = [
|
|
19
|
+
{ verb: 'create', method: 'GET', path: (n) => `/${n}/create`, nameSuffix: 'create' },
|
|
20
|
+
{ verb: 'store', method: 'POST', path: (n) => `/${n}`, nameSuffix: 'store' },
|
|
21
|
+
];
|
|
22
|
+
/** @internal — `.destroyable()` opt-in for singletons. */
|
|
23
|
+
export const SINGLETON_DESTROY_VERBS = [
|
|
24
|
+
{ verb: 'destroy', method: 'DELETE', path: (n) => `/${n}`, nameSuffix: 'destroy' },
|
|
25
|
+
];
|
|
26
|
+
/** @internal — apply `only` / `except` to a verb table. */
|
|
27
|
+
export function filterVerbs(table, opts) {
|
|
28
|
+
let verbs = table;
|
|
29
|
+
if (opts.only) {
|
|
30
|
+
const allow = new Set(opts.only);
|
|
31
|
+
verbs = verbs.filter(v => allow.has(v.verb));
|
|
32
|
+
}
|
|
33
|
+
if (opts.except) {
|
|
34
|
+
const deny = new Set(opts.except);
|
|
35
|
+
verbs = verbs.filter(v => !deny.has(v.verb));
|
|
36
|
+
}
|
|
37
|
+
return verbs;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* @internal — naive English singularizer for the default resource param name.
|
|
41
|
+
* Handles the three patterns Laravel users hit constantly (`posts → post`,
|
|
42
|
+
* `categories → category`, `boxes → box`). Anything irregular — `people`,
|
|
43
|
+
* `data`, etc. — should be overridden via the `parameters` option, exactly
|
|
44
|
+
* as in Laravel.
|
|
45
|
+
*/
|
|
46
|
+
export function singularize(name) {
|
|
47
|
+
if (/[^aeiou]ies$/i.test(name))
|
|
48
|
+
return name.slice(0, -3) + 'y'; // categories → category
|
|
49
|
+
if (/(s|x|z|ch|sh)es$/i.test(name))
|
|
50
|
+
return name.slice(0, -2); // boxes → box
|
|
51
|
+
if (/s$/i.test(name) && !/ss$/i.test(name))
|
|
52
|
+
return name.slice(0, -1); // posts → post
|
|
53
|
+
return name;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Returned by `router.resource()`/`apiResource()`. The `builders` array holds
|
|
57
|
+
* one `RouteBuilder` per registered route in declaration order — apply
|
|
58
|
+
* `where*()`, additional middleware, or rename individual routes by indexing
|
|
59
|
+
* directly. The `update` PATCH alias is included as a separate builder
|
|
60
|
+
* immediately after its PUT counterpart.
|
|
61
|
+
*/
|
|
62
|
+
export class ResourceRegistration {
|
|
63
|
+
builders;
|
|
64
|
+
constructor(builders) {
|
|
65
|
+
this.builders = builders;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Returned by `router.singleton()`. Adds two opt-in helpers on top of
|
|
70
|
+
* `ResourceRegistration` for resources that also expose a creation flow
|
|
71
|
+
* (`.creatable()`) or deletion flow (`.destroyable()`).
|
|
72
|
+
*/
|
|
73
|
+
export class SingletonRegistration extends ResourceRegistration {
|
|
74
|
+
_router;
|
|
75
|
+
_name;
|
|
76
|
+
_Ctrl;
|
|
77
|
+
_opts;
|
|
78
|
+
constructor(builders, _router, _name, _Ctrl, _opts) {
|
|
79
|
+
super(builders);
|
|
80
|
+
this._router = _router;
|
|
81
|
+
this._name = _name;
|
|
82
|
+
this._Ctrl = _Ctrl;
|
|
83
|
+
this._opts = _opts;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Add `GET /<name>/create` and `POST /<name>` — the create/store half of a
|
|
87
|
+
* full resource. Skipped for any verb the controller doesn't implement.
|
|
88
|
+
*/
|
|
89
|
+
creatable() {
|
|
90
|
+
const reg = this._router._registerResource(this._name, this._Ctrl, SINGLETON_CREATE_VERBS, this._opts);
|
|
91
|
+
this.builders.push(...reg.builders);
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Add `DELETE /<name>` — the destroy half of a full resource. Skipped if
|
|
96
|
+
* the controller doesn't implement `destroy()`.
|
|
97
|
+
*/
|
|
98
|
+
destroyable() {
|
|
99
|
+
const reg = this._router._registerResource(this._name, this._Ctrl, SINGLETON_DESTROY_VERBS, this._opts);
|
|
100
|
+
this.builders.push(...reg.builders);
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { AppRequest, MiddlewareHandler } from '@rudderjs/contracts';
|
|
2
|
+
export declare class Url {
|
|
3
|
+
/**
|
|
4
|
+
* Override the HMAC signing key used for signed URLs.
|
|
5
|
+
* Falls back to `process.env.APP_KEY`.
|
|
6
|
+
*/
|
|
7
|
+
static setKey(key: string): void;
|
|
8
|
+
/** The full URL of the current request. */
|
|
9
|
+
static current(req: AppRequest): string;
|
|
10
|
+
/** The previous URL from the `Referer` header, or `fallback`. */
|
|
11
|
+
static previous(req: AppRequest, fallback?: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Generate a signed URL for a named route.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* Url.signedRoute('invoice.download', { id: 42 })
|
|
17
|
+
* // → '/invoice/42?signature=abc123'
|
|
18
|
+
*/
|
|
19
|
+
static signedRoute(name: string, params?: Record<string, string | number>, expiresAt?: Date): string;
|
|
20
|
+
/**
|
|
21
|
+
* Generate a signed URL that expires after `seconds` seconds.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* Url.temporarySignedRoute('invoice.download', 3600, { id: 42 })
|
|
25
|
+
* // → '/invoice/42?expires=1234567890&signature=abc123'
|
|
26
|
+
*/
|
|
27
|
+
static temporarySignedRoute(name: string, seconds: number, params?: Record<string, string | number>): string;
|
|
28
|
+
/**
|
|
29
|
+
* Sign an arbitrary path string.
|
|
30
|
+
* Appends `?signature=...` (and `?expires=...` if `expiresAt` given).
|
|
31
|
+
*/
|
|
32
|
+
static sign(path: string, expiresAt?: Date): string;
|
|
33
|
+
/**
|
|
34
|
+
* Return `true` if the request has a valid (and non-expired) signature.
|
|
35
|
+
*/
|
|
36
|
+
static isValidSignature(req: AppRequest): boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Middleware that verifies a signed URL signature.
|
|
40
|
+
* Responds with 403 if the signature is missing, invalid, or expired.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* router.get('/invoice/:id/download', handler, [ValidateSignature()])
|
|
44
|
+
*/
|
|
45
|
+
export declare function ValidateSignature(): MiddlewareHandler;
|
|
46
|
+
//# sourceMappingURL=url-signing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-signing.d.ts","sourceRoot":"","sources":["../src/url-signing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AA6CxE,qBAAa,GAAG;IACd;;;OAGG;IACH,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIhC,2CAA2C;IAC3C,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,GAAG,MAAM;IAIvC,iEAAiE;IACjE,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,QAAQ,SAAM,GAAG,MAAM;IAIxD;;;;;;OAMG;IACH,MAAM,CAAC,WAAW,CAChB,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,EAC5C,SAAS,CAAC,EAAE,IAAI,GACf,MAAM;IAIT;;;;;;OAMG;IACH,MAAM,CAAC,oBAAoB,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAM,GAC3C,MAAM;IAIT;;;OAGG;IACH,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM;IAcnD;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO;CA2BlD;AAID;;;;;;GAMG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,CAOrD"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { route } from './index.js';
|
|
2
|
+
// ─── node:crypto lazy load ─────────────────────────────────
|
|
3
|
+
//
|
|
4
|
+
// Lazy-load node:crypto to avoid bundling it into the client. Only used by
|
|
5
|
+
// Url (signed URLs) and ValidateSignature — server-only features. The
|
|
6
|
+
// fire-and-forget import preloads on server and is a no-op in the browser
|
|
7
|
+
// where `globalThis.process` is undefined.
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
let _crypto;
|
|
10
|
+
if (typeof globalThis.process !== 'undefined') {
|
|
11
|
+
import(/* @vite-ignore */ 'node:crypto').then(m => { _crypto = m; }).catch(() => { });
|
|
12
|
+
}
|
|
13
|
+
// ─── Signing key + helpers ─────────────────────────────────
|
|
14
|
+
let _urlKey = '';
|
|
15
|
+
function _getSigningKey() {
|
|
16
|
+
const key = _urlKey || process.env['APP_KEY'] || '';
|
|
17
|
+
if (!key)
|
|
18
|
+
throw new Error('[RudderJS] No signing key configured. Set APP_KEY in your .env or call Url.setKey().');
|
|
19
|
+
return key;
|
|
20
|
+
}
|
|
21
|
+
function _splitPath(path) {
|
|
22
|
+
const idx = path.indexOf('?');
|
|
23
|
+
return idx === -1 ? [path, ''] : [path.slice(0, idx), path.slice(idx + 1)];
|
|
24
|
+
}
|
|
25
|
+
function _computeSignature(pathname, params) {
|
|
26
|
+
// Sort params for deterministic signing (exclude 'signature' itself)
|
|
27
|
+
const sorted = new URLSearchParams([...params.entries()]
|
|
28
|
+
.filter(([k]) => k !== 'signature')
|
|
29
|
+
.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
|
|
30
|
+
const toSign = sorted.size > 0 ? `${pathname}?${sorted.toString()}` : pathname;
|
|
31
|
+
if (!_crypto)
|
|
32
|
+
throw new Error('[RudderJS Router] node:crypto not available — Url signing requires a server environment.');
|
|
33
|
+
return _crypto.createHmac('sha256', _getSigningKey()).update(toSign).digest('hex');
|
|
34
|
+
}
|
|
35
|
+
// ─── Url ───────────────────────────────────────────────────
|
|
36
|
+
export class Url {
|
|
37
|
+
/**
|
|
38
|
+
* Override the HMAC signing key used for signed URLs.
|
|
39
|
+
* Falls back to `process.env.APP_KEY`.
|
|
40
|
+
*/
|
|
41
|
+
static setKey(key) {
|
|
42
|
+
_urlKey = key;
|
|
43
|
+
}
|
|
44
|
+
/** The full URL of the current request. */
|
|
45
|
+
static current(req) {
|
|
46
|
+
return req.url;
|
|
47
|
+
}
|
|
48
|
+
/** The previous URL from the `Referer` header, or `fallback`. */
|
|
49
|
+
static previous(req, fallback = '/') {
|
|
50
|
+
return req.headers['referer'] ?? fallback;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate a signed URL for a named route.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* Url.signedRoute('invoice.download', { id: 42 })
|
|
57
|
+
* // → '/invoice/42?signature=abc123'
|
|
58
|
+
*/
|
|
59
|
+
static signedRoute(name, params = {}, expiresAt) {
|
|
60
|
+
return Url.sign(route(name, params), expiresAt);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Generate a signed URL that expires after `seconds` seconds.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* Url.temporarySignedRoute('invoice.download', 3600, { id: 42 })
|
|
67
|
+
* // → '/invoice/42?expires=1234567890&signature=abc123'
|
|
68
|
+
*/
|
|
69
|
+
static temporarySignedRoute(name, seconds, params = {}) {
|
|
70
|
+
return Url.signedRoute(name, params, new Date(Date.now() + seconds * 1000));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Sign an arbitrary path string.
|
|
74
|
+
* Appends `?signature=...` (and `?expires=...` if `expiresAt` given).
|
|
75
|
+
*/
|
|
76
|
+
static sign(path, expiresAt) {
|
|
77
|
+
const [pathname, search] = _splitPath(path);
|
|
78
|
+
const params = new URLSearchParams(search);
|
|
79
|
+
if (expiresAt) {
|
|
80
|
+
params.set('expires', String(Math.floor(expiresAt.getTime() / 1000)));
|
|
81
|
+
}
|
|
82
|
+
const sig = _computeSignature(pathname, params);
|
|
83
|
+
params.set('signature', sig);
|
|
84
|
+
return `${pathname}?${params.toString()}`;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Return `true` if the request has a valid (and non-expired) signature.
|
|
88
|
+
*/
|
|
89
|
+
static isValidSignature(req) {
|
|
90
|
+
// `req.url` may be a full URL (Hono adapter populates protocol+host+path+query)
|
|
91
|
+
// or a bare path. `Url.sign(path)` only ever signs the pathname, so verification
|
|
92
|
+
// must hash the same shape. Use the URL parser so both forms collapse to a
|
|
93
|
+
// pathname + searchParams pair.
|
|
94
|
+
const u = new URL(req.url, 'http://placeholder.local');
|
|
95
|
+
const pathname = u.pathname;
|
|
96
|
+
const params = u.searchParams;
|
|
97
|
+
const signature = params.get('signature');
|
|
98
|
+
if (!signature)
|
|
99
|
+
return false;
|
|
100
|
+
// Check expiry before touching the signature
|
|
101
|
+
const expires = params.get('expires');
|
|
102
|
+
if (expires !== null) {
|
|
103
|
+
const expiry = parseInt(expires, 10);
|
|
104
|
+
if (isNaN(expiry) || Date.now() / 1000 > expiry)
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const expected = _computeSignature(pathname, params);
|
|
108
|
+
if (!_crypto)
|
|
109
|
+
return signature === expected;
|
|
110
|
+
const sigBuf = Buffer.from(signature);
|
|
111
|
+
const expBuf = Buffer.from(expected);
|
|
112
|
+
if (sigBuf.length !== expBuf.length)
|
|
113
|
+
return false;
|
|
114
|
+
return _crypto.timingSafeEqual(sigBuf, expBuf);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// ─── ValidateSignature middleware ───────────────────────────
|
|
118
|
+
/**
|
|
119
|
+
* Middleware that verifies a signed URL signature.
|
|
120
|
+
* Responds with 403 if the signature is missing, invalid, or expired.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* router.get('/invoice/:id/download', handler, [ValidateSignature()])
|
|
124
|
+
*/
|
|
125
|
+
export function ValidateSignature() {
|
|
126
|
+
return async (req, res, next) => {
|
|
127
|
+
if (!Url.isValidSignature(req)) {
|
|
128
|
+
return res.status(403).json({ message: 'Invalid or expired URL signature.' });
|
|
129
|
+
}
|
|
130
|
+
await next();
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=url-signing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-signing.js","sourceRoot":"","sources":["../src/url-signing.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rudderjs/router",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"directory": "packages/router"
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
13
|
+
},
|
|
11
14
|
"files": [
|
|
12
15
|
"dist",
|
|
13
16
|
"boost"
|
|
@@ -27,7 +30,7 @@
|
|
|
27
30
|
},
|
|
28
31
|
"dependencies": {
|
|
29
32
|
"reflect-metadata": "^0.2.2",
|
|
30
|
-
"@rudderjs/contracts": "^1.
|
|
33
|
+
"@rudderjs/contracts": "^1.6.1"
|
|
31
34
|
},
|
|
32
35
|
"devDependencies": {
|
|
33
36
|
"@types/node": "^20.0.0",
|