diff --git a/client/less/main.less b/client/less/main.less index a61a04b9e9..a7cd740b2e 100644 --- a/client/less/main.less +++ b/client/less/main.less @@ -1145,7 +1145,7 @@ and (max-width : 400px) { -webkit-overflow-scrolling: touch; } -// Reset/Delete Account Modal Styles +// Account Modal Styles .modal-dialog { margin: 80px; @@ -1184,6 +1184,14 @@ and (max-width : 400px) { background-color: #208e36; border-color: darkgreen; } + + .modal-textarea { + width: 100%; + max-width: 590px; + border: 2px solid #ccc; + border-radius: 5px; + padding: 5px; + } } } diff --git a/server/boot/user.js b/server/boot/user.js index be7833430b..f3f88e3eeb 100644 --- a/server/boot/user.js +++ b/server/boot/user.js @@ -12,7 +12,8 @@ import { import certTypes from '../utils/certTypes.json'; import { ifNoUser401, - ifNoUserRedirectTo + ifNoUserRedirectTo, + ifNotVerifiedRedirectToSettings } from '../utils/middleware'; import { observeQuery } from '../utils/rx'; import { @@ -140,6 +141,7 @@ module.exports = function(app) { const api = app.loopback.Router(); const User = app.models.User; const Block = app.models.Block; + const { Email } = app.models; const map$ = cachedMap(Block); function findUserByUsername$(username, fields) { return observeQuery( @@ -223,6 +225,18 @@ module.exports = function(app) { ); router.get('/:username', showUserProfile); + router.get( + '/:username/report-user/', + sendNonUserToMap, + ifNotVerifiedRedirectToSettings, + getReportUserProfile + ); + + api.post( + '/:username/report-user/', + ifNoUser401, + postReportUserProfile + ); app.use('/:lang', router); app.use(api); @@ -631,4 +645,55 @@ module.exports = function(app) { return res.render('account/forgot'); }); } + + function getReportUserProfile(req, res) { + const username = req.params.username.toLowerCase(); + return res.render('account/report-profile', { + title: 'Report User', + username + }); + } + + function postReportUserProfile(req, res, next) { + const { user } = req; + const { username } = req.params; + const report = req.sanitize('reportDescription').trimTags(); + + if (!username || !report || report === '') { + req.flash('errors', { + msg: 'Oops, something is not right please re-check your submission.' + }); + return next(); + } + + return Email.send$({ + type: 'email', + to: 'Team@FreeCodeCamp.com', + cc: user.email, + from: 'Team@FreeCodeCamp.com', + subject: 'Abuse Report : Reporting ' + username + '\'s profile.', + text: dedent(` + Hello Team,\n + This is to report the profile of ${username}.\n + Report Details:\n + ${report}\n\n + Reported by: + Username: ${user.username} + Name: ${user.name} + Email: ${user.email}\n + Thanks and regards, + ${user.name} + `) + }, err => { + if (err) { + err.redirectTo = '/' + username; + return next(err); + } + + req.flash('info', { + msg: 'A report was sent to the team with ' + user.email + ' in copy.' + }); + return res.redirect('/'); + }); + } }; diff --git a/server/middlewares/validator.js b/server/middlewares/validator.js index 80a7ede590..bca7b0f8f5 100644 --- a/server/middlewares/validator.js +++ b/server/middlewares/validator.js @@ -26,6 +26,30 @@ export default function() { // every file has contents keys.map(key => value[key]).every(file => isPoly(file)); } + }, + customSanitizers: { + // Refer : http://stackoverflow.com/a/430240/1932901 + trimTags(value) { + const tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*'; + const tagOrComment = new RegExp( + '<(?:' + // Comment body. + + '!--(?:(?:-*[^->])*--+|-?)' + // Special "raw text" elements whose content should be elided. + + '|script\\b' + tagBody + '>[\\s\\S]*?[\\s\\S]*?', + 'gi'); + let rawValue; + do { + rawValue = value; + value = value.replace(tagOrComment, ''); + } while (value !== rawValue); + return value.replace(/here.' + if (!user.emailVerified) { + req.flash('error', { + msg: 'We do not have your verified email address on record, ' + + 'please add it in the settings to continue with your request.' }); + return res.redirect('/settings'); } - */ + return next(); } diff --git a/server/views/account/report-profile.jade b/server/views/account/report-profile.jade new file mode 100644 index 0000000000..91d0f7df6d --- /dev/null +++ b/server/views/account/report-profile.jade @@ -0,0 +1,29 @@ +extends ../layout +block content + #modal-dialog.modal + .modal-dialog + .modal-content + .modal-header + a.close(href='/settings', data-dismiss='modal', aria-hidden='true') × + h3 Do you want to report #{username}'s profile for abuse? + .modal-body + p We will notify the community moderators' team, + | and a send copy of this report to your email: + strong #{user.email} + | . We may get back to you for more information, if required. + .modal-footer + form(action='/' + username +'/report-user/', method='POST') + input(type='hidden', name='_csrf', value=_csrf) + div + textarea.modal-textarea(name='reportDescription', cols='40', rows='5') + .spacer + button.btn.btn-danger.btn-block(type='submit') + | Yes, submit my report about this user's profile. + .spacer + a.btn.btn-success.btn-block(href='/settings', data-dismiss='modal', aria-hidden='true') + | Nevermind, I don't want to report this user. + script. + document.addEventListener('DOMContentLoaded', function() { + const modal$ = document.getElementById('modal-dialog'); + modal$.classList.add('show'); + }); diff --git a/server/views/account/show.jade b/server/views/account/show.jade index 964492cf6a..026e52a511 100644 --- a/server/views/account/show.jade +++ b/server/views/account/show.jade @@ -56,6 +56,9 @@ block content if isBackEndCert .button-spacer a.btn.btn-primary.btn-block(href='/' + username + '/back-end-certification') View My Back End Development Certification + if (user && user.username != username) + .button-spacer + a.btn.btn-primary.btn-block(href='/' + username + '/report-user/') Report this user's profile for abuse .row .col-xs-12.text-center if (badges.coreTeam && badges.coreTeam.length)