@suveren/gateway 0.25.1 → 0.26.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/bin/suveren-gateway.js +10 -1
- package/content/guides/3-mandates.md +1 -1
- package/dist/control-plane/index.mjs +368 -247
- package/dist/mcp-server/http.mjs +112 -10
- package/dist/ui/assets/index-46UkxmPE.css +1 -0
- package/dist/ui/assets/index-BC5hwcsC.js +120 -0
- package/dist/ui/index.html +2 -2
- package/lib/policy.d.mts +3 -1
- package/lib/policy.mjs +28 -2
- package/node_modules/ip-address/README.md +70 -70
- package/node_modules/ip-address/dist/ipv6.js +7 -4
- package/node_modules/ip-address/dist/ipv6.js.map +1 -1
- package/node_modules/ip-address/dist/v6/constants.d.ts +3 -0
- package/node_modules/ip-address/dist/v6/constants.js +3 -0
- package/node_modules/ip-address/dist/v6/constants.js.map +1 -1
- package/node_modules/ip-address/package.json +2 -1
- package/package.json +1 -1
- package/dist/ui/assets/index-Ccst5MQ7.css +0 -1
- package/dist/ui/assets/index-CxVyYf7C.js +0 -120
|
@@ -127,6 +127,13 @@ var Vault = class {
|
|
|
127
127
|
this.clearKey();
|
|
128
128
|
this.lockedReason = "as-tls-mismatch";
|
|
129
129
|
}
|
|
130
|
+
/** Lock because this is a managed gateway (IT policy `Team`) and the
|
|
131
|
+
* signed-in account's active workspace is no longer that team — see
|
|
132
|
+
* lib/managed-team.ts. Mirrors lockAsKeyMismatch() exactly. */
|
|
133
|
+
lockManagedWrongTeam() {
|
|
134
|
+
this.clearKey();
|
|
135
|
+
this.lockedReason = "managed-wrong-team";
|
|
136
|
+
}
|
|
130
137
|
/**
|
|
131
138
|
* Boot-time only: record that the gateway starts locked because the
|
|
132
139
|
* resolved Authority Server URL differs from the one it was last paired
|
|
@@ -1299,7 +1306,7 @@ import { dirname as dirname4, join as join6 } from "path";
|
|
|
1299
1306
|
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
|
|
1300
1307
|
import { join as join5 } from "path";
|
|
1301
1308
|
import { spawnSync } from "child_process";
|
|
1302
|
-
var POLICY_KEYS = ["AsUrl", "CaFile", "PinTls", "Proxy", "NoProxy", "Simulation", "InstallMethod"];
|
|
1309
|
+
var POLICY_KEYS = ["AsUrl", "CaFile", "PinTls", "Proxy", "NoProxy", "Simulation", "InstallMethod", "Team"];
|
|
1303
1310
|
var DEFAULT_REGISTRY_BASE_KEY = "SOFTWARE\\Policies\\Suveren\\Gateway";
|
|
1304
1311
|
function registryKeyPath(hive) {
|
|
1305
1312
|
const baseKey = process.env.SUVEREN_POLICY_REGISTRY_KEY || DEFAULT_REGISTRY_BASE_KEY;
|
|
@@ -1335,6 +1342,11 @@ function validateCaFile(candidate) {
|
|
|
1335
1342
|
if (!existsSync4(trimmed)) return { ok: false, error: `No file at "${trimmed}".` };
|
|
1336
1343
|
return { ok: true, path: trimmed };
|
|
1337
1344
|
}
|
|
1345
|
+
function validateTeamId(candidate) {
|
|
1346
|
+
const trimmed = candidate.trim();
|
|
1347
|
+
if (!trimmed) return { ok: false, error: "The team id is empty." };
|
|
1348
|
+
return { ok: true, id: trimmed };
|
|
1349
|
+
}
|
|
1338
1350
|
function validateProxyUrl(candidate) {
|
|
1339
1351
|
const trimmed = candidate.trim();
|
|
1340
1352
|
if (!trimmed) return { ok: false, error: "The proxy URL is empty." };
|
|
@@ -1508,6 +1520,14 @@ function readPolicy() {
|
|
|
1508
1520
|
policy.installMethod = "managed";
|
|
1509
1521
|
locked.add("installMethod");
|
|
1510
1522
|
}
|
|
1523
|
+
if (merged.Team !== void 0) {
|
|
1524
|
+
const v = validateTeamId(String(merged.Team));
|
|
1525
|
+
if (!v.ok) {
|
|
1526
|
+
throw new Error(`Invalid policy Team ("${String(merged.Team)}") from ${sourceOf.Team}: ${v.error}`);
|
|
1527
|
+
}
|
|
1528
|
+
policy.team = v.id;
|
|
1529
|
+
locked.add("team");
|
|
1530
|
+
}
|
|
1511
1531
|
cached = { policy, locked };
|
|
1512
1532
|
return cached;
|
|
1513
1533
|
}
|
|
@@ -1624,6 +1644,298 @@ function clearPinTlsExpectedFingerprint(dataDir) {
|
|
|
1624
1644
|
writeAsConfig(dataDir, { pinTlsExpectedFingerprint: void 0 });
|
|
1625
1645
|
}
|
|
1626
1646
|
|
|
1647
|
+
// src/lib/managed-team.ts
|
|
1648
|
+
async function activeTeamId(asUrl, cookie, pinning) {
|
|
1649
|
+
const { res } = await fetchAs(
|
|
1650
|
+
`${asUrl}/api/groups/me`,
|
|
1651
|
+
{ headers: { Cookie: cookie }, signal: AbortSignal.timeout(1e4) },
|
|
1652
|
+
pinning
|
|
1653
|
+
);
|
|
1654
|
+
if (!res.ok) throw new Error(`GET /api/groups/me answered ${res.status}`);
|
|
1655
|
+
const data = await res.json();
|
|
1656
|
+
return data.group?.id ?? null;
|
|
1657
|
+
}
|
|
1658
|
+
var ManagedTeamScheduler = class {
|
|
1659
|
+
constructor(check2, opts = {}) {
|
|
1660
|
+
this.check = check2;
|
|
1661
|
+
this.setIntervalFn = opts.setIntervalFn ?? setInterval;
|
|
1662
|
+
this.clearIntervalFn = opts.clearIntervalFn ?? clearInterval;
|
|
1663
|
+
this.intervalMs = opts.intervalMs ?? 5 * 6e4;
|
|
1664
|
+
}
|
|
1665
|
+
timer = null;
|
|
1666
|
+
setIntervalFn;
|
|
1667
|
+
clearIntervalFn;
|
|
1668
|
+
intervalMs;
|
|
1669
|
+
start() {
|
|
1670
|
+
if (this.timer) return;
|
|
1671
|
+
this.timer = this.setIntervalFn(() => {
|
|
1672
|
+
void this.check();
|
|
1673
|
+
}, this.intervalMs);
|
|
1674
|
+
}
|
|
1675
|
+
stop() {
|
|
1676
|
+
if (this.timer) this.clearIntervalFn(this.timer);
|
|
1677
|
+
this.timer = null;
|
|
1678
|
+
}
|
|
1679
|
+
/** Test-only: run the check immediately, bypassing the timer. */
|
|
1680
|
+
runNow() {
|
|
1681
|
+
return this.check();
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
|
|
1685
|
+
// src/lib/event-bus.ts
|
|
1686
|
+
var EventBus = class {
|
|
1687
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1688
|
+
/**
|
|
1689
|
+
* Subscribe to all bus events.
|
|
1690
|
+
* Returns an unsubscribe function — callers MUST call it on disconnect / unmount
|
|
1691
|
+
* to avoid listener leaks.
|
|
1692
|
+
*/
|
|
1693
|
+
subscribe(handler) {
|
|
1694
|
+
this.listeners.add(handler);
|
|
1695
|
+
return () => {
|
|
1696
|
+
this.listeners.delete(handler);
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
emit(type, payload) {
|
|
1700
|
+
const event = { type, payload };
|
|
1701
|
+
for (const handler of this.listeners) {
|
|
1702
|
+
try {
|
|
1703
|
+
handler(event);
|
|
1704
|
+
} catch (err) {
|
|
1705
|
+
console.error("[EventBus] subscriber threw:", err);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
/** How many listeners are currently subscribed. Useful for tests and health checks. */
|
|
1710
|
+
get listenerCount() {
|
|
1711
|
+
return this.listeners.size;
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
var eventBus = new EventBus();
|
|
1715
|
+
|
|
1716
|
+
// src/lib/desktop-notify.ts
|
|
1717
|
+
import { spawn, spawnSync as spawnSync2 } from "child_process";
|
|
1718
|
+
|
|
1719
|
+
// src/lib/notifier-icon.ts
|
|
1720
|
+
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
|
|
1721
|
+
import { homedir as homedir2 } from "os";
|
|
1722
|
+
import { join as join7, dirname as dirname5 } from "path";
|
|
1723
|
+
var ICON_BASE64 = "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";
|
|
1724
|
+
function ensureNotifierIcon(dataDir) {
|
|
1725
|
+
try {
|
|
1726
|
+
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join7(homedir2(), ".suveren");
|
|
1727
|
+
const path = join7(dir, "notifier-icon.png");
|
|
1728
|
+
if (!existsSync6(path)) {
|
|
1729
|
+
mkdirSync4(dirname5(path), { recursive: true });
|
|
1730
|
+
writeFileSync4(path, Buffer.from(ICON_BASE64, "base64"));
|
|
1731
|
+
}
|
|
1732
|
+
return path;
|
|
1733
|
+
} catch {
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
// src/lib/desktop-notify.ts
|
|
1739
|
+
function osaQuote(value) {
|
|
1740
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1741
|
+
}
|
|
1742
|
+
function psQuote(value) {
|
|
1743
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
1744
|
+
}
|
|
1745
|
+
function xmlEscape(value) {
|
|
1746
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1747
|
+
}
|
|
1748
|
+
var WINDOWS_AUMID = "Suveren.Gateway";
|
|
1749
|
+
function windowsToastScript(title, message, url, iconPath) {
|
|
1750
|
+
const toastXml = `<toast${url ? ` activationType="protocol" launch="${xmlEscape(url)}"` : ""}><visual><binding template="ToastGeneric"><text>${xmlEscape(title)}</text><text>${xmlEscape(message)}</text>` + (iconPath ? `<image placement="appLogoOverride" src="${xmlEscape(iconPath)}"/>` : "") + `</binding></visual><audio src="ms-winsoundevent:Notification.Default"/></toast>`;
|
|
1751
|
+
return `$ErrorActionPreference='SilentlyContinue'; $aumid=${psQuote(WINDOWS_AUMID)}; $reg="HKCU:\\Software\\Classes\\AppUserModelId\\$aumid"; if (-not (Test-Path $reg)) { New-Item -Path $reg -Force | Out-Null }; New-ItemProperty -Path $reg -Name DisplayName -Value 'Suveren' -PropertyType String -Force | Out-Null; ` + (iconPath ? `New-ItemProperty -Path $reg -Name IconUri -Value ${psQuote(iconPath)} -PropertyType String -Force | Out-Null; ` : "") + `[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType=WindowsRuntime] | Out-Null; [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType=WindowsRuntime] | Out-Null; $doc = New-Object Windows.Data.Xml.Dom.XmlDocument; $doc.LoadXml(${psQuote(toastXml)}); $toast = New-Object Windows.UI.Notifications.ToastNotification $doc; [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($aumid).Show($toast)`;
|
|
1752
|
+
}
|
|
1753
|
+
var MAC_SOUND = "Glass";
|
|
1754
|
+
function buildNotifyCommand(platform, title, message, options = {}) {
|
|
1755
|
+
const { url, hasTerminalNotifier: hasTerminalNotifier2 = false, iconPath } = options;
|
|
1756
|
+
switch (platform) {
|
|
1757
|
+
case "darwin":
|
|
1758
|
+
if (url && hasTerminalNotifier2) {
|
|
1759
|
+
return {
|
|
1760
|
+
cmd: "terminal-notifier",
|
|
1761
|
+
args: [
|
|
1762
|
+
"-title",
|
|
1763
|
+
title,
|
|
1764
|
+
"-message",
|
|
1765
|
+
message,
|
|
1766
|
+
"-open",
|
|
1767
|
+
url,
|
|
1768
|
+
"-sound",
|
|
1769
|
+
MAC_SOUND,
|
|
1770
|
+
...iconPath ? ["-appIcon", iconPath] : [],
|
|
1771
|
+
"-group",
|
|
1772
|
+
"ai.suveren.gateway"
|
|
1773
|
+
]
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
return {
|
|
1777
|
+
cmd: "osascript",
|
|
1778
|
+
args: [
|
|
1779
|
+
"-e",
|
|
1780
|
+
`display notification ${osaQuote(message)} with title ${osaQuote(title)} sound name ${osaQuote(MAC_SOUND)}`
|
|
1781
|
+
]
|
|
1782
|
+
};
|
|
1783
|
+
case "win32":
|
|
1784
|
+
return {
|
|
1785
|
+
cmd: "powershell",
|
|
1786
|
+
args: ["-NoProfile", "-NonInteractive", "-Command", windowsToastScript(title, message, url, iconPath)]
|
|
1787
|
+
};
|
|
1788
|
+
case "linux":
|
|
1789
|
+
return {
|
|
1790
|
+
cmd: "notify-send",
|
|
1791
|
+
args: [
|
|
1792
|
+
"--app-name=Suveren",
|
|
1793
|
+
...iconPath ? ["-i", iconPath] : [],
|
|
1794
|
+
title,
|
|
1795
|
+
message
|
|
1796
|
+
]
|
|
1797
|
+
};
|
|
1798
|
+
default:
|
|
1799
|
+
return null;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
function lockedNotification(port2) {
|
|
1803
|
+
const url = `http://localhost:${port2}`;
|
|
1804
|
+
return {
|
|
1805
|
+
title: "Suveren is running but locked",
|
|
1806
|
+
message: `Your agent has no authority until you unlock it. Open ${url} and enter your API key.`,
|
|
1807
|
+
url
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
function sessionExpiredNotification(port2) {
|
|
1811
|
+
const url = `http://localhost:${port2}`;
|
|
1812
|
+
return {
|
|
1813
|
+
title: "Suveren Gateway locked",
|
|
1814
|
+
message: "Your sign-in has ended. Sign in again so your agents can act.",
|
|
1815
|
+
url
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
var terminalNotifierCache = null;
|
|
1819
|
+
function hasTerminalNotifier() {
|
|
1820
|
+
if (terminalNotifierCache !== null) return terminalNotifierCache;
|
|
1821
|
+
try {
|
|
1822
|
+
const probe = spawnSync2("terminal-notifier", ["-help"], { stdio: "ignore" });
|
|
1823
|
+
terminalNotifierCache = probe.error === void 0;
|
|
1824
|
+
} catch {
|
|
1825
|
+
terminalNotifierCache = false;
|
|
1826
|
+
}
|
|
1827
|
+
return terminalNotifierCache;
|
|
1828
|
+
}
|
|
1829
|
+
function notifySpawnOptions(platform) {
|
|
1830
|
+
return {
|
|
1831
|
+
detached: platform !== "win32",
|
|
1832
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
function notify(title, message, platform = process.platform, url) {
|
|
1836
|
+
const command = buildNotifyCommand(platform, title, message, {
|
|
1837
|
+
url,
|
|
1838
|
+
hasTerminalNotifier: platform === "darwin" ? hasTerminalNotifier() : false,
|
|
1839
|
+
// Windows and Linux show it on the toast itself; macOS only via
|
|
1840
|
+
// terminal-notifier. Failure to write it must never block the sound.
|
|
1841
|
+
iconPath: ensureNotifierIcon()
|
|
1842
|
+
});
|
|
1843
|
+
if (!command) return;
|
|
1844
|
+
try {
|
|
1845
|
+
const child = spawn(command.cmd, command.args, notifySpawnOptions(platform));
|
|
1846
|
+
let stderr = "";
|
|
1847
|
+
child.stderr?.on("data", (d) => {
|
|
1848
|
+
stderr += d.toString().slice(0, 500);
|
|
1849
|
+
});
|
|
1850
|
+
child.on("close", (code) => {
|
|
1851
|
+
if (code !== 0) {
|
|
1852
|
+
console.error(`[notify] ${command.cmd} exited ${code}${stderr ? `: ${stderr.trim()}` : ""}`);
|
|
1853
|
+
}
|
|
1854
|
+
});
|
|
1855
|
+
child.on("error", () => {
|
|
1856
|
+
});
|
|
1857
|
+
child.unref();
|
|
1858
|
+
} catch {
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
// src/lib/session-lock.ts
|
|
1863
|
+
var SESSION_LOCKED_MESSAGE = "Your sign-in ended after 30 days or was revoked. Sign in again.";
|
|
1864
|
+
var AS_KEY_MISMATCH_MESSAGE = "The Authority Server presented a signing key that does not match the one pinned at pairing. Sign in again to review.";
|
|
1865
|
+
var AS_TLS_MISMATCH_MESSAGE = "A connection to the Authority Server presented a TLS certificate that does not match the one pinned at pairing. The connection was refused before anything was sent. If the certificate changed intentionally (a new key, not just renewal), clear the pairing and sign in again to re-pin it.";
|
|
1866
|
+
function managedWrongTeamMessage(team) {
|
|
1867
|
+
return `This gateway is set up by your IT for team ${team}. Your account is no longer in this team. Sign in with your team account, or ask your IT.`;
|
|
1868
|
+
}
|
|
1869
|
+
function createSessionLock(deps) {
|
|
1870
|
+
const { vault: vault2, port: port2 } = deps;
|
|
1871
|
+
const notifyFn = deps.notifyFn ?? notify;
|
|
1872
|
+
const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
|
|
1873
|
+
const emit = deps.emit ?? eventBus.emit.bind(eventBus);
|
|
1874
|
+
return function lockExpiredSession2() {
|
|
1875
|
+
if (!vault2.isUnlocked()) return;
|
|
1876
|
+
vault2.lockExpired();
|
|
1877
|
+
console.error("[Control Plane] Authority Server session ended \u2014 gateway LOCKED");
|
|
1878
|
+
void unconfigureSessionFn().catch((err) => {
|
|
1879
|
+
console.error("[Control Plane] Failed to push cleared session to MCP:", err);
|
|
1880
|
+
});
|
|
1881
|
+
emit("session-locked", { reason: "expired", message: SESSION_LOCKED_MESSAGE });
|
|
1882
|
+
const { title, message, url } = sessionExpiredNotification(port2);
|
|
1883
|
+
notifyFn(title, message, process.platform, url);
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
function createAsKeyMismatchLock(deps) {
|
|
1887
|
+
const { vault: vault2, port: port2 } = deps;
|
|
1888
|
+
const notifyFn = deps.notifyFn ?? notify;
|
|
1889
|
+
const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
|
|
1890
|
+
const emit = deps.emit ?? eventBus.emit.bind(eventBus);
|
|
1891
|
+
return function lockOnAsKeyMismatch() {
|
|
1892
|
+
if (!vault2.isUnlocked()) return;
|
|
1893
|
+
vault2.lockAsKeyMismatch();
|
|
1894
|
+
console.error("[Control Plane] Authority Server key mismatch \u2014 gateway LOCKED");
|
|
1895
|
+
void unconfigureSessionFn("as-key-mismatch").catch((err) => {
|
|
1896
|
+
console.error("[Control Plane] Failed to push cleared session to MCP:", err);
|
|
1897
|
+
});
|
|
1898
|
+
emit("session-locked", { reason: "as-key-mismatch", message: AS_KEY_MISMATCH_MESSAGE });
|
|
1899
|
+
const { title, url } = sessionExpiredNotification(port2);
|
|
1900
|
+
notifyFn(title, AS_KEY_MISMATCH_MESSAGE, process.platform, url);
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
function createAsTlsMismatchLock(deps) {
|
|
1904
|
+
const { vault: vault2, port: port2 } = deps;
|
|
1905
|
+
const notifyFn = deps.notifyFn ?? notify;
|
|
1906
|
+
const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
|
|
1907
|
+
const emit = deps.emit ?? eventBus.emit.bind(eventBus);
|
|
1908
|
+
return function lockOnAsTlsMismatch() {
|
|
1909
|
+
if (!vault2.isUnlocked()) return;
|
|
1910
|
+
vault2.lockAsTlsMismatch();
|
|
1911
|
+
console.error("[Control Plane] Authority Server TLS pin mismatch \u2014 gateway LOCKED");
|
|
1912
|
+
void unconfigureSessionFn("as-tls-mismatch").catch((err) => {
|
|
1913
|
+
console.error("[Control Plane] Failed to push cleared session to MCP:", err);
|
|
1914
|
+
});
|
|
1915
|
+
emit("session-locked", { reason: "as-tls-mismatch", message: AS_TLS_MISMATCH_MESSAGE });
|
|
1916
|
+
const { title, url } = sessionExpiredNotification(port2);
|
|
1917
|
+
notifyFn(title, AS_TLS_MISMATCH_MESSAGE, process.platform, url);
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
function createManagedWrongTeamLock(deps) {
|
|
1921
|
+
const { vault: vault2, port: port2 } = deps;
|
|
1922
|
+
const notifyFn = deps.notifyFn ?? notify;
|
|
1923
|
+
const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
|
|
1924
|
+
const emit = deps.emit ?? eventBus.emit.bind(eventBus);
|
|
1925
|
+
return function lockOnManagedWrongTeam(team) {
|
|
1926
|
+
if (!vault2.isUnlocked()) return;
|
|
1927
|
+
vault2.lockManagedWrongTeam();
|
|
1928
|
+
console.error("[Control Plane] Managed gateway: signed-in account is no longer in the locked team \u2014 gateway LOCKED");
|
|
1929
|
+
const message = managedWrongTeamMessage(team);
|
|
1930
|
+
void unconfigureSessionFn("managed-wrong-team").catch((err) => {
|
|
1931
|
+
console.error("[Control Plane] Failed to push cleared session to MCP:", err);
|
|
1932
|
+
});
|
|
1933
|
+
emit("session-locked", { reason: "managed-wrong-team", message });
|
|
1934
|
+
const { title, url } = sessionExpiredNotification(port2);
|
|
1935
|
+
notifyFn(title, message, process.platform, url);
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1627
1939
|
// src/routes/auth.ts
|
|
1628
1940
|
var DEFAULT_SP_URL = process.env.SUVEREN_AS_URL ?? "https://www.suveren.ai";
|
|
1629
1941
|
async function fetchAsPublicKey(asUrl) {
|
|
@@ -1706,7 +2018,7 @@ async function checkAsKeyBeforeLogin(asUrl, dataDir) {
|
|
|
1706
2018
|
};
|
|
1707
2019
|
}
|
|
1708
2020
|
function createAuthRouter(vault2, logoutAuth, loginRateLimit2, onSessionEstablished, pairing, lockAsTlsMismatch2 = () => {
|
|
1709
|
-
}) {
|
|
2021
|
+
}, managedTeamId) {
|
|
1710
2022
|
const router = Router();
|
|
1711
2023
|
const SP_URL2 = pairing?.asUrl ?? DEFAULT_SP_URL;
|
|
1712
2024
|
router.post("/login", loginRateLimit2, async (req, res) => {
|
|
@@ -1788,6 +2100,25 @@ function createAuthRouter(vault2, logoutAuth, loginRateLimit2, onSessionEstablis
|
|
|
1788
2100
|
const data = await spRes.json();
|
|
1789
2101
|
const setCookieHeaders = spRes.headers.getSetCookie?.() ?? [];
|
|
1790
2102
|
const sessionCookie = setCookieHeaders.join("; ");
|
|
2103
|
+
if (managedTeamId) {
|
|
2104
|
+
let activeTeam;
|
|
2105
|
+
try {
|
|
2106
|
+
activeTeam = await activeTeamId(SP_URL2, sessionCookie, sessionPinning);
|
|
2107
|
+
} catch (err) {
|
|
2108
|
+
res.status(502).json({
|
|
2109
|
+
error: "as_unreachable",
|
|
2110
|
+
message: `Could not confirm your team membership with the Authority Server \u2014 ${err instanceof Error ? err.message : String(err)}. Refusing to sign in.`
|
|
2111
|
+
});
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
2114
|
+
if (activeTeam !== managedTeamId) {
|
|
2115
|
+
res.status(403).json({
|
|
2116
|
+
error: "managed_wrong_team",
|
|
2117
|
+
message: managedWrongTeamMessage(managedTeamId)
|
|
2118
|
+
});
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
1791
2122
|
vault2.setSpCookie(sessionCookie);
|
|
1792
2123
|
await vault2.deriveAndSetKey(apiKey);
|
|
1793
2124
|
if (vault2.isVaultFromDifferentKey()) {
|
|
@@ -2561,17 +2892,17 @@ async function testAIConnectivity(config) {
|
|
|
2561
2892
|
}
|
|
2562
2893
|
|
|
2563
2894
|
// src/lib/agent-brief-store.ts
|
|
2564
|
-
import { existsSync as
|
|
2565
|
-
import { homedir as
|
|
2566
|
-
import { join as
|
|
2895
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
2896
|
+
import { homedir as homedir3 } from "os";
|
|
2897
|
+
import { join as join8 } from "path";
|
|
2567
2898
|
var AGENT_CONTEXT_MAX_BYTES = 16 * 1024;
|
|
2568
2899
|
function agentBriefPath() {
|
|
2569
|
-
const dataDir = process.env.SUVEREN_DATA_DIR ??
|
|
2570
|
-
return
|
|
2900
|
+
const dataDir = process.env.SUVEREN_DATA_DIR ?? join8(homedir3(), ".suveren");
|
|
2901
|
+
return join8(dataDir, "context.md");
|
|
2571
2902
|
}
|
|
2572
2903
|
function readAgentBrief() {
|
|
2573
2904
|
const filePath = agentBriefPath();
|
|
2574
|
-
if (!
|
|
2905
|
+
if (!existsSync7(filePath)) return "";
|
|
2575
2906
|
return readFileSync8(filePath, "utf-8");
|
|
2576
2907
|
}
|
|
2577
2908
|
|
|
@@ -2853,39 +3184,6 @@ async function backfillReadPolicyDefaults(data) {
|
|
|
2853
3184
|
|
|
2854
3185
|
// src/routes/mcp.ts
|
|
2855
3186
|
import { Router as Router5 } from "express";
|
|
2856
|
-
|
|
2857
|
-
// src/lib/event-bus.ts
|
|
2858
|
-
var EventBus = class {
|
|
2859
|
-
listeners = /* @__PURE__ */ new Set();
|
|
2860
|
-
/**
|
|
2861
|
-
* Subscribe to all bus events.
|
|
2862
|
-
* Returns an unsubscribe function — callers MUST call it on disconnect / unmount
|
|
2863
|
-
* to avoid listener leaks.
|
|
2864
|
-
*/
|
|
2865
|
-
subscribe(handler) {
|
|
2866
|
-
this.listeners.add(handler);
|
|
2867
|
-
return () => {
|
|
2868
|
-
this.listeners.delete(handler);
|
|
2869
|
-
};
|
|
2870
|
-
}
|
|
2871
|
-
emit(type, payload) {
|
|
2872
|
-
const event = { type, payload };
|
|
2873
|
-
for (const handler of this.listeners) {
|
|
2874
|
-
try {
|
|
2875
|
-
handler(event);
|
|
2876
|
-
} catch (err) {
|
|
2877
|
-
console.error("[EventBus] subscriber threw:", err);
|
|
2878
|
-
}
|
|
2879
|
-
}
|
|
2880
|
-
}
|
|
2881
|
-
/** How many listeners are currently subscribed. Useful for tests and health checks. */
|
|
2882
|
-
get listenerCount() {
|
|
2883
|
-
return this.listeners.size;
|
|
2884
|
-
}
|
|
2885
|
-
};
|
|
2886
|
-
var eventBus = new EventBus();
|
|
2887
|
-
|
|
2888
|
-
// src/routes/mcp.ts
|
|
2889
3187
|
function createMCPRouter() {
|
|
2890
3188
|
const router = Router5();
|
|
2891
3189
|
router.get("/health", async (_req, res) => {
|
|
@@ -2958,7 +3256,7 @@ function createMCPRouter() {
|
|
|
2958
3256
|
// src/routes/autostart.ts
|
|
2959
3257
|
import { Router as Router6 } from "express";
|
|
2960
3258
|
import { execFile } from "child_process";
|
|
2961
|
-
import { existsSync as
|
|
3259
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2962
3260
|
import { dirname as pathDirname, resolve } from "path";
|
|
2963
3261
|
import { fileURLToPath } from "url";
|
|
2964
3262
|
import { promisify } from "util";
|
|
@@ -2979,7 +3277,7 @@ function findCli(dirname10 = MODULE_DIR) {
|
|
|
2979
3277
|
// resolves differently between a shell and a login service — and it would
|
|
2980
3278
|
// mask a genuinely missing CLI whenever the cwd happened to be the repo.
|
|
2981
3279
|
];
|
|
2982
|
-
return candidates.find(
|
|
3280
|
+
return candidates.find(existsSync8) ?? null;
|
|
2983
3281
|
}
|
|
2984
3282
|
async function cli(args) {
|
|
2985
3283
|
const bin = findCli();
|
|
@@ -3056,152 +3354,6 @@ function createAutostartRouter() {
|
|
|
3056
3354
|
return router;
|
|
3057
3355
|
}
|
|
3058
3356
|
|
|
3059
|
-
// src/lib/desktop-notify.ts
|
|
3060
|
-
import { spawn, spawnSync as spawnSync2 } from "child_process";
|
|
3061
|
-
|
|
3062
|
-
// src/lib/notifier-icon.ts
|
|
3063
|
-
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, existsSync as existsSync8 } from "fs";
|
|
3064
|
-
import { homedir as homedir3 } from "os";
|
|
3065
|
-
import { join as join8, dirname as dirname5 } from "path";
|
|
3066
|
-
var ICON_BASE64 = "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";
|
|
3067
|
-
function ensureNotifierIcon(dataDir) {
|
|
3068
|
-
try {
|
|
3069
|
-
const dir = dataDir ?? process.env.SUVEREN_DATA_DIR ?? join8(homedir3(), ".suveren");
|
|
3070
|
-
const path = join8(dir, "notifier-icon.png");
|
|
3071
|
-
if (!existsSync8(path)) {
|
|
3072
|
-
mkdirSync4(dirname5(path), { recursive: true });
|
|
3073
|
-
writeFileSync4(path, Buffer.from(ICON_BASE64, "base64"));
|
|
3074
|
-
}
|
|
3075
|
-
return path;
|
|
3076
|
-
} catch {
|
|
3077
|
-
return null;
|
|
3078
|
-
}
|
|
3079
|
-
}
|
|
3080
|
-
|
|
3081
|
-
// src/lib/desktop-notify.ts
|
|
3082
|
-
function osaQuote(value) {
|
|
3083
|
-
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
3084
|
-
}
|
|
3085
|
-
function psQuote(value) {
|
|
3086
|
-
return `'${value.replace(/'/g, "''")}'`;
|
|
3087
|
-
}
|
|
3088
|
-
function xmlEscape(value) {
|
|
3089
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3090
|
-
}
|
|
3091
|
-
var WINDOWS_AUMID = "Suveren.Gateway";
|
|
3092
|
-
function windowsToastScript(title, message, url, iconPath) {
|
|
3093
|
-
const toastXml = `<toast${url ? ` activationType="protocol" launch="${xmlEscape(url)}"` : ""}><visual><binding template="ToastGeneric"><text>${xmlEscape(title)}</text><text>${xmlEscape(message)}</text>` + (iconPath ? `<image placement="appLogoOverride" src="${xmlEscape(iconPath)}"/>` : "") + `</binding></visual><audio src="ms-winsoundevent:Notification.Default"/></toast>`;
|
|
3094
|
-
return `$ErrorActionPreference='SilentlyContinue'; $aumid=${psQuote(WINDOWS_AUMID)}; $reg="HKCU:\\Software\\Classes\\AppUserModelId\\$aumid"; if (-not (Test-Path $reg)) { New-Item -Path $reg -Force | Out-Null }; New-ItemProperty -Path $reg -Name DisplayName -Value 'Suveren' -PropertyType String -Force | Out-Null; ` + (iconPath ? `New-ItemProperty -Path $reg -Name IconUri -Value ${psQuote(iconPath)} -PropertyType String -Force | Out-Null; ` : "") + `[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType=WindowsRuntime] | Out-Null; [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType=WindowsRuntime] | Out-Null; $doc = New-Object Windows.Data.Xml.Dom.XmlDocument; $doc.LoadXml(${psQuote(toastXml)}); $toast = New-Object Windows.UI.Notifications.ToastNotification $doc; [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($aumid).Show($toast)`;
|
|
3095
|
-
}
|
|
3096
|
-
var MAC_SOUND = "Glass";
|
|
3097
|
-
function buildNotifyCommand(platform, title, message, options = {}) {
|
|
3098
|
-
const { url, hasTerminalNotifier: hasTerminalNotifier2 = false, iconPath } = options;
|
|
3099
|
-
switch (platform) {
|
|
3100
|
-
case "darwin":
|
|
3101
|
-
if (url && hasTerminalNotifier2) {
|
|
3102
|
-
return {
|
|
3103
|
-
cmd: "terminal-notifier",
|
|
3104
|
-
args: [
|
|
3105
|
-
"-title",
|
|
3106
|
-
title,
|
|
3107
|
-
"-message",
|
|
3108
|
-
message,
|
|
3109
|
-
"-open",
|
|
3110
|
-
url,
|
|
3111
|
-
"-sound",
|
|
3112
|
-
MAC_SOUND,
|
|
3113
|
-
...iconPath ? ["-appIcon", iconPath] : [],
|
|
3114
|
-
"-group",
|
|
3115
|
-
"ai.suveren.gateway"
|
|
3116
|
-
]
|
|
3117
|
-
};
|
|
3118
|
-
}
|
|
3119
|
-
return {
|
|
3120
|
-
cmd: "osascript",
|
|
3121
|
-
args: [
|
|
3122
|
-
"-e",
|
|
3123
|
-
`display notification ${osaQuote(message)} with title ${osaQuote(title)} sound name ${osaQuote(MAC_SOUND)}`
|
|
3124
|
-
]
|
|
3125
|
-
};
|
|
3126
|
-
case "win32":
|
|
3127
|
-
return {
|
|
3128
|
-
cmd: "powershell",
|
|
3129
|
-
args: ["-NoProfile", "-NonInteractive", "-Command", windowsToastScript(title, message, url, iconPath)]
|
|
3130
|
-
};
|
|
3131
|
-
case "linux":
|
|
3132
|
-
return {
|
|
3133
|
-
cmd: "notify-send",
|
|
3134
|
-
args: [
|
|
3135
|
-
"--app-name=Suveren",
|
|
3136
|
-
...iconPath ? ["-i", iconPath] : [],
|
|
3137
|
-
title,
|
|
3138
|
-
message
|
|
3139
|
-
]
|
|
3140
|
-
};
|
|
3141
|
-
default:
|
|
3142
|
-
return null;
|
|
3143
|
-
}
|
|
3144
|
-
}
|
|
3145
|
-
function lockedNotification(port2) {
|
|
3146
|
-
const url = `http://localhost:${port2}`;
|
|
3147
|
-
return {
|
|
3148
|
-
title: "Suveren is running but locked",
|
|
3149
|
-
message: `Your agent has no authority until you unlock it. Open ${url} and enter your API key.`,
|
|
3150
|
-
url
|
|
3151
|
-
};
|
|
3152
|
-
}
|
|
3153
|
-
function sessionExpiredNotification(port2) {
|
|
3154
|
-
const url = `http://localhost:${port2}`;
|
|
3155
|
-
return {
|
|
3156
|
-
title: "Suveren Gateway locked",
|
|
3157
|
-
message: "Your sign-in has ended. Sign in again so your agents can act.",
|
|
3158
|
-
url
|
|
3159
|
-
};
|
|
3160
|
-
}
|
|
3161
|
-
var terminalNotifierCache = null;
|
|
3162
|
-
function hasTerminalNotifier() {
|
|
3163
|
-
if (terminalNotifierCache !== null) return terminalNotifierCache;
|
|
3164
|
-
try {
|
|
3165
|
-
const probe = spawnSync2("terminal-notifier", ["-help"], { stdio: "ignore" });
|
|
3166
|
-
terminalNotifierCache = probe.error === void 0;
|
|
3167
|
-
} catch {
|
|
3168
|
-
terminalNotifierCache = false;
|
|
3169
|
-
}
|
|
3170
|
-
return terminalNotifierCache;
|
|
3171
|
-
}
|
|
3172
|
-
function notifySpawnOptions(platform) {
|
|
3173
|
-
return {
|
|
3174
|
-
detached: platform !== "win32",
|
|
3175
|
-
stdio: ["ignore", "ignore", "pipe"]
|
|
3176
|
-
};
|
|
3177
|
-
}
|
|
3178
|
-
function notify(title, message, platform = process.platform, url) {
|
|
3179
|
-
const command = buildNotifyCommand(platform, title, message, {
|
|
3180
|
-
url,
|
|
3181
|
-
hasTerminalNotifier: platform === "darwin" ? hasTerminalNotifier() : false,
|
|
3182
|
-
// Windows and Linux show it on the toast itself; macOS only via
|
|
3183
|
-
// terminal-notifier. Failure to write it must never block the sound.
|
|
3184
|
-
iconPath: ensureNotifierIcon()
|
|
3185
|
-
});
|
|
3186
|
-
if (!command) return;
|
|
3187
|
-
try {
|
|
3188
|
-
const child = spawn(command.cmd, command.args, notifySpawnOptions(platform));
|
|
3189
|
-
let stderr = "";
|
|
3190
|
-
child.stderr?.on("data", (d) => {
|
|
3191
|
-
stderr += d.toString().slice(0, 500);
|
|
3192
|
-
});
|
|
3193
|
-
child.on("close", (code) => {
|
|
3194
|
-
if (code !== 0) {
|
|
3195
|
-
console.error(`[notify] ${command.cmd} exited ${code}${stderr ? `: ${stderr.trim()}` : ""}`);
|
|
3196
|
-
}
|
|
3197
|
-
});
|
|
3198
|
-
child.on("error", () => {
|
|
3199
|
-
});
|
|
3200
|
-
child.unref();
|
|
3201
|
-
} catch {
|
|
3202
|
-
}
|
|
3203
|
-
}
|
|
3204
|
-
|
|
3205
3357
|
// src/routes/encrypt-intent.ts
|
|
3206
3358
|
import { Router as Router7 } from "express";
|
|
3207
3359
|
|
|
@@ -4380,62 +4532,6 @@ function startNotificationDispatcher(opts = {}) {
|
|
|
4380
4532
|
return new NotificationDispatcher(opts).start();
|
|
4381
4533
|
}
|
|
4382
4534
|
|
|
4383
|
-
// src/lib/session-lock.ts
|
|
4384
|
-
var SESSION_LOCKED_MESSAGE = "Your sign-in ended after 30 days or was revoked. Sign in again.";
|
|
4385
|
-
var AS_KEY_MISMATCH_MESSAGE = "The Authority Server presented a signing key that does not match the one pinned at pairing. Sign in again to review.";
|
|
4386
|
-
var AS_TLS_MISMATCH_MESSAGE = "A connection to the Authority Server presented a TLS certificate that does not match the one pinned at pairing. The connection was refused before anything was sent. If the certificate changed intentionally (a new key, not just renewal), clear the pairing and sign in again to re-pin it.";
|
|
4387
|
-
function createSessionLock(deps) {
|
|
4388
|
-
const { vault: vault2, port: port2 } = deps;
|
|
4389
|
-
const notifyFn = deps.notifyFn ?? notify;
|
|
4390
|
-
const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
|
|
4391
|
-
const emit = deps.emit ?? eventBus.emit.bind(eventBus);
|
|
4392
|
-
return function lockExpiredSession2() {
|
|
4393
|
-
if (!vault2.isUnlocked()) return;
|
|
4394
|
-
vault2.lockExpired();
|
|
4395
|
-
console.error("[Control Plane] Authority Server session ended \u2014 gateway LOCKED");
|
|
4396
|
-
void unconfigureSessionFn().catch((err) => {
|
|
4397
|
-
console.error("[Control Plane] Failed to push cleared session to MCP:", err);
|
|
4398
|
-
});
|
|
4399
|
-
emit("session-locked", { reason: "expired", message: SESSION_LOCKED_MESSAGE });
|
|
4400
|
-
const { title, message, url } = sessionExpiredNotification(port2);
|
|
4401
|
-
notifyFn(title, message, process.platform, url);
|
|
4402
|
-
};
|
|
4403
|
-
}
|
|
4404
|
-
function createAsKeyMismatchLock(deps) {
|
|
4405
|
-
const { vault: vault2, port: port2 } = deps;
|
|
4406
|
-
const notifyFn = deps.notifyFn ?? notify;
|
|
4407
|
-
const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
|
|
4408
|
-
const emit = deps.emit ?? eventBus.emit.bind(eventBus);
|
|
4409
|
-
return function lockOnAsKeyMismatch() {
|
|
4410
|
-
if (!vault2.isUnlocked()) return;
|
|
4411
|
-
vault2.lockAsKeyMismatch();
|
|
4412
|
-
console.error("[Control Plane] Authority Server key mismatch \u2014 gateway LOCKED");
|
|
4413
|
-
void unconfigureSessionFn("as-key-mismatch").catch((err) => {
|
|
4414
|
-
console.error("[Control Plane] Failed to push cleared session to MCP:", err);
|
|
4415
|
-
});
|
|
4416
|
-
emit("session-locked", { reason: "as-key-mismatch", message: AS_KEY_MISMATCH_MESSAGE });
|
|
4417
|
-
const { title, url } = sessionExpiredNotification(port2);
|
|
4418
|
-
notifyFn(title, AS_KEY_MISMATCH_MESSAGE, process.platform, url);
|
|
4419
|
-
};
|
|
4420
|
-
}
|
|
4421
|
-
function createAsTlsMismatchLock(deps) {
|
|
4422
|
-
const { vault: vault2, port: port2 } = deps;
|
|
4423
|
-
const notifyFn = deps.notifyFn ?? notify;
|
|
4424
|
-
const unconfigureSessionFn = deps.unconfigureSessionFn ?? unconfigureSession;
|
|
4425
|
-
const emit = deps.emit ?? eventBus.emit.bind(eventBus);
|
|
4426
|
-
return function lockOnAsTlsMismatch() {
|
|
4427
|
-
if (!vault2.isUnlocked()) return;
|
|
4428
|
-
vault2.lockAsTlsMismatch();
|
|
4429
|
-
console.error("[Control Plane] Authority Server TLS pin mismatch \u2014 gateway LOCKED");
|
|
4430
|
-
void unconfigureSessionFn("as-tls-mismatch").catch((err) => {
|
|
4431
|
-
console.error("[Control Plane] Failed to push cleared session to MCP:", err);
|
|
4432
|
-
});
|
|
4433
|
-
emit("session-locked", { reason: "as-tls-mismatch", message: AS_TLS_MISMATCH_MESSAGE });
|
|
4434
|
-
const { title, url } = sessionExpiredNotification(port2);
|
|
4435
|
-
notifyFn(title, AS_TLS_MISMATCH_MESSAGE, process.platform, url);
|
|
4436
|
-
};
|
|
4437
|
-
}
|
|
4438
|
-
|
|
4439
4535
|
// src/lib/session-expiry-scheduler.ts
|
|
4440
4536
|
var MAX_TIMEOUT_MS = 2147483647;
|
|
4441
4537
|
var SessionExpiryScheduler = class {
|
|
@@ -4555,6 +4651,7 @@ var SP_URL = resolveAsUrl(DATA_DIR);
|
|
|
4555
4651
|
validatePinTlsForUrl(resolvePinTls(DATA_DIR), SP_URL);
|
|
4556
4652
|
var POLICY_LOCKED = [...readPolicy().locked];
|
|
4557
4653
|
var POLICY_LOCKED_INSTALL_METHOD = readPolicy().policy.installMethod;
|
|
4654
|
+
var POLICY_LOCKED_TEAM = readPolicy().policy.team;
|
|
4558
4655
|
var port = parseInt(process.env.SUVEREN_CP_PORT ?? "3402", 10);
|
|
4559
4656
|
var HAP_MODE = process.env.HAP_MODE ?? "personal";
|
|
4560
4657
|
var UI_DIST = process.env.HAP_UI_DIST ?? join14(import.meta.dirname ?? __dirname, "../../ui/dist");
|
|
@@ -4594,6 +4691,7 @@ function loginRateLimit(req, res, next) {
|
|
|
4594
4691
|
var lockExpiredSession = createSessionLock({ vault, port });
|
|
4595
4692
|
var lockAsKeyMismatch = createAsKeyMismatchLock({ vault, port });
|
|
4596
4693
|
var lockAsTlsMismatch = createAsTlsMismatchLock({ vault, port });
|
|
4694
|
+
var lockManagedWrongTeam = createManagedWrongTeamLock({ vault, port });
|
|
4597
4695
|
var sessionExpiryScheduler = new SessionExpiryScheduler(
|
|
4598
4696
|
() => vault.getSessionExpiresAt(),
|
|
4599
4697
|
lockExpiredSession
|
|
@@ -4603,8 +4701,26 @@ app.use("/auth", jsonParser, createAuthRouter(
|
|
|
4603
4701
|
requireAuth(vault),
|
|
4604
4702
|
loginRateLimit,
|
|
4605
4703
|
() => sessionExpiryScheduler.reschedule(),
|
|
4606
|
-
{ asUrl: SP_URL, dataDir: DATA_DIR }
|
|
4704
|
+
{ asUrl: SP_URL, dataDir: DATA_DIR },
|
|
4705
|
+
lockAsTlsMismatch,
|
|
4706
|
+
POLICY_LOCKED_TEAM
|
|
4607
4707
|
));
|
|
4708
|
+
var managedTeamScheduler = POLICY_LOCKED_TEAM ? new ManagedTeamScheduler(async () => {
|
|
4709
|
+
if (!vault.isUnlocked()) return;
|
|
4710
|
+
const cookie = vault.getSpCookie();
|
|
4711
|
+
if (!cookie) return;
|
|
4712
|
+
try {
|
|
4713
|
+
const pin = readPairing(DATA_DIR);
|
|
4714
|
+
const team = await activeTeamId(SP_URL, cookie, {
|
|
4715
|
+
enforce: resolvePinTls(DATA_DIR),
|
|
4716
|
+
pinnedSpkiHex: pin && pin.asUrl === SP_URL ? pin.tlsSpkiPinHex : void 0
|
|
4717
|
+
});
|
|
4718
|
+
if (team !== POLICY_LOCKED_TEAM) lockManagedWrongTeam(POLICY_LOCKED_TEAM);
|
|
4719
|
+
} catch (err) {
|
|
4720
|
+
console.error("[Control Plane] Managed-team check failed (will retry):", err);
|
|
4721
|
+
}
|
|
4722
|
+
}) : null;
|
|
4723
|
+
managedTeamScheduler?.start();
|
|
4608
4724
|
function resolveOrigin(req) {
|
|
4609
4725
|
const fwdHost = req.get("x-forwarded-host");
|
|
4610
4726
|
if (fwdHost) return `${req.protocol}://${fwdHost}`;
|
|
@@ -5141,8 +5257,13 @@ app.use(
|
|
|
5141
5257
|
eventBus.emit("proposal-approved");
|
|
5142
5258
|
} else if (method === "POST" && /^\/api\/proposals\/[^/]+\/reject$/.test(path)) {
|
|
5143
5259
|
eventBus.emit("proposal-rejected");
|
|
5144
|
-
} else if (method === "POST" && (path === "/api/groups" || path === "/api/groups/join" || /^\/api\/groups\/[^/]+\/(leave|disable|reactivate|admin-transfer)$/.test(path)))
|
|
5260
|
+
} else if (method === "POST" && (path === "/api/groups" || path === "/api/groups/join" || /^\/api\/groups\/[^/]+\/(leave|disable|reactivate|admin-transfer)$/.test(path)) || // WS0 (work-plan "Added 2026-10-11"): withdrawing a pending join
|
|
5261
|
+
// request — the account stays Personal, but the onboarding UI's
|
|
5262
|
+
// waiting state ends, same as every other membership-affecting
|
|
5263
|
+
// call here.
|
|
5264
|
+
method === "DELETE" && path === "/api/groups/join") {
|
|
5145
5265
|
eventBus.emit("team-membership-changed");
|
|
5266
|
+
if (managedTeamScheduler) void managedTeamScheduler.runNow();
|
|
5146
5267
|
}
|
|
5147
5268
|
}
|
|
5148
5269
|
}
|