Files
outline/plugins/slack/server/slack.ts
T
Tom Moor 1f097b0fdd chore: resolve no-explicit-any lint warnings in plugins (#12237)
* chore: resolve no-explicit-any lint warnings in plugins

Replaces uses of `any` in the plugins directory with concrete types,
`unknown`, or structured type assertions, addressing the remaining
typescript-eslint(no-explicit-any) warnings flagged by oxlint.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: address review feedback in GitLabIssueProvider

Drop trailing semicolon from log string and add early return in
`destroyNamespace` when neither `user_id` nor `full_path` is present
to avoid an unnecessary full-scan transaction.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 08:29:58 -04:00

106 lines
2.9 KiB
TypeScript

import querystring, { type ParsedUrlQueryInput } from "node:querystring";
import { InvalidRequestError } from "@server/errors";
import fetch from "@server/utils/fetch";
import { SlackUtils } from "../shared/SlackUtils";
import env from "./env";
const SLACK_API_URL = "https://slack.com/api";
/**
* Makes a POST request to the Slack API with JSON body.
*
* @param endpoint - the Slack API endpoint to call.
* @param body - the request body containing token and other parameters.
* @returns the parsed JSON response from Slack.
*/
export async function post(
endpoint: string,
body: { token: string } & Record<string, unknown>
) {
let data;
const { token, ...bodyWithoutToken } = body;
try {
const response = await fetch(`${SLACK_API_URL}/${endpoint}`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(bodyWithoutToken),
});
data = await response.json();
} catch (err) {
throw InvalidRequestError(err.message);
}
if (!data.ok) {
throw InvalidRequestError(data.error);
}
return data;
}
/**
* Makes a POST request to the Slack API with form-urlencoded body.
*
* @param endpoint - the Slack API endpoint to call.
* @param body - the request parameters.
* @returns the parsed JSON response from Slack.
*/
export async function request(
endpoint: string,
body: { client_id?: string; client_secret?: string } & ParsedUrlQueryInput
) {
let data;
const { client_id, client_secret, ...params } = body;
const headers: Record<string, string> = {
"Content-Type": "application/x-www-form-urlencoded",
};
// Use HTTP Basic authentication for client credentials as recommended by
// Slack documentation and OAuth 2.0 RFC 6749 Section 2.3.1.
// This prevents client_secret from being exposed in URLs and logs.
if (client_id && client_secret) {
const credentials = Buffer.from(`${client_id}:${client_secret}`).toString(
"base64"
);
headers["Authorization"] = `Basic ${credentials}`;
}
try {
const response = await fetch(`${SLACK_API_URL}/${endpoint}`, {
method: "POST",
headers,
body: querystring.stringify(params),
});
data = await response.json();
} catch (err) {
throw InvalidRequestError(err.message);
}
if (!data.ok) {
throw InvalidRequestError(data.error);
}
return data;
}
/**
* Exchanges an OAuth authorization code for an access token.
*
* @param code - the authorization code received from Slack.
* @param redirect_uri - the redirect URI used in the OAuth flow.
* @returns the OAuth access response containing the access token.
*/
export async function oauthAccess(
code: string,
redirect_uri = SlackUtils.callbackUrl()
) {
return request("oauth.access", {
client_id: env.SLACK_CLIENT_ID,
client_secret: env.SLACK_CLIENT_SECRET,
redirect_uri,
code,
});
}