From 6ba9064cdc8921042d107721dac40b27498cd361 Mon Sep 17 00:00:00 2001 From: Ben Vinegar Date: Sat, 21 Feb 2026 21:17:24 -0500 Subject: [PATCH] bridge: require registration token for broker registration --- AGENTS.md | 4 ++-- CONFIGURATION.md | 2 +- README.md | 2 +- bin/baudbot | 4 ++-- bin/broker-register.mjs | 42 ++++++++++-------------------------- bin/broker-register.test.mjs | 32 ++++++++++++++++++++------- docs/operations.md | 2 +- 7 files changed, 42 insertions(+), 46 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3ef92f2..7e0e457 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -127,7 +127,7 @@ sudo baudbot update sudo baudbot rollback previous # Register a server with Slack broker (after OAuth callback) -sudo baudbot broker register --broker-url https://broker.example.com --workspace-id T0123ABCD --auth-code +sudo baudbot broker register --broker-url https://broker.example.com --workspace-id T0123ABCD --registration-token # Rotate an API key after setup (prompts hidden input) sudo baudbot env set ANTHROPIC_API_KEY --restart @@ -146,7 +146,7 @@ tmux new-window -n baudbot 'sudo -u baudbot_agent ~/runtime/start.sh' ## Slack broker pull-mode notes - Broker delivery is now pull-based. Registration is callback-free: - - `sudo baudbot broker register --broker-url ... --workspace-id T... --auth-code ...` + - `sudo baudbot broker register --broker-url ... --workspace-id T... --registration-token ...` - After a successful broker registration, always restart to load new keys: - `sudo baudbot restart` - The runtime starts `broker-bridge.mjs` automatically when `SLACK_BROKER_*` vars are present. diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 025ecdc..84bfd5e 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -49,7 +49,7 @@ If you're using the Slack broker OAuth flow, register this server after install: sudo baudbot broker register \ --broker-url https://your-broker.example.com \ --workspace-id T0123ABCD \ - --auth-code + --registration-token ``` `baudbot setup` is host provisioning only; do not use `baudbot setup --slack-broker`. diff --git a/README.md b/README.md index 9b07a48..1d67de1 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ Slack broker registration (after OAuth callback). When `SLACK_BROKER_*` variable sudo baudbot broker register \ --broker-url https://your-broker.example.com \ --workspace-id T0123ABCD \ - --auth-code + --registration-token ``` Need to rotate/update a key later? diff --git a/bin/baudbot b/bin/baudbot index 8cb3bec..2b99f4b 100755 --- a/bin/baudbot +++ b/bin/baudbot @@ -421,11 +421,11 @@ case "${1:-}" in exec "$NODE_BIN" "$BAUDBOT_ROOT/bin/broker-register.mjs" "$@" ;; --help|-h|"") - echo "Usage: sudo baudbot broker register [--broker-url URL] [--workspace-id ID] [--auth-code CODE] [-v|--verbose]" + echo "Usage: sudo baudbot broker register [--broker-url URL] [--workspace-id ID] --registration-token TOKEN [-v|--verbose]" ;; *) echo "Unknown broker subcommand: ${1:-}" - echo "Usage: sudo baudbot broker register [--broker-url URL] [--workspace-id ID] [--auth-code CODE] [-v|--verbose]" + echo "Usage: sudo baudbot broker register [--broker-url URL] [--workspace-id ID] --registration-token TOKEN [-v|--verbose]" exit 1 ;; esac diff --git a/bin/broker-register.mjs b/bin/broker-register.mjs index cc983b5..5f32203 100755 --- a/bin/broker-register.mjs +++ b/bin/broker-register.mjs @@ -5,7 +5,7 @@ * Registers this baudbot server with a Slack broker workspace using: * - broker URL * - workspace ID - * - one-time auth code from OAuth callback + * - registration token from dashboard callback * * On success, stores broker config and generated server key material in: * - admin config: ~/.baudbot/.env @@ -47,8 +47,7 @@ export function usageText() { "Options:", " --broker-url URL Broker base URL (e.g. https://broker.example.com)", " --workspace-id ID Slack workspace ID (e.g. T0123ABCD)", - " --registration-token TOKEN 10-minute registration token from dashboard callback", - " --auth-code CODE Legacy one-time auth code (fallback)", + " --registration-token TOKEN Registration token from dashboard callback (required)", " -v, --verbose Show detailed registration progress", " -h, --help Show this help", "", @@ -61,7 +60,6 @@ export function parseArgs(argv) { brokerUrl: "", workspaceId: "", registrationToken: "", - authCode: "", verbose: false, help: false, }; @@ -109,15 +107,6 @@ export function parseArgs(argv) { continue; } - if (arg.startsWith("--auth-code=")) { - out.authCode = arg.slice("--auth-code=".length); - continue; - } - if (arg === "--auth-code") { - i++; - out.authCode = argv[i] || ""; - continue; - } throw new Error(`unknown argument: ${arg}`); } @@ -215,18 +204,15 @@ export async function fetchBrokerPubkeys(brokerUrl, fetchImpl = fetch) { export function mapRegisterError(status, errorText) { const text = String(errorText || "request failed"); + if (status === 400 && /missing registration proof/i.test(text)) { + return "registration token is required"; + } if (status === 403 && /invalid registration token/i.test(text)) { return "invalid registration token — re-run OAuth install and use a fresh token"; } if (status === 403 && /registration token already used/i.test(text)) { return "registration token already used — re-run OAuth install and use a fresh token"; } - if (status === 403 && /invalid auth code/i.test(text)) { - return "invalid auth code — re-run OAuth install and use the new auth code"; - } - if (status === 403 && /auth code already consumed/i.test(text)) { - return "auth code already consumed — re-install the Slack app to get a fresh code"; - } if (status === 409 && /already active/i.test(text)) { return "workspace already active — unregister the current server first"; } @@ -285,7 +271,6 @@ export async function registerWithBroker({ brokerUrl, workspaceId, registrationToken, - authCode, serverKeys, fetchImpl = fetch, logger = () => {}, @@ -298,8 +283,7 @@ export async function registerWithBroker({ workspace_id: workspaceId, server_pubkey: serverKeys.server_pubkey, server_signing_pubkey: serverKeys.server_signing_pubkey, - ...(registrationToken ? { registration_token: registrationToken } : {}), - ...(authCode ? { auth_code: authCode } : {}), + registration_token: registrationToken, }; logger(`Registering workspace ${workspaceId} at ${endpoint}`); @@ -536,18 +520,16 @@ async function collectInputs(parsedArgs) { || existing.SLACK_BROKER_WORKSPACE_ID || (await prompt("Workspace ID (starts with T): ")); - const registrationToken = parsedArgs.registrationToken || (await prompt("Registration token (leave blank to use auth code): ")); - const authCode = parsedArgs.authCode || (!registrationToken ? (await prompt("Auth code (legacy): ")) : ""); + const registrationToken = parsedArgs.registrationToken || (await prompt("Registration token: ")); - if (!registrationToken && !authCode) { - throw new Error("registration token or auth code is required"); + if (!registrationToken) { + throw new Error("registration token is required"); } return { brokerUrl: normalizeBrokerUrl(brokerUrl), workspaceId: workspaceId.trim(), registrationToken, - authCode, configTargets, }; } @@ -556,7 +538,6 @@ export async function runRegistration({ brokerUrl, workspaceId, registrationToken, - authCode, fetchImpl = fetch, logger = () => {}, }) { @@ -564,8 +545,8 @@ export async function runRegistration({ throw new Error("workspace ID must match Slack team ID format (e.g. T0123ABCD)"); } - if (!registrationToken && !authCode) { - throw new Error("registration token or auth code is required"); + if (!registrationToken) { + throw new Error("registration token is required"); } const normalizedBrokerUrl = normalizeBrokerUrl(brokerUrl); @@ -576,7 +557,6 @@ export async function runRegistration({ brokerUrl: normalizedBrokerUrl, workspaceId, registrationToken, - authCode, serverKeys, fetchImpl, logger, diff --git a/bin/broker-register.test.mjs b/bin/broker-register.test.mjs index 09cfc00..658f156 100644 --- a/bin/broker-register.test.mjs +++ b/bin/broker-register.test.mjs @@ -36,15 +36,14 @@ test("parseArgs parses long-form options", () => { "https://broker.example.com/", "--workspace-id", "T123ABC", - "--auth-code", - "secret-code", + "--registration-token", + "token-xyz", ]); assert.deepEqual(parsed, { brokerUrl: "https://broker.example.com/", workspaceId: "T123ABC", - registrationToken: "", - authCode: "secret-code", + registrationToken: "token-xyz", verbose: false, help: false, }); @@ -85,6 +84,10 @@ test("parseArgs rejects unknown arguments", () => { assert.throws(() => parseArgs(["--wat"]), /unknown argument/); }); +test("parseArgs rejects legacy auth-code argument", () => { + assert.throws(() => parseArgs(["--auth-code", "legacy"]), /unknown argument/); +}); + test("validation helpers normalize and enforce broker/workspace formats", () => { assert.equal(normalizeBrokerUrl("https://broker.example.com/"), "https://broker.example.com"); assert.equal(validateWorkspaceId("T0ABC123"), true); @@ -94,7 +97,8 @@ test("validation helpers normalize and enforce broker/workspace formats", () => }); test("mapRegisterError returns actionable messages", () => { - assert.match(mapRegisterError(403, "invalid auth code"), /invalid auth code/); + assert.match(mapRegisterError(400, "missing registration proof"), /registration token is required/); + assert.match(mapRegisterError(403, "invalid registration token"), /invalid registration token/); assert.match(mapRegisterError(409, "workspace already active"), /already active/); assert.match(mapRegisterError(500, "oops"), /broker server error/); }); @@ -117,7 +121,8 @@ test("registerWithBroker fetches pubkeys then posts registration payload", async assert.equal(payload.workspace_id, "TTEST123"); assert.equal(payload.server_pubkey, FIXTURE_SERVER_KEYS.server_pubkey); assert.equal(payload.server_signing_pubkey, FIXTURE_SERVER_KEYS.server_signing_pubkey); - assert.equal(payload.auth_code, "one-time-code"); + assert.equal(payload.registration_token, "token-abc"); + assert.equal(payload.auth_code, undefined); assert.equal(payload.server_callback_url, undefined); return jsonResponse({ @@ -133,7 +138,7 @@ test("registerWithBroker fetches pubkeys then posts registration payload", async const result = await registerWithBroker({ brokerUrl: "https://broker.example.com", workspaceId: "TTEST123", - authCode: "one-time-code", + registrationToken: "token-abc", serverKeys: FIXTURE_SERVER_KEYS, fetchImpl, }); @@ -212,11 +217,12 @@ test("runRegistration integration path succeeds against live local HTTP server", const result = await runRegistration({ brokerUrl, workspaceId: "TABC12345", - authCode: "auth-code-from-oauth", + registrationToken: "token-from-dashboard", }); assert.ok(receivedRegisterPayload); assert.equal(receivedRegisterPayload.workspace_id, "TABC12345"); + assert.equal(receivedRegisterPayload.registration_token, "token-from-dashboard"); assert.equal(receivedRegisterPayload.server_callback_url, undefined); assert.ok(result.updates.SLACK_BROKER_SERVER_PRIVATE_KEY); assert.ok(result.updates.SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY); @@ -227,6 +233,16 @@ test("runRegistration integration path succeeds against live local HTTP server", } }); +test("runRegistration requires registration token", async () => { + await assert.rejects( + runRegistration({ + brokerUrl: "https://broker.example.com", + workspaceId: "TABC12345", + }), + /registration token is required/, + ); +}); + test("upsertEnvContent updates existing values and appends new ones", () => { const existing = [ "SLACK_BOT_TOKEN=xoxb-old", diff --git a/docs/operations.md b/docs/operations.md index fd58fb8..07a625e 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -63,7 +63,7 @@ sudo baudbot env sync --restart sudo baudbot broker register \ --broker-url https://your-broker.example.com \ --workspace-id T0123ABCD \ - --auth-code + --registration-token ``` Do not use `baudbot setup --slack-broker` — `setup` is host provisioning only.