@selvajs/server 2.0.0-beta.18 → 2.0.0-beta.20
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/guards.d.ts +20 -3
- package/dist/access/guards.d.ts.map +1 -1
- package/dist/access/guards.js +44 -7
- package/dist/access/guards.js.map +1 -1
- package/dist/api/deps.d.ts +8 -1
- package/dist/api/deps.d.ts.map +1 -1
- package/dist/api/deps.js +2 -1
- package/dist/api/deps.js.map +1 -1
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +1 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/rate-limit.d.ts +6 -0
- package/dist/api/rate-limit.d.ts.map +1 -1
- package/dist/api/rate-limit.js +15 -0
- package/dist/api/rate-limit.js.map +1 -1
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +2 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/settings.d.ts +2 -0
- package/dist/config/settings.d.ts.map +1 -0
- package/dist/config/settings.js +261 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/handlers/me.d.ts.map +1 -1
- package/dist/handlers/me.js +4 -0
- package/dist/handlers/me.js.map +1 -1
- package/dist/handlers/projects.d.ts.map +1 -1
- package/dist/handlers/projects.js +14 -27
- package/dist/handlers/projects.js.map +1 -1
- package/dist/handlers/shareLinks.d.ts.map +1 -1
- package/dist/handlers/shareLinks.js +5 -2
- package/dist/handlers/shareLinks.js.map +1 -1
- package/dist/http/api-token.d.ts.map +1 -1
- package/dist/http/api-token.js +6 -2
- package/dist/http/api-token.js.map +1 -1
- package/dist/notifications/smtp-config.d.ts.map +1 -1
- package/dist/notifications/smtp-config.js +4 -2
- package/dist/notifications/smtp-config.js.map +1 -1
- package/dist/oauth/index.d.ts +8 -5
- package/dist/oauth/index.d.ts.map +1 -1
- package/dist/oauth/index.js +11 -5
- package/dist/oauth/index.js.map +1 -1
- package/dist/ops/deploymentConfig.d.ts +1 -0
- package/dist/ops/deploymentConfig.d.ts.map +1 -1
- package/dist/ops/deploymentConfig.js +15 -0
- package/dist/ops/deploymentConfig.js.map +1 -1
- package/dist/testing/harness.d.ts +2 -2
- package/dist/testing/harness.d.ts.map +1 -1
- package/dist/testing/harness.js +2 -1
- package/dist/testing/harness.js.map +1 -1
- package/dist/tokens/api-token-codec.d.ts +2 -2
- package/dist/tokens/api-token-codec.d.ts.map +1 -1
- package/dist/tokens/api-token-codec.js +7 -3
- package/dist/tokens/api-token-codec.js.map +1 -1
- package/dist/tokens/api-token-holder.d.ts +2 -0
- package/dist/tokens/api-token-holder.d.ts.map +1 -1
- package/dist/tokens/api-token-holder.js +18 -5
- package/dist/tokens/api-token-holder.js.map +1 -1
- package/dist/tokens/index.d.ts +1 -1
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +3 -3
- package/dist/tokens/index.js.map +1 -1
- package/dist/tokens/token-codec.d.ts +23 -18
- package/dist/tokens/token-codec.d.ts.map +1 -1
- package/dist/tokens/token-codec.js +37 -24
- package/dist/tokens/token-codec.js.map +1 -1
- package/package.json +10 -5
|
@@ -1,28 +1,23 @@
|
|
|
1
|
-
import { randomBytes, createHmac, timingSafeEqual } from 'node:crypto';
|
|
1
|
+
import { randomBytes, createHash, createHmac, timingSafeEqual } from 'node:crypto';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* invites, and anything shaped like them).
|
|
3
|
+
* Token codec — the primitive behind capability-URL tokens (share links,
|
|
4
|
+
* invites, API tokens, and anything shaped like them).
|
|
5
5
|
*
|
|
6
6
|
* ## Format
|
|
7
7
|
* raw = `<prefix><base64url(32 random bytes)>`
|
|
8
|
-
* hash = base64url(
|
|
8
|
+
* hash = `sha256:` + base64url( SHA-256(raw) )
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* The raw token is handed back once at mint time and never stored; the store
|
|
11
|
+
* holds only the hash. With 256 bits of entropy in the raw token, an unkeyed
|
|
12
|
+
* hash is as unreplayable from a database leak as a keyed one, and rotating
|
|
13
|
+
* the instance secret no longer invalidates stored tokens.
|
|
11
14
|
*
|
|
12
|
-
*
|
|
13
|
-
* back exactly once at mint time and never stored. This is what end-users
|
|
14
|
-
* carry in `?token=…`.
|
|
15
|
+
* ## Legacy hashes
|
|
15
16
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* Resolution hashes the supplied token with the same secret and looks up an
|
|
22
|
-
* exact match against the stored hash.
|
|
23
|
-
*
|
|
24
|
-
* Rotating the secret invalidates every token minted under it — give each token
|
|
25
|
-
* family (each prefix) its own secret so they rotate independently.
|
|
17
|
+
* Tokens minted before this format were stored as
|
|
18
|
+
* `base64url(HMAC-SHA256(secret, raw))`. `lookupHashes` returns those forms
|
|
19
|
+
* too, under the current and the previous secret, so they keep resolving
|
|
20
|
+
* through a rotation until they expire.
|
|
26
21
|
*/
|
|
27
22
|
/**
|
|
28
23
|
* Minimum accepted secret length, matching the "random ≥32-byte string"
|
|
@@ -31,18 +26,23 @@ import { randomBytes, createHmac, timingSafeEqual } from 'node:crypto';
|
|
|
31
26
|
*/
|
|
32
27
|
export const MIN_TOKEN_SECRET_LENGTH = 32;
|
|
33
28
|
export function createTokenCodec(config) {
|
|
34
|
-
const { prefix, secret } = config;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
`
|
|
29
|
+
const { prefix, secret, previousSecret } = config;
|
|
30
|
+
for (const s of [secret, ...(previousSecret ? [previousSecret] : [])]) {
|
|
31
|
+
if (s.length < MIN_TOKEN_SECRET_LENGTH) {
|
|
32
|
+
throw new Error(`Secret for "${prefix}" tokens must be at least ${MIN_TOKEN_SECRET_LENGTH} characters ` +
|
|
33
|
+
`(got ${s.length}). Generate one with: openssl rand -base64 32`);
|
|
34
|
+
}
|
|
38
35
|
}
|
|
36
|
+
const hashToken = (raw) => 'sha256:' + createHash('sha256').update(raw).digest('base64url');
|
|
39
37
|
return {
|
|
40
38
|
prefix,
|
|
41
39
|
mintRawToken() {
|
|
42
40
|
return prefix + randomBytes(32).toString('base64url');
|
|
43
41
|
},
|
|
44
|
-
hashToken
|
|
45
|
-
|
|
42
|
+
hashToken,
|
|
43
|
+
lookupHashes(raw) {
|
|
44
|
+
const legacy = [secret, ...(previousSecret ? [previousSecret] : [])].map((key) => createHmac('sha256', key).update(raw).digest('base64url'));
|
|
45
|
+
return [hashToken(raw), ...legacy];
|
|
46
46
|
},
|
|
47
47
|
hashesEqual(a, b) {
|
|
48
48
|
const ab = Buffer.from(a);
|
|
@@ -56,4 +56,17 @@ export function createTokenCodec(config) {
|
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Look a raw token up by each hash its record may carry. Sequential rather than
|
|
61
|
+
* one `IN` query so stores keep their single-hash interface; records are never
|
|
62
|
+
* rewritten here, so there is no rewrite for a lookup to race.
|
|
63
|
+
*/
|
|
64
|
+
export async function findByToken(codec, raw, lookup) {
|
|
65
|
+
for (const hash of codec.lookupHashes(raw)) {
|
|
66
|
+
const found = await lookup(hash);
|
|
67
|
+
if (found)
|
|
68
|
+
return found;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
59
72
|
//# sourceMappingURL=token-codec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-codec.js","sourceRoot":"","sources":["../../src/tokens/token-codec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"token-codec.js","sourceRoot":"","sources":["../../src/tokens/token-codec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnF;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AA0B1C,MAAM,UAAU,gBAAgB,CAAC,MAAwB;IACxD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,CAAC;IAClD,KAAK,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QACvE,IAAI,CAAC,CAAC,MAAM,GAAG,uBAAuB,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CACd,eAAe,MAAM,6BAA6B,uBAAuB,cAAc;gBACtF,QAAQ,CAAC,CAAC,MAAM,+CAA+C,CAChE,CAAC;QACH,CAAC;IACF,CAAC;IAED,MAAM,SAAS,GAAG,CAAC,GAAW,EAAU,EAAE,CACzC,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAElE,OAAO;QACN,MAAM;QACN,YAAY;YACX,OAAO,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QACvD,CAAC;QACD,SAAS;QACT,YAAY,CAAC,GAAW;YACvB,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAChF,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CACzD,CAAC;YACF,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,MAAM,CAAC,CAAC;QACpC,CAAC;QACD,WAAW,CAAC,CAAS,EAAE,CAAS;YAC/B,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC1B,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,MAAM;gBAAE,OAAO,KAAK,CAAC;YAC1C,OAAO,eAAe,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QAChC,CAAC;QACD,cAAc,CAAC,KAAa;YAC3B,OAAO,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC;KACD,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAChC,KAAuC,EACvC,GAAW,EACX,MAA2C;IAE3C,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IACzB,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selvajs/server",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.20",
|
|
4
4
|
"description": "Transport-agnostic server building blocks for apps built on the Selva engine (limits, rate limiting, SSRF guard, remote-definition fetch, definition service + render loader); the solve core lives in @selvajs/solve/server",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "VektorNode",
|
|
@@ -31,6 +31,11 @@
|
|
|
31
31
|
"types": "./dist/compute/index.d.ts",
|
|
32
32
|
"import": "./dist/compute/index.js"
|
|
33
33
|
},
|
|
34
|
+
"./config": {
|
|
35
|
+
"selva-source": "./src/config/index.ts",
|
|
36
|
+
"types": "./dist/config/index.d.ts",
|
|
37
|
+
"import": "./dist/config/index.js"
|
|
38
|
+
},
|
|
34
39
|
"./definitions": {
|
|
35
40
|
"selva-source": "./src/definitions/index.ts",
|
|
36
41
|
"types": "./dist/definitions/index.d.ts",
|
|
@@ -116,9 +121,9 @@
|
|
|
116
121
|
"dependencies": {
|
|
117
122
|
"zod": "^4.6.7",
|
|
118
123
|
"@selvajs/compute": "^5.0.0-beta.9",
|
|
119
|
-
"@selvajs/notifications": "^0.3.0-beta.
|
|
120
|
-
"@selvajs/
|
|
121
|
-
"@selvajs/
|
|
124
|
+
"@selvajs/notifications": "^0.3.0-beta.9",
|
|
125
|
+
"@selvajs/schemas": "^5.3.0-beta.3",
|
|
126
|
+
"@selvajs/platform": "^1.0.0-beta.9"
|
|
122
127
|
},
|
|
123
128
|
"peerDependencies": {
|
|
124
129
|
"@sentry/node": ">=8",
|
|
@@ -144,7 +149,7 @@
|
|
|
144
149
|
"typescript": "~6.0.3",
|
|
145
150
|
"vitest": "^5.0.5",
|
|
146
151
|
"@selvajs/config": "0.0.4",
|
|
147
|
-
"@selvajs/local-provider": "0.16.0-beta.
|
|
152
|
+
"@selvajs/local-provider": "0.16.0-beta.5"
|
|
148
153
|
},
|
|
149
154
|
"scripts": {
|
|
150
155
|
"build": "tsc",
|