@suveren/gateway 0.2.4 → 0.2.6

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.
@@ -25,6 +25,8 @@
25
25
  "authUrl": "https://accounts.google.com/o/oauth2/v2/auth",
26
26
  "tokenUrl": "https://oauth2.googleapis.com/token",
27
27
  "scopes": [
28
+ "openid",
29
+ "https://www.googleapis.com/auth/userinfo.email",
28
30
  "https://www.googleapis.com/auth/calendar.events",
29
31
  "https://www.googleapis.com/auth/calendar.readonly"
30
32
  ],
@@ -26,6 +26,8 @@
26
26
  "authUrl": "https://accounts.google.com/o/oauth2/v2/auth",
27
27
  "tokenUrl": "https://oauth2.googleapis.com/token",
28
28
  "scopes": [
29
+ "openid",
30
+ "https://www.googleapis.com/auth/userinfo.email",
29
31
  "https://www.googleapis.com/auth/gmail.modify",
30
32
  "https://www.googleapis.com/auth/gmail.send"
31
33
  ],
@@ -1583,6 +1583,18 @@ function resolveOrigin(req) {
1583
1583
  }
1584
1584
  var oauthRedirectUris = /* @__PURE__ */ new Map();
1585
1585
  var manifestCache = null;
1586
+ function decodeJwtEmail(idToken) {
1587
+ if (!idToken) return void 0;
1588
+ try {
1589
+ const payload = idToken.split(".")[1];
1590
+ if (!payload) return void 0;
1591
+ const json = Buffer.from(payload.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8");
1592
+ const claims = JSON.parse(json);
1593
+ return typeof claims.email === "string" ? claims.email : void 0;
1594
+ } catch {
1595
+ return void 0;
1596
+ }
1597
+ }
1586
1598
  async function getOAuthManifest(integrationId) {
1587
1599
  if (!manifestCache) {
1588
1600
  try {
@@ -1660,7 +1672,13 @@ app.get("/auth/oauth/:integrationId/callback", async (req, res) => {
1660
1672
  res.status(400).send(`<html><body><h2>Token exchange failed</h2><p>${String(tokens.error || "No token received")}</p><script>setTimeout(()=>window.close(),3000)</script></body></html>`);
1661
1673
  return;
1662
1674
  }
1663
- const updatedCreds = { ...creds, [oauth.tokenStorage]: tokenValue };
1675
+ const account = decodeJwtEmail(tokens.id_token);
1676
+ const updatedCreds = {
1677
+ ...creds,
1678
+ [oauth.tokenStorage]: tokenValue,
1679
+ _oauthConnectedAt: (/* @__PURE__ */ new Date()).toISOString()
1680
+ };
1681
+ if (account) updatedCreds._oauthAccount = account;
1664
1682
  vault.setCredential(integrationId, updatedCreds);
1665
1683
  console.log(`[Control Plane] ${integrationId} OAuth tokens stored in vault`);
1666
1684
  try {
@@ -1683,6 +1701,52 @@ app.get("/auth/gmail/callback", (req, res) => {
1683
1701
  res.redirect(`/auth/oauth/gmail/callback${qs ? "?" + qs : ""}`);
1684
1702
  });
1685
1703
  var authGuard = requireAuth(vault);
1704
+ app.get("/auth/oauth/:integrationId/health", authGuard, async (req, res) => {
1705
+ const integrationId = String(req.params.integrationId);
1706
+ const manifest = await getOAuthManifest(integrationId);
1707
+ if (!manifest?.oauth) {
1708
+ res.json({ status: "not_configured" });
1709
+ return;
1710
+ }
1711
+ const oauth = manifest.oauth;
1712
+ const creds = vault.getCredential(integrationId);
1713
+ const clientIdKey = oauth.credentialKeys.clientId ?? "clientId";
1714
+ const clientSecretKey = oauth.credentialKeys.clientSecret ?? "clientSecret";
1715
+ const account = creds?._oauthAccount;
1716
+ if (!creds?.[clientIdKey] || !creds?.[clientSecretKey]) {
1717
+ res.json({ status: "not_configured", account });
1718
+ return;
1719
+ }
1720
+ const token = creds[oauth.tokenStorage];
1721
+ if (!token) {
1722
+ res.json({ status: "not_connected", account });
1723
+ return;
1724
+ }
1725
+ if (!/refresh/i.test(oauth.tokenStorage)) {
1726
+ res.json({ status: "unverified", account });
1727
+ return;
1728
+ }
1729
+ try {
1730
+ const r = await fetch(oauth.tokenUrl, {
1731
+ method: "POST",
1732
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1733
+ body: new URLSearchParams({
1734
+ grant_type: "refresh_token",
1735
+ refresh_token: token,
1736
+ client_id: creds[clientIdKey],
1737
+ client_secret: creds[clientSecretKey]
1738
+ })
1739
+ });
1740
+ const body = await r.json().catch(() => ({}));
1741
+ if (r.ok && body.access_token) {
1742
+ res.json({ status: "ok", account });
1743
+ return;
1744
+ }
1745
+ res.json({ status: "failed", account, error: body.error_description || body.error || `HTTP ${r.status}` });
1746
+ } catch (err) {
1747
+ res.json({ status: "failed", account, error: err instanceof Error ? err.message : "probe failed" });
1748
+ }
1749
+ });
1686
1750
  app.get("/events", requireAuthQueryOrHeader(vault), createEventsHandler());
1687
1751
  app.use("/vault", jsonParser, authGuard, createVaultRouter(vault));
1688
1752
  app.use("/ai", jsonParser, authGuard, createAIRouter(vault));
@@ -1943,5 +2007,16 @@ app.listen(port, "0.0.0.0", () => {
1943
2007
  console.error(`[Control Plane] SP proxy: ${SP_URL2}`);
1944
2008
  console.error(`[Control Plane] UI dist: ${UI_DIST}`);
1945
2009
  console.error(`[Control Plane] Internal secret: configured`);
2010
+ console.error(`[Control Plane] MCP server: ${MCP_BASE}`);
2011
+ getManifests().then((d) => {
2012
+ const n = d?.manifests?.length ?? 0;
2013
+ if (n === 0) {
2014
+ console.error(`[Control Plane] \u26A0 MCP server at ${MCP_BASE} returned 0 integration manifests \u2014 wrong SUVEREN_MCP_INTERNAL_URL? (dev MCP is :3431, npm is :3430)`);
2015
+ } else {
2016
+ console.error(`[Control Plane] Integrations: ${n} manifests loaded from ${MCP_BASE}`);
2017
+ }
2018
+ }).catch((err) => {
2019
+ console.error(`[Control Plane] \u26A0 Could not reach MCP server at ${MCP_BASE} for manifests \u2014 wrong SUVEREN_MCP_INTERNAL_URL? (dev=:3431, npm=:3430). ${err instanceof Error ? err.message : err}`);
2020
+ });
1946
2021
  startUpdateChecker(INSTALL_METHOD, RUNNING_VERSION);
1947
2022
  });
@@ -1072,9 +1072,11 @@ Proposal ID: ${proposal.id}. Check status with check-pending-commitments(proposa
1072
1072
  );
1073
1073
  }
1074
1074
  await state2.spClient.postReceipt({
1075
- attestationHash: authHash,
1075
+ // v0.5: send the bare content address; the AS reconstructs the
1076
+ // per-user storage key. Fall back to frameHash only for legacy
1077
+ // (pre-v0.4) records that predate bounds_hash.
1078
+ boundsHash: auth.boundsHash ?? authHash,
1076
1079
  profileId: auth.profileId,
1077
- path: auth.path,
1078
1080
  action: tool.namespacedName,
1079
1081
  actionType,
1080
1082
  executionContext: { ...execution },
@@ -1501,10 +1503,10 @@ async function executeCommitted(proposal, state2, integrationManager2) {
1501
1503
  );
1502
1504
  }
1503
1505
  try {
1506
+ const boundsHash = proposal.frameHash.split(":").slice(0, 2).join(":");
1504
1507
  await state2.spClient.postReceipt({
1505
- attestationHash: proposal.frameHash,
1508
+ boundsHash,
1506
1509
  profileId: proposal.profileId,
1507
- path: proposal.path,
1508
1510
  action: proposal.tool,
1509
1511
  actionType: proposalActionType,
1510
1512
  executionContext: proposal.executionContext,