Skip to content

Commit 2ebbf90

Browse files
authored
More twiddles (#1892)
* Make these sections a tougher meal. Post #944 #1867 *(not optional atm)* Auto-merge
1 parent cc50a76 commit 2ebbf90

File tree

8 files changed

+157
-7
lines changed

8 files changed

+157
-7
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Repository | Reference | Recent Version
3636
[express-minify][express-minifyGHUrl] | [Documentation][express-minifyDOCUrl] | [![NPM version][express-minifyNPMVersionImage]][express-minifyNPMUrl]
3737
[express-rate-limit][express-rate-limitGHUrl] | [Documentation][express-rate-limitDOCUrl] | [![NPM version][express-rate-limitNPMVersionImage]][express-rate-limitNPMUrl]
3838
[express-session][express-sessionGHUrl] | [Documentation][express-sessionDOCUrl] | [![NPM version][express-sessionNPMVersionImage]][express-sessionNPMUrl]
39+
[express-svg-captcha][express-svg-captchaGHUrl] | [Documentation][express-svg-captchaDOCUrl] | [![NPM version][express-svg-captchaNPMVersionImage]][express-svg-captchaNPMUrl]
3940
[font-awesome][font-awesomeGHUrl] | [Documentation][font-awesomeDOCUrl] | [![NPM version][font-awesomeNPMVersionImage]][font-awesomeNPMUrl]
4041
[formidable][formidableGHUrl] | [Documentation][formidableDOCUrl] | [![NPM version][formidableNPMVersionImage]][formidableNPMUrl]
4142
[git-rev][git-revGHUrl] | [Documentation][git-revDOCUrl] | [![NPM version][git-revNPMVersionImage]][git-revNPMUrl]
@@ -204,6 +205,11 @@ Outdated dependencies list can be achieved with `$ npm outdated` from the termin
204205
[express-sessionNPMUrl]: https://www.npmjs.com/package/express-session
205206
[express-sessionNPMVersionImage]: https://img.shields.io/npm/v/express-session.svg?style=flat
206207

208+
[express-svg-captchaGHUrl]: https://github.com/cmd430/express-svg-captcha
209+
[express-svg-captchaDOCUrl]: https://github.com/cmd430/express-svg-captcha/blob/master/README.md
210+
[express-svg-captchaNPMUrl]: https://www.npmjs.com/package/express-svg-captcha
211+
[express-svg-captchaNPMVersionImage]: https://img.shields.io/npm/v/express-svg-captcha.svg?style=flat
212+
207213
[font-awesomeGHUrl]: https://github.com/FortAwesome/Font-Awesome
208214
[font-awesomeDOCUrl]: https://fontawesome.com/
209215
[font-awesomeNPMUrl]: https://www.npmjs.com/package/font-awesome

controllers/user.js

+63-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ var async = require('async');
1515
var _ = require('underscore');
1616
var util = require('util');
1717
var rfc2047 = require('rfc2047');
18+
var expressCaptcha = require('express-svg-captcha');
1819

1920
var SPDX = require('spdx-license-ids');
2021

@@ -941,6 +942,25 @@ exports.userSyncListPage = function (aReq, aRes, aNext) {
941942
});
942943
};
943944

945+
var captcha = new expressCaptcha({
946+
isMath: true, // if true will be a simple math equation
947+
useFont: null, // Can be path to ttf/otf font file
948+
size: 4, // number of characters for string capthca
949+
ignoreChars: '0o1i', // characters to not include in string capthca
950+
noise: 3, // number of noise lines
951+
color: true, // if true noise lines and captcha characters will be randomly colored
952+
// (is set to true if background is set)
953+
background: null, // HEX or RGB(a) value for background set to null for transparent
954+
width: 200, // width of captcha
955+
height: 50, // height of captcha
956+
fontSize: 56, // font size for captcha
957+
charPreset: null // string of characters for use with string captcha set to null for default aA-zZ
958+
});
959+
960+
exports.userEditProfilePageCaptcha = function (aReq, aRes, aNext) {
961+
(captcha.generate())(aReq, aRes, aNext);
962+
}
963+
944964
exports.userEditProfilePage = function (aReq, aRes, aNext) {
945965
var authedUser = aReq.session.user;
946966

@@ -2059,16 +2079,56 @@ exports.update = function (aReq, aRes, aNext) {
20592079
var authedUser = aReq.session.user;
20602080

20612081
// Update the about section of a user's profile
2062-
User.findOneAndUpdate({ _id: authedUser._id }, { about: aReq.body.about },
2063-
function (aErr, aUser) {
2082+
User.findOne({ _id: authedUser._id }, function (aErr, aUser) {
2083+
if (aErr) {
2084+
aRes.redirect('/');
2085+
return;
2086+
}
2087+
2088+
if (!aUser) {
2089+
msg = 'No user found.'
2090+
statusCodePage(aReq, aRes, aNext, {
2091+
statusCode: 500,
2092+
statusMessage: msg
2093+
});
2094+
return;
2095+
}
2096+
2097+
if (!captcha.validate(aReq, aReq.body.captcha)) {
2098+
aRes.redirect('/users/' + encodeURIComponent(aUser.name));
2099+
return;
2100+
}
2101+
2102+
// Update DB
2103+
aUser.about = aReq.body.about;
2104+
aUser.save(function (aErr, aUser) {
2105+
var msg = null;
2106+
20642107
if (aErr) {
2065-
aRes.redirect('/');
2108+
msg = 'Unknown error when saving Profile.';
2109+
statusCodePage(aReq, aRes, aNext, {
2110+
statusCode: 500,
2111+
statusMessage: [msg, 'Please contact Development'].join(' ')
2112+
});
2113+
console.error(aErr);
2114+
return;
2115+
}
2116+
if (!aUser) {
2117+
msg = 'No user handle when saving Profile.';
2118+
statusCodePage(aReq, aRes, aNext, {
2119+
statusCode: 500,
2120+
statusMessage: [msg, 'Please contact Development'].join(' ')
2121+
});
2122+
console.error(msg)
20662123
return;
20672124
}
20682125

2126+
// Update session
20692127
authedUser.about = aUser.about;
2128+
20702129
aRes.redirect('/users/' + encodeURIComponent(aUser.name));
20712130
});
2131+
});
20722132
};
20732133

20742134
// Submit a script through the web editor

libs/modelParser.js

+3
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,9 @@ var parseUser = function (aUser) {
726726
// Dates
727727
parseDateProperty(user, 'created');
728728

729+
// Misc
730+
user.hasCaptcha = true;
731+
729732
return user;
730733
};
731734
parseModelFnMap.User = parseUser;

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"express-minify": "1.0.0",
2222
"express-rate-limit": "5.5.1",
2323
"express-session": "1.17.2",
24+
"express-svg-captcha": "1.0.1",
2425
"font-awesome": "4.7.0",
2526
"formidable": "1.2.2",
2627
"git-rev": "0.2.1",

routes.js

+1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ module.exports = function (aApp) {
149149
aApp.route('/users/:username/github/import').post(authentication.validateUser, user.userGitHubImportScriptPage);
150150

151151
aApp.route('/users/:username/profile/edit').get(authentication.validateUser, user.userEditProfilePage).post(authentication.validateUser, user.update);
152+
aApp.route('/users/:username/profile/captcha').get(authentication.validateUser, user.userEditProfilePageCaptcha);
152153
aApp.route('/users/:username/update').post(authentication.validateUser, admin.adminUserUpdate);
153154
// NOTE: Some below inconsistent with priors
154155
aApp.route('/user/preferences').get(authentication.validateUser, user.userEditPreferencesPage);
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<script type="text/javascript">
2+
(function () {
3+
var textareaContent = null;
4+
5+
function show() {
6+
var textarea = document.querySelector('.user-content textarea[name=about]');
7+
if (textarea) {
8+
textarea.setAttribute('readonly', 'readonly');
9+
textareaContent = textarea.value;
10+
} else {
11+
textareaContent = '';
12+
}
13+
14+
var submit = document.querySelector('#captchaModal button[type=submit]');
15+
if (submit) {
16+
document.addEventListener('click', click, { capture: true, passive: true });
17+
}
18+
}
19+
20+
function hide() {
21+
var textarea = document.querySelector('.user-content textarea[name=about]');
22+
if (textarea) {
23+
textarea.removeAttribute('readonly', 'readonly');
24+
textareaContent = null;
25+
}
26+
}
27+
28+
function click() {
29+
var textarea = document.querySelector('.user-content textarea[name=about]');
30+
var input = document.querySelector('#captchaModal input[name=about]');
31+
var submit = document.querySelector('#captchaModal button[type=submit]');
32+
33+
if (input && textarea && textarea.value === textareaContent) {
34+
input.value = textareaContent;
35+
}
36+
if (submit) {
37+
submit.removeEventListener('click', click, { capture: true, passive: true });
38+
}
39+
}
40+
41+
// WARNING: jQuery required for event capture with *bootstrap*@3.x events
42+
$('#captchaModal').on('show.bs.modal', show);
43+
$('#captchaModal').on('hide.bs.modal', hide);
44+
45+
})();
46+
</script>

views/includes/siteModals.html

+35
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,38 @@ <h4 class="modal-title"><i class="fa fa-fw fa-money"></i> Donate for the site Op
1818
</div>
1919
</div>
2020
</div>
21+
{{#user.hasCaptcha}}
22+
<div class="modal fade" id="captchaModal">
23+
<div class="modal-dialog">
24+
<div class="modal-content">
25+
<div class="modal-header">
26+
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
27+
<h4 class="modal-title"><i class="fa fa-fw fa-user"></i> I am human</h4>
28+
</div>
29+
<div class="modal-body text-center d-block">
30+
<p>Please solve this.</p>
31+
</div>
32+
<div class="form-group has-feedback has-clear text-center">
33+
<img class="" src="/users/{{user.slugUrl}}/profile/captcha"/>
34+
</div>
35+
<form action="" method="post">
36+
<div class="modal-footer">
37+
<input type="hidden" name="about">
38+
<div class="input-group col-xs-12">
39+
<span class="input-group-btn">
40+
<button type="button" class="btn btn-default" data-dismiss="modal"><i class="fa fa-fw fa-close"></i> Close</button>
41+
</span>
42+
<input class="form-control text-center" type="text" name="captcha" placeholder="Type solution here" autocomplete="off"/>
43+
<span class="input-group-btn">
44+
<button type="submit" class="btn btn-success" title="Submit solution">
45+
<i class="fa fa-fw fa-check"></i><span > Submit</span>
46+
</button>
47+
</span>
48+
</div>
49+
</div>
50+
</form>
51+
</div>
52+
</div>
53+
</div>
54+
{{> includes/scripts/markdownCopy.html }}
55+
{{/user.hasCaptcha}}

views/pages/userEditProfilePage.html

+2-4
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,16 @@
1313
<div class="row">
1414
<div class="col-xs-12">
1515
{{> includes/userPageHeader.html }}
16-
<form action="" method="post">
1716
<div class="user-content">
1817
<textarea name="about" data-provide="markdown" data-iconlibrary="fa" rows="28" class="col-xs-12" placeholder="Type Profile here using Markdown.">{{user.about}}</textarea>
1918
</div>
2019
<div class="btn-toolbar">
2120
<a href="/about/Frequently-Asked-Questions"><i class="fa fa-question-circle" title="FAQ"></i></a>
2221
<a href="https://guides.github.com/features/mastering-markdown/" title="GitHub Flavor Markdown compatible"><i class="octicon octicon-markdown"></i></a>
23-
<button type="submit" class="btn btn-sm btn-success pull-right" title="Save Profile changes">
22+
<a rel="bookmark" referrerpolicy="same-origin" href="#" data-toggle="modal" data-target="#captchaModal" class="btn btn-sm btn-success pull-right" title="Save Profile changes">
2423
<i class="fa fa-fw fa-save"></i><span class="hidden-xs"> Save</span>
25-
</button>
24+
</a>
2625
</div>
27-
</form>
2826
</div>
2927
</div>
3028
</div>

0 commit comments

Comments
 (0)