Skip to content

Commit

Permalink
Implement reCAPTCHA for abuse prevention
Browse files Browse the repository at this point in the history
  • Loading branch information
A. Vatsaev authored and chadwhitacre committed May 27, 2017
1 parent 3ff77b9 commit a7467a5
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 20 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Expand Up @@ -10,4 +10,4 @@ RUN npm install --unsafe-perm

EXPOSE 3000

CMD ./bin/slackin --coc "$SLACK_COC" --channels "$SLACK_CHANNELS" --port $PORT $SLACK_SUBDOMAIN $SLACK_API_TOKEN
CMD ./bin/slackin --coc "$SLACK_COC" --channels "$SLACK_CHANNELS" --port $PORT $SLACK_SUBDOMAIN $SLACK_API_TOKEN $GOOGLE_CAPTCHA_SECRET $GOOGLE_CAPTCHA_SITEKEY
2 changes: 1 addition & 1 deletion Procfile
@@ -1 +1 @@
web: bin/slackin --coc "$SLACK_COC" --channels "$SLACK_CHANNELS" --port $PORT $SLACK_SUBDOMAIN $SLACK_API_TOKEN
web: bin/slackin --coc "$SLACK_COC" --channels "$SLACK_CHANNELS" --port $PORT $SLACK_SUBDOMAIN $SLACK_API_TOKEN $GOOGLE_CAPTCHA_SECRET $GOOGLE_CAPTCHA_SITEKEY
8 changes: 8 additions & 0 deletions app.json
Expand Up @@ -13,6 +13,14 @@
"description": "A Slack API token (find it on https://api.slack.com/web)",
"required": true
},
"GOOGLE_CAPTCHA_SECRET": {
"description": "Google captcha secret key",
"required": true
},
"GOOGLE_CAPTCHA_SITEKEY": {
"description": "Google captcha site key",
"required": true
},
"SLACK_COC": {
"description": "A URL to a Code of Conduct people must agree on before joining.",
"required": false
Expand Down
11 changes: 9 additions & 2 deletions bin/slackin
Expand Up @@ -17,24 +17,31 @@ args
.option(['?', 'help'], 'Show the usage information')

var flags = args.parse(process.argv, {
value: '<team-id> <api-token>',
value: '<team-id> <api-token> <google-captcha-secret> <google-captcha-sitekey>',
help: false
})

var org = args.sub[0] || process.env.SLACK_SUBDOMAIN
var token = args.sub[1] || process.env.SLACK_API_TOKEN
var emails = process.env.EMAIL_SLACK_LIST || ''

var gcaptcha_secret = args.sub[2] || process.env.GOOGLE_CAPTCHA_SECRET
var gcaptcha_sitekey = args.sub[3] || process.env.GOOGLE_CAPTCHA_SITEKEY



if (flags.help) {
args.showHelp()
}

if (!org || !token) {
if (!org || !token || !gcaptcha_sitekey || !gcaptcha_secret) {
args.showHelp()
} else {
flags.org = org
flags.token = token
flags.emails = emails
flags.gcaptcha_secret = gcaptcha_secret
flags.gcaptcha_sitekey = gcaptcha_sitekey
}

var port = flags.port
Expand Down
6 changes: 4 additions & 2 deletions lib/assets/client.js
Expand Up @@ -19,7 +19,8 @@ body.addEventListener('submit', function (ev){
button.disabled = true
button.className = ''
button.innerHTML = 'Please Wait'
invite(channel ? channel.value : null, coc && coc.checked ? 1 : 0, email.value, function (err, msg){
var gcaptcha_response = form.elements['g-recaptcha-response']
invite(channel ? channel.value : null, coc && coc.checked ? 1 : 0, email.value, gcaptcha_response.value, function (err, msg){
if (err) {
button.removeAttribute('disabled')
button.className = 'error'
Expand All @@ -31,10 +32,11 @@ body.addEventListener('submit', function (ev){
})
})

function invite (channel, coc, email, fn){
function invite (channel, coc, email, gcaptcha_response_value, fn){
request
.post(data.path + 'invite')
.send({
"g-recaptcha-response": gcaptcha_response_value,
coc: coc,
channel: channel,
email: email
Expand Down
83 changes: 70 additions & 13 deletions lib/index.js
Expand Up @@ -9,6 +9,7 @@ import { Server as http } from 'http'
import remail from 'email-regex'
import dom from 'vd'
import cors from 'cors'
import request from 'superagent';

// our code
import Slack from './slack'
Expand All @@ -22,6 +23,8 @@ export default function slackin ({
token,
interval = 5000, // jshint ignore:line
org,
gcaptcha_secret,
gcaptcha_sitekey,
css,
coc,
cors: useCors = false,
Expand All @@ -33,6 +36,8 @@ export default function slackin ({
// must haves
if (!token) throw new Error('Must provide a `token`.')
if (!org) throw new Error('Must provide an `org`.')
if (!gcaptcha_secret) throw new Error('Must provide a `gcaptcha_secret`.')
if (!gcaptcha_sitekey) throw new Error('Must provide an `gcaptcha_sitekey`.')

if (channels) {
// convert to an array
Expand Down Expand Up @@ -84,11 +89,12 @@ export default function slackin ({
dom('title',
'Join ', name, ' on Slack!'
),
dom("script src=https://www.google.com/recaptcha/api.js"),
dom('meta name=viewport content="width=device-width,initial-scale=1.0,minimum-scale=1.0,user-scalable=no"'),
dom('link rel="shortcut icon" href=https://slack.global.ssl.fastly.net/272a/img/icons/favicon-32.png'),
css && dom('link rel=stylesheet', { href: css })
),
splash({ coc, path, css, name, org, logo, channels, active, total })
splash({ coc, path, css, name, org, logo, channels, active, total, gcaptcha_sitekey})
)
res.type('html')
res.send(page.toHTML())
Expand Down Expand Up @@ -130,13 +136,20 @@ export default function slackin ({
}

let email = req.body.email
let captcha_response = req.body['g-recaptcha-response'];

if (!email) {
return res
.status(400)
.json({ msg: 'No email provided' })
}

if(captcha_response == undefined || !captcha_response.length){
return res
.status(400)
.send({ msg: 'Invalid captcha' });
}

if (!remail().test(email)) {
return res
.status(400)
Expand All @@ -156,23 +169,67 @@ export default function slackin ({
.json({ msg: 'Agreement to CoC is mandatory' })
}

invite({ token, org, email, channel: chanId }, err => {
if (err) {
if (err.message === `Sending you to Slack...`) {
return res
.status(303)
.json({ msg: err.message, redirectUrl: `https://${org}.slack.com` })
}
/////////////////////////////////////////////////////////////////////////


const captcha_data = {
secret: gcaptcha_secret,
response: captcha_response,
remoteip: req.connection.remoteAddress
}


const captcha_callback = (err, resp) => {

if (err) {
return res
.status(400)
.json({ msg: err.message })
.send({ msg: err });

}else{

if(resp.body.success){

let chanId = slack.channel ? slack.channel.id : null;

invite({ token, org, email, channel: chanId }, err => {
if (err) {
if (err.message === `Sending you to Slack...`) {
return res
.status(303)
.json({ msg: err.message, redirectUrl: `https://${org}.slack.com` })
}

return res
.status(400)
.json({ msg: err.message })
}

res
.status(200)
.json({ msg: 'WOOT. Check your email!' })
});

}else{

if (err) {
return res
.status(400)
.send({ msg: "Captcha check failed" });
}
}

}

res
.status(200)
.json({ msg: 'WOOT. Check your email!' })
})
}


request.post('https://www.google.com/recaptcha/api/siteverify')
.type('form')
.send(captcha_data)
.end(captcha_callback);


})

// iframe
Expand Down
4 changes: 3 additions & 1 deletion lib/splash.js
@@ -1,6 +1,6 @@
import dom from 'vd'

export default function splash ({ path, name, org, coc, logo, active, total, channels, large, iframe }){
export default function splash ({ path, name, org, coc, logo, active, total, channels, large, iframe, gcaptcha_sitekey }){
let div = dom('.splash',
!iframe && dom('.logos',
logo && dom('.logo.org'),
Expand Down Expand Up @@ -34,6 +34,8 @@ export default function splash ({ path, name, org, coc, logo, active, total, cha
),
dom('input.form-item type=email name=email placeholder=you@yourdomain.com '
+ (!iframe ? 'autofocus' : '')),
dom('br'),
dom(`div class="g-recaptcha" data-sitekey="${gcaptcha_sitekey}"`),
coc && dom('.coc',
dom('label',
dom('input type=checkbox name=coc value=1'),
Expand Down

0 comments on commit a7467a5

Please sign in to comment.