diff --git a/common/models/user.json b/common/models/user.json index 7a67551df4..db607a09b4 100644 --- a/common/models/user.json +++ b/common/models/user.json @@ -99,6 +99,10 @@ "twitter": { "type": "string" }, + "acceptedPrivacyTerms": { + "type": "boolean", + "default": false + }, "sendQuincyEmail": { "type": "boolean", "default": true diff --git a/server/boot/authentication.js b/server/boot/authentication.js index 27cf2dd3a8..1a95cb2e40 100644 --- a/server/boot/authentication.js +++ b/server/boot/authentication.js @@ -51,6 +51,20 @@ module.exports = function enableAuthentication(app) { }) ); + router.get( + '/accept-privacy-terms', + ifNoUserRedirectHome, + (req, res) => { + const { user } = req; + if (user && !user.acceptedPrivacyTerms) { + return res.render('account/accept-privacy-terms', { + title: 'Privacy Policy and Terms of Service' + }); + } + return res.redirect('/settings'); + } + ); + const defaultErrorMsg = dedent` Oops, something is not right, please request a fresh link to sign in / sign up. diff --git a/server/boot/settings.js b/server/boot/settings.js index 63b2477367..f2d67628f4 100644 --- a/server/boot/settings.js +++ b/server/boot/settings.js @@ -1,5 +1,4 @@ import { check } from 'express-validator/check'; - import { ifNoUser401, createValidatorErrorHandler @@ -147,6 +146,36 @@ export default function settingsController(app) { ); } + const updatePrivacyTerms = (req, res, next) => { + const { + user, + body: { quincyemails } + } = req; + const update = { + acceptedPrivacyTerms: true, + sendQuincyEmail: !!quincyemails + }; + return user.update$(update) + .do(() => { + req.user = Object.assign(req.user, update); + }) + .subscribe( + () => { + res.status(200).json({ + message: 'We have updated your preferences. ' + + 'You can now continue using freeCodeCamp.' + }); + }, + next + ); + }; + + api.post( + '/update-privacy-terms', + ifNoUser401, + updatePrivacyTerms + ); + api.post( '/refetch-user-completed-challenges', ifNoUser401, diff --git a/server/middleware.json b/server/middleware.json index 64e5c34eb5..f6ce16ed6f 100644 --- a/server/middleware.json +++ b/server/middleware.json @@ -57,7 +57,8 @@ "./middlewares/csp": {}, "./middlewares/jade-helpers": {}, "./middlewares/flash-cheaters": {}, - "./middlewares/passport-login": {} + "./middlewares/passport-login": {}, + "./middlewares/privacy-terms-notice": {} }, "files": {}, "final:after": { diff --git a/server/middlewares/privacy-terms-notice.js b/server/middlewares/privacy-terms-notice.js new file mode 100644 index 0000000000..1dc892a7be --- /dev/null +++ b/server/middlewares/privacy-terms-notice.js @@ -0,0 +1,22 @@ +const ALLOWED_METHODS = ['GET']; +const EXCLUDED_PATHS = [ + '/api/flyers/findOne', + '/signout', + '/accept-privacy-terms' +]; + +export default function privacyTermsNotAcceptedNotice() { + return function(req, res, next) { + if ( + ALLOWED_METHODS.indexOf(req.method) !== -1 && + EXCLUDED_PATHS.indexOf(req.path) === -1 + ) { + const { user } = req; + if (user && user.acceptedPrivacyTerms !== true) { + res.redirect('/accept-privacy-terms'); + return next; + } + } + return next(); + }; +} diff --git a/server/views/account/accept-privacy-terms.jade b/server/views/account/accept-privacy-terms.jade new file mode 100644 index 0000000000..f0b9f0ef63 --- /dev/null +++ b/server/views/account/accept-privacy-terms.jade @@ -0,0 +1,123 @@ +extends ../layout +block content + .container + .row.flashMessage.negative-30 + .col-sm-6.col-sm-offset-3 + #flash-board.alert.fade.in(style='display: none;') + button.close(type='button', data-dismiss='alert') + span.ion-close-circled#flash-close + #flash-content + .col-xs-12 + #accept-privacy-terms + .row + .text-center + h3 Please review our privacy policy and the terms of service. + br + .row + .col-sm-6.col-sm-offset-3 + form(method='POST', action='/update-privacy-terms') + input(type='hidden', name='_csrf', value=_csrf) + .checkbox + label + input(id='terms', name='privacy', type='checkbox') + span.cr + i.cr-icon.fa.fa-check + | I accept the + a(href='https://www.freecodecamp.org/terms' target='_blank') terms of service + | (required). + .checkbox + label + input(id='privacy', name='privacy', type='checkbox') + span.cr + i.cr-icon.fa.fa-check + | I accept the + a(href='https://www.freecodecamp.org/privacy' target='_blank') privacy policy + | (required). + .checkbox + label + input(id='quincyemails', name='quincyemails', type='checkbox') + span.cr + i.cr-icon.fa.fa-check + | I want weekly emails from Quincy (freeCodeCamp.org's founder) + .button-spacer + button.btn.btn-primary.btn-lg.btn-block(id='submit-button', type='submit') + .row + .col-sm-6.col-sm-offset-3 + a.btn.btn-primary.btn-lg.btn-block(id='continue-button', href='/', style='display: none;') + | Continue to freeCodeCamp + + script. + $(document).ready(function() { + + var checkedBoxCount = 0; + function disableContinueButtonForAgreement(isLaunched) { + if (isLaunched) { + $('#submit-button').prop('disabled', true).html( + 'Submit<\/span>'); + return; + } + + if (!isLaunched && checkedBoxCount === 2){ + $('#submit-button').prop('disabled', false).html( + 'Submit<\/span>'); + } + } + disableContinueButtonForAgreement(true); + + $('#terms').click(function() { + if (this.checked) { + checkedBoxCount++; + disableContinueButtonForAgreement(false); + } else { + checkedBoxCount--; + disableContinueButtonForAgreement(true); + } + }); + + $('#privacy').click(function() { + if (this.checked) { + checkedBoxCount++; + disableContinueButtonForAgreement(false); + } else { + checkedBoxCount--; + disableContinueButtonForAgreement(true); + } + }); + + $('form').submit(function(event){ + event.preventDefault(); + $('#flash-board').hide(); + var $form = $(event.target); + $.ajax({ + type : 'POST', + url : $form.attr('action'), + data : $form.serialize(), + dataType : 'json', + encode : true, + xhrFields : { withCredentials: true } + }) + .fail(error => { + if (error.responseText){ + var data = JSON.parse(error.responseText); + if(data.message) + $('#flash-content').html(data.message); + $('#flash-board') + .removeClass('alert-success') + .addClass('alert-info') + .fadeIn(); + } + }) + .done(data =>{ + if(data && data.message){ + $('#flash-content').html(data.message); + $('#flash-board') + .removeClass('alert-info') + .addClass('alert-success') + .fadeIn(); + + $('#accept-privacy-terms').hide(); + $('#continue-button').show(); + } + }); + }); + });