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

Implement reCAPTCHA for abuse prevention #311

Merged
merged 2 commits into from
Jun 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- A landing page you can point users to fill in their emails and receive an invite (`https://slack.yourdomain.com`)
- An `<iframe>` badge to embed on any website that shows connected users in *realtime* with socket.io.
- A SVG badge that works well from static mediums (like GitHub README pages)
- Abuse prevention via [Google reCAPTCHA](https://www.google.com/recaptcha/intro/)

Check out the [Demo](https://slackin.now.sh/) or read more about the [motivations and history](http://rauchg.com/slackin) behind Slackin.

Expand All @@ -13,7 +14,11 @@ Check out the [Demo](https://slackin.now.sh/) or read more about the [motivation
Set up [Now](https://zeit.co/now) on your device and run this command:

```bash
$ now -e SLACK_API_TOKEN="<token>" -e SLACK_SUBDOMAIN="<team-name>" now-examples/slackin
$ now -e SLACK_API_TOKEN="<token>" \
-e SLACK_SUBDOMAIN="<team-name>" \
-e GOOGLE_CAPTCHA_SECRET="<secret>" \
-e GOOGLE_CAPTCHA_SITEKEY="<sitekey>" \
now-examples/slackin
```

Other platforms:
Expand All @@ -35,6 +40,9 @@ times 5. If you are not getting invite emails, this might be the reason.
Workaround: sign up for a free org, and set up Slackin to point to it
(all channels will be visible).

Here is where to [generate a secret and
sitekey](https://www.google.com/recaptcha/admin) for Google reCAPTCHA.

### Badges

#### Realtime ([demo](https://cldup.com/IaiPnDEAA6.gif))
Expand Down
8 changes: 8 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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