108 lines
3.5 KiB
TypeScript
108 lines
3.5 KiB
TypeScript
import type {
|
|
NormalizedDelivery,
|
|
ProviderSlug,
|
|
SignatureScheme,
|
|
} from '../types/integration.js';
|
|
import { normalizeLinear, normalizeRest, normalizeSlack } from './normalizers.js';
|
|
import { enrichSlackDelivery } from '../services/slack.service.js';
|
|
|
|
export type ChallengeResponse = { status: number; body: unknown };
|
|
|
|
export type ProviderConfig = {
|
|
slug: ProviderSlug;
|
|
signatureScheme: SignatureScheme;
|
|
/** Header carrying the signature. Empty only when the scheme is 'none'. */
|
|
signatureHeader: string;
|
|
timestampHeader?: string;
|
|
/** Environment variable holding the shared secret when the install has none. */
|
|
secretEnvVar?: string;
|
|
/** Returns a response when the request is a handshake rather than an event. */
|
|
challenge?: (body: unknown) => ChallengeResponse | null;
|
|
/**
|
|
* Absent until that provider's integration is built. The transport above is
|
|
* infrastructure; the payload mapping belongs to the integration itself.
|
|
*/
|
|
normalize?: (payload: unknown) => NormalizedDelivery;
|
|
/**
|
|
* Runs after the ack, inside the retry queue, so it may make network calls.
|
|
* Receives the raw payload because some providers deliver a reference rather
|
|
* than content: a Slack reaction names a message without including its text.
|
|
*/
|
|
enrich?: (
|
|
delivery: NormalizedDelivery,
|
|
payload: unknown
|
|
) => Promise<NormalizedDelivery>;
|
|
oauth?: {
|
|
authorizeUrl: string;
|
|
tokenUrl: string;
|
|
scopes: string[];
|
|
};
|
|
};
|
|
|
|
const slackChallenge = (body: unknown): ChallengeResponse | null => {
|
|
if (typeof body !== 'object' || body === null) return null;
|
|
const candidate = body as { type?: unknown; challenge?: unknown };
|
|
if (candidate.type !== 'url_verification') return null;
|
|
if (typeof candidate.challenge !== 'string') return null;
|
|
return { status: 200, body: { challenge: candidate.challenge } };
|
|
};
|
|
|
|
export const providers: Record<ProviderSlug, ProviderConfig> = {
|
|
rest: {
|
|
slug: 'rest',
|
|
signatureScheme: 'none',
|
|
signatureHeader: '',
|
|
normalize: normalizeRest,
|
|
},
|
|
linear: {
|
|
slug: 'linear',
|
|
signatureScheme: 'linear-sha256',
|
|
signatureHeader: 'linear-signature',
|
|
secretEnvVar: 'LINEAR_SIGNING_SECRET',
|
|
normalize: normalizeLinear,
|
|
oauth: {
|
|
authorizeUrl: 'https://linear.app/oauth/authorize',
|
|
tokenUrl: 'https://api.linear.app/oauth/token',
|
|
scopes: ['read'],
|
|
},
|
|
},
|
|
slack: {
|
|
slug: 'slack',
|
|
signatureScheme: 'slack-v0',
|
|
signatureHeader: 'x-slack-signature',
|
|
timestampHeader: 'x-slack-request-timestamp',
|
|
secretEnvVar: 'SLACK_SIGNING_SECRET',
|
|
challenge: slackChallenge,
|
|
normalize: normalizeSlack,
|
|
enrich: enrichSlackDelivery,
|
|
oauth: {
|
|
authorizeUrl: 'https://slack.com/oauth/v2/authorize',
|
|
tokenUrl: 'https://slack.com/api/oauth.v2.access',
|
|
scopes: ['channels:history', 'reactions:read', 'users:read'],
|
|
},
|
|
},
|
|
github: {
|
|
slug: 'github',
|
|
signatureScheme: 'github-sha256',
|
|
signatureHeader: 'x-hub-signature-256',
|
|
secretEnvVar: 'GITHUB_WEBHOOK_SECRET',
|
|
oauth: {
|
|
authorizeUrl: 'https://github.com/login/oauth/authorize',
|
|
tokenUrl: 'https://github.com/login/oauth/access_token',
|
|
scopes: ['repo', 'read:discussion'],
|
|
},
|
|
},
|
|
jira: {
|
|
slug: 'jira',
|
|
signatureScheme: 'none',
|
|
signatureHeader: '',
|
|
oauth: {
|
|
authorizeUrl: 'https://auth.atlassian.com/authorize',
|
|
tokenUrl: 'https://auth.atlassian.com/oauth/token',
|
|
scopes: ['read:jira-work', 'offline_access'],
|
|
},
|
|
},
|
|
};
|
|
|
|
export const getProvider = (slug: ProviderSlug): ProviderConfig => providers[slug];
|