@zero-server/sdk 0.9.1 → 0.9.3
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/LICENSE +21 -21
- package/README.md +460 -443
- package/index.js +414 -412
- package/lib/app.js +1172 -1172
- package/lib/auth/authorize.js +399 -399
- package/lib/auth/enrollment.js +367 -367
- package/lib/auth/index.js +57 -57
- package/lib/auth/jwt.js +731 -731
- package/lib/auth/oauth.js +362 -362
- package/lib/auth/session.js +588 -588
- package/lib/auth/trustedDevice.js +409 -409
- package/lib/auth/twoFactor.js +1150 -1150
- package/lib/auth/webauthn.js +946 -946
- package/lib/body/index.js +14 -14
- package/lib/body/json.js +109 -109
- package/lib/body/multipart.js +440 -440
- package/lib/body/raw.js +71 -71
- package/lib/body/rawBuffer.js +160 -160
- package/lib/body/sendError.js +25 -25
- package/lib/body/text.js +75 -75
- package/lib/body/typeMatch.js +41 -41
- package/lib/body/urlencoded.js +235 -235
- package/lib/cli.js +845 -845
- package/lib/cluster.js +666 -666
- package/lib/debug.js +372 -372
- package/lib/env/index.js +465 -465
- package/lib/errors.js +683 -683
- package/lib/fetch/index.js +256 -256
- package/lib/grpc/balancer.js +378 -378
- package/lib/grpc/call.js +708 -708
- package/lib/grpc/client.js +764 -764
- package/lib/grpc/codec.js +1221 -1221
- package/lib/grpc/credentials.js +398 -398
- package/lib/grpc/frame.js +262 -262
- package/lib/grpc/health.js +287 -287
- package/lib/grpc/index.js +121 -121
- package/lib/grpc/metadata.js +461 -461
- package/lib/grpc/proto.js +821 -821
- package/lib/grpc/reflection.js +590 -590
- package/lib/grpc/server.js +445 -445
- package/lib/grpc/status.js +118 -118
- package/lib/grpc/watch.js +173 -173
- package/lib/http/index.js +10 -10
- package/lib/http/request.js +727 -727
- package/lib/http/response.js +799 -799
- package/lib/lifecycle.js +557 -557
- package/lib/middleware/compress.js +230 -230
- package/lib/middleware/cookieParser.js +237 -237
- package/lib/middleware/cors.js +93 -93
- package/lib/middleware/csrf.js +137 -137
- package/lib/middleware/errorHandler.js +101 -101
- package/lib/middleware/helmet.js +175 -175
- package/lib/middleware/index.js +19 -17
- package/lib/middleware/logger.js +74 -74
- package/lib/middleware/rateLimit.js +88 -88
- package/lib/middleware/requestId.js +53 -53
- package/lib/middleware/static.js +326 -326
- package/lib/middleware/timeout.js +71 -71
- package/lib/middleware/validator.js +255 -255
- package/lib/observe/health.js +326 -326
- package/lib/observe/index.js +50 -50
- package/lib/observe/logger.js +359 -359
- package/lib/observe/metrics.js +805 -805
- package/lib/observe/tracing.js +592 -592
- package/lib/orm/adapters/json.js +290 -290
- package/lib/orm/adapters/memory.js +764 -764
- package/lib/orm/adapters/mongo.js +764 -764
- package/lib/orm/adapters/mysql.js +933 -933
- package/lib/orm/adapters/postgres.js +1144 -1144
- package/lib/orm/adapters/redis.js +1534 -1534
- package/lib/orm/adapters/sql-base.js +212 -212
- package/lib/orm/adapters/sqlite.js +858 -858
- package/lib/orm/audit.js +649 -649
- package/lib/orm/cache.js +394 -394
- package/lib/orm/geo.js +387 -387
- package/lib/orm/index.js +784 -784
- package/lib/orm/migrate.js +432 -432
- package/lib/orm/model.js +1706 -1706
- package/lib/orm/plugin.js +375 -375
- package/lib/orm/procedures.js +836 -836
- package/lib/orm/profiler.js +233 -233
- package/lib/orm/query.js +1772 -1772
- package/lib/orm/replicas.js +241 -241
- package/lib/orm/schema.js +307 -307
- package/lib/orm/search.js +380 -380
- package/lib/orm/seed/data/commerce.js +136 -136
- package/lib/orm/seed/data/internet.js +111 -111
- package/lib/orm/seed/data/locations.js +204 -204
- package/lib/orm/seed/data/names.js +338 -338
- package/lib/orm/seed/data/person.js +128 -128
- package/lib/orm/seed/data/phone.js +211 -211
- package/lib/orm/seed/data/words.js +134 -134
- package/lib/orm/seed/factory.js +178 -178
- package/lib/orm/seed/fake.js +1186 -1186
- package/lib/orm/seed/index.js +18 -18
- package/lib/orm/seed/rng.js +70 -70
- package/lib/orm/seed/seeder.js +124 -124
- package/lib/orm/seed/unique.js +68 -68
- package/lib/orm/snapshot.js +366 -366
- package/lib/orm/tenancy.js +605 -605
- package/lib/orm/views.js +350 -350
- package/lib/router/index.js +436 -436
- package/lib/sse/index.js +8 -8
- package/lib/sse/stream.js +349 -349
- package/lib/ws/connection.js +451 -451
- package/lib/ws/handshake.js +125 -125
- package/lib/ws/index.js +14 -14
- package/lib/ws/room.js +223 -223
- package/package.json +73 -73
- package/types/app.d.ts +223 -223
- package/types/auth.d.ts +520 -520
- package/types/body.d.ts +14 -0
- package/types/cli.d.ts +2 -0
- package/types/cluster.d.ts +75 -75
- package/types/env.d.ts +80 -80
- package/types/errors.d.ts +316 -316
- package/types/fetch.d.ts +43 -43
- package/types/grpc.d.ts +432 -432
- package/types/index.d.ts +384 -384
- package/types/lifecycle.d.ts +60 -60
- package/types/middleware.d.ts +320 -320
- package/types/observe.d.ts +304 -304
- package/types/orm.d.ts +1887 -1887
- package/types/request.d.ts +109 -109
- package/types/response.d.ts +157 -157
- package/types/router.d.ts +78 -78
- package/types/sse.d.ts +78 -78
- package/types/websocket.d.ts +126 -126
|
@@ -1,237 +1,237 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module cookieParser
|
|
3
|
-
* @description Cookie parsing middleware.
|
|
4
|
-
* Parses the `Cookie` header and populates `req.cookies`.
|
|
5
|
-
* Supports signed cookies, JSON cookies, secret rotation,
|
|
6
|
-
* and timing-safe signature verification.
|
|
7
|
-
*/
|
|
8
|
-
const crypto = require('crypto');
|
|
9
|
-
|
|
10
|
-
// -- Internal helpers ------------------------------------
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Timing-safe HMAC-SHA256 signature comparison.
|
|
14
|
-
* Prevents timing-based side-channel attacks on cookie signatures.
|
|
15
|
-
*
|
|
16
|
-
* @param {string} data - The cookie payload.
|
|
17
|
-
* @param {string} sig - The provided signature (base64, no padding).
|
|
18
|
-
* @param {string} secret - Secret to verify against.
|
|
19
|
-
* @returns {boolean} `true` if the signature is valid.
|
|
20
|
-
* @private
|
|
21
|
-
*/
|
|
22
|
-
function _timingSafeVerify(data, sig, secret)
|
|
23
|
-
{
|
|
24
|
-
try
|
|
25
|
-
{
|
|
26
|
-
const expected = crypto
|
|
27
|
-
.createHmac('sha256', secret)
|
|
28
|
-
.update(data)
|
|
29
|
-
.digest('base64')
|
|
30
|
-
.replace(/=+$/, '');
|
|
31
|
-
if (expected.length !== sig.length) return false;
|
|
32
|
-
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
|
|
33
|
-
}
|
|
34
|
-
catch (e) { return false; }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Verify and unsign a signed cookie value.
|
|
39
|
-
* Signed cookies have the format: `s:<value>.<signature>`.
|
|
40
|
-
* All secret(s) are attempted to support key rotation.
|
|
41
|
-
*
|
|
42
|
-
* @param {string} val - Raw cookie value.
|
|
43
|
-
* @param {string[]} secrets - Array of secrets to try.
|
|
44
|
-
* @returns {string|false} Unsigned value on success, `false` on failure.
|
|
45
|
-
* @private
|
|
46
|
-
*/
|
|
47
|
-
function _unsign(val, secrets)
|
|
48
|
-
{
|
|
49
|
-
if (typeof val !== 'string' || !val.startsWith('s:')) return val;
|
|
50
|
-
const payload = val.slice(2);
|
|
51
|
-
const dotIdx = payload.lastIndexOf('.');
|
|
52
|
-
if (dotIdx === -1) return false;
|
|
53
|
-
|
|
54
|
-
const data = payload.slice(0, dotIdx);
|
|
55
|
-
const sig = payload.slice(dotIdx + 1);
|
|
56
|
-
|
|
57
|
-
for (const s of secrets)
|
|
58
|
-
{
|
|
59
|
-
if (_timingSafeVerify(data, sig, s)) return data;
|
|
60
|
-
}
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Try to parse a value as a JSON cookie (prefixed with `j:`).
|
|
66
|
-
*
|
|
67
|
-
* @param {string} val - Cookie value.
|
|
68
|
-
* @returns {*} Parsed value or original string.
|
|
69
|
-
* @private
|
|
70
|
-
*/
|
|
71
|
-
function _parseJSONCookie(val)
|
|
72
|
-
{
|
|
73
|
-
if (typeof val !== 'string' || !val.startsWith('j:')) return val;
|
|
74
|
-
try { return JSON.parse(val.slice(2)); }
|
|
75
|
-
catch (e) { return val; }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// -- Middleware factory ----------------------------------
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Create a cookie parsing middleware.
|
|
82
|
-
*
|
|
83
|
-
* Features:
|
|
84
|
-
* - Signed cookies with HMAC-SHA256 and timing-safe verification
|
|
85
|
-
* - Secret rotation (array of secrets, newest first)
|
|
86
|
-
* - JSON cookies (`j:` prefix, auto-parsed)
|
|
87
|
-
* - `req.secret` / `req.secrets` exposed for downstream middleware
|
|
88
|
-
* - URI-decode toggle
|
|
89
|
-
*
|
|
90
|
-
* @param {string|string[]} [secret] - Secret(s) for signing / verifying cookies.
|
|
91
|
-
* @param {object} [opts] - Configuration options.
|
|
92
|
-
* @param {boolean} [opts.decode=true] - URI-decode cookie values.
|
|
93
|
-
* @returns {Function} Middleware `(req, res, next) => void`.
|
|
94
|
-
*
|
|
95
|
-
* @example
|
|
96
|
-
* app.use(cookieParser());
|
|
97
|
-
* app.use(cookieParser('my-secret'));
|
|
98
|
-
* app.use(cookieParser(['new-secret', 'old-secret'])); // key rotation
|
|
99
|
-
*/
|
|
100
|
-
function cookieParser(secret, opts = {})
|
|
101
|
-
{
|
|
102
|
-
const secrets = secret
|
|
103
|
-
? (Array.isArray(secret) ? secret : [secret])
|
|
104
|
-
: [];
|
|
105
|
-
const decode = opts.decode !== false;
|
|
106
|
-
|
|
107
|
-
return (req, res, next) =>
|
|
108
|
-
{
|
|
109
|
-
const header = req.headers.cookie;
|
|
110
|
-
req.cookies = {};
|
|
111
|
-
req.signedCookies = {};
|
|
112
|
-
|
|
113
|
-
// Expose secret(s) for downstream use (res.cookie signed:true, csrf, etc.)
|
|
114
|
-
if (secrets.length)
|
|
115
|
-
{
|
|
116
|
-
req.secret = secrets[0];
|
|
117
|
-
req.secrets = secrets;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (!header)
|
|
121
|
-
{
|
|
122
|
-
return next();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const pairs = header.split(';');
|
|
126
|
-
for (const pair of pairs)
|
|
127
|
-
{
|
|
128
|
-
const eqIdx = pair.indexOf('=');
|
|
129
|
-
if (eqIdx === -1) continue;
|
|
130
|
-
|
|
131
|
-
const name = pair.slice(0, eqIdx).trim();
|
|
132
|
-
let val = pair.slice(eqIdx + 1).trim();
|
|
133
|
-
|
|
134
|
-
// Remove surrounding quotes if any
|
|
135
|
-
if (val.length >= 2 && val[0] === '"' && val[val.length - 1] === '"')
|
|
136
|
-
{
|
|
137
|
-
val = val.slice(1, -1);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// URI decode
|
|
141
|
-
if (decode)
|
|
142
|
-
{
|
|
143
|
-
try { val = decodeURIComponent(val); } catch (e) { /* keep raw */ }
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Signed cookies → verify, then JSON-parse if j: prefixed
|
|
147
|
-
if (secrets.length > 0 && val.startsWith('s:'))
|
|
148
|
-
{
|
|
149
|
-
const unsigned = _unsign(val, secrets);
|
|
150
|
-
if (unsigned !== false)
|
|
151
|
-
{
|
|
152
|
-
req.signedCookies[name] = _parseJSONCookie(unsigned);
|
|
153
|
-
}
|
|
154
|
-
// Failed-verification signed cookies are silently dropped
|
|
155
|
-
}
|
|
156
|
-
// JSON cookies → auto-parse
|
|
157
|
-
else if (val.startsWith('j:'))
|
|
158
|
-
{
|
|
159
|
-
req.cookies[name] = _parseJSONCookie(val);
|
|
160
|
-
}
|
|
161
|
-
else
|
|
162
|
-
{
|
|
163
|
-
req.cookies[name] = val;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
next();
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// -- Static helpers --------------------------------------
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Sign a cookie value with the given secret.
|
|
175
|
-
*
|
|
176
|
-
* @param {string} val - Cookie value to sign.
|
|
177
|
-
* @param {string} secret - Signing secret.
|
|
178
|
-
* @returns {string} Signed value in format `s:<value>.<signature>`.
|
|
179
|
-
*
|
|
180
|
-
* @example
|
|
181
|
-
* const signed = cookieParser.sign('hello', 'my-secret');
|
|
182
|
-
* // => 's:hello.DGDyS...'
|
|
183
|
-
*/
|
|
184
|
-
cookieParser.sign = function sign(val, secret)
|
|
185
|
-
{
|
|
186
|
-
const sig = crypto
|
|
187
|
-
.createHmac('sha256', secret)
|
|
188
|
-
.update(String(val))
|
|
189
|
-
.digest('base64')
|
|
190
|
-
.replace(/=+$/, '');
|
|
191
|
-
return `s:${val}.${sig}`;
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Verify and unsign a signed cookie value.
|
|
196
|
-
*
|
|
197
|
-
* @param {string} val - Signed cookie value (`s:data.sig`).
|
|
198
|
-
* @param {string|string[]} secret - Secret or array of secrets (for rotation).
|
|
199
|
-
* @returns {string|false} Unsigned value on success, `false` on failure.
|
|
200
|
-
*
|
|
201
|
-
* @example
|
|
202
|
-
* const value = cookieParser.unsign('s:hello.DGDyS...', 'my-secret');
|
|
203
|
-
* // => 'hello' or false
|
|
204
|
-
*/
|
|
205
|
-
cookieParser.unsign = function unsign(val, secret)
|
|
206
|
-
{
|
|
207
|
-
const secrets = Array.isArray(secret) ? secret : [secret];
|
|
208
|
-
return _unsign(val, secrets);
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Serialize a value as a JSON cookie string (prefixed with `j:`).
|
|
213
|
-
*
|
|
214
|
-
* @param {*} val - Value to serialize (object, array, etc.).
|
|
215
|
-
* @returns {string} JSON cookie string.
|
|
216
|
-
*
|
|
217
|
-
* @example
|
|
218
|
-
* const jcookie = cookieParser.jsonCookie({ cart: [1,2,3] });
|
|
219
|
-
* // => 'j:{"cart":[1,2,3]}'
|
|
220
|
-
*/
|
|
221
|
-
cookieParser.jsonCookie = function jsonCookie(val)
|
|
222
|
-
{
|
|
223
|
-
return 'j:' + JSON.stringify(val);
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Parse a JSON cookie string (must start with `j:`).
|
|
228
|
-
*
|
|
229
|
-
* @param {string} str - JSON cookie string.
|
|
230
|
-
* @returns {*} Parsed value, or the original string if not a valid JSON cookie.
|
|
231
|
-
*/
|
|
232
|
-
cookieParser.parseJSON = function parseJSON(str)
|
|
233
|
-
{
|
|
234
|
-
return _parseJSONCookie(str);
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
module.exports = cookieParser;
|
|
1
|
+
/**
|
|
2
|
+
* @module cookieParser
|
|
3
|
+
* @description Cookie parsing middleware.
|
|
4
|
+
* Parses the `Cookie` header and populates `req.cookies`.
|
|
5
|
+
* Supports signed cookies, JSON cookies, secret rotation,
|
|
6
|
+
* and timing-safe signature verification.
|
|
7
|
+
*/
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
|
|
10
|
+
// -- Internal helpers ------------------------------------
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Timing-safe HMAC-SHA256 signature comparison.
|
|
14
|
+
* Prevents timing-based side-channel attacks on cookie signatures.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} data - The cookie payload.
|
|
17
|
+
* @param {string} sig - The provided signature (base64, no padding).
|
|
18
|
+
* @param {string} secret - Secret to verify against.
|
|
19
|
+
* @returns {boolean} `true` if the signature is valid.
|
|
20
|
+
* @private
|
|
21
|
+
*/
|
|
22
|
+
function _timingSafeVerify(data, sig, secret)
|
|
23
|
+
{
|
|
24
|
+
try
|
|
25
|
+
{
|
|
26
|
+
const expected = crypto
|
|
27
|
+
.createHmac('sha256', secret)
|
|
28
|
+
.update(data)
|
|
29
|
+
.digest('base64')
|
|
30
|
+
.replace(/=+$/, '');
|
|
31
|
+
if (expected.length !== sig.length) return false;
|
|
32
|
+
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
|
|
33
|
+
}
|
|
34
|
+
catch (e) { return false; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Verify and unsign a signed cookie value.
|
|
39
|
+
* Signed cookies have the format: `s:<value>.<signature>`.
|
|
40
|
+
* All secret(s) are attempted to support key rotation.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} val - Raw cookie value.
|
|
43
|
+
* @param {string[]} secrets - Array of secrets to try.
|
|
44
|
+
* @returns {string|false} Unsigned value on success, `false` on failure.
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
function _unsign(val, secrets)
|
|
48
|
+
{
|
|
49
|
+
if (typeof val !== 'string' || !val.startsWith('s:')) return val;
|
|
50
|
+
const payload = val.slice(2);
|
|
51
|
+
const dotIdx = payload.lastIndexOf('.');
|
|
52
|
+
if (dotIdx === -1) return false;
|
|
53
|
+
|
|
54
|
+
const data = payload.slice(0, dotIdx);
|
|
55
|
+
const sig = payload.slice(dotIdx + 1);
|
|
56
|
+
|
|
57
|
+
for (const s of secrets)
|
|
58
|
+
{
|
|
59
|
+
if (_timingSafeVerify(data, sig, s)) return data;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Try to parse a value as a JSON cookie (prefixed with `j:`).
|
|
66
|
+
*
|
|
67
|
+
* @param {string} val - Cookie value.
|
|
68
|
+
* @returns {*} Parsed value or original string.
|
|
69
|
+
* @private
|
|
70
|
+
*/
|
|
71
|
+
function _parseJSONCookie(val)
|
|
72
|
+
{
|
|
73
|
+
if (typeof val !== 'string' || !val.startsWith('j:')) return val;
|
|
74
|
+
try { return JSON.parse(val.slice(2)); }
|
|
75
|
+
catch (e) { return val; }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// -- Middleware factory ----------------------------------
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a cookie parsing middleware.
|
|
82
|
+
*
|
|
83
|
+
* Features:
|
|
84
|
+
* - Signed cookies with HMAC-SHA256 and timing-safe verification
|
|
85
|
+
* - Secret rotation (array of secrets, newest first)
|
|
86
|
+
* - JSON cookies (`j:` prefix, auto-parsed)
|
|
87
|
+
* - `req.secret` / `req.secrets` exposed for downstream middleware
|
|
88
|
+
* - URI-decode toggle
|
|
89
|
+
*
|
|
90
|
+
* @param {string|string[]} [secret] - Secret(s) for signing / verifying cookies.
|
|
91
|
+
* @param {object} [opts] - Configuration options.
|
|
92
|
+
* @param {boolean} [opts.decode=true] - URI-decode cookie values.
|
|
93
|
+
* @returns {Function} Middleware `(req, res, next) => void`.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* app.use(cookieParser());
|
|
97
|
+
* app.use(cookieParser('my-secret'));
|
|
98
|
+
* app.use(cookieParser(['new-secret', 'old-secret'])); // key rotation
|
|
99
|
+
*/
|
|
100
|
+
function cookieParser(secret, opts = {})
|
|
101
|
+
{
|
|
102
|
+
const secrets = secret
|
|
103
|
+
? (Array.isArray(secret) ? secret : [secret])
|
|
104
|
+
: [];
|
|
105
|
+
const decode = opts.decode !== false;
|
|
106
|
+
|
|
107
|
+
return (req, res, next) =>
|
|
108
|
+
{
|
|
109
|
+
const header = req.headers.cookie;
|
|
110
|
+
req.cookies = {};
|
|
111
|
+
req.signedCookies = {};
|
|
112
|
+
|
|
113
|
+
// Expose secret(s) for downstream use (res.cookie signed:true, csrf, etc.)
|
|
114
|
+
if (secrets.length)
|
|
115
|
+
{
|
|
116
|
+
req.secret = secrets[0];
|
|
117
|
+
req.secrets = secrets;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!header)
|
|
121
|
+
{
|
|
122
|
+
return next();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const pairs = header.split(';');
|
|
126
|
+
for (const pair of pairs)
|
|
127
|
+
{
|
|
128
|
+
const eqIdx = pair.indexOf('=');
|
|
129
|
+
if (eqIdx === -1) continue;
|
|
130
|
+
|
|
131
|
+
const name = pair.slice(0, eqIdx).trim();
|
|
132
|
+
let val = pair.slice(eqIdx + 1).trim();
|
|
133
|
+
|
|
134
|
+
// Remove surrounding quotes if any
|
|
135
|
+
if (val.length >= 2 && val[0] === '"' && val[val.length - 1] === '"')
|
|
136
|
+
{
|
|
137
|
+
val = val.slice(1, -1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// URI decode
|
|
141
|
+
if (decode)
|
|
142
|
+
{
|
|
143
|
+
try { val = decodeURIComponent(val); } catch (e) { /* keep raw */ }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Signed cookies → verify, then JSON-parse if j: prefixed
|
|
147
|
+
if (secrets.length > 0 && val.startsWith('s:'))
|
|
148
|
+
{
|
|
149
|
+
const unsigned = _unsign(val, secrets);
|
|
150
|
+
if (unsigned !== false)
|
|
151
|
+
{
|
|
152
|
+
req.signedCookies[name] = _parseJSONCookie(unsigned);
|
|
153
|
+
}
|
|
154
|
+
// Failed-verification signed cookies are silently dropped
|
|
155
|
+
}
|
|
156
|
+
// JSON cookies → auto-parse
|
|
157
|
+
else if (val.startsWith('j:'))
|
|
158
|
+
{
|
|
159
|
+
req.cookies[name] = _parseJSONCookie(val);
|
|
160
|
+
}
|
|
161
|
+
else
|
|
162
|
+
{
|
|
163
|
+
req.cookies[name] = val;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
next();
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// -- Static helpers --------------------------------------
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Sign a cookie value with the given secret.
|
|
175
|
+
*
|
|
176
|
+
* @param {string} val - Cookie value to sign.
|
|
177
|
+
* @param {string} secret - Signing secret.
|
|
178
|
+
* @returns {string} Signed value in format `s:<value>.<signature>`.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* const signed = cookieParser.sign('hello', 'my-secret');
|
|
182
|
+
* // => 's:hello.DGDyS...'
|
|
183
|
+
*/
|
|
184
|
+
cookieParser.sign = function sign(val, secret)
|
|
185
|
+
{
|
|
186
|
+
const sig = crypto
|
|
187
|
+
.createHmac('sha256', secret)
|
|
188
|
+
.update(String(val))
|
|
189
|
+
.digest('base64')
|
|
190
|
+
.replace(/=+$/, '');
|
|
191
|
+
return `s:${val}.${sig}`;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Verify and unsign a signed cookie value.
|
|
196
|
+
*
|
|
197
|
+
* @param {string} val - Signed cookie value (`s:data.sig`).
|
|
198
|
+
* @param {string|string[]} secret - Secret or array of secrets (for rotation).
|
|
199
|
+
* @returns {string|false} Unsigned value on success, `false` on failure.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* const value = cookieParser.unsign('s:hello.DGDyS...', 'my-secret');
|
|
203
|
+
* // => 'hello' or false
|
|
204
|
+
*/
|
|
205
|
+
cookieParser.unsign = function unsign(val, secret)
|
|
206
|
+
{
|
|
207
|
+
const secrets = Array.isArray(secret) ? secret : [secret];
|
|
208
|
+
return _unsign(val, secrets);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Serialize a value as a JSON cookie string (prefixed with `j:`).
|
|
213
|
+
*
|
|
214
|
+
* @param {*} val - Value to serialize (object, array, etc.).
|
|
215
|
+
* @returns {string} JSON cookie string.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* const jcookie = cookieParser.jsonCookie({ cart: [1,2,3] });
|
|
219
|
+
* // => 'j:{"cart":[1,2,3]}'
|
|
220
|
+
*/
|
|
221
|
+
cookieParser.jsonCookie = function jsonCookie(val)
|
|
222
|
+
{
|
|
223
|
+
return 'j:' + JSON.stringify(val);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Parse a JSON cookie string (must start with `j:`).
|
|
228
|
+
*
|
|
229
|
+
* @param {string} str - JSON cookie string.
|
|
230
|
+
* @returns {*} Parsed value, or the original string if not a valid JSON cookie.
|
|
231
|
+
*/
|
|
232
|
+
cookieParser.parseJSON = function parseJSON(str)
|
|
233
|
+
{
|
|
234
|
+
return _parseJSONCookie(str);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
module.exports = cookieParser;
|
package/lib/middleware/cors.js
CHANGED
|
@@ -1,93 +1,93 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module cors
|
|
3
|
-
* @description CORS middleware. Supports exact origins, wildcard `'*'`,
|
|
4
|
-
* arrays of allowed origins, and suffix matching with a leading dot
|
|
5
|
-
* (e.g. `'.example.com'` matches `sub.example.com`).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Create a CORS middleware.
|
|
10
|
-
*
|
|
11
|
-
* @param {object} [options] - Configuration options.
|
|
12
|
-
* @param {string|string[]} [options.origin='*'] - Allowed origin(s). Use `'*'` for any,
|
|
13
|
-
* an array for a whitelist, or a string
|
|
14
|
-
* starting with `'.'` for suffix matching.
|
|
15
|
-
* @param {string} [options.methods='GET,POST,PUT,DELETE,OPTIONS'] - Allowed HTTP methods.
|
|
16
|
-
* @param {string} [options.allowedHeaders='Content-Type,Authorization'] - Allowed request headers.
|
|
17
|
-
* @param {string} [options.exposedHeaders] - Headers the browser is allowed to read.
|
|
18
|
-
* @param {boolean} [options.credentials=false] - Whether to set `Access-Control-Allow-Credentials`.
|
|
19
|
-
* @param {number} [options.maxAge] - Preflight cache duration in seconds.
|
|
20
|
-
* @returns {Function} Middleware `(req, res, next) => void`.
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* app.use(cors()); // allow all origins
|
|
24
|
-
* app.use(cors({ origin: 'https://example.com' })); // single origin
|
|
25
|
-
* app.use(cors({ // fine-grained
|
|
26
|
-
* origin: ['https://my.example.com', '.example.com'],
|
|
27
|
-
* credentials: true,
|
|
28
|
-
* maxAge: 86400,
|
|
29
|
-
* }));
|
|
30
|
-
*/
|
|
31
|
-
function cors(options = {})
|
|
32
|
-
{
|
|
33
|
-
const allowOrigin = (options.hasOwnProperty('origin')) ? options.origin : '*';
|
|
34
|
-
const allowMethods = (options.methods || 'GET,POST,PUT,DELETE,OPTIONS');
|
|
35
|
-
const allowHeaders = (options.allowedHeaders || 'Content-Type,Authorization');
|
|
36
|
-
|
|
37
|
-
// RFC 6454: credentials cannot be used with wildcard origin
|
|
38
|
-
if (options.credentials && allowOrigin === '*')
|
|
39
|
-
{
|
|
40
|
-
throw new Error('CORS credentials cannot be used with wildcard origin "*". Specify explicit origins instead.');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Resolve the Origin header value to echo back based on the configured
|
|
45
|
-
* allow-list. Returns `null` when the origin should not be allowed.
|
|
46
|
-
*
|
|
47
|
-
* @private
|
|
48
|
-
* @param {string|undefined} reqOrigin - The request's `Origin` header.
|
|
49
|
-
* @returns {string|null} Origin value to set, or `null`.
|
|
50
|
-
*/
|
|
51
|
-
function matchOrigin(reqOrigin)
|
|
52
|
-
{
|
|
53
|
-
if (!allowOrigin) return null; // origin explicitly disabled
|
|
54
|
-
if (typeof allowOrigin === 'string') return allowOrigin === '*' ? '*' : allowOrigin;
|
|
55
|
-
if (Array.isArray(allowOrigin))
|
|
56
|
-
{
|
|
57
|
-
if (!reqOrigin) return null;
|
|
58
|
-
for (const o of allowOrigin)
|
|
59
|
-
{
|
|
60
|
-
if (!o) continue;
|
|
61
|
-
if (o === reqOrigin) return reqOrigin;
|
|
62
|
-
// allow suffix match with leading dot (e.g. .example.com)
|
|
63
|
-
if (o[0] === '.' && reqOrigin.endsWith(o)) return reqOrigin;
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return (req, res, next) =>
|
|
71
|
-
{
|
|
72
|
-
const reqOrigin = req.headers && (req.headers.origin || req.headers.Origin);
|
|
73
|
-
const originValue = matchOrigin(reqOrigin);
|
|
74
|
-
|
|
75
|
-
if (originValue)
|
|
76
|
-
{
|
|
77
|
-
res.set('Access-Control-Allow-Origin', originValue);
|
|
78
|
-
// Set Vary: Origin when not using wildcard (important for caching proxies)
|
|
79
|
-
if (originValue !== '*') res.vary('Origin');
|
|
80
|
-
if (options.credentials) res.set('Access-Control-Allow-Credentials', 'true');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (allowMethods) res.set('Access-Control-Allow-Methods', allowMethods);
|
|
84
|
-
if (allowHeaders) res.set('Access-Control-Allow-Headers', allowHeaders);
|
|
85
|
-
if (options.exposedHeaders) res.set('Access-Control-Expose-Headers', options.exposedHeaders);
|
|
86
|
-
if (options.maxAge !== undefined) res.set('Access-Control-Max-Age', String(options.maxAge));
|
|
87
|
-
|
|
88
|
-
if (req.method === 'OPTIONS') return res.status(204).send();
|
|
89
|
-
next();
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
module.exports = cors;
|
|
1
|
+
/**
|
|
2
|
+
* @module cors
|
|
3
|
+
* @description CORS middleware. Supports exact origins, wildcard `'*'`,
|
|
4
|
+
* arrays of allowed origins, and suffix matching with a leading dot
|
|
5
|
+
* (e.g. `'.example.com'` matches `sub.example.com`).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create a CORS middleware.
|
|
10
|
+
*
|
|
11
|
+
* @param {object} [options] - Configuration options.
|
|
12
|
+
* @param {string|string[]} [options.origin='*'] - Allowed origin(s). Use `'*'` for any,
|
|
13
|
+
* an array for a whitelist, or a string
|
|
14
|
+
* starting with `'.'` for suffix matching.
|
|
15
|
+
* @param {string} [options.methods='GET,POST,PUT,DELETE,OPTIONS'] - Allowed HTTP methods.
|
|
16
|
+
* @param {string} [options.allowedHeaders='Content-Type,Authorization'] - Allowed request headers.
|
|
17
|
+
* @param {string} [options.exposedHeaders] - Headers the browser is allowed to read.
|
|
18
|
+
* @param {boolean} [options.credentials=false] - Whether to set `Access-Control-Allow-Credentials`.
|
|
19
|
+
* @param {number} [options.maxAge] - Preflight cache duration in seconds.
|
|
20
|
+
* @returns {Function} Middleware `(req, res, next) => void`.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* app.use(cors()); // allow all origins
|
|
24
|
+
* app.use(cors({ origin: 'https://example.com' })); // single origin
|
|
25
|
+
* app.use(cors({ // fine-grained
|
|
26
|
+
* origin: ['https://my.example.com', '.example.com'],
|
|
27
|
+
* credentials: true,
|
|
28
|
+
* maxAge: 86400,
|
|
29
|
+
* }));
|
|
30
|
+
*/
|
|
31
|
+
function cors(options = {})
|
|
32
|
+
{
|
|
33
|
+
const allowOrigin = (options.hasOwnProperty('origin')) ? options.origin : '*';
|
|
34
|
+
const allowMethods = (options.methods || 'GET,POST,PUT,DELETE,OPTIONS');
|
|
35
|
+
const allowHeaders = (options.allowedHeaders || 'Content-Type,Authorization');
|
|
36
|
+
|
|
37
|
+
// RFC 6454: credentials cannot be used with wildcard origin
|
|
38
|
+
if (options.credentials && allowOrigin === '*')
|
|
39
|
+
{
|
|
40
|
+
throw new Error('CORS credentials cannot be used with wildcard origin "*". Specify explicit origins instead.');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the Origin header value to echo back based on the configured
|
|
45
|
+
* allow-list. Returns `null` when the origin should not be allowed.
|
|
46
|
+
*
|
|
47
|
+
* @private
|
|
48
|
+
* @param {string|undefined} reqOrigin - The request's `Origin` header.
|
|
49
|
+
* @returns {string|null} Origin value to set, or `null`.
|
|
50
|
+
*/
|
|
51
|
+
function matchOrigin(reqOrigin)
|
|
52
|
+
{
|
|
53
|
+
if (!allowOrigin) return null; // origin explicitly disabled
|
|
54
|
+
if (typeof allowOrigin === 'string') return allowOrigin === '*' ? '*' : allowOrigin;
|
|
55
|
+
if (Array.isArray(allowOrigin))
|
|
56
|
+
{
|
|
57
|
+
if (!reqOrigin) return null;
|
|
58
|
+
for (const o of allowOrigin)
|
|
59
|
+
{
|
|
60
|
+
if (!o) continue;
|
|
61
|
+
if (o === reqOrigin) return reqOrigin;
|
|
62
|
+
// allow suffix match with leading dot (e.g. .example.com)
|
|
63
|
+
if (o[0] === '.' && reqOrigin.endsWith(o)) return reqOrigin;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (req, res, next) =>
|
|
71
|
+
{
|
|
72
|
+
const reqOrigin = req.headers && (req.headers.origin || req.headers.Origin);
|
|
73
|
+
const originValue = matchOrigin(reqOrigin);
|
|
74
|
+
|
|
75
|
+
if (originValue)
|
|
76
|
+
{
|
|
77
|
+
res.set('Access-Control-Allow-Origin', originValue);
|
|
78
|
+
// Set Vary: Origin when not using wildcard (important for caching proxies)
|
|
79
|
+
if (originValue !== '*') res.vary('Origin');
|
|
80
|
+
if (options.credentials) res.set('Access-Control-Allow-Credentials', 'true');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (allowMethods) res.set('Access-Control-Allow-Methods', allowMethods);
|
|
84
|
+
if (allowHeaders) res.set('Access-Control-Allow-Headers', allowHeaders);
|
|
85
|
+
if (options.exposedHeaders) res.set('Access-Control-Expose-Headers', options.exposedHeaders);
|
|
86
|
+
if (options.maxAge !== undefined) res.set('Access-Control-Max-Age', String(options.maxAge));
|
|
87
|
+
|
|
88
|
+
if (req.method === 'OPTIONS') return res.status(204).send();
|
|
89
|
+
next();
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = cors;
|