import passport from "@outlinewiki/koa-passport"; import type { Context } from "koa"; import Router from "koa-router"; import { Profile } from "passport"; import { Strategy as SlackStrategy } from "passport-slack-oauth2"; import { IntegrationService, IntegrationType } from "@shared/types"; import accountProvisioner from "@server/commands/accountProvisioner"; import auth from "@server/middlewares/authentication"; import passportMiddleware from "@server/middlewares/passport"; import validate from "@server/middlewares/validate"; import { IntegrationAuthentication, Collection, Integration, Team, User, } from "@server/models"; import { APIContext, AuthenticationResult } from "@server/types"; import { getClientFromContext, getTeamFromContext, StateStore, } from "@server/utils/passport"; import env from "../env"; import * as Slack from "../slack"; import * as T from "./schema"; import { SlackUtils } from "plugins/slack/shared/SlackUtils"; type SlackProfile = Profile & { team: { id: string; name: string; domain: string; image_192: string; image_230: string; }; user: { id: string; name: string; email: string; image_192: string; image_230: string; }; }; const router = new Router(); const providerName = "slack"; const scopes = [ "identity.email", "identity.basic", "identity.avatar", "identity.team", ]; function redirectOnClient(ctx: Context, url: string) { ctx.type = "text/html"; ctx.body = `
`; } if (env.SLACK_CLIENT_ID && env.SLACK_CLIENT_SECRET) { const strategy = new SlackStrategy( { clientID: env.SLACK_CLIENT_ID, clientSecret: env.SLACK_CLIENT_SECRET, callbackURL: SlackUtils.callbackUrl(), passReqToCallback: true, // @ts-expect-error StateStore store: new StateStore(), scope: scopes, }, async function ( ctx: Context, accessToken: string, refreshToken: string, params: { expires_in: number }, profile: SlackProfile, done: ( err: Error | null, user: User | null, result?: AuthenticationResult ) => void ) { try { const team = await getTeamFromContext(ctx); const client = getClientFromContext(ctx); const result = await accountProvisioner({ ip: ctx.ip, team: { teamId: team?.id, name: profile.team.name, subdomain: profile.team.domain, avatarUrl: profile.team.image_230, }, user: { name: profile.user.name, email: profile.user.email, avatarUrl: profile.user.image_192, }, authenticationProvider: { name: providerName, providerId: profile.team.id, }, authentication: { providerId: profile.user.id, accessToken, refreshToken, expiresIn: params.expires_in, scopes, }, }); return done(null, result.user, { ...result, client }); } catch (err) { return done(err, null); } } ); // For some reason the author made the strategy name capatilised, I don't know // why but we need everything lowercase so we just monkey-patch it here. strategy.name = providerName; passport.use(strategy); router.get("slack", passport.authenticate(providerName)); router.get("slack.callback", passportMiddleware(providerName)); router.get( "slack.commands", auth({ optional: true, }), validate(T.SlackCommandsSchema), async (ctx: APIContext