@selvajs/server 0.2.0-beta.1 → 0.2.0-beta.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/dist/access/__tests__/project-access-input.test.d.ts +2 -0
- package/dist/access/__tests__/project-access-input.test.d.ts.map +1 -0
- package/dist/access/__tests__/project-access-input.test.js +146 -0
- package/dist/access/__tests__/project-access-input.test.js.map +1 -0
- package/dist/access/index.d.ts +2 -0
- package/dist/access/index.d.ts.map +1 -0
- package/dist/access/index.js +4 -0
- package/dist/access/index.js.map +1 -0
- package/dist/access/project-access-input.d.ts +52 -0
- package/dist/access/project-access-input.d.ts.map +1 -0
- package/dist/access/project-access-input.js +56 -0
- package/dist/access/project-access-input.js.map +1 -0
- package/dist/compute/__tests__/client-cache.test.js +52 -1
- package/dist/compute/__tests__/client-cache.test.js.map +1 -1
- package/dist/compute/__tests__/rate-limit.test.js +36 -0
- package/dist/compute/__tests__/rate-limit.test.js.map +1 -1
- package/dist/compute/__tests__/solve-pipeline.test.js +60 -13
- package/dist/compute/__tests__/solve-pipeline.test.js.map +1 -1
- package/dist/compute/__tests__/transform-input.test.js +7 -1
- package/dist/compute/__tests__/transform-input.test.js.map +1 -1
- package/dist/compute/client-cache.d.ts +20 -8
- package/dist/compute/client-cache.d.ts.map +1 -1
- package/dist/compute/client-cache.js +12 -3
- package/dist/compute/client-cache.js.map +1 -1
- package/dist/compute/limits.d.ts +10 -0
- package/dist/compute/limits.d.ts.map +1 -1
- package/dist/compute/limits.js +3 -1
- package/dist/compute/limits.js.map +1 -1
- package/dist/compute/rate-limit.d.ts +12 -0
- package/dist/compute/rate-limit.d.ts.map +1 -1
- package/dist/compute/rate-limit.js +18 -0
- package/dist/compute/rate-limit.js.map +1 -1
- package/dist/compute/solve-pipeline.d.ts.map +1 -1
- package/dist/compute/solve-pipeline.js +20 -7
- package/dist/compute/solve-pipeline.js.map +1 -1
- package/dist/compute/transform-input.d.ts.map +1 -1
- package/dist/compute/transform-input.js +21 -6
- package/dist/compute/transform-input.js.map +1 -1
- package/dist/errors/SentryErrorReporter.d.ts +17 -0
- package/dist/errors/SentryErrorReporter.d.ts.map +1 -0
- package/dist/errors/SentryErrorReporter.js +60 -0
- package/dist/errors/SentryErrorReporter.js.map +1 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +3 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/http/__tests__/body-size.test.d.ts +2 -0
- package/dist/http/__tests__/body-size.test.d.ts.map +1 -0
- package/dist/http/__tests__/body-size.test.js +24 -0
- package/dist/http/__tests__/body-size.test.js.map +1 -0
- package/dist/http/__tests__/route-classifier.test.d.ts +2 -0
- package/dist/http/__tests__/route-classifier.test.d.ts.map +1 -0
- package/dist/http/__tests__/route-classifier.test.js +54 -0
- package/dist/http/__tests__/route-classifier.test.js.map +1 -0
- package/dist/http/__tests__/safe-redirect.test.d.ts +2 -0
- package/dist/http/__tests__/safe-redirect.test.d.ts.map +1 -0
- package/dist/http/__tests__/safe-redirect.test.js +44 -0
- package/dist/http/__tests__/safe-redirect.test.js.map +1 -0
- package/dist/http/__tests__/security-headers.test.d.ts +2 -0
- package/dist/http/__tests__/security-headers.test.d.ts.map +1 -0
- package/dist/http/__tests__/security-headers.test.js +24 -0
- package/dist/http/__tests__/security-headers.test.js.map +1 -0
- package/dist/http/body-size.d.ts +21 -0
- package/dist/http/body-size.d.ts.map +1 -0
- package/dist/http/body-size.js +20 -0
- package/dist/http/body-size.js.map +1 -0
- package/dist/http/index.d.ts +5 -0
- package/dist/http/index.d.ts.map +1 -0
- package/dist/http/index.js +6 -0
- package/dist/http/index.js.map +1 -0
- package/dist/http/redirect.d.ts +11 -0
- package/dist/http/redirect.d.ts.map +1 -0
- package/dist/http/redirect.js +20 -0
- package/dist/http/redirect.js.map +1 -0
- package/dist/http/route-classifier.d.ts +47 -0
- package/dist/http/route-classifier.d.ts.map +1 -0
- package/dist/http/route-classifier.js +37 -0
- package/dist/http/route-classifier.js.map +1 -0
- package/dist/http/security-headers.d.ts +31 -0
- package/dist/http/security-headers.d.ts.map +1 -0
- package/dist/http/security-headers.js +31 -0
- package/dist/http/security-headers.js.map +1 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/ops/__tests__/semver.test.d.ts +2 -0
- package/dist/ops/__tests__/semver.test.d.ts.map +1 -0
- package/dist/ops/__tests__/semver.test.js +38 -0
- package/dist/ops/__tests__/semver.test.js.map +1 -0
- package/dist/ops/index.d.ts +2 -0
- package/dist/ops/index.d.ts.map +1 -0
- package/dist/ops/index.js +3 -0
- package/dist/ops/index.js.map +1 -0
- package/dist/ops/semver.d.ts +35 -0
- package/dist/ops/semver.d.ts.map +1 -0
- package/dist/ops/semver.js +55 -0
- package/dist/ops/semver.js.map +1 -0
- package/dist/tokens/__tests__/token-codec.test.d.ts +2 -0
- package/dist/tokens/__tests__/token-codec.test.d.ts.map +1 -0
- package/dist/tokens/__tests__/token-codec.test.js +50 -0
- package/dist/tokens/__tests__/token-codec.test.js.map +1 -0
- package/dist/tokens/index.d.ts +2 -0
- package/dist/tokens/index.d.ts.map +1 -0
- package/dist/tokens/index.js +3 -0
- package/dist/tokens/index.js.map +1 -0
- package/dist/tokens/token-codec.d.ts +54 -0
- package/dist/tokens/token-codec.d.ts.map +1 -0
- package/dist/tokens/token-codec.js +62 -0
- package/dist/tokens/token-codec.js.map +1 -0
- package/package.json +35 -2
- package/src/access/__tests__/project-access-input.test.ts +175 -0
- package/src/access/index.ts +9 -0
- package/src/access/project-access-input.ts +145 -0
- package/src/compute/__tests__/client-cache.test.ts +61 -1
- package/src/compute/__tests__/rate-limit.test.ts +37 -0
- package/src/compute/__tests__/solve-pipeline.test.ts +60 -13
- package/src/compute/__tests__/transform-input.test.ts +8 -1
- package/src/compute/client-cache.ts +35 -13
- package/src/compute/limits.ts +13 -1
- package/src/compute/rate-limit.ts +27 -0
- package/src/compute/solve-pipeline.ts +22 -7
- package/src/compute/transform-input.ts +20 -6
- package/src/errors/SentryErrorReporter.ts +88 -0
- package/src/errors/index.ts +3 -0
- package/src/http/__tests__/body-size.test.ts +27 -0
- package/src/http/__tests__/route-classifier.test.ts +60 -0
- package/src/http/__tests__/safe-redirect.test.ts +47 -0
- package/src/http/__tests__/security-headers.test.ts +29 -0
- package/src/http/body-size.ts +24 -0
- package/src/http/index.ts +10 -0
- package/src/http/redirect.ts +16 -0
- package/src/http/route-classifier.ts +74 -0
- package/src/http/security-headers.ts +42 -0
- package/src/index.ts +11 -2
- package/src/ops/__tests__/semver.test.ts +42 -0
- package/src/ops/index.ts +3 -0
- package/src/ops/semver.ts +60 -0
- package/src/tokens/__tests__/token-codec.test.ts +63 -0
- package/src/tokens/index.ts +8 -0
- package/src/tokens/token-codec.ts +86 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { randomBytes, createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HMAC token codec — the primitive behind capability-URL tokens (share links,
|
|
5
|
+
* invites, and anything shaped like them).
|
|
6
|
+
*
|
|
7
|
+
* ## Two-part design
|
|
8
|
+
*
|
|
9
|
+
* Each minted token involves TWO secrets:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Per-token secret** — generated in `mintRawToken()` from 32 random
|
|
12
|
+
* bytes, returned to the caller exactly once at mint time. Different
|
|
13
|
+
* every token. This is what end-users carry in `?token=…`.
|
|
14
|
+
*
|
|
15
|
+
* 2. **Instance-wide HMAC secret** (`config.secret`) — used to hash tokens
|
|
16
|
+
* for storage. The store sees only `HMAC-SHA256(secret, token)`. A
|
|
17
|
+
* DB-only leak therefore can't be replayed: the attacker would need the
|
|
18
|
+
* secret too, and it lives in env, not the database.
|
|
19
|
+
*
|
|
20
|
+
* ## Format
|
|
21
|
+
* raw = `<prefix><base64url(32 random bytes)>`
|
|
22
|
+
* hash = base64url( HMAC-SHA256(secret, raw) )
|
|
23
|
+
*
|
|
24
|
+
* The resolution path hashes the supplied token with the same secret and looks
|
|
25
|
+
* up by exact match against the stored hash.
|
|
26
|
+
*
|
|
27
|
+
* Rotating the secret invalidates every token minted under it — give each
|
|
28
|
+
* token family (each prefix) its own secret so they rotate independently.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Minimum accepted secret length. Matches the "random ≥32-byte string"
|
|
33
|
+
* guidance everywhere these secrets are documented; enforced here so a
|
|
34
|
+
* short dev secret can't reach production silently (presence-only checks
|
|
35
|
+
* let a 4-char secret through).
|
|
36
|
+
*/
|
|
37
|
+
export const MIN_TOKEN_SECRET_LENGTH = 32;
|
|
38
|
+
|
|
39
|
+
export interface TokenCodecConfig {
|
|
40
|
+
/** Token-family marker prepended to every raw token, e.g. `share_`. */
|
|
41
|
+
prefix: string;
|
|
42
|
+
/** Instance-wide HMAC secret. Must be at least 32 characters. */
|
|
43
|
+
secret: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface TokenCodec {
|
|
47
|
+
/** Mint: generate a fresh raw token. Show to the caller exactly once. */
|
|
48
|
+
mintRawToken(): string;
|
|
49
|
+
/** Hash a raw token to its store-side representation. */
|
|
50
|
+
hashToken(raw: string): string;
|
|
51
|
+
/** Constant-time equality for two stored hashes. */
|
|
52
|
+
hashesEqual(a: string, b: string): boolean;
|
|
53
|
+
/** Recognize this codec's token format on inbound requests. */
|
|
54
|
+
looksLikeToken(value: string): boolean;
|
|
55
|
+
/** The configured prefix, for callers that build URLs or error copy. */
|
|
56
|
+
readonly prefix: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createTokenCodec(config: TokenCodecConfig): TokenCodec {
|
|
60
|
+
const { prefix, secret } = config;
|
|
61
|
+
if (secret.length < MIN_TOKEN_SECRET_LENGTH) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Secret for "${prefix}" tokens must be at least ${MIN_TOKEN_SECRET_LENGTH} characters ` +
|
|
64
|
+
`(got ${secret.length}). Generate one with: openssl rand -base64 32`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
prefix,
|
|
70
|
+
mintRawToken(): string {
|
|
71
|
+
return prefix + randomBytes(32).toString('base64url');
|
|
72
|
+
},
|
|
73
|
+
hashToken(raw: string): string {
|
|
74
|
+
return createHmac('sha256', secret).update(raw).digest('base64url');
|
|
75
|
+
},
|
|
76
|
+
hashesEqual(a: string, b: string): boolean {
|
|
77
|
+
const ab = Buffer.from(a);
|
|
78
|
+
const bb = Buffer.from(b);
|
|
79
|
+
if (ab.length !== bb.length) return false;
|
|
80
|
+
return timingSafeEqual(ab, bb);
|
|
81
|
+
},
|
|
82
|
+
looksLikeToken(value: string): boolean {
|
|
83
|
+
return value.startsWith(prefix);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|