Skip to content

Commit 1775e10

Browse files
committed
bridge: fail fast on expired broker access token
1 parent d3aad0d commit 1775e10

2 files changed

Lines changed: 73 additions & 0 deletions

File tree

slack-bridge/broker-bridge.mjs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const directSlackRateLimiter = createRateLimiter({ maxRequests: 1, windowMs: 1_0
9595
const workspaceId = process.env.SLACK_BROKER_WORKSPACE_ID;
9696
const brokerBaseUrl = String(process.env.SLACK_BROKER_URL || "").replace(/\/$/, "");
9797
const brokerAccessToken = String(process.env.SLACK_BROKER_ACCESS_TOKEN || "").trim();
98+
const brokerAccessTokenExpiresAt = String(process.env.SLACK_BROKER_ACCESS_TOKEN_EXPIRES_AT || "").trim();
9899

99100
// Check if direct Slack API mode is available
100101
const hasDirectSlackToken = Boolean(process.env.SLACK_BOT_TOKEN);
@@ -110,6 +111,7 @@ let socketPath = null;
110111
let cryptoState = null;
111112

112113
const dedupe = new Map();
114+
let brokerTokenExpiryFormatWarned = false;
113115

114116
const brokerHealth = {
115117
started_at: new Date().toISOString(),
@@ -386,7 +388,29 @@ function signPullRequest(timestamp, maxMessages, waitSeconds) {
386388
});
387389
}
388390

391+
function isBrokerAccessTokenExpired() {
392+
if (!brokerAccessToken || !brokerAccessTokenExpiresAt) return false;
393+
const ts = Date.parse(brokerAccessTokenExpiresAt);
394+
if (!Number.isFinite(ts)) {
395+
if (!brokerTokenExpiryFormatWarned) {
396+
logWarn("⚠️ invalid SLACK_BROKER_ACCESS_TOKEN_EXPIRES_AT format; expected ISO-8601 timestamp");
397+
brokerTokenExpiryFormatWarned = true;
398+
}
399+
return false;
400+
}
401+
return Date.now() >= ts;
402+
}
403+
404+
function enforceBrokerTokenFreshnessOrExit() {
405+
if (!isBrokerAccessTokenExpired()) return;
406+
407+
logError("❌ broker access token is expired; broker API auth will fail.");
408+
logError(" run: sudo baudbot broker register && sudo baudbot restart");
409+
process.exit(1);
410+
}
411+
389412
async function brokerFetch(pathname, body) {
413+
enforceBrokerTokenFreshnessOrExit();
390414
const url = `${brokerBaseUrl}${pathname}`;
391415
const headers = { "Content-Type": "application/json" };
392416
if (brokerAccessToken) {
@@ -992,6 +1016,8 @@ async function startPollLoop() {
9921016
serverSignSecretKey: signKeypair.privateKey,
9931017
};
9941018

1019+
enforceBrokerTokenFreshnessOrExit();
1020+
9951021
refreshSocket();
9961022
startApiServer();
9971023
persistBrokerHealth();

test/broker-bridge.integration.test.mjs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,4 +727,51 @@ describe("broker pull bridge semi-integration", () => {
727727

728728
bridge.kill("SIGTERM");
729729
});
730+
731+
it("exits when broker access token is expired", async () => {
732+
await sodium.ready;
733+
734+
const testFileDir = path.dirname(fileURLToPath(import.meta.url));
735+
const repoRoot = path.dirname(testFileDir);
736+
const bridgePath = path.join(repoRoot, "slack-bridge", "broker-bridge.mjs");
737+
const bridgeCwd = path.join(repoRoot, "slack-bridge");
738+
739+
let bridgeStdout = "";
740+
let bridgeStderr = "";
741+
742+
const bridge = spawn("node", [bridgePath], {
743+
cwd: bridgeCwd,
744+
env: {
745+
...process.env,
746+
SLACK_BROKER_URL: "http://127.0.0.1:65535",
747+
SLACK_BROKER_WORKSPACE_ID: "T123BROKER",
748+
SLACK_BROKER_SERVER_PRIVATE_KEY: b64(32, 11),
749+
SLACK_BROKER_SERVER_PUBLIC_KEY: b64(32, 12),
750+
SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: Buffer.alloc(32, 25).toString("base64"),
751+
SLACK_BROKER_PUBLIC_KEY: b64(32, 14),
752+
SLACK_BROKER_SIGNING_PUBLIC_KEY: b64(32, 15),
753+
SLACK_BROKER_ACCESS_TOKEN: "expired-token",
754+
SLACK_BROKER_ACCESS_TOKEN_EXPIRES_AT: "2000-01-01T00:00:00.000Z",
755+
SLACK_ALLOWED_USERS: "U_ALLOWED",
756+
BRIDGE_API_PORT: "0",
757+
},
758+
stdio: ["ignore", "pipe", "pipe"],
759+
});
760+
761+
bridge.stdout.on("data", (chunk) => {
762+
bridgeStdout += chunk.toString();
763+
});
764+
bridge.stderr.on("data", (chunk) => {
765+
bridgeStderr += chunk.toString();
766+
});
767+
768+
children.push(bridge);
769+
770+
const exited = await new Promise((resolve) => {
771+
bridge.on("exit", (code, signal) => resolve({ code, signal }));
772+
});
773+
774+
expect(exited.code).toBe(1);
775+
expect(`${bridgeStdout}\n${bridgeStderr}`).toContain("broker access token is expired");
776+
});
730777
});

0 commit comments

Comments
 (0)