@voltro/protocol 0.5.0 → 0.6.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/CHANGELOG.md +48 -1
- package/dist/apikey.d.ts +6 -0
- package/dist/apikey.js +4 -1
- package/dist/index.d.ts +202 -0
- package/dist/index.js +278 -230
- package/dist/rest.js +1 -1
- package/dist/{serverErrorBus-XDdIJk0c.js → serverErrorBus-B3hDwGoL.js} +0 -0
- package/dist/session.d.ts +22 -11
- package/dist/session.js +40 -35
- package/package.json +2 -2
package/dist/rest.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as e, i as t, o as n, r, t as i } from "./serverErrorBus-
|
|
1
|
+
import { a as e, i as t, o as n, r, t as i } from "./serverErrorBus-B3hDwGoL.js";
|
|
2
2
|
import { s as a } from "./auth-DCE6m7Bo.js";
|
|
3
3
|
import { Effect as o, Schema as s } from "effect";
|
|
4
4
|
//#region src/publicApi.ts
|
|
Binary file
|
package/dist/session.d.ts
CHANGED
|
@@ -2,9 +2,9 @@ import { Schema } from 'effect';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Fail-closed check that a real session secret is configured. Throws a plain
|
|
5
|
-
* `Error` (a fatal boot condition, not a wire error) when
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
5
|
+
* `Error` (a fatal boot condition, not a wire error) when
|
|
6
|
+
* `VOLTRO_SESSION_SECRET` is missing or too short. Idempotent — safe to call
|
|
7
|
+
* from every serve entry.
|
|
8
8
|
*/
|
|
9
9
|
export declare const assertProductionSessionSecret: (env?: NodeJS.ProcessEnv) => void;
|
|
10
10
|
|
|
@@ -45,9 +45,9 @@ export declare interface KeyedSecret {
|
|
|
45
45
|
readonly secret: string;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/** Minimum length we accept for a
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
/** Minimum length we accept for a session secret. 32 bytes of entropy is the
|
|
49
|
+
* floor for HMAC-SHA256; shorter values are almost always hand-typed
|
|
50
|
+
* placeholders, not real secrets. */
|
|
51
51
|
export declare const MIN_SESSION_SECRET_LENGTH: 32;
|
|
52
52
|
|
|
53
53
|
/**
|
|
@@ -56,14 +56,27 @@ export declare const MIN_SESSION_SECRET_LENGTH: 32;
|
|
|
56
56
|
*/
|
|
57
57
|
export declare const readCookie: (header: string | undefined, name: string) => string | undefined;
|
|
58
58
|
|
|
59
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* The same resolution as `resolveSessionSecrets`, but yielding `undefined`
|
|
61
|
+
* instead of throwing when `VOLTRO_SESSION_SECRET` is unset.
|
|
62
|
+
*
|
|
63
|
+
* This exists for callers that were GIVEN a secret explicitly and consult the
|
|
64
|
+
* environment only for adornment — the `kid` to stamp, and any `previous` key
|
|
65
|
+
* to also accept during a rotation. For them an unconfigured environment is
|
|
66
|
+
* simply "no rotation set up", not an error: they already hold everything
|
|
67
|
+
* needed to sign and verify. Making them go through the throwing resolver
|
|
68
|
+
* would turn a working explicit-secret setup into a boot failure.
|
|
69
|
+
*/
|
|
70
|
+
export declare const resolveOptionalSessionSecrets: () => SessionSecrets | undefined;
|
|
71
|
+
|
|
72
|
+
/** Resolve the HMAC session secret. Throws when unset — there is no fallback. */
|
|
60
73
|
export declare const resolveSessionSecret: () => string;
|
|
61
74
|
|
|
62
75
|
/**
|
|
63
76
|
* Resolve the keyed session-secret set for multi-key rotation.
|
|
64
77
|
*
|
|
65
|
-
* - `current` is built from `VOLTRO_SESSION_SECRET` (
|
|
66
|
-
*
|
|
78
|
+
* - `current` is built from `VOLTRO_SESSION_SECRET` (required — throws
|
|
79
|
+
* when unset), keyed `VOLTRO_SESSION_KID` (defaults to `'k0'`).
|
|
67
80
|
* - `previous` is built from `VOLTRO_SESSION_SECRET_PREVIOUS`, keyed
|
|
68
81
|
* `VOLTRO_SESSION_KID_PREVIOUS` (defaults to `'k-previous'` — the kid
|
|
69
82
|
* is a non-secret label, so rotation needs only the secret var).
|
|
@@ -257,6 +270,4 @@ export declare const verifySession: (cookieValue: string, secret: string) => Sub
|
|
|
257
270
|
*/
|
|
258
271
|
export declare const verifySessionKeyed: (cookieValue: string, secrets: SessionSecrets, options?: VerifyOptions) => VerifyResult | null;
|
|
259
272
|
|
|
260
|
-
export declare const VOLTRO_DEV_SESSION_SECRET: "voltro-dev-session-secret-32b-DO-NOT-USE-IN-PROD";
|
|
261
|
-
|
|
262
273
|
export { }
|
package/dist/session.js
CHANGED
|
@@ -7,65 +7,70 @@ var i = t.Struct({
|
|
|
7
7
|
exp: t.Number,
|
|
8
8
|
iat: t.Number,
|
|
9
9
|
kid: t.optional(t.String)
|
|
10
|
-
}), a = "k0", o = "k-previous", s =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
}), a = "k0", o = "k-previous", s = () => {
|
|
11
|
+
throw Error("VOLTRO_SESSION_SECRET is not set. It signs and verifies session cookies, and the framework refuses to invent one at this point: a fixed fallback would be readable by anyone with the source, making every session forgeable. `voltro dev` mints a project-local value into .env.local on boot; for a deployment, generate one with `voltro secret generate session`.");
|
|
12
|
+
}, c = () => process.env.VOLTRO_SESSION_SECRET ?? s(), l = () => d() ?? { current: {
|
|
13
|
+
kid: u(),
|
|
14
|
+
secret: c()
|
|
15
|
+
} }, u = () => process.env.VOLTRO_SESSION_KID ?? "k0", d = () => {
|
|
16
|
+
let e = process.env.VOLTRO_SESSION_SECRET;
|
|
17
|
+
if (e === void 0 || e === "") return;
|
|
18
|
+
let t = {
|
|
19
|
+
kid: u(),
|
|
20
|
+
secret: e
|
|
21
|
+
}, n = process.env.VOLTRO_SESSION_SECRET_PREVIOUS;
|
|
22
|
+
return n ? {
|
|
23
|
+
current: t,
|
|
17
24
|
previous: {
|
|
18
25
|
kid: process.env.VOLTRO_SESSION_KID_PREVIOUS ?? "k-previous",
|
|
19
|
-
secret:
|
|
26
|
+
secret: n
|
|
20
27
|
}
|
|
21
|
-
} : { current:
|
|
22
|
-
},
|
|
23
|
-
if (e.NODE_ENV !== "production") return;
|
|
28
|
+
} : { current: t };
|
|
29
|
+
}, f = 32, p = (e = process.env) => {
|
|
24
30
|
let t = e.VOLTRO_SESSION_SECRET;
|
|
25
|
-
if (t === void 0 || t === "") throw Error("VOLTRO_SESSION_SECRET is not set
|
|
26
|
-
if (t
|
|
27
|
-
|
|
28
|
-
}, f = t.encodeSync(i), p = t.decodeUnknownSync(i), m = (e) => (typeof e == "string" ? Buffer.from(e, "utf8") : e).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""), h = (e) => {
|
|
31
|
+
if (t === void 0 || t === "") throw Error("VOLTRO_SESSION_SECRET is not set — refusing to serve. It signs session cookies, and there is no fallback: a built-in one would be readable by anyone with the source. Generate one with `voltro secret generate session`. (`voltro dev` mints a project-local value automatically; a deployment needs its own.)");
|
|
32
|
+
if (t.length < 32) throw Error(`VOLTRO_SESSION_SECRET is only ${t.length} chars — too short for a session secret (need >= 32). This looks like a placeholder. Generate a real one with \`voltro secret generate session\`.`);
|
|
33
|
+
}, m = t.encodeSync(i), h = t.decodeUnknownSync(i), g = (e) => (typeof e == "string" ? Buffer.from(e, "utf8") : e).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""), _ = (e) => {
|
|
29
34
|
let t = e.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - e.length % 4) % 4);
|
|
30
35
|
return Buffer.from(t, "base64");
|
|
31
|
-
},
|
|
36
|
+
}, v = 3600 * 24 * 7, y = .7, b = (e, t) => g(n("sha256", t).update(e).digest()), x = (e) => typeof e == "string" ? {
|
|
32
37
|
kid: "k0",
|
|
33
38
|
secret: e
|
|
34
|
-
} : e,
|
|
35
|
-
let r =
|
|
39
|
+
} : e, S = (e, t, n = {}) => {
|
|
40
|
+
let r = x(t);
|
|
36
41
|
if (!r.secret) throw Error("signSession: secret must be a non-empty string");
|
|
37
|
-
let i = n.ttlSeconds ??
|
|
42
|
+
let i = n.ttlSeconds ?? v, a = Math.floor((n.now ?? Date.now)() / 1e3), o = {
|
|
38
43
|
subject: e,
|
|
39
44
|
exp: a + i,
|
|
40
45
|
iat: a,
|
|
41
46
|
...typeof t == "string" ? {} : { kid: r.kid }
|
|
42
|
-
}, s =
|
|
43
|
-
return `${s}.${
|
|
44
|
-
},
|
|
45
|
-
let o = n("sha256", i).update(e).digest(), s =
|
|
47
|
+
}, s = g(JSON.stringify(m(o)));
|
|
48
|
+
return `${s}.${b(s, r.secret)}`;
|
|
49
|
+
}, C = (e, t, i, a) => {
|
|
50
|
+
let o = n("sha256", i).update(e).digest(), s = _(t);
|
|
46
51
|
if (s.length !== o.length || !r(s, o)) return null;
|
|
47
52
|
try {
|
|
48
|
-
let t =
|
|
53
|
+
let t = _(e).toString("utf8"), n = h(JSON.parse(t));
|
|
49
54
|
return n.exp < Math.floor(a() / 1e3) ? null : n;
|
|
50
55
|
} catch {
|
|
51
56
|
return null;
|
|
52
57
|
}
|
|
53
|
-
},
|
|
58
|
+
}, w = (e, t) => {
|
|
54
59
|
if (!e || !t) return null;
|
|
55
60
|
let n = e.indexOf(".");
|
|
56
61
|
if (n <= 0 || n === e.length - 1) return null;
|
|
57
|
-
let r =
|
|
62
|
+
let r = C(e.slice(0, n), e.slice(n + 1), t, Date.now);
|
|
58
63
|
return r ? r.subject : null;
|
|
59
|
-
},
|
|
64
|
+
}, T = (e, t, n = {}) => {
|
|
60
65
|
if (!e) return null;
|
|
61
66
|
let r = e.indexOf(".");
|
|
62
67
|
if (r <= 0 || r === e.length - 1) return null;
|
|
63
68
|
let i = e.slice(0, r), a = e.slice(r + 1), o = t.previous ? [t.current, t.previous] : [t.current], s = n.now ?? Date.now;
|
|
64
69
|
for (let e of o) {
|
|
65
70
|
if (!e.secret) continue;
|
|
66
|
-
let t =
|
|
71
|
+
let t = C(i, a, e.secret, s);
|
|
67
72
|
if (!t) continue;
|
|
68
|
-
let r = n.renewFraction ??
|
|
73
|
+
let r = n.renewFraction ?? y, o = Math.floor(s() / 1e3), c = t.exp - t.iat, l = t.iat + c * r;
|
|
69
74
|
return {
|
|
70
75
|
subject: t.subject,
|
|
71
76
|
renew: o >= l,
|
|
@@ -75,14 +80,14 @@ var i = t.Struct({
|
|
|
75
80
|
};
|
|
76
81
|
}
|
|
77
82
|
return null;
|
|
78
|
-
},
|
|
83
|
+
}, E = (e, t) => {
|
|
79
84
|
let n = Buffer.from(e, "utf8"), i = Buffer.from(t, "utf8");
|
|
80
85
|
return n.length === i.length && r(n, i);
|
|
81
|
-
},
|
|
86
|
+
}, D = (e, t) => {
|
|
82
87
|
if (!t || t.length === 0) return !0;
|
|
83
88
|
let n = e.authorization ?? e.Authorization;
|
|
84
|
-
return !n || !n.startsWith("Bearer ") ? !1 :
|
|
85
|
-
},
|
|
89
|
+
return !n || !n.startsWith("Bearer ") ? !1 : E(n.slice(7).trim(), t);
|
|
90
|
+
}, O = (e, t) => {
|
|
86
91
|
if (e) for (let n of e.split(";")) {
|
|
87
92
|
let e = n.indexOf("=");
|
|
88
93
|
if (e < 0 || n.slice(0, e).trim() !== t) continue;
|
|
@@ -94,7 +99,7 @@ var i = t.Struct({
|
|
|
94
99
|
return r;
|
|
95
100
|
}
|
|
96
101
|
}
|
|
97
|
-
},
|
|
102
|
+
}, k = (e, t, n = {}) => {
|
|
98
103
|
let r = [`${e}=${encodeURIComponent(t)}`];
|
|
99
104
|
if (r.push(`Path=${n.path ?? "/"}`), n.maxAgeSeconds !== void 0 && r.push(`Max-Age=${n.maxAgeSeconds}`), r.push("HttpOnly"), n.httpOnly === !1) {
|
|
100
105
|
let e = r.indexOf("HttpOnly");
|
|
@@ -103,4 +108,4 @@ var i = t.Struct({
|
|
|
103
108
|
return r.push(`SameSite=${n.sameSite ?? "Lax"}`), (n.secure ?? !0) && r.push("Secure"), n.domain && r.push(`Domain=${n.domain}`), r.join("; ");
|
|
104
109
|
};
|
|
105
110
|
//#endregion
|
|
106
|
-
export { o as DEFAULT_PREVIOUS_SESSION_KID, a as DEFAULT_SESSION_KID,
|
|
111
|
+
export { o as DEFAULT_PREVIOUS_SESSION_KID, a as DEFAULT_SESSION_KID, f as MIN_SESSION_SECRET_LENGTH, p as assertProductionSessionSecret, k as buildSetCookie, D as checkBearer, O as readCookie, d as resolveOptionalSessionSecrets, c as resolveSessionSecret, l as resolveSessionSecrets, S as signSession, E as timingSafeStringEqual, w as verifySession, T as verifySessionKeyed };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voltro/protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "The Voltro wire + plugin contract — defineQuery/Mutation/Action/Stream, definePlugin, sessions / JWT / API-keys, and the RPC protocol.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"voltro",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@effect/sql": "^0.51.1",
|
|
56
|
-
"@voltro/database": "0.
|
|
56
|
+
"@voltro/database": "0.6.0",
|
|
57
57
|
"jose": "^6.2.3"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|