Skip to content

Implement an optional captcha #1867

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

Merged
merged 1 commit into from
Dec 6, 2021
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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Repository | Reference | Recent Version
[connect-mongo][connect-mongoGHUrl] | [Documentation][connect-mongoDOCUrl] | [![NPM version][connect-mongoNPMVersionImage]][connect-mongoNPMUrl]
[diff][diffGHUrl] | [Documentation][diffDOCUrl] | [![NPM version][diffNPMVersionImage]][diffNPMUrl]
[express][expressGHUrl] | [Documentation][expressDOCUrl] | [![NPM version][expressNPMVersionImage]][expressNPMUrl]
[express-hcaptcha][express-hcaptchaGHUrl] <br />&#x22D4; [`fork`][express-hcaptchaGHUrlForkUrl] | [Documentation][express-hcaptchaDOCUrl] | [![NPM version][express-hcaptchaNPMVersionImage]][express-hcaptchaNPMUrl]
[express-minify][express-minifyGHUrl] | [Documentation][express-minifyDOCUrl] | [![NPM version][express-minifyNPMVersionImage]][express-minifyNPMUrl]
[express-rate-limit][express-rate-limitGHUrl] | [Documentation][express-rate-limitDOCUrl] | [![NPM version][express-rate-limitNPMVersionImage]][express-rate-limitNPMUrl]
[express-session][express-sessionGHUrl] | [Documentation][express-sessionDOCUrl] | [![NPM version][express-sessionNPMVersionImage]][express-sessionNPMUrl]
Expand Down Expand Up @@ -182,6 +183,12 @@ Outdated dependencies list can be achieved with `$ npm outdated` from the termin
[expressNPMUrl]: https://www.npmjs.com/package/express
[expressNPMVersionImage]: https://img.shields.io/npm/v/express.svg?style=flat

[express-hcaptchaGHUrl]: https://github.com/vastus/express-hcaptcha
[express-hcaptchaGHUrlForkUrl]: https://github.com/OpenUserJS/express-hcaptcha/tree/fork
[express-hcaptchaDOCUrl]: https://github.com/vastus/express-hcaptcha/blob/master/README.md
[express-hcaptchaNPMUrl]: https://www.npmjs.com/package/express-hcaptcha
[express-hcaptchaNPMVersionImage]: https://img.shields.io/npm/v/express-hcaptcha.svg?style=flat

[express-minifyGHUrl]: https://github.com/breeswish/express-minify
[express-minifyDOCUrl]: https://github.com/breeswish/express-minify/blob/master/README.md
[express-minifyNPMUrl]: https://www.npmjs.com/package/express-minify
Expand Down
48 changes: 48 additions & 0 deletions controllers/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ var isDbg = require('../libs/debug').isDbg;
//

//--- Dependency inclusions
var request = require('request');
var passport = require('passport');
var url = require('url');
var colors = require('ansi-colors');
Expand Down Expand Up @@ -74,6 +75,48 @@ function getRedirect(aReq) {
return redirect;
}

exports.preauth = function (aReq, aRes, aNext) {
var authedUser = aReq.session.user;
var username = aReq.body.username || aReq.session.username ||
(authedUser ? authedUser.name : null);
var SECRET = process.env.HCAPTCHA_SECRET_KEY;

if (!username) {
aRes.redirect('/login?noname');
return;
}
// Clean the username of leading and trailing whitespace,
// and other stuff that is unsafe in a url
username = cleanFilename(username.replace(/^\s+|\s+$/g, ''));

// The username could be empty after the replacements
if (!username) {
aRes.redirect('/login?noname');
return;
}

if (username.length > 64) {
aRes.redirect('/login?toolong');
return;
}

User.findOne({ name: { $regex: new RegExp('^' + username + '$', 'i') } },
function (aErr, aUser) {
if (aErr) {
console.error('Authfail with no User found of', username, aErr);
aRes.redirect('/login?usernamefail');
return;
}

if (aUser || !SECRET) {
// Skip captcha for known individuals and not implemented
exports.auth(aReq, aRes, aNext);
} else {
aNext();
}
});
};

exports.auth = function (aReq, aRes, aNext) {
function auth() {
var authenticate = null;
Expand Down Expand Up @@ -112,6 +155,11 @@ exports.auth = function (aReq, aRes, aNext) {
var authOpts = { failureRedirect: '/login?stratfail' };
var passportKey = aReq._passport.instance._key;

if (!authedUser) {
// TODO: Send out token and sitekey back to https://hcaptcha.com/siteverify
// Maybe postauth routine with req hcaptcha?
}

// Yet another passport hack.
// Initialize the passport session data only when we need it.
if (!aReq.session[passportKey] && aReq._passport.session) {
Expand Down
3 changes: 3 additions & 0 deletions controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,15 @@ exports.register = function (aReq, aRes) {
var authedUser = aReq.session.user;
var tasks = [];

var SECRET = process.env.HCAPTCHA_SECRET_KEY;

// If already logged in, go back.
if (authedUser) {
aRes.redirect(getRedirect(aReq));
return;
}

options.hasCaptcha = (SECRET ? true : false);
options.redirectTo = getRedirect(aReq);

// Page metadata
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"connect-mongo": "3.2.0",
"diff": "5.0.0",
"express": "4.17.1",
"express-hcaptcha": "git+https://github.com/OpenUserJS/express-hcaptcha.git#fork",
"express-minify": "1.0.0",
"express-rate-limit": "5.5.0",
"express-session": "1.17.2",
Expand Down
12 changes: 11 additions & 1 deletion routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ var isDbg = require('./libs/debug').isDbg;
var rateLimit = require('express-rate-limit');
var MongoStore = require('rate-limit-mongo');
var exec = require('child_process').exec;
var hcaptcha = require('express-hcaptcha');
var SECRET = process.env.HCAPTCHA_SECRET_KEY;

//
var main = require('./controllers/index');
Expand Down Expand Up @@ -121,7 +123,15 @@ module.exports = function (aApp) {

//--- Routes
// Authentication routes
aApp.route('/auth/').post(authentication.auth);
aApp.route('/auth/').post(authentication.preauth, hcaptcha.middleware.validate(SECRET),
function (aErr, aReq, aRes, aNext) {
if (aErr) {
aRes.redirect(302, '/login?authfail');
} else {
aNext();
}
}, authentication.auth);

aApp.route('/auth/:strategy').get(authentication.auth);
aApp.route('/auth/:strategy/callback/:junk?').get(authentication.callback);
aApp.route('/login').get(main.register);
Expand Down
9 changes: 9 additions & 0 deletions views/includes/scripts/loginEcho.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
var auth = document.querySelector('select[name="auth"]');
var consent = document.querySelector('input[name="consent"]');
var action = document.querySelector('button#action');
var captcha = document.querySelector('div.h-captcha');

function onInput(aEv) {
var req = new XMLHttpRequest();
Expand All @@ -27,6 +28,10 @@
action.classList.add('btn-success');
action.classList.remove('btn-info');

if (captcha) {
captcha.style.display = 'none';
}

if (aConsent) {
consent.checked = false;
} else {
Expand All @@ -39,6 +44,10 @@
action.classList.remove('btn-success');
action.classList.add('btn-info');

if (captcha) {
captcha.style.display = 'block';
}

consent.checked = false;
}

Expand Down
16 changes: 11 additions & 5 deletions views/pages/loginPage.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
<head>
<title>{{title}}</title>
{{> includes/head.html }}
{{#hasCaptcha}}
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
{{/hasCaptcha}}
</head>
<body>
{{> includes/header.html }}
Expand All @@ -16,7 +19,7 @@ <h3>
</h3>
<noscript>
<div class="alert alert-danger small" role="alert">
<i class="fa fa-exclamation-triangle"></i> <strong>WARNING</strong>: The username entered may include additional sanitizing for web friendly URLs and Userscript engine compatibility. If you wish to see the sanitization changes please enable JavaScript and <a href="/login">start over</a>.
<i class="fa fa-exclamation-triangle"></i> <strong>WARNING</strong>: JavaScript must be enabled for new users to register.
</div>
</noscript>
<div class="input-group">
Expand All @@ -37,6 +40,9 @@ <h3>
</select>
<div style="width: 100%;">
<div class="input-group-addon">
{{#hasCaptcha}}
<div class="h-captcha" data-sitekey="5c63330a-3689-44ea-a1a7-cbf8a4bd23dc"></div>
{{/hasCaptcha}}
<p>
<ul class="nav nav-pills nav-justified">
<li><a href="/about/Terms-of-Service"><i class="fa fa-legal"></i> Terms of Service</a></li>
Expand All @@ -47,13 +53,13 @@ <h3>
<label for="consent" class="small" style="white-space: normal;"><input type="checkbox" id="consent" name="consent" value="true" required="required"> I understand and agree to these binding terms &amp; policies.</label>
</p>
</div>
<div class="input-group-addon">
<button class="btn btn-info" type="submit" id="action" class="pull-left">Next <i class="fa fa-sign-in fa-fw"></i></button>
</div>
</div>
</div>
<div class="input-group-addon">
<button class="btn btn-info" type="submit" id="action" class="pull-left">Next <i class="fa fa-sign-in fa-fw"></i></button>
</div>
</form>
</div>
</div>
</div>
{{> includes/footer.html }}
{{> includes/scripts/loginEcho.html }}
Expand Down