@suveren/gateway 0.8.6 → 0.8.7
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/control-plane/index.mjs +262 -46
- package/dist/mcp-server/http.mjs +73 -9
- package/dist/ui/assets/index-tzStUGuj.js +110 -0
- package/dist/ui/icons/apple-touch-icon.png +0 -0
- package/dist/ui/icons/icon-192.png +0 -0
- package/dist/ui/icons/icon-512.png +0 -0
- package/dist/ui/icons/icon-maskable-512.png +0 -0
- package/dist/ui/index.html +7 -1
- package/dist/ui/manifest.webmanifest +15 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.d.ts +15 -4
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js +22 -3
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/client/auth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/express.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/express.js +3 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/express.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/requestBody.d.ts +25 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/requestBody.d.ts.map +1 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/requestBody.js +64 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/requestBody.js.map +1 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/webStandardStreamableHttp.d.ts +10 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/webStandardStreamableHttp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/webStandardStreamableHttp.js +13 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/webStandardStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.d.ts +15 -4
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js +22 -3
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/client/auth.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/express.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/express.js +3 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/express.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/requestBody.d.ts +25 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/requestBody.d.ts.map +1 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/requestBody.js +58 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/requestBody.js.map +1 -0
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.d.ts +10 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.d.ts.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js +13 -1
- package/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js.map +1 -1
- package/node_modules/@modelcontextprotocol/sdk/package.json +1 -1
- package/node_modules/hono/dist/adapter/aws-lambda/handler.js +6 -3
- package/node_modules/hono/dist/adapter/lambda-edge/handler.js +26 -12
- package/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +6 -3
- package/node_modules/hono/dist/cjs/adapter/lambda-edge/handler.js +26 -12
- package/node_modules/hono/dist/cjs/helper/accepts/accepts.js +7 -5
- package/node_modules/hono/dist/cjs/jsx/components.js +1 -1
- package/node_modules/hono/dist/cjs/jsx/streaming.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/pretty-json/index.js +7 -1
- package/node_modules/hono/dist/cjs/router/linear-router/router.js +3 -3
- package/node_modules/hono/dist/cjs/utils/jwt/jwt.js +7 -6
- package/node_modules/hono/dist/helper/accepts/accepts.js +7 -5
- package/node_modules/hono/dist/jsx/components.js +1 -1
- package/node_modules/hono/dist/jsx/streaming.js +1 -1
- package/node_modules/hono/dist/middleware/pretty-json/index.js +7 -1
- package/node_modules/hono/dist/router/linear-router/router.js +3 -3
- package/node_modules/hono/dist/utils/jwt/jwt.js +7 -6
- package/node_modules/hono/package.json +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/index-DWpjjPIf.js +0 -110
|
@@ -5,8 +5,8 @@ import {
|
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
7
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
8
|
-
import { existsSync as existsSync7, readFileSync as
|
|
9
|
-
import { dirname as dirname6, join as
|
|
8
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, renameSync as renameSync2 } from "fs";
|
|
9
|
+
import { dirname as dirname6, join as join9 } from "path";
|
|
10
10
|
import { homedir as homedir7 } from "os";
|
|
11
11
|
import express from "express";
|
|
12
12
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
@@ -33,6 +33,15 @@ var Vault = class {
|
|
|
33
33
|
spSessionCookie = null;
|
|
34
34
|
unsealedBy = null;
|
|
35
35
|
dataDir;
|
|
36
|
+
/**
|
|
37
|
+
* When the AS session (captured at login into `spSessionCookie`) is due to
|
|
38
|
+
* end — unix seconds, from the login response's `sessionExpiresAt`. Held in
|
|
39
|
+
* memory only, exactly like the vault key and the cookie: never written to
|
|
40
|
+
* disk, gone the moment the process restarts or the vault locks.
|
|
41
|
+
*/
|
|
42
|
+
spSessionExpiresAt = null;
|
|
43
|
+
/** Set by lockExpired(); cleared by any fresh unseal (a new login). */
|
|
44
|
+
lockedReason = null;
|
|
36
45
|
constructor(dataDir) {
|
|
37
46
|
this.dataDir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join(homedir(), ".suveren");
|
|
38
47
|
}
|
|
@@ -59,6 +68,7 @@ var Vault = class {
|
|
|
59
68
|
async unseal(provider) {
|
|
60
69
|
this.vaultKey = await provider.deriveKey(this.ensureSalt());
|
|
61
70
|
this.unsealedBy = provider.id;
|
|
71
|
+
this.lockedReason = null;
|
|
62
72
|
}
|
|
63
73
|
/**
|
|
64
74
|
* Unlock from the user's API key. Also records the key's hash so subsequent
|
|
@@ -86,6 +96,33 @@ var Vault = class {
|
|
|
86
96
|
this.apiKeyHash = null;
|
|
87
97
|
this.spSessionCookie = null;
|
|
88
98
|
this.unsealedBy = null;
|
|
99
|
+
this.spSessionExpiresAt = null;
|
|
100
|
+
this.lockedReason = null;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Lock because the AS session ended — a 401 surfaced it, or the scheduled
|
|
104
|
+
* expiry arrived. Distinct from logout/clearKey() only in that it records
|
|
105
|
+
* WHY, so the next `/health` call and the next locked-tool notice can say
|
|
106
|
+
* something better than "boots locked by design" when that is not what
|
|
107
|
+
* happened.
|
|
108
|
+
*/
|
|
109
|
+
lockExpired() {
|
|
110
|
+
this.clearKey();
|
|
111
|
+
this.lockedReason = "expired";
|
|
112
|
+
}
|
|
113
|
+
/** What locked the vault, if known. `null` covers both "still unlocked"
|
|
114
|
+
* and the default boot-locked case — callers that care about the
|
|
115
|
+
* difference should check `isUnlocked()` first. */
|
|
116
|
+
getLockedReason() {
|
|
117
|
+
return this.lockedReason;
|
|
118
|
+
}
|
|
119
|
+
/** unix seconds the current AS session is due to end, or null (locked, or
|
|
120
|
+
* the AS didn't report one — a pre-30-day-rollout server). Never persisted. */
|
|
121
|
+
setSessionExpiresAt(expiresAt) {
|
|
122
|
+
this.spSessionExpiresAt = expiresAt;
|
|
123
|
+
}
|
|
124
|
+
getSessionExpiresAt() {
|
|
125
|
+
return this.spSessionExpiresAt;
|
|
89
126
|
}
|
|
90
127
|
/**
|
|
91
128
|
* Check if the vault was created by a different API key.
|
|
@@ -263,6 +300,15 @@ async function configure(sessionCookie, vaultKeyHex) {
|
|
|
263
300
|
clearTimeout(timeout);
|
|
264
301
|
}
|
|
265
302
|
}
|
|
303
|
+
async function unconfigureSession() {
|
|
304
|
+
const res = await fetch(`${MCP_BASE}/internal/clear-session`, {
|
|
305
|
+
method: "POST",
|
|
306
|
+
headers: internalHeaders()
|
|
307
|
+
});
|
|
308
|
+
if (!res.ok) {
|
|
309
|
+
throw new Error(`MCP clear-session failed: ${res.status}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
266
312
|
async function pushGateContent(data) {
|
|
267
313
|
const res = await fetch(`${MCP_BASE}/internal/gate-content`, {
|
|
268
314
|
method: "POST",
|
|
@@ -552,9 +598,45 @@ async function loadOrGenerateKeyPair(vault2) {
|
|
|
552
598
|
return kp;
|
|
553
599
|
}
|
|
554
600
|
|
|
601
|
+
// src/lib/client-version.ts
|
|
602
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
603
|
+
import { join as join2 } from "path";
|
|
604
|
+
var UNKNOWN = "unknown";
|
|
605
|
+
function readJson(path) {
|
|
606
|
+
try {
|
|
607
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
608
|
+
} catch {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function detectGatewayVersion() {
|
|
613
|
+
const override = process.env.SUVEREN_GATEWAY_VERSION;
|
|
614
|
+
if (override) return override;
|
|
615
|
+
const start = import.meta.dirname ?? process.cwd();
|
|
616
|
+
let dir = start;
|
|
617
|
+
for (let up = 0; up < 6; up++) {
|
|
618
|
+
const pkg = readJson(join2(dir, "package.json"));
|
|
619
|
+
const name = pkg?.name;
|
|
620
|
+
const version = pkg?.version;
|
|
621
|
+
if (typeof version === "string" && (name === "@suveren/gateway" || name === "suveren-gateway")) {
|
|
622
|
+
return version;
|
|
623
|
+
}
|
|
624
|
+
const parent = join2(dir, "..");
|
|
625
|
+
if (parent === dir) break;
|
|
626
|
+
dir = parent;
|
|
627
|
+
}
|
|
628
|
+
return process.env.HAP_BUILD_SHA ?? UNKNOWN;
|
|
629
|
+
}
|
|
630
|
+
var GATEWAY_VERSION = detectGatewayVersion();
|
|
631
|
+
function clientVersionHeaders() {
|
|
632
|
+
return {
|
|
633
|
+
"x-suveren-gateway-version": GATEWAY_VERSION
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
555
637
|
// src/routes/auth.ts
|
|
556
638
|
var SP_URL = process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai";
|
|
557
|
-
function createAuthRouter(vault2, logoutAuth, loginRateLimit2) {
|
|
639
|
+
function createAuthRouter(vault2, logoutAuth, loginRateLimit2, onSessionEstablished) {
|
|
558
640
|
const router = Router();
|
|
559
641
|
router.post("/login", loginRateLimit2, async (req, res) => {
|
|
560
642
|
const apiKey = req.headers["x-api-key"] || req.body?.apiKey;
|
|
@@ -568,7 +650,11 @@ function createAuthRouter(vault2, logoutAuth, loginRateLimit2) {
|
|
|
568
650
|
method: "POST",
|
|
569
651
|
headers: {
|
|
570
652
|
"Content-Type": "application/json",
|
|
571
|
-
"X-API-Key": apiKey
|
|
653
|
+
"X-API-Key": apiKey,
|
|
654
|
+
// The AS grants a 30-day gateway session ONLY to a login that
|
|
655
|
+
// identifies itself this way; without it, a 24-hour browser
|
|
656
|
+
// session (see doc at the top of this file).
|
|
657
|
+
...clientVersionHeaders()
|
|
572
658
|
}
|
|
573
659
|
});
|
|
574
660
|
if (!spRes.ok) {
|
|
@@ -576,6 +662,7 @@ function createAuthRouter(vault2, logoutAuth, loginRateLimit2) {
|
|
|
576
662
|
res.status(spRes.status).json(err);
|
|
577
663
|
return;
|
|
578
664
|
}
|
|
665
|
+
const data = await spRes.json();
|
|
579
666
|
const setCookieHeaders = spRes.headers.getSetCookie?.() ?? [];
|
|
580
667
|
const sessionCookie = setCookieHeaders.join("; ");
|
|
581
668
|
vault2.setSpCookie(sessionCookie);
|
|
@@ -605,6 +692,9 @@ function createAuthRouter(vault2, logoutAuth, loginRateLimit2) {
|
|
|
605
692
|
vault2.wipe();
|
|
606
693
|
await vault2.deriveAndSetKey(apiKey);
|
|
607
694
|
}
|
|
695
|
+
const sessionExpiresAt = typeof data.sessionExpiresAt === "number" ? data.sessionExpiresAt : null;
|
|
696
|
+
vault2.setSessionExpiresAt(sessionExpiresAt);
|
|
697
|
+
onSessionEstablished?.();
|
|
608
698
|
if (sessionCookie) {
|
|
609
699
|
try {
|
|
610
700
|
await configure(sessionCookie, vault2.getVaultKeyHex());
|
|
@@ -612,7 +702,6 @@ function createAuthRouter(vault2, logoutAuth, loginRateLimit2) {
|
|
|
612
702
|
console.error("[Control Plane] Failed to configure MCP:", err);
|
|
613
703
|
}
|
|
614
704
|
}
|
|
615
|
-
const data = await spRes.json();
|
|
616
705
|
res.json(data);
|
|
617
706
|
(async () => {
|
|
618
707
|
try {
|
|
@@ -684,7 +773,16 @@ function createAuthRouter(vault2, logoutAuth, loginRateLimit2) {
|
|
|
684
773
|
}
|
|
685
774
|
});
|
|
686
775
|
router.post("/logout", logoutAuth, async (_req, res) => {
|
|
776
|
+
const cookie = vault2.getSpCookie();
|
|
687
777
|
vault2.clearKey();
|
|
778
|
+
await Promise.allSettled([
|
|
779
|
+
unconfigureSession(),
|
|
780
|
+
cookie ? fetch(`${SP_URL}/api/auth/logout`, {
|
|
781
|
+
method: "POST",
|
|
782
|
+
headers: { cookie, ...clientVersionHeaders() },
|
|
783
|
+
redirect: "manual"
|
|
784
|
+
}) : Promise.resolve()
|
|
785
|
+
]);
|
|
688
786
|
res.json({ ok: true });
|
|
689
787
|
});
|
|
690
788
|
return router;
|
|
@@ -1294,18 +1392,18 @@ async function testAIConnectivity(config) {
|
|
|
1294
1392
|
}
|
|
1295
1393
|
|
|
1296
1394
|
// src/lib/agent-brief-store.ts
|
|
1297
|
-
import { existsSync as existsSync2, readFileSync as
|
|
1395
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
1298
1396
|
import { homedir as homedir2 } from "os";
|
|
1299
|
-
import { join as
|
|
1397
|
+
import { join as join3 } from "path";
|
|
1300
1398
|
var AGENT_CONTEXT_MAX_BYTES = 16 * 1024;
|
|
1301
1399
|
function agentBriefPath() {
|
|
1302
|
-
const dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
1303
|
-
return
|
|
1400
|
+
const dataDir = process.env.SUVEREN_DATA_DIR ?? join3(homedir2(), ".suveren");
|
|
1401
|
+
return join3(dataDir, "context.md");
|
|
1304
1402
|
}
|
|
1305
1403
|
function readAgentBrief() {
|
|
1306
1404
|
const filePath = agentBriefPath();
|
|
1307
1405
|
if (!existsSync2(filePath)) return "";
|
|
1308
|
-
return
|
|
1406
|
+
return readFileSync3(filePath, "utf-8");
|
|
1309
1407
|
}
|
|
1310
1408
|
|
|
1311
1409
|
// src/routes/ai.ts
|
|
@@ -1773,12 +1871,12 @@ import { spawn, spawnSync } from "child_process";
|
|
|
1773
1871
|
// src/lib/notifier-icon.ts
|
|
1774
1872
|
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
|
|
1775
1873
|
import { homedir as homedir3 } from "os";
|
|
1776
|
-
import { join as
|
|
1874
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
1777
1875
|
var ICON_BASE64 = "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";
|
|
1778
1876
|
function ensureNotifierIcon(dataDir) {
|
|
1779
1877
|
try {
|
|
1780
|
-
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ??
|
|
1781
|
-
const path =
|
|
1878
|
+
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join4(homedir3(), ".suveren");
|
|
1879
|
+
const path = join4(dir, "notifier-icon.png");
|
|
1782
1880
|
if (!existsSync4(path)) {
|
|
1783
1881
|
mkdirSync2(dirname2(path), { recursive: true });
|
|
1784
1882
|
writeFileSync2(path, Buffer.from(ICON_BASE64, "base64"));
|
|
@@ -1861,6 +1959,14 @@ function lockedNotification(port2) {
|
|
|
1861
1959
|
url
|
|
1862
1960
|
};
|
|
1863
1961
|
}
|
|
1962
|
+
function sessionExpiredNotification(port2) {
|
|
1963
|
+
const url = `http://localhost:${port2}`;
|
|
1964
|
+
return {
|
|
1965
|
+
title: "Suveren Gateway locked",
|
|
1966
|
+
message: "Your sign-in has ended. Sign in again so your agents can act.",
|
|
1967
|
+
url
|
|
1968
|
+
};
|
|
1969
|
+
}
|
|
1864
1970
|
var terminalNotifierCache = null;
|
|
1865
1971
|
function hasTerminalNotifier() {
|
|
1866
1972
|
if (terminalNotifierCache !== null) return terminalNotifierCache;
|
|
@@ -1975,17 +2081,17 @@ import { Router as Router9 } from "express";
|
|
|
1975
2081
|
|
|
1976
2082
|
// src/routes/approved-intents.ts
|
|
1977
2083
|
import { Router as Router8 } from "express";
|
|
1978
|
-
import { readFileSync as
|
|
2084
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
1979
2085
|
import { homedir as homedir4 } from "os";
|
|
1980
|
-
import { join as
|
|
1981
|
-
var SUVEREN_DATA_DIR = process.env.SUVEREN_DATA_DIR ??
|
|
1982
|
-
var FILE_PATH =
|
|
2086
|
+
import { join as join5 } from "path";
|
|
2087
|
+
var SUVEREN_DATA_DIR = process.env.SUVEREN_DATA_DIR ?? join5(homedir4(), ".suveren");
|
|
2088
|
+
var FILE_PATH = join5(SUVEREN_DATA_DIR, "approved-intents.enc.json");
|
|
1983
2089
|
function readFile() {
|
|
1984
2090
|
if (!existsSync5(FILE_PATH)) {
|
|
1985
2091
|
return { version: 1, entries: {} };
|
|
1986
2092
|
}
|
|
1987
2093
|
try {
|
|
1988
|
-
return JSON.parse(
|
|
2094
|
+
return JSON.parse(readFileSync4(FILE_PATH, "utf-8"));
|
|
1989
2095
|
} catch {
|
|
1990
2096
|
return { version: 1, entries: {} };
|
|
1991
2097
|
}
|
|
@@ -2367,6 +2473,7 @@ function startUpdateChecker(method, version) {
|
|
|
2367
2473
|
|
|
2368
2474
|
// src/routes/events.ts
|
|
2369
2475
|
var KEEPALIVE_INTERVAL_MS = 25e3;
|
|
2476
|
+
var PAYLOAD_ALLOWED = /* @__PURE__ */ new Set(["session-locked"]);
|
|
2370
2477
|
function createEventsHandler() {
|
|
2371
2478
|
return function eventsHandler(_req, res) {
|
|
2372
2479
|
res.setHeader("Content-Type", "text/event-stream");
|
|
@@ -2375,8 +2482,9 @@ function createEventsHandler() {
|
|
|
2375
2482
|
res.setHeader("X-Accel-Buffering", "no");
|
|
2376
2483
|
res.flushHeaders();
|
|
2377
2484
|
const unsubscribe = eventBus.subscribe((event) => {
|
|
2485
|
+
const data = PAYLOAD_ALLOWED.has(event.type) ? JSON.stringify(event.payload ?? null) : "null";
|
|
2378
2486
|
res.write(`event: ${event.type}
|
|
2379
|
-
data:
|
|
2487
|
+
data: ${data}
|
|
2380
2488
|
|
|
2381
2489
|
`);
|
|
2382
2490
|
});
|
|
@@ -2394,19 +2502,19 @@ data: null
|
|
|
2394
2502
|
import { Router as Router12 } from "express";
|
|
2395
2503
|
|
|
2396
2504
|
// src/lib/gateway-settings.ts
|
|
2397
|
-
import { readFileSync as
|
|
2505
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
2398
2506
|
import { homedir as homedir5 } from "os";
|
|
2399
|
-
import { join as
|
|
2507
|
+
import { join as join6, dirname as dirname4 } from "path";
|
|
2400
2508
|
var DEFAULT_SETTINGS = {
|
|
2401
2509
|
desktopNotifications: true
|
|
2402
2510
|
};
|
|
2403
2511
|
function settingsPath(dataDir) {
|
|
2404
|
-
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ??
|
|
2405
|
-
return
|
|
2512
|
+
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join6(homedir5(), ".suveren");
|
|
2513
|
+
return join6(dir, "settings.json");
|
|
2406
2514
|
}
|
|
2407
2515
|
function readSettings(dataDir) {
|
|
2408
2516
|
try {
|
|
2409
|
-
const raw = JSON.parse(
|
|
2517
|
+
const raw = JSON.parse(readFileSync5(settingsPath(dataDir), "utf8"));
|
|
2410
2518
|
return {
|
|
2411
2519
|
desktopNotifications: typeof raw.desktopNotifications === "boolean" ? raw.desktopNotifications : DEFAULT_SETTINGS.desktopNotifications
|
|
2412
2520
|
};
|
|
@@ -2453,20 +2561,20 @@ function createGatewaySettingsRouter() {
|
|
|
2453
2561
|
import { Router as Router13 } from "express";
|
|
2454
2562
|
|
|
2455
2563
|
// src/lib/archived-mandates-store.ts
|
|
2456
|
-
import { readFileSync as
|
|
2564
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, renameSync } from "fs";
|
|
2457
2565
|
import { homedir as homedir6 } from "os";
|
|
2458
|
-
import { join as
|
|
2566
|
+
import { join as join7, dirname as dirname5 } from "path";
|
|
2459
2567
|
var ID_PATTERN = /^[A-Za-z0-9_.:-]{1,200}$/;
|
|
2460
2568
|
function isValidMandateId(id) {
|
|
2461
2569
|
return typeof id === "string" && ID_PATTERN.test(id);
|
|
2462
2570
|
}
|
|
2463
2571
|
function storePath(dataDir) {
|
|
2464
|
-
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ??
|
|
2465
|
-
return
|
|
2572
|
+
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join7(homedir6(), ".suveren");
|
|
2573
|
+
return join7(dir, "archived-mandates.json");
|
|
2466
2574
|
}
|
|
2467
2575
|
function readArchived(dataDir) {
|
|
2468
2576
|
try {
|
|
2469
|
-
const raw = JSON.parse(
|
|
2577
|
+
const raw = JSON.parse(readFileSync6(storePath(dataDir), "utf8"));
|
|
2470
2578
|
if (!Array.isArray(raw.archived)) return [];
|
|
2471
2579
|
return [...new Set(raw.archived.filter(isValidMandateId))];
|
|
2472
2580
|
} catch {
|
|
@@ -2518,6 +2626,7 @@ function createArchivedMandatesRouter(dataDir) {
|
|
|
2518
2626
|
import { Router as Router14 } from "express";
|
|
2519
2627
|
import { timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
2520
2628
|
var ALLOWED = ["proposal-added", "action-approval-needed"];
|
|
2629
|
+
var SESSION_EXPIRED = "session-expired";
|
|
2521
2630
|
function secretMatches(provided, expected) {
|
|
2522
2631
|
if (!expected) return false;
|
|
2523
2632
|
if (!provided) return false;
|
|
@@ -2526,7 +2635,7 @@ function secretMatches(provided, expected) {
|
|
|
2526
2635
|
if (a.length !== b.length) return false;
|
|
2527
2636
|
return timingSafeEqual2(a, b);
|
|
2528
2637
|
}
|
|
2529
|
-
function createInternalEventsRouter(getSecret) {
|
|
2638
|
+
function createInternalEventsRouter(getSecret, onSessionExpired) {
|
|
2530
2639
|
const router = Router14();
|
|
2531
2640
|
router.post("/event", (req, res) => {
|
|
2532
2641
|
if (!secretMatches(req.headers["x-internal-secret"], getSecret())) {
|
|
@@ -2534,6 +2643,11 @@ function createInternalEventsRouter(getSecret) {
|
|
|
2534
2643
|
return;
|
|
2535
2644
|
}
|
|
2536
2645
|
const type = (req.body ?? {}).type;
|
|
2646
|
+
if (type === SESSION_EXPIRED) {
|
|
2647
|
+
onSessionExpired?.();
|
|
2648
|
+
res.json({ ok: true });
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2537
2651
|
if (typeof type !== "string" || !ALLOWED.includes(type)) {
|
|
2538
2652
|
res.status(400).json({ error: "Unsupported event type" });
|
|
2539
2653
|
return;
|
|
@@ -2630,18 +2744,105 @@ function startNotificationDispatcher(opts = {}) {
|
|
|
2630
2744
|
return new NotificationDispatcher(opts).start();
|
|
2631
2745
|
}
|
|
2632
2746
|
|
|
2747
|
+
// src/lib/session-lock.ts
|
|
2748
|
+
var SESSION_LOCKED_MESSAGE = "Your sign-in ended after 30 days or was revoked. Sign in again.";
|
|
2749
|
+
function createSessionLock(deps) {
|
|
2750
|
+
const { vault: vault2, port: port2 } = deps;
|
|
2751
|
+
const notifyFn = deps.notifyFn ?? notify;
|
|
2752
|
+
const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
|
|
2753
|
+
const emit = deps.emit ?? eventBus.emit.bind(eventBus);
|
|
2754
|
+
return function lockExpiredSession2() {
|
|
2755
|
+
if (!vault2.isUnlocked()) return;
|
|
2756
|
+
vault2.lockExpired();
|
|
2757
|
+
console.error("[Control Plane] Authority Server session ended \u2014 gateway LOCKED");
|
|
2758
|
+
void unconfigureSessionFn().catch((err) => {
|
|
2759
|
+
console.error("[Control Plane] Failed to push cleared session to MCP:", err);
|
|
2760
|
+
});
|
|
2761
|
+
emit("session-locked", { reason: "expired", message: SESSION_LOCKED_MESSAGE });
|
|
2762
|
+
const { title, message, url } = sessionExpiredNotification(port2);
|
|
2763
|
+
notifyFn(title, message, process.platform, url);
|
|
2764
|
+
};
|
|
2765
|
+
}
|
|
2766
|
+
|
|
2767
|
+
// src/lib/session-expiry-scheduler.ts
|
|
2768
|
+
var MAX_TIMEOUT_MS = 2147483647;
|
|
2769
|
+
var SessionExpiryScheduler = class {
|
|
2770
|
+
constructor(getExpiresAt, onExpired, opts = {}) {
|
|
2771
|
+
this.getExpiresAt = getExpiresAt;
|
|
2772
|
+
this.onExpired = onExpired;
|
|
2773
|
+
this.now = opts.now ?? Date.now;
|
|
2774
|
+
this.setTimeoutFn = opts.setTimeoutFn ?? setTimeout;
|
|
2775
|
+
this.clearTimeoutFn = opts.clearTimeoutFn ?? clearTimeout;
|
|
2776
|
+
this.setIntervalFn = opts.setIntervalFn ?? setInterval;
|
|
2777
|
+
this.clearIntervalFn = opts.clearIntervalFn ?? clearInterval;
|
|
2778
|
+
this.coarseIntervalMs = opts.coarseIntervalMs ?? 5 * 6e4;
|
|
2779
|
+
}
|
|
2780
|
+
preciseTimer = null;
|
|
2781
|
+
coarseTimer = null;
|
|
2782
|
+
now;
|
|
2783
|
+
setTimeoutFn;
|
|
2784
|
+
clearTimeoutFn;
|
|
2785
|
+
setIntervalFn;
|
|
2786
|
+
clearIntervalFn;
|
|
2787
|
+
coarseIntervalMs;
|
|
2788
|
+
/** Start the coarse safety-net interval and arm the precise timer. */
|
|
2789
|
+
start() {
|
|
2790
|
+
if (this.coarseTimer) return;
|
|
2791
|
+
this.coarseTimer = this.setIntervalFn(() => this.check(), this.coarseIntervalMs);
|
|
2792
|
+
this.reschedule();
|
|
2793
|
+
}
|
|
2794
|
+
stop() {
|
|
2795
|
+
if (this.preciseTimer) this.clearTimeoutFn(this.preciseTimer);
|
|
2796
|
+
if (this.coarseTimer) this.clearIntervalFn(this.coarseTimer);
|
|
2797
|
+
this.preciseTimer = null;
|
|
2798
|
+
this.coarseTimer = null;
|
|
2799
|
+
}
|
|
2800
|
+
/** Call whenever `sessionExpiresAt` changes — a login (new value) or a
|
|
2801
|
+
* lock (now null, via getExpiresAt) — to re-arm the precise timer. */
|
|
2802
|
+
reschedule() {
|
|
2803
|
+
if (this.preciseTimer) {
|
|
2804
|
+
this.clearTimeoutFn(this.preciseTimer);
|
|
2805
|
+
this.preciseTimer = null;
|
|
2806
|
+
}
|
|
2807
|
+
const exp = this.getExpiresAt();
|
|
2808
|
+
if (exp === null) return;
|
|
2809
|
+
const delayMs = Math.max(0, exp * 1e3 - this.now());
|
|
2810
|
+
if (delayMs > MAX_TIMEOUT_MS) {
|
|
2811
|
+
this.preciseTimer = this.setTimeoutFn(() => this.reschedule(), MAX_TIMEOUT_MS);
|
|
2812
|
+
return;
|
|
2813
|
+
}
|
|
2814
|
+
this.preciseTimer = this.setTimeoutFn(() => this.check(), delayMs);
|
|
2815
|
+
}
|
|
2816
|
+
/** Idempotent: only fires onExpired when the expiry has actually arrived. */
|
|
2817
|
+
check() {
|
|
2818
|
+
const exp = this.getExpiresAt();
|
|
2819
|
+
if (exp !== null && this.now() >= exp * 1e3) this.onExpired();
|
|
2820
|
+
}
|
|
2821
|
+
};
|
|
2822
|
+
|
|
2823
|
+
// src/lib/session-health.ts
|
|
2824
|
+
function buildSessionHealth(vault2) {
|
|
2825
|
+
const unlocked = vault2.isUnlocked();
|
|
2826
|
+
const lockedReason = vault2.getLockedReason();
|
|
2827
|
+
return {
|
|
2828
|
+
state: unlocked ? "active" : "locked",
|
|
2829
|
+
expiresAt: unlocked ? vault2.getSessionExpiresAt() : null,
|
|
2830
|
+
...lockedReason ? { lockedReason } : {}
|
|
2831
|
+
};
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2633
2834
|
// src/lib/denials-reader.ts
|
|
2634
|
-
import { existsSync as existsSync6, readFileSync as
|
|
2635
|
-
import { join as
|
|
2835
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
2836
|
+
import { join as join8 } from "path";
|
|
2636
2837
|
function loadDenials(dataDir, decrypt) {
|
|
2637
|
-
const encPath =
|
|
2638
|
-
const plainPath =
|
|
2838
|
+
const encPath = join8(dataDir, "denials.enc.json");
|
|
2839
|
+
const plainPath = join8(dataDir, "denials.json");
|
|
2639
2840
|
if (existsSync6(encPath)) {
|
|
2640
|
-
const { blob } = JSON.parse(
|
|
2841
|
+
const { blob } = JSON.parse(readFileSync7(encPath, "utf-8"));
|
|
2641
2842
|
return JSON.parse(decrypt(blob)).records ?? [];
|
|
2642
2843
|
}
|
|
2643
2844
|
if (existsSync6(plainPath)) {
|
|
2644
|
-
return JSON.parse(
|
|
2845
|
+
return JSON.parse(readFileSync7(plainPath, "utf-8")).records ?? [];
|
|
2645
2846
|
}
|
|
2646
2847
|
return [];
|
|
2647
2848
|
}
|
|
@@ -2658,8 +2859,8 @@ function selectDenials(records, opts = {}) {
|
|
|
2658
2859
|
// src/index.ts
|
|
2659
2860
|
(function migrateDataDir() {
|
|
2660
2861
|
if (process.env.SUVEREN_DATA_DIR) return;
|
|
2661
|
-
const oldDir =
|
|
2662
|
-
const newDir =
|
|
2862
|
+
const oldDir = join9(homedir7(), ".hap");
|
|
2863
|
+
const newDir = join9(homedir7(), ".suveren");
|
|
2663
2864
|
if (!existsSync7(newDir) && existsSync7(oldDir)) {
|
|
2664
2865
|
try {
|
|
2665
2866
|
renameSync2(oldDir, newDir);
|
|
@@ -2673,7 +2874,7 @@ function selectDenials(records, opts = {}) {
|
|
|
2673
2874
|
var SP_URL2 = process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai";
|
|
2674
2875
|
var port = parseInt(process.env.SUVEREN_CP_PORT ?? "3402", 10);
|
|
2675
2876
|
var HAP_MODE = process.env.HAP_MODE ?? "personal";
|
|
2676
|
-
var UI_DIST = process.env.HAP_UI_DIST ??
|
|
2877
|
+
var UI_DIST = process.env.HAP_UI_DIST ?? join9(import.meta.dirname ?? __dirname, "../../ui/dist");
|
|
2677
2878
|
var vault = new Vault();
|
|
2678
2879
|
var internalSecret2 = process.env.SUVEREN_INTERNAL_SECRET ?? randomBytes2(32).toString("hex");
|
|
2679
2880
|
setInternalSecret(internalSecret2);
|
|
@@ -2697,7 +2898,17 @@ function loginRateLimit(req, res, next) {
|
|
|
2697
2898
|
}
|
|
2698
2899
|
next();
|
|
2699
2900
|
}
|
|
2700
|
-
|
|
2901
|
+
var lockExpiredSession = createSessionLock({ vault, port });
|
|
2902
|
+
var sessionExpiryScheduler = new SessionExpiryScheduler(
|
|
2903
|
+
() => vault.getSessionExpiresAt(),
|
|
2904
|
+
lockExpiredSession
|
|
2905
|
+
);
|
|
2906
|
+
app.use("/auth", jsonParser, createAuthRouter(
|
|
2907
|
+
vault,
|
|
2908
|
+
requireAuth(vault),
|
|
2909
|
+
loginRateLimit,
|
|
2910
|
+
() => sessionExpiryScheduler.reschedule()
|
|
2911
|
+
));
|
|
2701
2912
|
function resolveOrigin(req) {
|
|
2702
2913
|
const fwdHost = req.get("x-forwarded-host");
|
|
2703
2914
|
if (fwdHost) return `${req.protocol}://${fwdHost}`;
|
|
@@ -2903,7 +3114,7 @@ app.get("/auth/oauth/:integrationId/health", authGuard, async (req, res) => {
|
|
|
2903
3114
|
}
|
|
2904
3115
|
});
|
|
2905
3116
|
app.get("/events", requireAllowedHost, requireAuthQueryOrHeader(vault), createEventsHandler());
|
|
2906
|
-
app.use("/internal", jsonParser, createInternalEventsRouter(() => internalSecret2));
|
|
3117
|
+
app.use("/internal", jsonParser, createInternalEventsRouter(() => internalSecret2, lockExpiredSession));
|
|
2907
3118
|
app.use("/vault", jsonParser, authGuard, createVaultRouter(vault));
|
|
2908
3119
|
app.use("/ai", jsonParser, authGuard, createAIRouter(vault));
|
|
2909
3120
|
app.use("/ai-prompts", jsonParser, authGuard, createAIPromptsRouter());
|
|
@@ -3044,7 +3255,7 @@ app.get("/active-authorizations", authGuard, async (_req, res) => {
|
|
|
3044
3255
|
});
|
|
3045
3256
|
app.get("/denials", authGuard, (req, res) => {
|
|
3046
3257
|
try {
|
|
3047
|
-
const dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
3258
|
+
const dataDir = process.env.SUVEREN_DATA_DIR ?? join9(homedir7(), ".suveren");
|
|
3048
3259
|
const all = loadDenials(dataDir, (blob) => vault.decrypt(blob));
|
|
3049
3260
|
const since = Number(req.query.since);
|
|
3050
3261
|
const limit = Number(req.query.limit);
|
|
@@ -3122,6 +3333,9 @@ app.use(
|
|
|
3122
3333
|
},
|
|
3123
3334
|
proxyRes: (proxyRes, req) => {
|
|
3124
3335
|
const status = proxyRes.statusCode ?? 0;
|
|
3336
|
+
if (status === 401 && vault.isUnlocked()) {
|
|
3337
|
+
lockExpiredSession();
|
|
3338
|
+
}
|
|
3125
3339
|
if (status < 200 || status >= 300) return;
|
|
3126
3340
|
const method = req.method?.toUpperCase();
|
|
3127
3341
|
const url = req.url ?? "";
|
|
@@ -3154,7 +3368,7 @@ function detectInstallMethod() {
|
|
|
3154
3368
|
if (dir.includes("/node_modules/@suveren/gateway/")) return "npm";
|
|
3155
3369
|
let cursor = dir;
|
|
3156
3370
|
for (let i = 0; i < 8; i++) {
|
|
3157
|
-
if (existsSync7(
|
|
3371
|
+
if (existsSync7(join9(cursor, ".git"))) return "dev";
|
|
3158
3372
|
const parent = dirname6(cursor);
|
|
3159
3373
|
if (parent === cursor) break;
|
|
3160
3374
|
cursor = parent;
|
|
@@ -3167,10 +3381,10 @@ function detectRunningVersion() {
|
|
|
3167
3381
|
return process.env.HAP_BUILD_SHA ?? "dev";
|
|
3168
3382
|
}
|
|
3169
3383
|
const dir = import.meta.dirname ?? __dirname;
|
|
3170
|
-
const bundlePkg =
|
|
3384
|
+
const bundlePkg = join9(dir, "..", "..", "package.json");
|
|
3171
3385
|
if (existsSync7(bundlePkg)) {
|
|
3172
3386
|
try {
|
|
3173
|
-
const pkg = JSON.parse(
|
|
3387
|
+
const pkg = JSON.parse(readFileSync8(bundlePkg, "utf8"));
|
|
3174
3388
|
if (pkg.name === "@suveren/gateway" && typeof pkg.version === "string") {
|
|
3175
3389
|
return pkg.version;
|
|
3176
3390
|
}
|
|
@@ -3193,6 +3407,7 @@ app.get("/health", async (req, res) => {
|
|
|
3193
3407
|
updateAvailable: update.updateAvailable,
|
|
3194
3408
|
installMethod: INSTALL_METHOD,
|
|
3195
3409
|
spUrl: SP_URL2,
|
|
3410
|
+
session: buildSessionHealth(vault),
|
|
3196
3411
|
security: {
|
|
3197
3412
|
note: "Gateway secures tool execution. Agent host isolation is the user's responsibility."
|
|
3198
3413
|
}
|
|
@@ -3208,7 +3423,7 @@ if (existsSync7(UI_DIST)) {
|
|
|
3208
3423
|
}));
|
|
3209
3424
|
app.get("*", (_req, res) => {
|
|
3210
3425
|
res.setHeader("Cache-Control", "no-store, must-revalidate");
|
|
3211
|
-
res.sendFile(
|
|
3426
|
+
res.sendFile(join9(UI_DIST, "index.html"));
|
|
3212
3427
|
});
|
|
3213
3428
|
} else {
|
|
3214
3429
|
app.get("/", (_req, res) => {
|
|
@@ -3225,6 +3440,7 @@ app.listen(port, "0.0.0.0", () => {
|
|
|
3225
3440
|
notify(title, message, process.platform, url);
|
|
3226
3441
|
}
|
|
3227
3442
|
startNotificationDispatcher({ url: `http://localhost:${port}` });
|
|
3443
|
+
sessionExpiryScheduler.start();
|
|
3228
3444
|
console.error(`[Control Plane] SP proxy: ${SP_URL2}`);
|
|
3229
3445
|
console.error(`[Control Plane] UI dist: ${UI_DIST}`);
|
|
3230
3446
|
console.error(`[Control Plane] Internal secret: configured`);
|