@voltro/protocol 0.5.0 → 0.7.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/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-XDdIJk0c.js";
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
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 `NODE_ENV` is
6
- * `'production'` and `VOLTRO_SESSION_SECRET` is missing, the dev fallback, or
7
- * too short. No-op otherwise. Idempotent — safe to call from every prod entry.
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 production session secret. 32 bytes of
49
- * entropy is the floor for HMAC-SHA256; shorter values are almost always
50
- * hand-typed placeholders, not real secrets. */
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
- /** Resolve the HMAC session secret with the dev fallback applied. */
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` (dev fallback
66
- * applied), keyed `VOLTRO_SESSION_KID` (defaults to `'k0'`).
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 = "voltro-dev-session-secret-32b-DO-NOT-USE-IN-PROD", c = () => process.env.VOLTRO_SESSION_SECRET ?? "voltro-dev-session-secret-32b-DO-NOT-USE-IN-PROD", l = () => {
11
- let e = {
12
- kid: process.env.VOLTRO_SESSION_KID ?? "k0",
13
- secret: process.env.VOLTRO_SESSION_SECRET ?? "voltro-dev-session-secret-32b-DO-NOT-USE-IN-PROD"
14
- }, t = process.env.VOLTRO_SESSION_SECRET_PREVIOUS;
15
- return t ? {
16
- current: e,
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: t
26
+ secret: n
20
27
  }
21
- } : { current: e };
22
- }, u = 32, d = (e = process.env) => {
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. In production the framework refuses to boot rather than sign session cookies with the PUBLIC dev fallback secret (which would let anyone forge any user's session). Set VOLTRO_SESSION_SECRET to a high-entropy value (>= 32 chars). Generate one with `voltro secret generate session`.");
26
- if (t === "voltro-dev-session-secret-32b-DO-NOT-USE-IN-PROD") throw Error("VOLTRO_SESSION_SECRET is set to the built-in dev fallback secret, which is PUBLIC in the framework source. Set a real, high-entropy secret in production. Generate one with `voltro secret generate session`.");
27
- if (t.length < 32) throw Error(`VOLTRO_SESSION_SECRET is only ${t.length} chars too short for a production session secret (need >= 32). This looks like a placeholder. Generate a real one with \`voltro secret generate session\`.`);
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
- }, g = 3600 * 24 * 7, _ = .7, v = (e, t) => m(n("sha256", t).update(e).digest()), y = (e) => typeof e == "string" ? {
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, b = (e, t, n = {}) => {
35
- let r = y(t);
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 ?? g, a = Math.floor((n.now ?? Date.now)() / 1e3), o = {
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 = m(JSON.stringify(f(o)));
43
- return `${s}.${v(s, r.secret)}`;
44
- }, x = (e, t, i, a) => {
45
- let o = n("sha256", i).update(e).digest(), s = h(t);
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 = h(e).toString("utf8"), n = p(JSON.parse(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
- }, S = (e, t) => {
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 = x(e.slice(0, n), e.slice(n + 1), t, Date.now);
62
+ let r = C(e.slice(0, n), e.slice(n + 1), t, Date.now);
58
63
  return r ? r.subject : null;
59
- }, C = (e, t, n = {}) => {
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 = x(i, a, e.secret, s);
71
+ let t = C(i, a, e.secret, s);
67
72
  if (!t) continue;
68
- let r = n.renewFraction ?? _, o = Math.floor(s() / 1e3), c = t.exp - t.iat, l = t.iat + c * r;
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
- }, w = (e, t) => {
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
- }, T = (e, t) => {
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 : w(n.slice(7).trim(), t);
85
- }, E = (e, t) => {
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
- }, D = (e, t, n = {}) => {
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, u as MIN_SESSION_SECRET_LENGTH, s as VOLTRO_DEV_SESSION_SECRET, d as assertProductionSessionSecret, D as buildSetCookie, T as checkBearer, E as readCookie, c as resolveSessionSecret, l as resolveSessionSecrets, b as signSession, w as timingSafeStringEqual, S as verifySession, C as verifySessionKeyed };
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.5.0",
3
+ "version": "0.7.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.5.0",
56
+ "@voltro/database": "0.7.0",
57
57
  "jose": "^6.2.3"
58
58
  },
59
59
  "peerDependencies": {