@devorash/core 0.1.0
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 -0
- package/README.md +187 -0
- package/dist/constants/api-url.gen.d.ts +4 -0
- package/dist/constants/api-url.gen.d.ts.map +1 -0
- package/dist/constants/api-url.gen.example.d.ts +7 -0
- package/dist/constants/api-url.gen.example.d.ts.map +1 -0
- package/dist/constants/api-url.gen.example.js +7 -0
- package/dist/constants/api-url.gen.example.js.map +1 -0
- package/dist/constants/api-url.gen.js +4 -0
- package/dist/constants/api-url.gen.js.map +1 -0
- package/dist/constants/index.d.ts +203 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +236 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/constants/sessionLifecycle.d.ts +22 -0
- package/dist/constants/sessionLifecycle.d.ts.map +1 -0
- package/dist/constants/sessionLifecycle.js +21 -0
- package/dist/constants/sessionLifecycle.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/security/index.d.ts +118 -0
- package/dist/security/index.d.ts.map +1 -0
- package/dist/security/index.js +345 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/request-body.d.ts +16 -0
- package/dist/security/request-body.d.ts.map +1 -0
- package/dist/security/request-body.js +84 -0
- package/dist/security/request-body.js.map +1 -0
- package/dist/security/signing.d.ts +127 -0
- package/dist/security/signing.d.ts.map +1 -0
- package/dist/security/signing.js +297 -0
- package/dist/security/signing.js.map +1 -0
- package/dist/types/index.d.ts +487 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +19 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.d.ts +211 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +600 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security utilities for Devora SDKs
|
|
3
|
+
* @module @devorash/core/security
|
|
4
|
+
*/
|
|
5
|
+
import { SDK_DEFAULTS, READ_METHODS } from "../constants/index.js";
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Timestamps
|
|
8
|
+
// ============================================================================
|
|
9
|
+
/**
|
|
10
|
+
* A timestamp tolerance is a whole, non-negative number of seconds. Zero means
|
|
11
|
+
* only the current second is accepted; there is no "falsy means default".
|
|
12
|
+
*/
|
|
13
|
+
export function isValidTimestampTolerance(value) {
|
|
14
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
15
|
+
}
|
|
16
|
+
/** Throws for configuration values that {@link isValidTimestampTolerance} rejects. */
|
|
17
|
+
export function assertTimestampTolerance(value, name = "timestampTolerance") {
|
|
18
|
+
if (value !== undefined && !isValidTimestampTolerance(value))
|
|
19
|
+
throw new Error(`Devora SDK: ${name} must be a non-negative integer number of seconds`);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validate timestamp is within acceptable window
|
|
23
|
+
*/
|
|
24
|
+
export function validateTimestamp(requestTimestamp, toleranceSeconds = SDK_DEFAULTS.TIMESTAMP_TOLERANCE) {
|
|
25
|
+
// NaN/Infinity/negative tolerances would make every drift comparison pass.
|
|
26
|
+
if (!isValidTimestampTolerance(toleranceSeconds)) {
|
|
27
|
+
return { valid: false, error: "Invalid timestamp tolerance" };
|
|
28
|
+
}
|
|
29
|
+
if (!Number.isFinite(requestTimestamp) || requestTimestamp <= 0) {
|
|
30
|
+
return {
|
|
31
|
+
valid: false,
|
|
32
|
+
error: "Invalid timestamp",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
36
|
+
const drift = Math.abs(currentTime - requestTimestamp);
|
|
37
|
+
if (drift > toleranceSeconds) {
|
|
38
|
+
return {
|
|
39
|
+
valid: false,
|
|
40
|
+
error: `Request timestamp outside tolerance window (drift: ${drift}s, max: ${toleranceSeconds}s)`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { valid: true };
|
|
44
|
+
}
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Scope Enforcement Utilities
|
|
47
|
+
// ============================================================================
|
|
48
|
+
/**
|
|
49
|
+
* Check if HTTP method is a write operation
|
|
50
|
+
*/
|
|
51
|
+
export function isWriteMethod(method) {
|
|
52
|
+
// Unknown verbs are not safe just because they are absent from our CRUD list.
|
|
53
|
+
return !READ_METHODS.includes(method.toUpperCase());
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Path of an origin-form request target (`/path?query`), still percent-encoded.
|
|
57
|
+
*
|
|
58
|
+
* Absolute-form targets (`http://host/path`) are deliberately NOT unwrapped:
|
|
59
|
+
* routers disagree with any regex about where such a host ends (a `?` inside
|
|
60
|
+
* it moves the routed path to `/`), so the result does not start with `/` and
|
|
61
|
+
* {@link isAmbiguousRequestPath} rejects it.
|
|
62
|
+
*/
|
|
63
|
+
export function getRawRequestPath(value) {
|
|
64
|
+
return (value ?? "").split(/[?#]/)[0] || "/";
|
|
65
|
+
}
|
|
66
|
+
/** Headers through which common middleware lets a client change the effective method. */
|
|
67
|
+
export const METHOD_OVERRIDE_HEADERS = [
|
|
68
|
+
"x-http-method-override",
|
|
69
|
+
"x-http-method",
|
|
70
|
+
"x-method-override",
|
|
71
|
+
];
|
|
72
|
+
/** The query parameter method-override middleware reads (`?_method=DELETE`). */
|
|
73
|
+
export const METHOD_OVERRIDE_QUERY_PARAM = "_method";
|
|
74
|
+
/**
|
|
75
|
+
* Every method a request might execute as. The guard judges all of them, so a
|
|
76
|
+
* method-override middleware ordered after the guard cannot turn an allowed
|
|
77
|
+
* POST into a blocked DELETE. `target` (the raw request target) adds any
|
|
78
|
+
* `_method` query parameters. A `_method` field in a request body is invisible
|
|
79
|
+
* to a guard that runs before body parsing; see the guard docs.
|
|
80
|
+
*/
|
|
81
|
+
export function getPolicyMethods(method, headers, target) {
|
|
82
|
+
const methods = new Set([method.toUpperCase()]);
|
|
83
|
+
for (const name of METHOD_OVERRIDE_HEADERS) {
|
|
84
|
+
const value = headers?.[name];
|
|
85
|
+
for (const item of Array.isArray(value) ? value : value ? [value] : [])
|
|
86
|
+
for (const part of item.split(","))
|
|
87
|
+
if (part.trim())
|
|
88
|
+
methods.add(part.trim().toUpperCase());
|
|
89
|
+
}
|
|
90
|
+
const queryStart = target?.indexOf("?") ?? -1;
|
|
91
|
+
if (target && queryStart !== -1) {
|
|
92
|
+
const query = target.slice(queryStart + 1).split("#")[0];
|
|
93
|
+
try {
|
|
94
|
+
for (const value of new URLSearchParams(query).getAll(METHOD_OVERRIDE_QUERY_PARAM))
|
|
95
|
+
if (value.trim())
|
|
96
|
+
methods.add(value.trim().toUpperCase());
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// An unparseable query overrides nothing; the route guard judges it.
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return [...methods];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Methods a deny rule must cover. Frameworks answer HEAD with the GET handler,
|
|
106
|
+
* so a GET deny rule also denies HEAD.
|
|
107
|
+
*/
|
|
108
|
+
export function getDenyMethods(methods) {
|
|
109
|
+
return methods.includes("HEAD") && !methods.includes("GET") ? [...methods, "GET"] : methods;
|
|
110
|
+
}
|
|
111
|
+
/** Join a router mount prefix and a mount-relative origin-form target. */
|
|
112
|
+
export function joinMountedTarget(mountPath, target) {
|
|
113
|
+
if (!mountPath || mountPath === "/")
|
|
114
|
+
return target;
|
|
115
|
+
// A mounted router sees `/` for both `/mount` and `/mount/`; both dispatch
|
|
116
|
+
// to the router's root handler, so report the mount path itself.
|
|
117
|
+
if (target === "/" || target.startsWith("/?") || target.startsWith("/#"))
|
|
118
|
+
return mountPath + target.slice(1);
|
|
119
|
+
return mountPath + target;
|
|
120
|
+
}
|
|
121
|
+
// A plain space (`%20` once decoded) is ordinary route-parameter content and
|
|
122
|
+
// does not move a segment boundary; control characters and backslashes do.
|
|
123
|
+
function hasPathControl(value) {
|
|
124
|
+
return Array.from(value).some((char) => char.charCodeAt(0) < 32 || char.charCodeAt(0) === 127 || char.charCodeAt(0) === 92);
|
|
125
|
+
}
|
|
126
|
+
/** Reject paths whose segment boundaries differ between common routers. */
|
|
127
|
+
export function isAmbiguousRequestPath(value) {
|
|
128
|
+
const raw = getRawRequestPath(value);
|
|
129
|
+
if (raw.length > 8192 || !raw.startsWith("/") || hasPathControl(raw))
|
|
130
|
+
return true;
|
|
131
|
+
try {
|
|
132
|
+
const decoded = decodeURIComponent(raw);
|
|
133
|
+
return (
|
|
134
|
+
// A still-encoded sequence after one decode means double encoding; a
|
|
135
|
+
// bare `%` (from a literal `%25`) is ordinary parameter content.
|
|
136
|
+
/%[0-9a-f]{2}/i.test(decoded) ||
|
|
137
|
+
// Routers may treat semicolons as query/matrix delimiters. A deny
|
|
138
|
+
// rule must not compare a different path from the selected handler.
|
|
139
|
+
decoded.includes(";") ||
|
|
140
|
+
hasPathControl(decoded) ||
|
|
141
|
+
/%(?:2f|5c)/i.test(raw) ||
|
|
142
|
+
decoded.includes("//") ||
|
|
143
|
+
decoded.split("/").some((part) => part === "." || part === ".."));
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** Decode one segment at a time, preserving case and separators. */
|
|
150
|
+
export function normalizeRequestPath(value) {
|
|
151
|
+
const path = getRawRequestPath(value);
|
|
152
|
+
try {
|
|
153
|
+
return decodeURIComponent(path);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return path;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/** Linear-space wildcard matching within one path segment. */
|
|
160
|
+
function segmentMatches(pattern, value) {
|
|
161
|
+
let previous = new Uint8Array(value.length + 1);
|
|
162
|
+
previous[0] = 1;
|
|
163
|
+
for (const token of pattern) {
|
|
164
|
+
const current = new Uint8Array(value.length + 1);
|
|
165
|
+
if (token === "*")
|
|
166
|
+
current[0] = previous[0];
|
|
167
|
+
for (let j = 1; j <= value.length; j++) {
|
|
168
|
+
current[j] =
|
|
169
|
+
token === "*"
|
|
170
|
+
? previous[j] || current[j - 1]
|
|
171
|
+
? 1
|
|
172
|
+
: 0
|
|
173
|
+
: previous[j - 1] && token === value[j - 1]
|
|
174
|
+
? 1
|
|
175
|
+
: 0;
|
|
176
|
+
}
|
|
177
|
+
previous = current;
|
|
178
|
+
}
|
|
179
|
+
return previous[value.length] === 1;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Bounded iterative glob matching: * stays within a segment; ** spans zero or
|
|
183
|
+
* more whole segments. Allow rules preserve case/trailing slashes. Deny rules
|
|
184
|
+
* opt into conservative case-insensitive matching for non-strict routers.
|
|
185
|
+
*/
|
|
186
|
+
export function matchEndpointPattern(pattern, path, options = {}) {
|
|
187
|
+
if (pattern.length > 500 || path.length > 8192)
|
|
188
|
+
return false;
|
|
189
|
+
let patternPath = pattern.startsWith("/") ? pattern : `/${pattern}`;
|
|
190
|
+
let requestPath = path.startsWith("/") ? path : `/${path}`;
|
|
191
|
+
if (options.caseSensitive === false) {
|
|
192
|
+
patternPath = patternPath.toLowerCase();
|
|
193
|
+
requestPath = requestPath.toLowerCase();
|
|
194
|
+
}
|
|
195
|
+
if (options.ignoreTrailingSlash) {
|
|
196
|
+
patternPath = patternPath.replace(/\/+$/, "") || "/";
|
|
197
|
+
requestPath = requestPath.replace(/\/+$/, "") || "/";
|
|
198
|
+
}
|
|
199
|
+
const patterns = patternPath.slice(1).split("/");
|
|
200
|
+
const parts = requestPath.slice(1).split("/");
|
|
201
|
+
let previous = new Uint8Array(parts.length + 1);
|
|
202
|
+
previous[0] = 1;
|
|
203
|
+
for (const token of patterns) {
|
|
204
|
+
const current = new Uint8Array(parts.length + 1);
|
|
205
|
+
if (token === "**")
|
|
206
|
+
current[0] = previous[0];
|
|
207
|
+
for (let j = 1; j <= parts.length; j++) {
|
|
208
|
+
current[j] =
|
|
209
|
+
token === "**"
|
|
210
|
+
? previous[j] || current[j - 1]
|
|
211
|
+
? 1
|
|
212
|
+
: 0
|
|
213
|
+
: previous[j - 1] && segmentMatches(token, parts[j - 1])
|
|
214
|
+
? 1
|
|
215
|
+
: 0;
|
|
216
|
+
}
|
|
217
|
+
previous = current;
|
|
218
|
+
}
|
|
219
|
+
return previous[parts.length] === 1;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Create a blocked response for scope violations
|
|
223
|
+
*/
|
|
224
|
+
export function createBlockedResponse(method, url) {
|
|
225
|
+
return new Response(JSON.stringify({
|
|
226
|
+
error: "Write operations disabled in read-only mode",
|
|
227
|
+
method,
|
|
228
|
+
url,
|
|
229
|
+
code: "SCOPE_VIOLATION",
|
|
230
|
+
}), {
|
|
231
|
+
status: 403,
|
|
232
|
+
headers: { "Content-Type": "application/json" },
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
// ============================================================================
|
|
236
|
+
// Token Utilities
|
|
237
|
+
// ============================================================================
|
|
238
|
+
/**
|
|
239
|
+
* Generate a cryptographically secure random string for tokens/IDs.
|
|
240
|
+
*
|
|
241
|
+
* @throws {Error} If no secure random source is available.
|
|
242
|
+
* This is intentional - we must never fall back to Math.random()
|
|
243
|
+
* for security-sensitive operations like token generation.
|
|
244
|
+
*/
|
|
245
|
+
export function generateRandomString(length = 32) {
|
|
246
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
247
|
+
let result = "";
|
|
248
|
+
// Check for Web Crypto API (browser and modern Node.js)
|
|
249
|
+
if (typeof crypto === "undefined" || !crypto.getRandomValues) {
|
|
250
|
+
// NO FALLBACK - throw error instead of using insecure Math.random()
|
|
251
|
+
throw new Error("Devora SDK: Secure random number generator not available. " +
|
|
252
|
+
"This environment does not support Web Crypto API. " +
|
|
253
|
+
"For Node.js < 19, ensure you're using the crypto module.");
|
|
254
|
+
}
|
|
255
|
+
// Use rejection sampling to avoid modulo bias
|
|
256
|
+
const maxValidValue = 256 - (256 % chars.length); // 248 for 62 chars
|
|
257
|
+
for (let i = 0; i < length; i++) {
|
|
258
|
+
let randomValue;
|
|
259
|
+
do {
|
|
260
|
+
const randomBytes = new Uint8Array(1);
|
|
261
|
+
crypto.getRandomValues(randomBytes);
|
|
262
|
+
// Non-null assertion is safe: we just created a 1-byte array
|
|
263
|
+
randomValue = randomBytes[0];
|
|
264
|
+
} while (randomValue >= maxValidValue);
|
|
265
|
+
result += chars[randomValue % chars.length];
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Convert ArrayBuffer to hex string
|
|
271
|
+
*/
|
|
272
|
+
export function arrayBufferToHex(buffer) {
|
|
273
|
+
const bytes = new Uint8Array(buffer);
|
|
274
|
+
return Array.from(bytes)
|
|
275
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
276
|
+
.join("");
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Convert hex string to ArrayBuffer
|
|
280
|
+
*/
|
|
281
|
+
export function hexToArrayBuffer(hex) {
|
|
282
|
+
if (hex.length % 2 !== 0) {
|
|
283
|
+
throw new Error(`Invalid hex string: length must be even (got ${hex.length})`);
|
|
284
|
+
}
|
|
285
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
286
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
287
|
+
bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
288
|
+
}
|
|
289
|
+
return bytes.buffer;
|
|
290
|
+
}
|
|
291
|
+
// ============================================================================
|
|
292
|
+
// Error Types
|
|
293
|
+
// ============================================================================
|
|
294
|
+
/**
|
|
295
|
+
* Base error class for Devora SDK errors
|
|
296
|
+
*/
|
|
297
|
+
export class DevoraSDKError extends Error {
|
|
298
|
+
code;
|
|
299
|
+
statusCode;
|
|
300
|
+
constructor(message, code, statusCode = 400) {
|
|
301
|
+
super(message);
|
|
302
|
+
this.name = "DevoraSDKError";
|
|
303
|
+
this.code = code;
|
|
304
|
+
this.statusCode = statusCode;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Security-related errors
|
|
309
|
+
*/
|
|
310
|
+
export class DevoraSecurityError extends DevoraSDKError {
|
|
311
|
+
securityCode;
|
|
312
|
+
constructor(message, securityCode, statusCode = 401) {
|
|
313
|
+
super(message, `SECURITY_${securityCode}`, statusCode);
|
|
314
|
+
this.name = "DevoraSecurityError";
|
|
315
|
+
this.securityCode = securityCode;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Validation errors
|
|
320
|
+
*/
|
|
321
|
+
export class DevoraValidationError extends DevoraSDKError {
|
|
322
|
+
constructor(message, code = "VALIDATION_ERROR") {
|
|
323
|
+
super(message, code, 400);
|
|
324
|
+
this.name = "DevoraValidationError";
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Configuration errors
|
|
329
|
+
*/
|
|
330
|
+
export class DevoraConfigError extends DevoraSDKError {
|
|
331
|
+
constructor(message) {
|
|
332
|
+
super(message, "CONFIG_ERROR", 500);
|
|
333
|
+
this.name = "DevoraConfigError";
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Network/connectivity errors
|
|
338
|
+
*/
|
|
339
|
+
export class DevoraNetworkError extends DevoraSDKError {
|
|
340
|
+
constructor(message) {
|
|
341
|
+
super(message, "NETWORK_ERROR", 503);
|
|
342
|
+
this.name = "DevoraNetworkError";
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Failures raised before an oversized or stalled body is fully materialized. */
|
|
2
|
+
export declare class BodyReadError extends Error {
|
|
3
|
+
readonly code: "BODY_TOO_LARGE" | "BODY_TIMEOUT" | "INVALID_BODY";
|
|
4
|
+
readonly status: 400 | 408 | 413;
|
|
5
|
+
constructor(code: "BODY_TOO_LARGE" | "BODY_TIMEOUT" | "INVALID_BODY", status: 400 | 408 | 413, message: string);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Read the exact body bytes with a size bound and a total deadline, cancelling
|
|
9
|
+
* on failure. A single transport chunk may exceed the limit; it is rejected
|
|
10
|
+
* before being retained. Framework/ingress limits must also bound the
|
|
11
|
+
* transport's own buffering.
|
|
12
|
+
*/
|
|
13
|
+
export declare function readBoundedBytes(request: Pick<Request, "headers" | "body">, maxBytes: number, timeoutMs?: number): Promise<Uint8Array>;
|
|
14
|
+
/** {@link readBoundedBytes}, decoded as UTF-8 (invalid sequences become U+FFFD). */
|
|
15
|
+
export declare function readBoundedBody(request: Pick<Request, "headers" | "body">, maxBytes: number, timeoutMs?: number): Promise<string>;
|
|
16
|
+
//# sourceMappingURL=request-body.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-body.d.ts","sourceRoot":"","sources":["../../src/security/request-body.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,qBAAa,aAAc,SAAQ,KAAK;aAEtB,IAAI,EAAE,gBAAgB,GAAG,cAAc,GAAG,cAAc;aACxD,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG;gBADvB,IAAI,EAAE,gBAAgB,GAAG,cAAc,GAAG,cAAc,EACxD,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EACvC,OAAO,EAAE,MAAM;CAKhB;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACrC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAAC,EAC1C,QAAQ,EAAE,MAAM,EAChB,SAAS,SAAQ,GACf,OAAO,CAAC,UAAU,CAAC,CA8DrB;AAED,oFAAoF;AACpF,wBAAsB,eAAe,CACpC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,MAAM,CAAC,EAC1C,QAAQ,EAAE,MAAM,EAChB,SAAS,SAAQ,GACf,OAAO,CAAC,MAAM,CAAC,CAEjB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** Failures raised before an oversized or stalled body is fully materialized. */
|
|
2
|
+
export class BodyReadError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
status;
|
|
5
|
+
constructor(code, status, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.name = "BodyReadError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Read the exact body bytes with a size bound and a total deadline, cancelling
|
|
14
|
+
* on failure. A single transport chunk may exceed the limit; it is rejected
|
|
15
|
+
* before being retained. Framework/ingress limits must also bound the
|
|
16
|
+
* transport's own buffering.
|
|
17
|
+
*/
|
|
18
|
+
export async function readBoundedBytes(request, maxBytes, timeoutMs = 5_000) {
|
|
19
|
+
if (!Number.isSafeInteger(maxBytes) ||
|
|
20
|
+
maxBytes < 0 ||
|
|
21
|
+
!Number.isFinite(timeoutMs) ||
|
|
22
|
+
timeoutMs <= 0)
|
|
23
|
+
throw new Error("Invalid body budget");
|
|
24
|
+
const declared = request.headers.get("content-length");
|
|
25
|
+
const cancel = () => {
|
|
26
|
+
void request.body?.cancel().catch(() => { });
|
|
27
|
+
};
|
|
28
|
+
if (declared !== null && !/^\d+$/.test(declared)) {
|
|
29
|
+
cancel();
|
|
30
|
+
throw new BodyReadError("INVALID_BODY", 400, "Invalid Content-Length");
|
|
31
|
+
}
|
|
32
|
+
if (declared !== null && Number(declared) > maxBytes) {
|
|
33
|
+
cancel();
|
|
34
|
+
throw new BodyReadError("BODY_TOO_LARGE", 413, "Request body too large");
|
|
35
|
+
}
|
|
36
|
+
if (!request.body)
|
|
37
|
+
return new Uint8Array();
|
|
38
|
+
const reader = request.body.getReader();
|
|
39
|
+
let timer;
|
|
40
|
+
let finished = false;
|
|
41
|
+
try {
|
|
42
|
+
return await Promise.race([
|
|
43
|
+
(async () => {
|
|
44
|
+
const chunks = [];
|
|
45
|
+
let total = 0;
|
|
46
|
+
while (!finished) {
|
|
47
|
+
const { value, done } = await reader.read();
|
|
48
|
+
if (finished)
|
|
49
|
+
break;
|
|
50
|
+
if (done) {
|
|
51
|
+
const bytes = new Uint8Array(total);
|
|
52
|
+
let offset = 0;
|
|
53
|
+
for (const chunk of chunks) {
|
|
54
|
+
bytes.set(chunk, offset);
|
|
55
|
+
offset += chunk.byteLength;
|
|
56
|
+
}
|
|
57
|
+
return bytes;
|
|
58
|
+
}
|
|
59
|
+
total += value.byteLength;
|
|
60
|
+
if (total > maxBytes)
|
|
61
|
+
throw new BodyReadError("BODY_TOO_LARGE", 413, "Request body too large");
|
|
62
|
+
chunks.push(value);
|
|
63
|
+
}
|
|
64
|
+
throw new BodyReadError("BODY_TIMEOUT", 408, "Request body timed out");
|
|
65
|
+
})(),
|
|
66
|
+
new Promise((_, reject) => {
|
|
67
|
+
timer = setTimeout(() => reject(new BodyReadError("BODY_TIMEOUT", 408, "Request body timed out")), timeoutMs);
|
|
68
|
+
}),
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
finished = true;
|
|
73
|
+
if (timer !== undefined)
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
// Do not wait for an uncooperative upstream cancellation.
|
|
76
|
+
void reader.cancel().catch(() => { });
|
|
77
|
+
reader.releaseLock();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** {@link readBoundedBytes}, decoded as UTF-8 (invalid sequences become U+FFFD). */
|
|
81
|
+
export async function readBoundedBody(request, maxBytes, timeoutMs = 5_000) {
|
|
82
|
+
return new TextDecoder().decode(await readBoundedBytes(request, maxBytes, timeoutMs));
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=request-body.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-body.js","sourceRoot":"","sources":["../../src/security/request-body.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devora request signing, version 3.
|
|
3
|
+
*
|
|
4
|
+
* The signature covers the exact bytes on the wire: raw path, raw query and a
|
|
5
|
+
* SHA-256 of the raw body, plus the direction the request travels in. Every
|
|
6
|
+
* header has one accepted spelling. The canonical string joins fields with
|
|
7
|
+
* `\n`; each field's grammar excludes `\n`, so the encoding is injective.
|
|
8
|
+
*
|
|
9
|
+
* This module is pure (no hashing): runtimes supply SHA-256 and HMAC.
|
|
10
|
+
* Conformance vectors: packages/sdks/test/signing-v3-vectors.json.
|
|
11
|
+
*
|
|
12
|
+
* @module @devorash/core/security/signing
|
|
13
|
+
*/
|
|
14
|
+
export declare const SIGNING: {
|
|
15
|
+
readonly VERSION: "3";
|
|
16
|
+
readonly ALGORITHM: "DEVORA-HMAC-SHA256";
|
|
17
|
+
/** Devora backend -> customer backend (the SDK verifies). */
|
|
18
|
+
readonly DEVORA_TO_CUSTOMER: "devora-to-customer";
|
|
19
|
+
/** Customer backend SDK -> Devora backend (Devora verifies). */
|
|
20
|
+
readonly CUSTOMER_TO_DEVORA: "customer-to-devora";
|
|
21
|
+
/** SHA-256 of an empty body. */
|
|
22
|
+
readonly EMPTY_BODY_SHA256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
|
23
|
+
};
|
|
24
|
+
export type SigningDirection = typeof SIGNING.DEVORA_TO_CUSTOMER | typeof SIGNING.CUSTOMER_TO_DEVORA;
|
|
25
|
+
/** Exact header grammars. (?![\s\S]) requires the actual end of input;
|
|
26
|
+
* JavaScript's $ also accepts a position before a final newline. */
|
|
27
|
+
export declare const SIGNED_HEADER_PATTERNS: {
|
|
28
|
+
readonly signatureVersion: RegExp;
|
|
29
|
+
readonly keyId: RegExp;
|
|
30
|
+
readonly orgId: RegExp;
|
|
31
|
+
readonly sentAt: RegExp;
|
|
32
|
+
readonly requestId: RegExp;
|
|
33
|
+
readonly signature: RegExp;
|
|
34
|
+
readonly method: RegExp;
|
|
35
|
+
readonly bodySha256: RegExp;
|
|
36
|
+
/** The full secret string; its ASCII bytes are the HMAC key. */
|
|
37
|
+
readonly secretKey: RegExp;
|
|
38
|
+
};
|
|
39
|
+
export type SignatureErrorCode = "INVALID_SIGNATURE_HEADERS" | "UNSUPPORTED_SIGNATURE_VERSION" | "ORG_MISMATCH" | "TIMESTAMP_EXPIRED" | "INVALID_REQUEST_TARGET" | "UNSUPPORTED_CONTENT_ENCODING" | "INVALID_SIGNATURE" | "REPLAYED_REQUEST" | "REPLAY_STORE_UNAVAILABLE";
|
|
40
|
+
/** Header values as frameworks expose them. */
|
|
41
|
+
export type HeaderMap = Record<string, string | string[] | undefined>;
|
|
42
|
+
export interface SignatureHeaders {
|
|
43
|
+
keyId: string;
|
|
44
|
+
orgId: string;
|
|
45
|
+
sentAt: string;
|
|
46
|
+
requestId: string;
|
|
47
|
+
signature: string;
|
|
48
|
+
}
|
|
49
|
+
export type SignatureHeaderResult = {
|
|
50
|
+
valid: true;
|
|
51
|
+
headers: SignatureHeaders;
|
|
52
|
+
} | {
|
|
53
|
+
valid: false;
|
|
54
|
+
errorCode: SignatureErrorCode;
|
|
55
|
+
error: string;
|
|
56
|
+
};
|
|
57
|
+
export interface CanonicalFields {
|
|
58
|
+
direction: SigningDirection;
|
|
59
|
+
keyId: string;
|
|
60
|
+
orgId: string;
|
|
61
|
+
sentAt: string;
|
|
62
|
+
requestId: string;
|
|
63
|
+
method: string;
|
|
64
|
+
/** Raw, percent-encoded path (mount-relative for devora-to-customer). */
|
|
65
|
+
path: string;
|
|
66
|
+
/** Raw query after the first `?`, exactly as sent; "" when absent. */
|
|
67
|
+
query: string;
|
|
68
|
+
/** Lowercase hex SHA-256 of the exact body bytes. */
|
|
69
|
+
bodySha256: string;
|
|
70
|
+
}
|
|
71
|
+
/** UTF-8 percent-encode every byte outside `A-Z a-z 0-9 - _ . ~`, with uppercase hex. */
|
|
72
|
+
export declare function strictEncode(value: string): string;
|
|
73
|
+
/**
|
|
74
|
+
* Encode one path parameter. `""`, `.` and `..` are refused: URL parsers
|
|
75
|
+
* remove dot segments (even `%2E%2E`), so they cannot be sent as signed.
|
|
76
|
+
*/
|
|
77
|
+
export declare function encodePathParam(value: string): string;
|
|
78
|
+
/** Strict-encoded `key=value` pairs, in the given order (the order is signed). */
|
|
79
|
+
export declare function buildStrictQuery(pairs: ReadonlyArray<readonly [string, string]>): string;
|
|
80
|
+
/**
|
|
81
|
+
* A raw request path as signed: absolute, printable ASCII, uppercase escapes,
|
|
82
|
+
* and no empty or dot segments.
|
|
83
|
+
*/
|
|
84
|
+
export declare function isValidSignedPath(path: string): boolean;
|
|
85
|
+
/** A raw query as signed: printable ASCII, no fragment, uppercase escapes. */
|
|
86
|
+
export declare function isValidSignedQuery(query: string): boolean;
|
|
87
|
+
/** Build the canonical string. Throws if any field is outside its grammar. */
|
|
88
|
+
export declare function buildCanonicalString(fields: CanonicalFields): string;
|
|
89
|
+
/**
|
|
90
|
+
* The single value of a header, or `null` when it is present more than once
|
|
91
|
+
* (an array, or a comma-joined value that the grammar then rejects).
|
|
92
|
+
*/
|
|
93
|
+
export declare function getSingleHeader(headers: HeaderMap, name: string): string | undefined | null;
|
|
94
|
+
/** Parse and validate the signature headers. Never first-wins, never trims. */
|
|
95
|
+
export declare function parseSignatureHeaders(headers: HeaderMap): SignatureHeaderResult;
|
|
96
|
+
/** Accept only an absent or `identity` Content-Encoding (the body digest is over wire bytes). */
|
|
97
|
+
export declare function hasIdentityContentEncoding(headers: HeaderMap): boolean;
|
|
98
|
+
/** Replay-store namespace: version, direction and key. */
|
|
99
|
+
export declare function replayNamespace(direction: SigningDirection, keyId: string): string;
|
|
100
|
+
/**
|
|
101
|
+
* Nonce lifetime covering the whole acceptance window: the floor second of the
|
|
102
|
+
* timestamp check plus one second of clock jitter.
|
|
103
|
+
*/
|
|
104
|
+
export declare function replayExpiresAtMs(sentAt: number, nowSeconds: number, toleranceSeconds: number): number;
|
|
105
|
+
/**
|
|
106
|
+
* Parse a verified raw query into a null-prototype map. Repeated keys become
|
|
107
|
+
* arrays in wire order; `__proto__` and `constructor` are ordinary keys.
|
|
108
|
+
*/
|
|
109
|
+
export declare function parseVerifiedQuery(query: string): Record<string, string | string[]>;
|
|
110
|
+
export type VerifiedBodyResult = {
|
|
111
|
+
ok: true;
|
|
112
|
+
value: unknown;
|
|
113
|
+
} | {
|
|
114
|
+
ok: false;
|
|
115
|
+
error: string;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Decode a verified body: empty means `undefined`; otherwise it must be JSON
|
|
119
|
+
* (declared `application/json`) and strictly valid UTF-8.
|
|
120
|
+
*/
|
|
121
|
+
export declare function parseVerifiedJsonBody(bytes: Uint8Array, contentType: string | undefined | null): VerifiedBodyResult;
|
|
122
|
+
/**
|
|
123
|
+
* Last `count` segments of a raw path, i.e. the path relative to a mount whose
|
|
124
|
+
* route template has `count` segments. `null` when the path is shorter.
|
|
125
|
+
*/
|
|
126
|
+
export declare function routeRelativePath(rawPath: string, routeTemplate: string): string | null;
|
|
127
|
+
//# sourceMappingURL=signing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../../src/security/signing.ts"],"names":[],"mappings":"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"}
|