Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>
sudo baudbot broker register --broker-url https://broker.example.com --workspace-id T0123ABCD --registration-token <token>

# Rotate an API key after setup (prompts hidden input)
sudo baudbot env set ANTHROPIC_API_KEY --restart
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <auth-code-from-oauth-callback>
--registration-token <token-from-dashboard-callback>
```

`baudbot setup` is host provisioning only; do not use `baudbot setup --slack-broker`.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <auth-code-from-oauth-callback>
--registration-token <token-from-dashboard-callback>
```

Need to rotate/update a key later?
Expand Down
4 changes: 2 additions & 2 deletions bin/baudbot
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 11 additions & 31 deletions bin/broker-register.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
"",
Expand All @@ -61,7 +60,6 @@ export function parseArgs(argv) {
brokerUrl: "",
workspaceId: "",
registrationToken: "",
authCode: "",
verbose: false,
help: false,
};
Expand Down Expand Up @@ -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}`);
}
Expand Down Expand Up @@ -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";
}
Expand Down Expand Up @@ -285,7 +271,6 @@ export async function registerWithBroker({
brokerUrl,
workspaceId,
registrationToken,
authCode,
serverKeys,
fetchImpl = fetch,
logger = () => {},
Expand All @@ -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}`);
Expand Down Expand Up @@ -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,
};
}
Expand All @@ -556,16 +538,15 @@ export async function runRegistration({
brokerUrl,
workspaceId,
registrationToken,
authCode,
fetchImpl = fetch,
logger = () => {},
}) {
if (!validateWorkspaceId(workspaceId)) {
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);
Expand All @@ -576,7 +557,6 @@ export async function runRegistration({
brokerUrl: normalizedBrokerUrl,
workspaceId,
registrationToken,
authCode,
serverKeys,
fetchImpl,
logger,
Expand Down
32 changes: 24 additions & 8 deletions bin/broker-register.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand Down Expand Up @@ -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);
Expand All @@ -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/);
});
Expand All @@ -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({
Expand All @@ -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,
});
Expand Down Expand Up @@ -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);
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <auth-code-from-oauth-callback>
--registration-token <token-from-dashboard-callback>
```

Do not use `baudbot setup --slack-broker` — `setup` is host provisioning only.
Expand Down