Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: @slack/oauth: add support and examples for CSRF mitigation #1013

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
103 changes: 82 additions & 21 deletions examples/oauth-v1/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ const { InstallProvider } = require('@slack/oauth');
const { createEventAdapter } = require('@slack/events-api');
const { WebClient } = require('@slack/web-api');
const express = require('express');
const cookie = require('cookie');
const { sign, verify } = require('jsonwebtoken');

const app = express();
const port = 3000;
Expand All @@ -11,24 +13,40 @@ const slackEvents = createEventAdapter(process.env.SLACK_SIGNING_SECRET, {includ
// Set path to receive events
app.use('/slack/events', slackEvents.requestListener());

const stateSecret = '1f0e1b640397bf93ad3369de65dbaf52';
const deviceSecret = '56b5317ab86840f9f3feca188778be3';
const installer = new InstallProvider({
stateSecret,
clientId: process.env.SLACK_CLIENT_ID,
clientSecret: process.env.SLACK_CLIENT_SECRET,
authVersion: 'v1',
stateSecret: 'super-secret'
});

app.get('/', (req, res) => res.send('go to /slack/install'));

app.get('/slack/install', async (req, res, next) => {
app.get('/', (req, res) =>
res.send(`<a href="/slack/install"><img alt=""Add to Slack"" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/[email protected] 2x" /></a>`)
);


app.get('/slack/install', async (req, res) => {
try {
// feel free to modify the scopes
const url = await installer.generateInstallUrl({
scopes: ['channels:read', 'groups:read', 'incoming-webhook', 'bot' ],
const { url, synchronizer } = await installer.makeInstallUrl({
scopes: ['channels:read'], // , 'groups:read', 'channels:manage', 'chat:write', 'incoming-webhook'],
metadata: 'some_metadata',
})

res.send(`<a href=${url}><img alt=""Add to Slack"" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/[email protected] 2x" /></a>`);
redirectUri: `https://${req.get('host')}/slack/oauth_redirect`,
});
const token = await sign({ synchronizer }, deviceSecret, { expiresIn: '3m' });

res.setHeader('Set-Cookie', cookie.serialize('slack_oauth', token, {
maxAge: 180, // will expire in 3 minutes
sameSite: 'lax', // limit the scope of the cookie to this site, but allow top level redirects
path: '/', // set the relative path that the cookie is scoped for
secure: true, // only support HTTPS connections
httpOnly: true, // dissallow client-side access to the cookie
overwrite: true, // overwrite the cookie every time, so nonce data is never re-used
}));
res.redirect(url);
} catch(error) {
console.log(error)
}
Expand All @@ -37,24 +55,67 @@ app.get('/slack/install', async (req, res, next) => {
// example 1
// use default success and failure handlers
app.get('/slack/oauth_redirect', async (req, res) => {
await installer.handleCallback(req, res);
try {
const cookies = cookie.parse(req.get('cookie') || '');
res.setHeader('Set-Cookie', cookie.serialize('slack_oauth', 'expired', {
maxAge: -99999999, // set the cookie to expire in the past
sameSite: 'lax', // limit the scope of the cookie to this site, but allow top level redirects
path: '/', // set the relative path that the cookie is scoped for
secure: true, // only support HTTPS connections
httpOnly: true, // dissallow client-side access to the cookie
overwrite: true, // overwrite the cookie every time, so nonce data is never re-used
}));

const { synchronizer } = await verify(cookies.slack_oauth, deviceSecret);

if (typeof synchronizer !== 'string') {
throw new Error('Expected a synchronizer token to exist in the JWT');
}

await installer.handleCallback(req, res, { synchronizer });
} catch (e) {
console.log(e);
res.send(`Something went wrong. Try again? <a href="/slack/install"><img alt=""Add to Slack"" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/[email protected] 2x" /></a>`)
}
});

// example 2
// using custom success and failure handlers
// const callbackOptions = {
// success: (installation, metadata, req, res) => {
// res.send('successful!');
// },
// failure: (error, installOptions , req, res) => {
// res.send('failure');
// },
// }
//
// app.get('/slack/oauth_redirect', async (req, res) => {
// await installer.handleCallback(req, res, callbackOptions);
// try {
// const cookies = cookie.parse(req.get('cookie') || '');
// res.setHeader('Set-Cookie', cookie.serialize('slack_oauth', 'expired', {
// maxAge: -99999999, // set the cookie to expire in the past
// sameSite: 'lax', // limit the scope of the cookie to this site, but allow top level redirects
// path: '/', // set the relative path that the cookie is scoped for
// secure: true, // only support HTTPS connections
// httpOnly: true, // dissallow client-side access to the cookie
// overwrite: true, // overwrite the cookie every time, so nonce data is never re-used
// }));

// const { synchronizer } = await verify(cookies.slack_oauth, deviceSecret);

// if (typeof synchronizer !== 'string') {
// throw new Error('Expected a synchronizer token to exist in the JWT');
// }

// await installer.handleCallback(req, res, {
// synchronizer,
// success: (installation, metadata, req, res) => {
// res.send('successful!');
// },
// failure: (error, installOptions , req, res) => {
// console.log(error);
// res.send(`Something went wrong. Try again? <a href="/slack/install"><img alt=""Add to Slack"" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/[email protected] 2x" /></a>`);
// },
// });
// } catch (e) {
// console.log(e);
// res.send(`Something went wrong. Try again? <a href="/slack/install"><img alt=""Add to Slack"" height="40" width="139" src="https://platform.slack-edge.com/img/add_to_slack.png" srcset="https://platform.slack-edge.com/img/add_to_slack.png 1x, https://platform.slack-edge.com/img/[email protected] 2x" /></a>`);
// }
// });


// When a user navigates to the app home, grab the token from our database and publish a view
slackEvents.on('app_home_opened', async (event, body) => {
try {
Expand All @@ -63,7 +124,7 @@ slackEvents.on('app_home_opened', async (event, body) => {
const web = new WebClient(DBInstallData.botToken);
await web.views.publish({
user_id: event.user,
view: {
view: {
"type":"home",
"blocks":[
{
Expand All @@ -84,4 +145,4 @@ slackEvents.on('app_home_opened', async (event, body) => {
}
});

app.listen(port, () => console.log(`Example app listening on port ${port}! Go to http://localhost:3000/slack/install to initiate oauth flow`))
app.listen(port, () => console.log(`Example app listening on port ${port}! Go to http://localhost:3000 to initiate oauth flow`))
Loading