2021-03-26 18:31:07 +00:00
|
|
|
// @flow
|
|
|
|
import passport from "@outlinewiki/koa-passport";
|
|
|
|
import Router from "koa-router";
|
|
|
|
import { Strategy as SlackStrategy } from "passport-slack-oauth2";
|
|
|
|
import accountProvisioner from "../../commands/accountProvisioner";
|
|
|
|
import env from "../../env";
|
|
|
|
import auth from "../../middlewares/authentication";
|
|
|
|
import passportMiddleware from "../../middlewares/passport";
|
|
|
|
import { Authentication, Collection, Integration, Team } from "../../models";
|
|
|
|
import * as Slack from "../../slack";
|
|
|
|
import { StateStore } from "../../utils/passport";
|
|
|
|
|
|
|
|
const router = new Router();
|
|
|
|
const providerName = "slack";
|
|
|
|
const SLACK_CLIENT_ID = process.env.SLACK_KEY;
|
|
|
|
const SLACK_CLIENT_SECRET = process.env.SLACK_SECRET;
|
|
|
|
|
|
|
|
const scopes = [
|
|
|
|
"identity.email",
|
|
|
|
"identity.basic",
|
|
|
|
"identity.avatar",
|
|
|
|
"identity.team",
|
|
|
|
];
|
|
|
|
|
|
|
|
export const config = {
|
|
|
|
name: "Slack",
|
|
|
|
enabled: !!SLACK_CLIENT_ID,
|
|
|
|
};
|
|
|
|
|
2021-03-30 05:03:40 +00:00
|
|
|
if (SLACK_CLIENT_ID) {
|
|
|
|
const strategy = new SlackStrategy(
|
|
|
|
{
|
|
|
|
clientID: SLACK_CLIENT_ID,
|
|
|
|
clientSecret: SLACK_CLIENT_SECRET,
|
|
|
|
callbackURL: `${env.URL}/auth/slack.callback`,
|
|
|
|
passReqToCallback: true,
|
|
|
|
store: new StateStore(),
|
|
|
|
scope: scopes,
|
|
|
|
},
|
|
|
|
async function (req, accessToken, refreshToken, profile, done) {
|
2021-03-26 18:31:07 +00:00
|
|
|
try {
|
2021-03-30 05:03:40 +00:00
|
|
|
const result = await accountProvisioner({
|
|
|
|
ip: req.ip,
|
|
|
|
team: {
|
|
|
|
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,
|
|
|
|
scopes,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return done(null, result.user, result);
|
2021-03-26 18:31:07 +00:00
|
|
|
} catch (err) {
|
2021-03-30 05:03:40 +00:00
|
|
|
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));
|
|
|
|
|
2021-04-09 03:40:04 +00:00
|
|
|
router.get("slack.callback", passportMiddleware(providerName));
|
2021-03-30 05:03:40 +00:00
|
|
|
|
|
|
|
router.get("slack.commands", auth({ required: false }), async (ctx) => {
|
|
|
|
const { code, state, error } = ctx.request.query;
|
|
|
|
const user = ctx.state.user;
|
|
|
|
ctx.assertPresent(code || error, "code is required");
|
|
|
|
|
|
|
|
if (error) {
|
|
|
|
ctx.redirect(`/settings/integrations/slack?error=${error}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// this code block accounts for the root domain being unable to
|
|
|
|
// access authentication for subdomains. We must forward to the appropriate
|
|
|
|
// subdomain to complete the oauth flow
|
|
|
|
if (!user) {
|
|
|
|
if (state) {
|
|
|
|
try {
|
|
|
|
const team = await Team.findByPk(state);
|
|
|
|
return ctx.redirect(
|
|
|
|
`${team.url}/auth${ctx.request.path}?${ctx.request.querystring}`
|
|
|
|
);
|
|
|
|
} catch (err) {
|
|
|
|
return ctx.redirect(
|
|
|
|
`/settings/integrations/slack?error=unauthenticated`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
2021-03-26 18:31:07 +00:00
|
|
|
return ctx.redirect(
|
|
|
|
`/settings/integrations/slack?error=unauthenticated`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-30 05:03:40 +00:00
|
|
|
const endpoint = `${process.env.URL || ""}/auth/slack.commands`;
|
|
|
|
const data = await Slack.oauthAccess(code, endpoint);
|
|
|
|
|
|
|
|
const authentication = await Authentication.create({
|
|
|
|
service: "slack",
|
|
|
|
userId: user.id,
|
|
|
|
teamId: user.teamId,
|
|
|
|
token: data.access_token,
|
|
|
|
scopes: data.scope.split(","),
|
|
|
|
});
|
|
|
|
|
|
|
|
await Integration.create({
|
|
|
|
service: "slack",
|
|
|
|
type: "command",
|
|
|
|
userId: user.id,
|
|
|
|
teamId: user.teamId,
|
|
|
|
authenticationId: authentication.id,
|
|
|
|
settings: {
|
|
|
|
serviceTeamId: data.team_id,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
ctx.redirect("/settings/integrations/slack");
|
2021-03-26 18:31:07 +00:00
|
|
|
});
|
|
|
|
|
2021-03-30 05:03:40 +00:00
|
|
|
router.get("slack.post", auth({ required: false }), async (ctx) => {
|
|
|
|
const { code, error, state } = ctx.request.query;
|
|
|
|
const user = ctx.state.user;
|
|
|
|
ctx.assertPresent(code || error, "code is required");
|
2021-03-26 18:31:07 +00:00
|
|
|
|
2021-03-30 05:03:40 +00:00
|
|
|
const collectionId = state;
|
|
|
|
ctx.assertUuid(collectionId, "collectionId must be an uuid");
|
2021-03-26 18:31:07 +00:00
|
|
|
|
2021-03-30 05:03:40 +00:00
|
|
|
if (error) {
|
|
|
|
ctx.redirect(`/settings/integrations/slack?error=${error}`);
|
|
|
|
return;
|
|
|
|
}
|
2021-03-26 18:31:07 +00:00
|
|
|
|
2021-03-30 05:03:40 +00:00
|
|
|
// this code block accounts for the root domain being unable to
|
2021-04-19 01:34:49 +00:00
|
|
|
// access authentication for subdomains. We must forward to the
|
2021-03-30 05:03:40 +00:00
|
|
|
// appropriate subdomain to complete the oauth flow
|
|
|
|
if (!user) {
|
|
|
|
try {
|
|
|
|
const collection = await Collection.findByPk(state);
|
|
|
|
const team = await Team.findByPk(collection.teamId);
|
|
|
|
return ctx.redirect(
|
|
|
|
`${team.url}/auth${ctx.request.path}?${ctx.request.querystring}`
|
|
|
|
);
|
|
|
|
} catch (err) {
|
|
|
|
return ctx.redirect(
|
|
|
|
`/settings/integrations/slack?error=unauthenticated`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2021-03-26 18:31:07 +00:00
|
|
|
|
2021-03-30 05:03:40 +00:00
|
|
|
const endpoint = `${process.env.URL || ""}/auth/slack.post`;
|
|
|
|
const data = await Slack.oauthAccess(code, endpoint);
|
|
|
|
|
|
|
|
const authentication = await Authentication.create({
|
|
|
|
service: "slack",
|
|
|
|
userId: user.id,
|
|
|
|
teamId: user.teamId,
|
|
|
|
token: data.access_token,
|
|
|
|
scopes: data.scope.split(","),
|
|
|
|
});
|
|
|
|
|
|
|
|
await Integration.create({
|
|
|
|
service: "slack",
|
|
|
|
type: "post",
|
|
|
|
userId: user.id,
|
|
|
|
teamId: user.teamId,
|
|
|
|
authenticationId: authentication.id,
|
|
|
|
collectionId,
|
|
|
|
events: [],
|
|
|
|
settings: {
|
|
|
|
url: data.incoming_webhook.url,
|
|
|
|
channel: data.incoming_webhook.channel,
|
|
|
|
channelId: data.incoming_webhook.channel_id,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
ctx.redirect("/settings/integrations/slack");
|
2021-03-26 18:31:07 +00:00
|
|
|
});
|
2021-03-30 05:03:40 +00:00
|
|
|
}
|
2021-03-26 18:31:07 +00:00
|
|
|
|
|
|
|
export default router;
|