@zero-server/sdk 0.9.0 → 0.9.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/LICENSE +21 -21
- package/README.md +460 -437
- 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 +460 -460
- 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 +136 -136
- 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 +254 -254
- 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/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
package/lib/body/raw.js
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module body/raw
|
|
3
|
-
* @description Raw-buffer body-parsing middleware.
|
|
4
|
-
* Stores the full request body as a Buffer on `req.body`.
|
|
5
|
-
* Also sets `req.rawBody` for signature verification workflows.
|
|
6
|
-
*/
|
|
7
|
-
const rawBuffer = require('./rawBuffer');
|
|
8
|
-
const isTypeMatch = require('./typeMatch');
|
|
9
|
-
const sendError = require('./sendError');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Create a raw-buffer body-parsing middleware.
|
|
13
|
-
*
|
|
14
|
-
* @param {object} [options] - Configuration options.
|
|
15
|
-
* @param {string|number} [options.limit] - Max body size. Default `'1mb'`.
|
|
16
|
-
* @param {string|string[]|Function} [options.type='application/octet-stream'] - Content-Type(s) to match.
|
|
17
|
-
* @param {boolean} [options.requireSecure=false] - When true, reject non-HTTPS requests with 403.
|
|
18
|
-
* @param {Function} [options.verify] - `verify(req, res, buf)` — called before setting body. Throw to reject with 403.
|
|
19
|
-
* @param {boolean} [options.inflate=true] - Decompress gzip/deflate/br bodies. When false, compressed bodies return 415.
|
|
20
|
-
* @returns {Function} Async middleware `(req, res, next) => void`.
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* const { raw } = require('@zero-server/sdk');
|
|
24
|
-
*
|
|
25
|
-
* app.use(raw({ type: 'application/octet-stream', limit: '5mb' }));
|
|
26
|
-
*
|
|
27
|
-
* app.post('/upload', (req, res) => {
|
|
28
|
-
* console.log(req.body); // Buffer
|
|
29
|
-
* res.send('received ' + req.body.length + ' bytes');
|
|
30
|
-
* });
|
|
31
|
-
*/
|
|
32
|
-
function raw(options = {})
|
|
33
|
-
{
|
|
34
|
-
const opts = options || {};
|
|
35
|
-
const limit = opts.limit !== undefined ? opts.limit : '1mb';
|
|
36
|
-
const typeOpt = opts.type || 'application/octet-stream';
|
|
37
|
-
const requireSecure = !!opts.requireSecure;
|
|
38
|
-
const verify = opts.verify;
|
|
39
|
-
const inflate = opts.inflate !== undefined ? opts.inflate : true;
|
|
40
|
-
|
|
41
|
-
return async (req, res, next) =>
|
|
42
|
-
{
|
|
43
|
-
if (requireSecure && !req.secure) return sendError(res, 403, 'HTTPS required');
|
|
44
|
-
const ct = (req.headers['content-type'] || '');
|
|
45
|
-
if (!isTypeMatch(ct, typeOpt)) return next();
|
|
46
|
-
try
|
|
47
|
-
{
|
|
48
|
-
const buf = await rawBuffer(req, { limit, inflate });
|
|
49
|
-
|
|
50
|
-
// Store raw body for signature verification
|
|
51
|
-
req.rawBody = buf;
|
|
52
|
-
|
|
53
|
-
// Optional verification callback
|
|
54
|
-
if (verify)
|
|
55
|
-
{
|
|
56
|
-
try { verify(req, res, buf); }
|
|
57
|
-
catch (e) { return sendError(res, 403, e.message || 'verification failed'); }
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
req.body = buf;
|
|
61
|
-
} catch (err)
|
|
62
|
-
{
|
|
63
|
-
if (err && err.status === 413) return sendError(res, 413, 'payload too large');
|
|
64
|
-
if (err && err.status === 415) return sendError(res, 415, err.message || 'unsupported encoding');
|
|
65
|
-
req.body = Buffer.alloc(0);
|
|
66
|
-
}
|
|
67
|
-
next();
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
module.exports = raw;
|
|
1
|
+
/**
|
|
2
|
+
* @module body/raw
|
|
3
|
+
* @description Raw-buffer body-parsing middleware.
|
|
4
|
+
* Stores the full request body as a Buffer on `req.body`.
|
|
5
|
+
* Also sets `req.rawBody` for signature verification workflows.
|
|
6
|
+
*/
|
|
7
|
+
const rawBuffer = require('./rawBuffer');
|
|
8
|
+
const isTypeMatch = require('./typeMatch');
|
|
9
|
+
const sendError = require('./sendError');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create a raw-buffer body-parsing middleware.
|
|
13
|
+
*
|
|
14
|
+
* @param {object} [options] - Configuration options.
|
|
15
|
+
* @param {string|number} [options.limit] - Max body size. Default `'1mb'`.
|
|
16
|
+
* @param {string|string[]|Function} [options.type='application/octet-stream'] - Content-Type(s) to match.
|
|
17
|
+
* @param {boolean} [options.requireSecure=false] - When true, reject non-HTTPS requests with 403.
|
|
18
|
+
* @param {Function} [options.verify] - `verify(req, res, buf)` — called before setting body. Throw to reject with 403.
|
|
19
|
+
* @param {boolean} [options.inflate=true] - Decompress gzip/deflate/br bodies. When false, compressed bodies return 415.
|
|
20
|
+
* @returns {Function} Async middleware `(req, res, next) => void`.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const { raw } = require('@zero-server/sdk');
|
|
24
|
+
*
|
|
25
|
+
* app.use(raw({ type: 'application/octet-stream', limit: '5mb' }));
|
|
26
|
+
*
|
|
27
|
+
* app.post('/upload', (req, res) => {
|
|
28
|
+
* console.log(req.body); // Buffer
|
|
29
|
+
* res.send('received ' + req.body.length + ' bytes');
|
|
30
|
+
* });
|
|
31
|
+
*/
|
|
32
|
+
function raw(options = {})
|
|
33
|
+
{
|
|
34
|
+
const opts = options || {};
|
|
35
|
+
const limit = opts.limit !== undefined ? opts.limit : '1mb';
|
|
36
|
+
const typeOpt = opts.type || 'application/octet-stream';
|
|
37
|
+
const requireSecure = !!opts.requireSecure;
|
|
38
|
+
const verify = opts.verify;
|
|
39
|
+
const inflate = opts.inflate !== undefined ? opts.inflate : true;
|
|
40
|
+
|
|
41
|
+
return async (req, res, next) =>
|
|
42
|
+
{
|
|
43
|
+
if (requireSecure && !req.secure) return sendError(res, 403, 'HTTPS required');
|
|
44
|
+
const ct = (req.headers['content-type'] || '');
|
|
45
|
+
if (!isTypeMatch(ct, typeOpt)) return next();
|
|
46
|
+
try
|
|
47
|
+
{
|
|
48
|
+
const buf = await rawBuffer(req, { limit, inflate });
|
|
49
|
+
|
|
50
|
+
// Store raw body for signature verification
|
|
51
|
+
req.rawBody = buf;
|
|
52
|
+
|
|
53
|
+
// Optional verification callback
|
|
54
|
+
if (verify)
|
|
55
|
+
{
|
|
56
|
+
try { verify(req, res, buf); }
|
|
57
|
+
catch (e) { return sendError(res, 403, e.message || 'verification failed'); }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
req.body = buf;
|
|
61
|
+
} catch (err)
|
|
62
|
+
{
|
|
63
|
+
if (err && err.status === 413) return sendError(res, 413, 'payload too large');
|
|
64
|
+
if (err && err.status === 415) return sendError(res, 415, err.message || 'unsupported encoding');
|
|
65
|
+
req.body = Buffer.alloc(0);
|
|
66
|
+
}
|
|
67
|
+
next();
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = raw;
|
package/lib/body/rawBuffer.js
CHANGED
|
@@ -1,161 +1,161 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module body/rawBuffer
|
|
3
|
-
* @description Low-level helper that collects the raw request body into a
|
|
4
|
-
* single Buffer, enforcing an optional byte-size limit.
|
|
5
|
-
* Supports Content-Encoding decompression (gzip, deflate, br)
|
|
6
|
-
* and Content-Length pre-checking for early rejection.
|
|
7
|
-
*/
|
|
8
|
-
const zlib = require('zlib');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Parse a human-readable size string (e.g. `'10kb'`, `'2mb'`) into bytes.
|
|
12
|
-
*
|
|
13
|
-
* @private
|
|
14
|
-
* @param {string|number|null} limit - Size limit value.
|
|
15
|
-
* @returns {number|null} Byte limit, or `null` for unlimited.
|
|
16
|
-
*/
|
|
17
|
-
function parseLimit(limit)
|
|
18
|
-
{
|
|
19
|
-
if (!limit && limit !== 0) return null;
|
|
20
|
-
if (typeof limit === 'number') return limit;
|
|
21
|
-
if (typeof limit === 'string')
|
|
22
|
-
{
|
|
23
|
-
const v = limit.trim().toLowerCase();
|
|
24
|
-
const num = Number(v.replace(/[^0-9.]/g, ''));
|
|
25
|
-
if (v.endsWith('kb')) return Math.floor(num * 1024);
|
|
26
|
-
if (v.endsWith('mb')) return Math.floor(num * 1024 * 1024);
|
|
27
|
-
if (v.endsWith('gb')) return Math.floor(num * 1024 * 1024 * 1024);
|
|
28
|
-
return Math.floor(num);
|
|
29
|
-
}
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Extract and normalise the charset from a Content-Type header value
|
|
35
|
-
* into a Node.js-compatible `BufferEncoding` name.
|
|
36
|
-
*
|
|
37
|
-
* @param {string} contentType - Full Content-Type header value.
|
|
38
|
-
* @returns {string|null} Normalised encoding or `null` when not specified.
|
|
39
|
-
*/
|
|
40
|
-
function charsetFromContentType(contentType)
|
|
41
|
-
{
|
|
42
|
-
if (!contentType) return null;
|
|
43
|
-
const m = contentType.match(/charset=["']?([^\s;"']+)/i);
|
|
44
|
-
if (!m) return null;
|
|
45
|
-
const raw = m[1].toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
46
|
-
if (raw === 'utf8') return 'utf8';
|
|
47
|
-
if (raw === 'utf16le' || raw === 'utf16' || raw === 'ucs2') return 'utf16le';
|
|
48
|
-
if (raw === 'latin1' || raw === 'iso88591') return 'latin1';
|
|
49
|
-
if (raw === 'ascii' || raw === 'usascii') return 'ascii';
|
|
50
|
-
return 'utf8'; // safe fallback for unknown charsets
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Collect the raw request body into a Buffer.
|
|
55
|
-
*
|
|
56
|
-
* - Rejects with `{ status: 413 }` when `opts.limit` is exceeded.
|
|
57
|
-
* - Rejects with `{ status: 415 }` for unsupported Content-Encoding or
|
|
58
|
-
* when `opts.inflate` is `false` and the body is compressed.
|
|
59
|
-
* - Automatically decompresses gzip / deflate / br when `opts.inflate`
|
|
60
|
-
* is `true` (the default).
|
|
61
|
-
*
|
|
62
|
-
* @param {import('../http/request')} req - Wrapped request (`.raw` stream, `.headers`).
|
|
1
|
+
/**
|
|
2
|
+
* @module body/rawBuffer
|
|
3
|
+
* @description Low-level helper that collects the raw request body into a
|
|
4
|
+
* single Buffer, enforcing an optional byte-size limit.
|
|
5
|
+
* Supports Content-Encoding decompression (gzip, deflate, br)
|
|
6
|
+
* and Content-Length pre-checking for early rejection.
|
|
7
|
+
*/
|
|
8
|
+
const zlib = require('zlib');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parse a human-readable size string (e.g. `'10kb'`, `'2mb'`) into bytes.
|
|
12
|
+
*
|
|
13
|
+
* @private
|
|
14
|
+
* @param {string|number|null} limit - Size limit value.
|
|
15
|
+
* @returns {number|null} Byte limit, or `null` for unlimited.
|
|
16
|
+
*/
|
|
17
|
+
function parseLimit(limit)
|
|
18
|
+
{
|
|
19
|
+
if (!limit && limit !== 0) return null;
|
|
20
|
+
if (typeof limit === 'number') return limit;
|
|
21
|
+
if (typeof limit === 'string')
|
|
22
|
+
{
|
|
23
|
+
const v = limit.trim().toLowerCase();
|
|
24
|
+
const num = Number(v.replace(/[^0-9.]/g, ''));
|
|
25
|
+
if (v.endsWith('kb')) return Math.floor(num * 1024);
|
|
26
|
+
if (v.endsWith('mb')) return Math.floor(num * 1024 * 1024);
|
|
27
|
+
if (v.endsWith('gb')) return Math.floor(num * 1024 * 1024 * 1024);
|
|
28
|
+
return Math.floor(num);
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Extract and normalise the charset from a Content-Type header value
|
|
35
|
+
* into a Node.js-compatible `BufferEncoding` name.
|
|
36
|
+
*
|
|
37
|
+
* @param {string} contentType - Full Content-Type header value.
|
|
38
|
+
* @returns {string|null} Normalised encoding or `null` when not specified.
|
|
39
|
+
*/
|
|
40
|
+
function charsetFromContentType(contentType)
|
|
41
|
+
{
|
|
42
|
+
if (!contentType) return null;
|
|
43
|
+
const m = contentType.match(/charset=["']?([^\s;"']+)/i);
|
|
44
|
+
if (!m) return null;
|
|
45
|
+
const raw = m[1].toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
46
|
+
if (raw === 'utf8') return 'utf8';
|
|
47
|
+
if (raw === 'utf16le' || raw === 'utf16' || raw === 'ucs2') return 'utf16le';
|
|
48
|
+
if (raw === 'latin1' || raw === 'iso88591') return 'latin1';
|
|
49
|
+
if (raw === 'ascii' || raw === 'usascii') return 'ascii';
|
|
50
|
+
return 'utf8'; // safe fallback for unknown charsets
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Collect the raw request body into a Buffer.
|
|
55
|
+
*
|
|
56
|
+
* - Rejects with `{ status: 413 }` when `opts.limit` is exceeded.
|
|
57
|
+
* - Rejects with `{ status: 415 }` for unsupported Content-Encoding or
|
|
58
|
+
* when `opts.inflate` is `false` and the body is compressed.
|
|
59
|
+
* - Automatically decompresses gzip / deflate / br when `opts.inflate`
|
|
60
|
+
* is `true` (the default).
|
|
61
|
+
*
|
|
62
|
+
* @param {import('../http/request')} req - Wrapped request (`.raw` stream, `.headers`).
|
|
63
63
|
* @param {object} [opts] - Configuration options.
|
|
64
|
-
* @param {string|number|null} [opts.limit] - Max body size (post-decompression).
|
|
65
|
-
* @param {boolean} [opts.inflate=true] - Decompress gzip/deflate/br bodies.
|
|
66
|
-
* @returns {Promise<Buffer>} Resolved with the full body buffer.
|
|
67
|
-
*/
|
|
68
|
-
function rawBuffer(req, opts = {})
|
|
69
|
-
{
|
|
70
|
-
const limit = parseLimit(opts.limit);
|
|
71
|
-
const inflate = opts.inflate !== false;
|
|
72
|
-
|
|
73
|
-
return new Promise((resolve, reject) =>
|
|
74
|
-
{
|
|
75
|
-
const headers = req.headers || (req.raw && req.raw.headers) || {};
|
|
76
|
-
|
|
77
|
-
// Content-Encoding handling
|
|
78
|
-
const encoding = (headers['content-encoding'] || '').toLowerCase().trim();
|
|
79
|
-
const isCompressed = encoding && encoding !== 'identity';
|
|
80
|
-
|
|
81
|
-
// Content-Length pre-check (skip for compressed bodies — CL is the compressed size)
|
|
82
|
-
if (!isCompressed)
|
|
83
|
-
{
|
|
84
|
-
const cl = parseInt(headers['content-length'], 10);
|
|
85
|
-
if (limit && cl && cl > limit)
|
|
86
|
-
{
|
|
87
|
-
const err = new Error('payload too large');
|
|
88
|
-
err.status = 413;
|
|
89
|
-
return reject(err);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Select stream source (possibly a decompression transform)
|
|
94
|
-
let stream = req.raw;
|
|
95
|
-
if (isCompressed)
|
|
96
|
-
{
|
|
97
|
-
if (!inflate)
|
|
98
|
-
{
|
|
99
|
-
const err = new Error('compressed bodies not accepted');
|
|
100
|
-
err.status = 415;
|
|
101
|
-
return reject(err);
|
|
102
|
-
}
|
|
103
|
-
if (encoding === 'gzip' || encoding === 'x-gzip')
|
|
104
|
-
{
|
|
105
|
-
stream = req.raw.pipe(zlib.createGunzip());
|
|
106
|
-
}
|
|
107
|
-
else if (encoding === 'deflate')
|
|
108
|
-
{
|
|
109
|
-
stream = req.raw.pipe(zlib.createInflate());
|
|
110
|
-
}
|
|
111
|
-
else if (encoding === 'br')
|
|
112
|
-
{
|
|
113
|
-
stream = req.raw.pipe(zlib.createBrotliDecompress());
|
|
114
|
-
}
|
|
115
|
-
else
|
|
116
|
-
{
|
|
117
|
-
const err = new Error('unsupported Content-Encoding: ' + encoding);
|
|
118
|
-
err.status = 415;
|
|
119
|
-
return reject(err);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const chunks = [];
|
|
124
|
-
let total = 0;
|
|
125
|
-
|
|
126
|
-
function cleanup()
|
|
127
|
-
{
|
|
128
|
-
stream.removeListener('data', onData);
|
|
129
|
-
stream.removeListener('end', onEnd);
|
|
130
|
-
stream.removeListener('error', onError);
|
|
131
|
-
}
|
|
132
|
-
function onData(c)
|
|
133
|
-
{
|
|
134
|
-
total += c.length;
|
|
135
|
-
if (limit && total > limit)
|
|
136
|
-
{
|
|
137
|
-
cleanup();
|
|
138
|
-
const err = new Error('payload too large');
|
|
139
|
-
err.status = 413;
|
|
140
|
-
return reject(err);
|
|
141
|
-
}
|
|
142
|
-
chunks.push(c);
|
|
143
|
-
}
|
|
144
|
-
function onEnd()
|
|
145
|
-
{
|
|
146
|
-
cleanup();
|
|
147
|
-
resolve(Buffer.concat(chunks));
|
|
148
|
-
}
|
|
149
|
-
function onError(e)
|
|
150
|
-
{
|
|
151
|
-
cleanup();
|
|
152
|
-
reject(e);
|
|
153
|
-
}
|
|
154
|
-
stream.on('data', onData);
|
|
155
|
-
stream.on('end', onEnd);
|
|
156
|
-
stream.on('error', onError);
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
module.exports = rawBuffer;
|
|
161
|
-
module.exports.charsetFromContentType = charsetFromContentType;
|
|
64
|
+
* @param {string|number|null} [opts.limit] - Max body size (post-decompression).
|
|
65
|
+
* @param {boolean} [opts.inflate=true] - Decompress gzip/deflate/br bodies.
|
|
66
|
+
* @returns {Promise<Buffer>} Resolved with the full body buffer.
|
|
67
|
+
*/
|
|
68
|
+
function rawBuffer(req, opts = {})
|
|
69
|
+
{
|
|
70
|
+
const limit = parseLimit(opts.limit);
|
|
71
|
+
const inflate = opts.inflate !== false;
|
|
72
|
+
|
|
73
|
+
return new Promise((resolve, reject) =>
|
|
74
|
+
{
|
|
75
|
+
const headers = req.headers || (req.raw && req.raw.headers) || {};
|
|
76
|
+
|
|
77
|
+
// Content-Encoding handling
|
|
78
|
+
const encoding = (headers['content-encoding'] || '').toLowerCase().trim();
|
|
79
|
+
const isCompressed = encoding && encoding !== 'identity';
|
|
80
|
+
|
|
81
|
+
// Content-Length pre-check (skip for compressed bodies — CL is the compressed size)
|
|
82
|
+
if (!isCompressed)
|
|
83
|
+
{
|
|
84
|
+
const cl = parseInt(headers['content-length'], 10);
|
|
85
|
+
if (limit && cl && cl > limit)
|
|
86
|
+
{
|
|
87
|
+
const err = new Error('payload too large');
|
|
88
|
+
err.status = 413;
|
|
89
|
+
return reject(err);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Select stream source (possibly a decompression transform)
|
|
94
|
+
let stream = req.raw;
|
|
95
|
+
if (isCompressed)
|
|
96
|
+
{
|
|
97
|
+
if (!inflate)
|
|
98
|
+
{
|
|
99
|
+
const err = new Error('compressed bodies not accepted');
|
|
100
|
+
err.status = 415;
|
|
101
|
+
return reject(err);
|
|
102
|
+
}
|
|
103
|
+
if (encoding === 'gzip' || encoding === 'x-gzip')
|
|
104
|
+
{
|
|
105
|
+
stream = req.raw.pipe(zlib.createGunzip());
|
|
106
|
+
}
|
|
107
|
+
else if (encoding === 'deflate')
|
|
108
|
+
{
|
|
109
|
+
stream = req.raw.pipe(zlib.createInflate());
|
|
110
|
+
}
|
|
111
|
+
else if (encoding === 'br')
|
|
112
|
+
{
|
|
113
|
+
stream = req.raw.pipe(zlib.createBrotliDecompress());
|
|
114
|
+
}
|
|
115
|
+
else
|
|
116
|
+
{
|
|
117
|
+
const err = new Error('unsupported Content-Encoding: ' + encoding);
|
|
118
|
+
err.status = 415;
|
|
119
|
+
return reject(err);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const chunks = [];
|
|
124
|
+
let total = 0;
|
|
125
|
+
|
|
126
|
+
function cleanup()
|
|
127
|
+
{
|
|
128
|
+
stream.removeListener('data', onData);
|
|
129
|
+
stream.removeListener('end', onEnd);
|
|
130
|
+
stream.removeListener('error', onError);
|
|
131
|
+
}
|
|
132
|
+
function onData(c)
|
|
133
|
+
{
|
|
134
|
+
total += c.length;
|
|
135
|
+
if (limit && total > limit)
|
|
136
|
+
{
|
|
137
|
+
cleanup();
|
|
138
|
+
const err = new Error('payload too large');
|
|
139
|
+
err.status = 413;
|
|
140
|
+
return reject(err);
|
|
141
|
+
}
|
|
142
|
+
chunks.push(c);
|
|
143
|
+
}
|
|
144
|
+
function onEnd()
|
|
145
|
+
{
|
|
146
|
+
cleanup();
|
|
147
|
+
resolve(Buffer.concat(chunks));
|
|
148
|
+
}
|
|
149
|
+
function onError(e)
|
|
150
|
+
{
|
|
151
|
+
cleanup();
|
|
152
|
+
reject(e);
|
|
153
|
+
}
|
|
154
|
+
stream.on('data', onData);
|
|
155
|
+
stream.on('end', onEnd);
|
|
156
|
+
stream.on('error', onError);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = rawBuffer;
|
|
161
|
+
module.exports.charsetFromContentType = charsetFromContentType;
|
package/lib/body/sendError.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module body/sendError
|
|
3
|
-
* @private
|
|
4
|
-
* @description Shared helper for sending HTTP error responses from body parsers.
|
|
5
|
-
* Centralizes the pattern used across all parsers so changes only happen in one place.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Send an HTTP error response.
|
|
10
|
-
*
|
|
11
|
-
* @private
|
|
12
|
-
* @param {object} res - The response wrapper (or raw response).
|
|
13
|
-
* @param {number} status - HTTP status code.
|
|
14
|
-
* @param {string} message - Error message string for the JSON body.
|
|
15
|
-
*/
|
|
16
|
-
function sendError(res, status, message)
|
|
17
|
-
{
|
|
18
|
-
const raw = res.raw || res;
|
|
19
|
-
if (raw.headersSent) return;
|
|
20
|
-
raw.statusCode = status;
|
|
21
|
-
raw.setHeader('Content-Type', 'application/json');
|
|
22
|
-
raw.end(JSON.stringify({ error: message }));
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
module.exports = sendError;
|
|
1
|
+
/**
|
|
2
|
+
* @module body/sendError
|
|
3
|
+
* @private
|
|
4
|
+
* @description Shared helper for sending HTTP error responses from body parsers.
|
|
5
|
+
* Centralizes the pattern used across all parsers so changes only happen in one place.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Send an HTTP error response.
|
|
10
|
+
*
|
|
11
|
+
* @private
|
|
12
|
+
* @param {object} res - The response wrapper (or raw response).
|
|
13
|
+
* @param {number} status - HTTP status code.
|
|
14
|
+
* @param {string} message - Error message string for the JSON body.
|
|
15
|
+
*/
|
|
16
|
+
function sendError(res, status, message)
|
|
17
|
+
{
|
|
18
|
+
const raw = res.raw || res;
|
|
19
|
+
if (raw.headersSent) return;
|
|
20
|
+
raw.statusCode = status;
|
|
21
|
+
raw.setHeader('Content-Type', 'application/json');
|
|
22
|
+
raw.end(JSON.stringify({ error: message }));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = sendError;
|
package/lib/body/text.js
CHANGED
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module body/text
|
|
3
|
-
* @description Plain-text body-parsing middleware.
|
|
4
|
-
* Reads the request body as a string and sets `req.body`.
|
|
5
|
-
* Stores the raw buffer on `req.rawBody` for signature verification.
|
|
6
|
-
*/
|
|
7
|
-
const rawBuffer = require('./rawBuffer');
|
|
8
|
-
const { charsetFromContentType } = require('./rawBuffer');
|
|
9
|
-
const isTypeMatch = require('./typeMatch');
|
|
10
|
-
const sendError = require('./sendError');
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Create a plain-text body-parsing middleware.
|
|
14
|
-
*
|
|
15
|
-
* @param {object} [options] - Configuration options.
|
|
16
|
-
* @param {string|number} [options.limit] - Max body size. Default `'1mb'`.
|
|
17
|
-
* @param {string} [options.encoding='utf8'] - Fallback character encoding when Content-Type has no charset.
|
|
18
|
-
* @param {string|string[]|Function} [options.type='text/*'] - Content-Type(s) to match.
|
|
19
|
-
* @param {boolean} [options.requireSecure=false] - When true, reject non-HTTPS requests with 403.
|
|
20
|
-
* @param {Function} [options.verify] - `verify(req, res, buf, encoding)` — called before decoding. Throw to reject with 403.
|
|
21
|
-
* @param {boolean} [options.inflate=true] - Decompress gzip/deflate/br bodies. When false, compressed bodies return 415.
|
|
22
|
-
* @returns {Function} Async middleware `(req, res, next) => void`.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* const { text } = require('@zero-server/sdk');
|
|
26
|
-
*
|
|
27
|
-
* app.use(text({ type: 'text/plain', limit: '256kb' }));
|
|
28
|
-
*
|
|
29
|
-
* app.post('/log', (req, res) => {
|
|
30
|
-
* console.log(req.body); // raw string
|
|
31
|
-
* res.send('ok');
|
|
32
|
-
* });
|
|
33
|
-
*/
|
|
34
|
-
function text(options = {})
|
|
35
|
-
{
|
|
36
|
-
const opts = options || {};
|
|
37
|
-
const limit = opts.limit !== undefined ? opts.limit : '1mb';
|
|
38
|
-
const defaultEncoding = opts.encoding || 'utf8';
|
|
39
|
-
const typeOpt = opts.type || 'text/*';
|
|
40
|
-
const requireSecure = !!opts.requireSecure;
|
|
41
|
-
const verify = opts.verify;
|
|
42
|
-
const inflate = opts.inflate !== undefined ? opts.inflate : true;
|
|
43
|
-
|
|
44
|
-
return async (req, res, next) =>
|
|
45
|
-
{
|
|
46
|
-
if (requireSecure && !req.secure) return sendError(res, 403, 'HTTPS required');
|
|
47
|
-
const ct = (req.headers['content-type'] || '');
|
|
48
|
-
if (!isTypeMatch(ct, typeOpt)) return next();
|
|
49
|
-
try
|
|
50
|
-
{
|
|
51
|
-
const buf = await rawBuffer(req, { limit, inflate });
|
|
52
|
-
const encoding = charsetFromContentType(ct) || defaultEncoding;
|
|
53
|
-
|
|
54
|
-
// Store raw body for signature verification
|
|
55
|
-
req.rawBody = buf;
|
|
56
|
-
|
|
57
|
-
// Optional verification callback
|
|
58
|
-
if (verify)
|
|
59
|
-
{
|
|
60
|
-
try { verify(req, res, buf, encoding); }
|
|
61
|
-
catch (e) { return sendError(res, 403, e.message || 'verification failed'); }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
req.body = buf.toString(encoding);
|
|
65
|
-
} catch (err)
|
|
66
|
-
{
|
|
67
|
-
if (err && err.status === 413) return sendError(res, 413, 'payload too large');
|
|
68
|
-
if (err && err.status === 415) return sendError(res, 415, err.message || 'unsupported encoding');
|
|
69
|
-
req.body = '';
|
|
70
|
-
}
|
|
71
|
-
next();
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
module.exports = text;
|
|
1
|
+
/**
|
|
2
|
+
* @module body/text
|
|
3
|
+
* @description Plain-text body-parsing middleware.
|
|
4
|
+
* Reads the request body as a string and sets `req.body`.
|
|
5
|
+
* Stores the raw buffer on `req.rawBody` for signature verification.
|
|
6
|
+
*/
|
|
7
|
+
const rawBuffer = require('./rawBuffer');
|
|
8
|
+
const { charsetFromContentType } = require('./rawBuffer');
|
|
9
|
+
const isTypeMatch = require('./typeMatch');
|
|
10
|
+
const sendError = require('./sendError');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a plain-text body-parsing middleware.
|
|
14
|
+
*
|
|
15
|
+
* @param {object} [options] - Configuration options.
|
|
16
|
+
* @param {string|number} [options.limit] - Max body size. Default `'1mb'`.
|
|
17
|
+
* @param {string} [options.encoding='utf8'] - Fallback character encoding when Content-Type has no charset.
|
|
18
|
+
* @param {string|string[]|Function} [options.type='text/*'] - Content-Type(s) to match.
|
|
19
|
+
* @param {boolean} [options.requireSecure=false] - When true, reject non-HTTPS requests with 403.
|
|
20
|
+
* @param {Function} [options.verify] - `verify(req, res, buf, encoding)` — called before decoding. Throw to reject with 403.
|
|
21
|
+
* @param {boolean} [options.inflate=true] - Decompress gzip/deflate/br bodies. When false, compressed bodies return 415.
|
|
22
|
+
* @returns {Function} Async middleware `(req, res, next) => void`.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* const { text } = require('@zero-server/sdk');
|
|
26
|
+
*
|
|
27
|
+
* app.use(text({ type: 'text/plain', limit: '256kb' }));
|
|
28
|
+
*
|
|
29
|
+
* app.post('/log', (req, res) => {
|
|
30
|
+
* console.log(req.body); // raw string
|
|
31
|
+
* res.send('ok');
|
|
32
|
+
* });
|
|
33
|
+
*/
|
|
34
|
+
function text(options = {})
|
|
35
|
+
{
|
|
36
|
+
const opts = options || {};
|
|
37
|
+
const limit = opts.limit !== undefined ? opts.limit : '1mb';
|
|
38
|
+
const defaultEncoding = opts.encoding || 'utf8';
|
|
39
|
+
const typeOpt = opts.type || 'text/*';
|
|
40
|
+
const requireSecure = !!opts.requireSecure;
|
|
41
|
+
const verify = opts.verify;
|
|
42
|
+
const inflate = opts.inflate !== undefined ? opts.inflate : true;
|
|
43
|
+
|
|
44
|
+
return async (req, res, next) =>
|
|
45
|
+
{
|
|
46
|
+
if (requireSecure && !req.secure) return sendError(res, 403, 'HTTPS required');
|
|
47
|
+
const ct = (req.headers['content-type'] || '');
|
|
48
|
+
if (!isTypeMatch(ct, typeOpt)) return next();
|
|
49
|
+
try
|
|
50
|
+
{
|
|
51
|
+
const buf = await rawBuffer(req, { limit, inflate });
|
|
52
|
+
const encoding = charsetFromContentType(ct) || defaultEncoding;
|
|
53
|
+
|
|
54
|
+
// Store raw body for signature verification
|
|
55
|
+
req.rawBody = buf;
|
|
56
|
+
|
|
57
|
+
// Optional verification callback
|
|
58
|
+
if (verify)
|
|
59
|
+
{
|
|
60
|
+
try { verify(req, res, buf, encoding); }
|
|
61
|
+
catch (e) { return sendError(res, 403, e.message || 'verification failed'); }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
req.body = buf.toString(encoding);
|
|
65
|
+
} catch (err)
|
|
66
|
+
{
|
|
67
|
+
if (err && err.status === 413) return sendError(res, 413, 'payload too large');
|
|
68
|
+
if (err && err.status === 415) return sendError(res, 415, err.message || 'unsupported encoding');
|
|
69
|
+
req.body = '';
|
|
70
|
+
}
|
|
71
|
+
next();
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = text;
|