@suveren/gateway 0.2.5 → 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
  });