Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
TypeScript
at incident-api
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259import crypto from "node:crypto";
import { getLogger } from "@logtape/logtape";import { db, eq } from "@openstatus/db";import { selectWorkspaceSchema, workspace as workspaceTable,} from "@openstatus/db/src/schema";import { installSlackAgent, SLACK_BOT_SCOPES,} from "@openstatus/services/integration";import type { Context } from "hono";import { z } from "zod";
import type { SlackConfig, SlackEnv } from "./config";
const logger = getLogger(["api-server", "slack", "oauth"]);
const SLACK_OAUTH_URL = "https://slack.com/oauth/v2/authorize";const SLACK_TOKEN_URL = "https://slack.com/api/oauth.v2.access";
const BOT_SCOPES = SLACK_BOT_SCOPES.join(",");
const oauthStateSchema = z.object({ workspaceId: z.number().int(), userId: z.number().int(), ts: z.number(),});type OAuthState = z.infer<typeof oauthStateSchema>;
interface SlackOAuthResponse { ok: boolean; error?: string; access_token: string; token_type: string; scope: string; bot_user_id: string; app_id: string; team: { id: string; name: string }; authed_user: { id: string }; enterprise?: { id: string; name: string } | null;}
export async function handleSlackInstall(c: Context<SlackEnv>) { const config = c.get("slackConfig"); const token = c.req.query("token"); if (!token) { return c.json({ error: "token is required" }, 400); }
const installPayload = verifyInstallToken(config, token); if (!installPayload) { return c.json({ error: "Invalid or expired token" }, 403); }
if (!config.clientId) { return c.json({ error: "Slack OAuth not configured" }, 503); }
const state = encodeState(config, { workspaceId: installPayload.workspaceId, userId: installPayload.userId, ts: Date.now(), });
const params = new URLSearchParams({ client_id: config.clientId, scope: BOT_SCOPES, redirect_uri: getRedirectUri(c), state, });
return c.redirect(`${SLACK_OAUTH_URL}?${params.toString()}`);}
export async function handleSlackOAuthCallback(c: Context<SlackEnv>) { const config = c.get("slackConfig"); const code = c.req.query("code"); const stateParam = c.req.query("state"); const error = c.req.query("error");
if (error) { return c.redirect( `${config.dashboardUrl}/settings/integrations?slack=error`, ); }
if (!code || !stateParam) { return c.json({ error: "Missing code or state" }, 400); }
const state = decodeState(config, stateParam); if (!state || Date.now() - state.ts > 10 * 60 * 1000) { return c.json({ error: "Invalid or expired state" }, 400); }
if (!config.clientId || !config.clientSecret) { return c.json({ error: "Slack OAuth not configured" }, 503); }
const tokenRes = await fetch(SLACK_TOKEN_URL, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ client_id: config.clientId, client_secret: config.clientSecret, code, redirect_uri: getRedirectUri(c), }), });
const tokenData = (await tokenRes.json()) as SlackOAuthResponse; if (!tokenData.ok) { logger.error("token exchange failed", { error: tokenData.error }); return c.redirect( `${config.dashboardUrl}/settings/integrations?slack=error`, ); }
const workspaceRow = await db .select() .from(workspaceTable) .where(eq(workspaceTable.id, state.workspaceId)) .get(); if (!workspaceRow) { logger.error("workspace not found at callback", { workspaceId: state.workspaceId, }); return c.redirect( `${config.dashboardUrl}/settings/integrations?slack=error`, ); }
const workspaceParsed = selectWorkspaceSchema.safeParse(workspaceRow); if (!workspaceParsed.success) { logger.error("workspace row failed schema parse", { workspaceId: state.workspaceId, issues: workspaceParsed.error.issues, }); return c.redirect( `${config.dashboardUrl}/settings/integrations?slack=error`, ); }
try { await installSlackAgent({ ctx: { workspace: workspaceParsed.data, actor: { type: "slack", teamId: tokenData.team.id, slackUserId: tokenData.authed_user.id, userId: state.userId, }, }, input: { externalId: tokenData.team.id, credential: { botToken: tokenData.access_token, botUserId: tokenData.bot_user_id, }, data: { teamId: tokenData.team.id, teamName: tokenData.team.name, appId: tokenData.app_id, scopes: tokenData.scope, installedBy: tokenData.authed_user.id, }, }, }); } catch (err) { logger.error("installSlackAgent failed", { workspaceId: state.workspaceId, err: err instanceof Error ? err.message : String(err), }); return c.redirect( `${config.dashboardUrl}/settings/integrations?slack=error`, ); }
return c.redirect( `${config.dashboardUrl}/settings/integrations?slack=success`, );}
function getRedirectUri(c: Context<SlackEnv>): string { const configured = c.get("slackConfig").redirectUri; if (configured) return configured; const url = new URL(c.req.url); return `${url.origin}/slack/oauth/callback`;}
function encodeState(config: SlackConfig, state: OAuthState): string { const payload = JSON.stringify(state); const signature = computeHmac(config, payload); return Buffer.from(`${payload}.${signature}`).toString("base64url");}
function decodeState(config: SlackConfig, encoded: string): OAuthState | null { try { const decoded = Buffer.from(encoded, "base64url").toString(); const dotIdx = decoded.lastIndexOf("."); if (dotIdx === -1) return null;
const payload = decoded.slice(0, dotIdx); const signature = decoded.slice(dotIdx + 1);
if (!verifyHmac(config, payload, signature)) return null;
const parsed = oauthStateSchema.safeParse(JSON.parse(payload)); return parsed.success ? parsed.data : null; } catch { return null; }}
function computeHmac(config: SlackConfig, payload: string): string { const secret = config.signingSecret; if (!secret) throw new Error("Slack signing secret not configured"); return crypto.createHmac("sha256", secret).update(payload).digest("hex");}
function verifyHmac( config: SlackConfig, payload: string, signature: string,): boolean { const expected = computeHmac(config, payload); if (expected.length !== signature.length) return false; return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));}
const INSTALL_TOKEN_TTL_MS = 5 * 60 * 1000;
function verifyInstallToken( config: SlackConfig, token: string,): { workspaceId: number; userId: number } | null { try { const decoded = Buffer.from(token, "base64url").toString(); const dotIdx = decoded.lastIndexOf("."); if (dotIdx === -1) return null;
const payload = decoded.slice(0, dotIdx); const signature = decoded.slice(dotIdx + 1);
if (!verifyHmac(config, payload, signature)) return null;
const parsed = oauthStateSchema.safeParse(JSON.parse(payload)); if (!parsed.success) return null; if (Date.now() - parsed.data.ts > INSTALL_TOKEN_TTL_MS) return null;
return { workspaceId: parsed.data.workspaceId, userId: parsed.data.userId }; } catch { return null; }}