From 3bba227620567f333fddba96cb8dacdcd3932a3a Mon Sep 17 00:00:00 2001 From: William Dimaculangan Date: Sat, 23 Apr 2016 11:44:18 +0800 Subject: [PATCH 01/21] Stop inject from mangling label when offset from loop --- public/js/lib/loop-protect/loop-protect.js | 7 +++++-- test/public/js/loop-protect-test.js | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 test/public/js/loop-protect-test.js diff --git a/public/js/lib/loop-protect/loop-protect.js b/public/js/lib/loop-protect/loop-protect.js index 60031599f5..b4f2fdb454 100644 --- a/public/js/lib/loop-protect/loop-protect.js +++ b/public/js/lib/loop-protect/loop-protect.js @@ -130,6 +130,7 @@ if (typeof DEBUG === 'undefined') { DEBUG = true; } var ignore = {}; var pushonly = {}; var labelPostion = null; + var labelIndex = -1; function insertReset(lineNum, line, matchPosition) { // recompile the line with the reset **just** before the actual loop @@ -179,6 +180,7 @@ if (typeof DEBUG === 'undefined') { DEBUG = true; } if (directlyBeforeLoop(index, lineNum, lines)) { DEBUG && debug('- found a label: "' + labelMatch[0] + '"'); // jshint ignore:line labelPostion = lineNum; + labelIndex = index; } else { DEBUG && debug('- ignored "label", false positive'); // jshint ignore:line } @@ -309,10 +311,11 @@ if (typeof DEBUG === 'undefined') { DEBUG = true; } DEBUG && debug('- reset inserted above matched label on line ' + labelPostion); // jshint ignore:line if (recompiled[labelPostion] === undefined) { labelPostion--; - matchPosition = 0; + labelIndex = 0; } - recompiled[labelPostion] = insertReset(printLineNumber, recompiled[labelPostion], matchPosition); + recompiled[labelPostion] = insertReset(printLineNumber, recompiled[labelPostion], labelIndex); labelPostion = null; + labelIndex = -1; } } diff --git a/test/public/js/loop-protect-test.js b/test/public/js/loop-protect-test.js new file mode 100644 index 0000000000..6ec3cbebdd --- /dev/null +++ b/test/public/js/loop-protect-test.js @@ -0,0 +1,15 @@ +'use strict'; + +let loopProtect = require('../../../public/js/lib/loop-protect/loop-protect'); + +let test = require('tape'); + +test('LoopProtect injection', function(t) { + t.plan(1); + + // Label indented 2 spaces - loop indented three spaces + t.true( + loopProtect(' loop1:\n while(true) {\n\n}').indexOf('loop1') > 0, + 'Should keep loop label intact if not lined up with loop.' + ); +}); \ No newline at end of file From 65600cb370e2f7c296140f3246c81630bddf9437 Mon Sep 17 00:00:00 2001 From: Vince Cooley Date: Sat, 30 Apr 2016 23:11:07 -0500 Subject: [PATCH 02/21] Modals now close map, wiki, and chat asides when they are displayed Changed aside tags to select by class or id --- client/commonFramework/bindings.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/client/commonFramework/bindings.js b/client/commonFramework/bindings.js index 18b98fb29e..36e1d5d9f4 100644 --- a/client/commonFramework/bindings.js +++ b/client/commonFramework/bindings.js @@ -150,6 +150,11 @@ window.common = (function(global) { window.ga('send', 'event', 'Challenge', 'load', common.gaName); } + $('.modal').on('show.bs.modal', function() { + $('.gitter-chat-embed, .wiki-aside, .map-aside') + .addClass('is-collapsed'); + }); + $('#complete-courseware-dialog').on('hidden.bs.modal', function() { if (common.editor.focus) { common.editor.focus(); @@ -176,6 +181,15 @@ window.common = (function(global) { $('#complete-courseware-dialog').modal('show'); }); + $('#show-solution').on('click', function() { + $('#complete-courseware-dialog').modal('hide'); + $('#nav-wiki-btn').click(); + }); + + $('#challenge-help-btn').on('click', function() { + $('.wiki-aside, .map-aside, #chat-embed-main').addClass('is-collapsed'); + }); + $('#help-ive-found-a-bug-wiki-article').on('click', function() { window.open( 'https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/' + From 971b0310d9bdd5a2a008cb80592062bfeb82efc8 Mon Sep 17 00:00:00 2001 From: nishant-tomer Date: Tue, 17 May 2016 23:22:21 +0530 Subject: [PATCH 03/21] add a test in Record Collection --- .../basic-javascript.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/seed/challenges/01-front-end-development-certification/basic-javascript.json b/seed/challenges/01-front-end-development-certification/basic-javascript.json index 2c7000056f..138effc5c1 100644 --- a/seed/challenges/01-front-end-development-certification/basic-javascript.json +++ b/seed/challenges/01-front-end-development-certification/basic-javascript.json @@ -4548,10 +4548,11 @@ "(function(x) { return \"collection = \\n\" + JSON.stringify(x, '\\n', 2); })(collection);" ], "solutions": [ - "var collection = {\n 2548: {\n album: \"Slippery When Wet\",\n artist: \"Bon Jovi\",\n tracks: [ \n \"Let It Rock\", \n \"You Give Love a Bad Name\" \n ]\n },\n 2468: {\n album: \"1999\",\n artist: \"Prince\",\n tracks: [ \n \"1999\", \n \"Little Red Corvette\" \n ]\n },\n 1245: {\n artist: \"Robert Palmer\",\n tracks: [ ]\n },\n 5439: {\n album: \"ABBA Gold\"\n }\n};\n// Keep a copy of the collection for tests\nvar collectionCopy = JSON.parse(JSON.stringify(collection));\n\n// Only change code below this line\nfunction updateRecords(id, prop, value) {\n if(value !== \"\") {\n if(prop === \"tracks\") {\n collection[id][prop].push(value);\n } else {\n collection[id][prop] = value;\n }\n } else {\n delete collection[id][prop];\n }\n\n return collection;\n}" + "var collection = {\n 2548: {\n album: \"Slippery When Wet\",\n artist: \"Bon Jovi\",\n tracks: [ \n \"Let It Rock\", \n \"You Give Love a Bad Name\" \n ]\n },\n 2468: {\n album: \"1999\",\n artist: \"Prince\",\n tracks: [ \n \"1999\", \n \"Little Red Corvette\" \n ]\n },\n 1245: {\n artist: \"Robert Palmer\",\n tracks: [ ]\n },\n 5439: {\n album: \"ABBA Gold\"\n }\n};\n// Keep a copy of the collection for tests\nvar collectionCopy = JSON.parse(JSON.stringify(collection));\n\n// Only change code below this line\nfunction updateRecords(id, prop, value) {\n if(value !== \"\") {\n if(prop === \"tracks\") {\n collection[id][prop]= collection[id][prop] || [];\n collection[id][prop].push(value);\n } else {\n collection[id][prop] = value;\n }\n } else {\n delete collection[id][prop];\n }\n\n return collection;\n}" ], "tests": [ "collection = collectionCopy; assert(updateRecords(5439, \"artist\", \"ABBA\")[5439][\"artist\"] === \"ABBA\", 'message: After updateRecords(5439, \"artist\", \"ABBA\"), artist should be \"ABBA\"');", + "assert(updateRecords(5439, \"tracks\", \"Take a Chance on Me\")[5439][\"tracks\"].pop() === \"Take a Chance on Me\", 'message: After updateRecords(5439, \"tracks\", \"Take a Chance on Me\"), tracks should have \"Take a Chance on Me\" as the last element.');", "updateRecords(2548, \"artist\", \"\"); assert(!collection[2548].hasOwnProperty(\"artist\"), 'message: After updateRecords(2548, \"artist\", \"\"), artist should not be set');", "assert(updateRecords(1245, \"tracks\", \"Addicted to Love\")[1245][\"tracks\"].pop() === \"Addicted to Love\", 'message: After updateRecords(1245, \"tracks\", \"Addicted to Love\"), tracks should have \"Addicted to Love\" as the last element.');", "updateRecords(2548, \"tracks\", \"\"); assert(!collection[2548].hasOwnProperty(\"tracks\"), 'message: After updateRecords(2548, \"tracks\", \"\"), tracks should not be set');" From d9f48e81e3232b69d903a4b86dd3e84e58cc8ac4 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 26 May 2016 18:53:01 +0100 Subject: [PATCH 04/21] Added fixed header to wiki aside * Added title --- client/less/wiki.less | 6 ++---- server/views/partials/footer.jade | 4 +++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/less/wiki.less b/client/less/wiki.less index b5731a84ef..6a454032a9 100644 --- a/client/less/wiki.less +++ b/client/less/wiki.less @@ -90,10 +90,8 @@ } .wiki-fixed-header { - position: fixed; - background: white; - padding-top: 5px; - width: 100%; + background: linear-gradient(180deg,#fff 0,#fff 70%,hsla(0,0%,100%,0)); + padding: 5px 0 18px 0; z-index: 1; left: 0; top: 0; diff --git a/server/views/partials/footer.jade b/server/views/partials/footer.jade index d529a08b4e..f67b64325b 100644 --- a/server/views/partials/footer.jade +++ b/server/views/partials/footer.jade @@ -5,6 +5,8 @@ aside.map-aside.is-collapsed a.map-aside-action-item.map-aside-action-pop-out(href='/map' target='_blank' aria-label='open map in new tab') button.map-aside-action-item.map-aside-action-collapse(aria-label='close map aside') aside.wiki-aside.is-collapsed - .wiki-aside-action-bar + .wiki-aside-action-bar.wiki-fixed-header + .chat-embed-main-title + span Free Code Camp's Wiki a.wiki-aside-action-item.wiki-aside-action-pop-out(href='/wiki' target='_blank' aria-label='open wiki in new tab') button.wiki-aside-action-item.wiki-aside-action-collapse(aria-label='close wiki aside') From b951f8a035499d7c4c15aafba20a779db0380b81 Mon Sep 17 00:00:00 2001 From: Tara Lerias Date: Fri, 27 May 2016 12:32:31 -0400 Subject: [PATCH 05/21] Send CSRF Value in Remove Pledge Form Submit --- server/views/commit/index.jade | 1 + 1 file changed, 1 insertion(+) diff --git a/server/views/commit/index.jade b/server/views/commit/index.jade index 722654e64c..d160d38cda 100644 --- a/server/views/commit/index.jade +++ b/server/views/commit/index.jade @@ -71,6 +71,7 @@ block content if pledge form.row(name='stop-pledge' action='/commit/stop-commitment' method='post') + input(type='hidden', name='_csrf', value=_csrf) .col-xs-12.col-sm-6.col-sm-offset-3.text-center .button-spacer button.btn.btn-block.btn-lg.btn-default(name='submit' type='submit') Stop my current pledge From 650f214c4f8ad9e4ff049d9cf8b0faae0d6342e8 Mon Sep 17 00:00:00 2001 From: DealPete Date: Fri, 27 May 2016 22:57:25 -0230 Subject: [PATCH 06/21] add period to "Escaping Literal Quotes in Strings". --- .../basic-javascript.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/seed/challenges/01-front-end-development-certification/basic-javascript.json b/seed/challenges/01-front-end-development-certification/basic-javascript.json index 7ac5e11aca..db8ed1a3e4 100644 --- a/seed/challenges/01-front-end-development-certification/basic-javascript.json +++ b/seed/challenges/01-front-end-development-certification/basic-javascript.json @@ -951,7 +951,7 @@ "Alan said, \"Peter is learning JavaScript\".", "

Instructions

", "Use backslashes to assign a string to the myStr variable so that if you were to print it to the console, you would see:", - "I am a \"double quoted\" string inside \"double quotes\"" + "I am a \"double quoted\" string inside \"double quotes\"." ], "releasedOn": "January 1, 2016", "challengeSeed": [ @@ -969,11 +969,11 @@ "})();" ], "solutions": [ - "var myStr = \"I am a \\\"double quoted\\\" string inside \\\"double quotes\\\"\";" + "var myStr = \"I am a \\\"double quoted\\\" string inside \\\"double quotes\\\".\";" ], "tests": [ - "assert(code.match(/\\\\\"/g).length === 4 && code.match(/[^\\\\]\"/g).length === 2, 'message: You should use two double quotes (") and four escaped double quotes (\") ');", - "assert(myStr === \"I am a \\\"double quoted\\\" string inside \\\"double quotes\\\"\", 'message: Variable myStr should equal to (I am a \"double quoted\" string inside \"double quotes\").');" + "assert(code.match(/\\\\\"/g).length === 4 && code.match(/[^\\\\]\"/g).length === 2, 'message: You should use two double quotes (") and four escaped double quotes (\").');", + "assert(myStr === \"I am a \\\"double quoted\\\" string inside \\\"double quotes\\\".\", 'message: Variable myStr should equal to (I am a \"double quoted\" string inside \"double quotes\".).');" ], "type": "waypoint", "challengeType": 1, @@ -986,7 +986,7 @@ "Alan dijo, \"Pedro está aprendiendo JavaScript\".", "

Instructiones

", "Usa barras invertidaspara asigar una cadena a la variable myStr de modo que si tu fueras a imprimirla en la consola, tu verías:", - "I am a \"double quoted\" string inside \"double quotes\"" + "I am a \"double quoted\" string inside \"double quotes\"." ] }, { From 9fe18ba4e1183716b092abc29fb26380b86f8247 Mon Sep 17 00:00:00 2001 From: Thanakom Sangnetra Date: Sat, 28 May 2016 15:33:00 +0700 Subject: [PATCH 07/21] update client/less/main.less - add btn-default hover background color --- client/less/main.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/less/main.less b/client/less/main.less index 98d2a1846f..a42153283b 100644 --- a/client/less/main.less +++ b/client/less/main.less @@ -1155,6 +1155,12 @@ code { } } +// make btn-default hover more visible +.btn-default:active, +.btn-default:hover { + background-color: @brand-primary; +} + @import "chat.less"; @import "jobs.less"; @import "challenge.less"; From a0b1e3c59b06f289a2197ab12a61045fe379ca40 Mon Sep 17 00:00:00 2001 From: Arsen Melikyan Date: Sat, 28 May 2016 20:18:19 +0400 Subject: [PATCH 08/21] Do not auto toggle to Question view when video ends --- common/app/routes/Hikes/components/Lecture.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/common/app/routes/Hikes/components/Lecture.jsx b/common/app/routes/Hikes/components/Lecture.jsx index 42dd83a775..ca7601ba63 100644 --- a/common/app/routes/Hikes/components/Lecture.jsx +++ b/common/app/routes/Hikes/components/Lecture.jsx @@ -76,7 +76,6 @@ export class Lecture extends React.Component { From a52e22dfb66f992c96df65c075abbce4d4cbfd24 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Sun, 29 May 2016 00:23:05 +0300 Subject: [PATCH 09/21] fix grammar in this lecture --- seed/challenges/04-video-challenges/jslingo.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/seed/challenges/04-video-challenges/jslingo.json b/seed/challenges/04-video-challenges/jslingo.json index b74b2bbb05..89928545ea 100644 --- a/seed/challenges/04-video-challenges/jslingo.json +++ b/seed/challenges/04-video-challenges/jslingo.json @@ -78,7 +78,7 @@ "title": "JavaScript Lingo: Variables & camelCase", "description": [ "We are going to cover what constitutes a variable, and the reasoning behind camelCase.", - "A variable, also referred to as 'var', is the name or placeholder for a boolean, string, number, or other piece of static information.", + "A variable also referred to as 'var', is the name or placeholder for a boolean, string, number, or another piece of static information.", "You can use Google Dev Tools to inspect the Free Code Camp home page and look for some variables.", "You 'declare' variables the first time with 'var' in front of it, but those can be referenced later in your script.", "camelCase is the way that JavaScript pushes words together and still keeps them legible. The first letter of the first word is lowercase, along with the remainder of the word, but the first letter of every consecutive word is capitalized. There are no spaces. Examples: brianaLovesHerPets, bestFoodIsCheese, and codeIsWorthLearning.", @@ -152,7 +152,7 @@ "id": "56b15f15632298c12f315189", "title": "JavaScript Lingo: Finding and Indexing Data in Arrays", "description": [ - "There are many reasons you might need to access a certain piece of data from within a larger set, and you do that by referencing it's index.", + "There are many reasons you might need to access a certain piece of data from within a larger set, and you do that by referencing its index.", "We won't get into syntax now, but you should know that the first thing in an array is actually index 0.", "This goes for strings and objects, too. All of these indices start at 0, so if you're looking asking the code to find indexArr[2], you're really going to get the third piece of information in that array." ], @@ -185,7 +185,7 @@ "title": "JavaScript Lingo: Manipulating Data", "description": [ "Methods and functions are ways you can manipulate variables or other sets of information.", - "Methods are built in to JavaScript, and you will become more familiar with these as you write functions for the bonfires.", + "Methods are built into JavaScript, and you will become more familiar with these as you write functions for the bonfires.", "I'd suggest looking through the MDN (Mozilla Developer Network that we discussed in the first video) to familiarize yourself with the sheer amount and general potential for the methods that exist. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Methods_Index", "Examples include '.split(' ')', which will turn a string into an array and can do so in many different ways. Methods are powerful - many times when you want your script to do something, there's a method for that.", "Functions, like variables, are something you define and create.", @@ -296,7 +296,7 @@ "description": [ "RegExp is not formatted like anything else in JS, and can have a steep learning curve.", "RegExp can also be an incredibly useful and efficient tool.", - "Using RegExp, you can match, replace, search, and split a string, one of the more difficult types of values to manipulate.", + "Using RegExp, you can match, replace, search, and split a string, one of the most difficult types of values to manipulate.", "Like with all of the other videos, we won't get into the nitty gritty, but I want to show you a few examples of where RegExp is useful.", "If you wanted to create a registration page that verified passwords contained at least a number and a capital letter, you could use RegExp.", "If you wanted to ensure that dates entered in a page were all valid dates in the future, you could use RegExp.", From 93b2b1b54153110098a74ff9c67714db464c497b Mon Sep 17 00:00:00 2001 From: Daksh Shah Date: Sun, 29 May 2016 12:47:11 +0300 Subject: [PATCH 10/21] Fix test in Accessing Obj Properties with Vars --- .../basic-javascript.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/seed/challenges/01-front-end-development-certification/basic-javascript.json b/seed/challenges/01-front-end-development-certification/basic-javascript.json index db8ed1a3e4..f1753f0185 100644 --- a/seed/challenges/01-front-end-development-certification/basic-javascript.json +++ b/seed/challenges/01-front-end-development-certification/basic-javascript.json @@ -4002,7 +4002,8 @@ "assert(typeof playerNumber === 'number', 'message: playerNumber should be a number');", "assert(typeof player === 'string', 'message: The variable player should be a string');", "assert(player === 'Montana', 'message: The value of player should be \"Montana\"');", - "assert(/testObj\\s*?\\[\\s*playerNumber\\s*\\]/.test(code),'message: You should use bracket notation to access testObj');" + "assert(/testObj\\s*?\\[.*?\\]/.test(code),'message: You should use bracket notation to access testObj');", + "assert(/testObj\\s*?\\[\\s*playerNumber\\s*\\]/.test(code),'message: You should be using the variable playerNumber in your bracket notation');" ], "type": "waypoint", "challengeType": 1, From 1de8664451fce8fde530d6c04be289d6bf43da90 Mon Sep 17 00:00:00 2001 From: Pranay Berry Date: Sun, 29 May 2016 10:32:32 +0530 Subject: [PATCH 11/21] Updated test case --- .../basic-javascript.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/challenges/01-front-end-development-certification/basic-javascript.json b/seed/challenges/01-front-end-development-certification/basic-javascript.json index db8ed1a3e4..1c8b207fd8 100644 --- a/seed/challenges/01-front-end-development-certification/basic-javascript.json +++ b/seed/challenges/01-front-end-development-certification/basic-javascript.json @@ -643,7 +643,7 @@ ], "tests": [ "assert(remainder === 2, 'message: The value of remainder should be 2');", - "assert(/\\d+\\s*%\\s*\\d+/.test(code), 'message: You should use the % operator');" + "assert(/var\\s*?remainder\\s*?=\\s*?.*%.*;/.test(code), 'message: You should use the % operator');" ], "type": "waypoint", "challengeType": 1, From 856874a7272e2ec2f2eadd307664fb3c9b585409 Mon Sep 17 00:00:00 2001 From: hangaebal Date: Tue, 31 May 2016 11:21:17 +0900 Subject: [PATCH 12/21] Fix Bug when CodeMirror return function object --- client/commonFramework/output-display.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/commonFramework/output-display.js b/client/commonFramework/output-display.js index 9f96908a1f..291e61d7f1 100644 --- a/client/commonFramework/output-display.js +++ b/client/commonFramework/output-display.js @@ -39,6 +39,9 @@ window.common = (function(global) { codeOutput.setSize('100%', '100%'); common.updateOutputDisplay = function updateOutputDisplay(str = '') { + if (typeof str === 'function') { + str = str.toString(); + } if (typeof str !== 'string') { str = JSON.stringify(str); } From 66f2019dbe1da63e777897200848386507edb54b Mon Sep 17 00:00:00 2001 From: Salt7900 Date: Mon, 30 May 2016 22:53:07 -0500 Subject: [PATCH 13/21] Change wording about a number not being positve --- .../01-front-end-development-certification/basic-bonfires.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/challenges/01-front-end-development-certification/basic-bonfires.json b/seed/challenges/01-front-end-development-certification/basic-bonfires.json index 5e1bcee431..7eb7cc5772 100644 --- a/seed/challenges/01-front-end-development-certification/basic-bonfires.json +++ b/seed/challenges/01-front-end-development-certification/basic-bonfires.json @@ -362,7 +362,7 @@ "id": "afcc8d540bea9ea2669306b6", "title": "Repeat a string repeat a string", "description": [ - "Repeat a given string (first argument) num times (second argument). Return an empty string if num is a negative number.", + "Repeat a given string (first argument) num times (second argument). Return an empty string if num is not a positive number.", "Remember to use Read-Search-Ask if you get stuck. Write your own code." ], "challengeSeed": [ From ff4dfb09da2dbd68c43b963770cc1bdafbd311d7 Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra Date: Sat, 7 May 2016 17:46:39 +0530 Subject: [PATCH 14/21] Add email verification and notifications This commit - [x] Fixes the flash notice color (Trivial) - [x] Adds flash message for user with no email. - [x] Adds checks to see if user's email is verified, and displays corresponding notification. - [x] Adds email templates. --- common/models/user.js | 97 ++++++++++--------- server/boot/a-extendUser.js | 23 ++--- server/boot/challenge.js | 8 +- server/boot/user.js | 3 + server/utils/middleware.js | 23 +++++ server/views/account/update-email.jade | 6 +- server/views/emails/a-extend-user-welcome.ejs | 24 +++++ server/views/emails/user-email-verify.ejs | 15 +++ 8 files changed, 135 insertions(+), 64 deletions(-) create mode 100644 server/views/emails/a-extend-user-welcome.ejs create mode 100644 server/views/emails/user-email-verify.ejs diff --git a/common/models/user.js b/common/models/user.js index 4d2def2304..246a2984d1 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -4,6 +4,7 @@ import moment from 'moment'; import dedent from 'dedent'; import debugFactory from 'debug'; import { isEmail } from 'validator'; +import path from 'path'; import { saveUser, observeMethod } from '../../server/utils/rx'; import { blacklistedUsernames } from '../../server/utils/constants'; @@ -89,7 +90,7 @@ module.exports = function(User) { 'You\'re email has been confirmed!' ] }); - ctx.res.redirect('/email-signin'); + ctx.res.redirect('/'); }); User.beforeRemote('create', function({ req, res }, _, next) { @@ -340,7 +341,52 @@ module.exports = function(User) { new Error(`${email} is already associated with another account.`) ); } - return this.update$({ email }).toPromise(); + + const emailVerified = false; + return this.update$({ + email, emailVerified + }) + .do(() => { + this.email = email; + this.emailVerified = emailVerified; + }) + .flatMap(() => { + var mailOptions = { + type: 'email', + to: email, + from: 'Team@freecodecamp.com', + subject: 'Welcome to Free Code Camp!', + template: path.join( + __dirname, + '..', + '..', + 'server', + 'views', + 'emails', + 'user-email-verify.ejs' + ) + }; + return this.verify(mailOptions).then( + (data) => { + if (data) { + return Promise.resolve( + dedent` + Your email has been updated successfully, Please + follow the link we sent you, to confirm. + `); + } + return Promise.reject( + 'Oops, something went wrong, please try again later' + ); + }, + (error) => { + debug(error); + return Promise.reject( + 'Oops, something went wrong, please try again later' + ); + } + ); + }).toPromise(); }); }; @@ -358,8 +404,8 @@ module.exports = function(User) { ], returns: [ { - arg: 'status', - type: 'object' + arg: 'message', + type: 'string' } ], http: { @@ -486,52 +532,11 @@ module.exports = function(User) { } ); - User.prototype.updateEmail = function updateEmail(email) { - if (this.email && this.email === email) { - return Promise.reject(new Error( - `${email} is already associated with this account.` - )); - } - return User.doesExist(null, email) - .then(exists => { - if (exists) { - return Promise.reject( - new Error(`${email} is already associated with another account.`) - ); - } - return this.update$({ email }).toPromise(); - }); - }; - - User.remoteMethod( - 'updateEmail', - { - isStatic: false, - description: 'updates the email of the user object', - accepts: [ - { - arg: 'email', - type: 'string', - required: true - } - ], - returns: [ - { - arg: 'status', - type: 'object' - } - ], - http: { - path: '/update-email', - verb: 'POST' - } - } - ); - User.themes = { night: true, default: true }; + User.prototype.updateTheme = function updateTheme(theme) { if (!this.constructor.themes[theme]) { const err = new Error( diff --git a/server/boot/a-extendUser.js b/server/boot/a-extendUser.js index 932bd7dcdb..9d62c62e0f 100644 --- a/server/boot/a-extendUser.js +++ b/server/boot/a-extendUser.js @@ -1,6 +1,7 @@ import { Observable } from 'rx'; import debugFactory from 'debug'; import { isEmail } from 'validator'; +import path from 'path'; const debug = debugFactory('fcc:user:remote'); @@ -15,7 +16,6 @@ module.exports = function(app) { var User = app.models.User; var UserIdentity = app.models.UserIdentity; var UserCredential = app.models.UserCredential; - var Email = app.models.Email; User.observe('before delete', function(ctx, next) { debug('removing user', ctx.where); var id = ctx.where && ctx.where.id ? ctx.where.id : null; @@ -70,21 +70,18 @@ module.exports = function(app) { to: user.email, from: 'Team@freecodecamp.com', subject: 'Welcome to Free Code Camp!', - redirect: '/', - text: [ - 'Greetings from San Francisco!\n\n', - 'Thank you for joining our community.\n', - 'Feel free to email us at this address if you have ', - 'any questions about Free Code Camp.\n', - 'And if you have a moment, check out our blog: ', - 'medium.freecodecamp.com.\n\n', - 'Good luck with the challenges!\n\n', - '- the Free Code Camp Team' - ].join('') + template: path.join( + __dirname, + '..', + 'views', + 'emails', + 'a-extend-user-welcome.ejs' + ), + redirect: '/' }; debug('sending welcome email'); - return Email.send(mailOptions, function(err) { + return user.verify(mailOptions, function(err) { if (err) { return next(err); } return req.logIn(user, function(err) { if (err) { return next(err); } diff --git a/server/boot/challenge.js b/server/boot/challenge.js index 0325c5248a..380a6c9b03 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -18,7 +18,8 @@ import { import { observeMethod } from '../utils/rx'; import { - ifNoUserSend + ifNoUserSend, + flashIfNotVerified } from '../utils/middleware'; import getFromDisk$ from '../utils/getFromDisk$'; @@ -419,7 +420,10 @@ module.exports = function(app) { redirectToNextChallenge ); - router.get('/challenges/:challengeName', showChallenge); + router.get('/challenges/:challengeName', + flashIfNotVerified, + showChallenge + ); app.use(router); diff --git a/server/boot/user.js b/server/boot/user.js index 685a2aed5c..2391f25cd8 100644 --- a/server/boot/user.js +++ b/server/boot/user.js @@ -19,6 +19,8 @@ import { calcLongestStreak } from '../utils/user-stats'; +import { flashIfNotVerified } from '../utils/middleware'; + const debug = debugFactory('fcc:boot:user'); const sendNonUserToMap = ifNoUserRedirectTo('/map'); const certIds = { @@ -183,6 +185,7 @@ module.exports = function(app) { router.get( '/settings', sendNonUserToMap, + flashIfNotVerified, getSettings ); router.get('/vote1', vote1); diff --git a/server/utils/middleware.js b/server/utils/middleware.js index 458168bac3..51fe4e45ae 100644 --- a/server/utils/middleware.js +++ b/server/utils/middleware.js @@ -1,3 +1,5 @@ +import dedent from 'dedent'; + export function ifNoUserRedirectTo(url, message, type = 'errors') { return function(req, res, next) { const { path } = req; @@ -28,3 +30,24 @@ export function ifNoUser401(req, res, next) { } return res.status(401).end(); } + +export function flashIfNotVerified(req, res, next) { + const user = req.user; + if (!user) { + return next(); + } + const email = req.user.email; + const emailVerified = req.user.emailVerified; + if (!email) { + req.flash('info', { msg: + dedent `Please update your email address when you get a moment in + your Settings Page.` + }); + } else if (!emailVerified) { + req.flash('info', { msg: + dedent `We have your email address with us, but its not yet verified. + Please follow the link we sent you, when you get a moment.` + }); + } + return next(); +} diff --git a/server/views/account/update-email.jade b/server/views/account/update-email.jade index bef48afab9..c7dce563aa 100644 --- a/server/views/account/update-email.jade +++ b/server/views/account/update-email.jade @@ -46,10 +46,10 @@ block content } }) .done(data =>{ - if(data.status && data.status.count){ - $('#flash-content').html("Your email has been updated successfully!"); + if(data && data.message){ + $('#flash-content').html(data.message); $('#flash-board') - .removeClass('alert-danger') + .removeClass('alert-info') .addClass('alert-success') .fadeIn(); } diff --git a/server/views/emails/a-extend-user-welcome.ejs b/server/views/emails/a-extend-user-welcome.ejs new file mode 100644 index 0000000000..be361302e2 --- /dev/null +++ b/server/views/emails/a-extend-user-welcome.ejs @@ -0,0 +1,24 @@ +

+ Greetings from San Francisco! +

+

+ Thank you for joining our community. +

+

+ Please verify your email by following the link below: +

+

+ <%= verifyHref %> +

+

+ Feel free to email us at this address if you have any questions about Free Code Camp. +

+

+ And if you have a moment, check out our blog: https://medium.freecodecamp.com. +

+

+ Good luck with the challenges! +

+

+ - the Free Code Camp Team. +

diff --git a/server/views/emails/user-email-verify.ejs b/server/views/emails/user-email-verify.ejs new file mode 100644 index 0000000000..1df3b84627 --- /dev/null +++ b/server/views/emails/user-email-verify.ejs @@ -0,0 +1,15 @@ +

+ Thank you, for updating you contact details. +

+

+ Please verify your email by following the link below: +

+

+ <%= verifyHref %> +

+

+ Feel free to email us at this address if you have any questions about Free Code Camp. +

+

+ - the Free Code Camp Team. +

From 869191a53f93eb205d4130f5c607c9d013562f1d Mon Sep 17 00:00:00 2001 From: malayaleecoder Date: Wed, 1 Jun 2016 15:54:30 +0530 Subject: [PATCH 15/21] Fix Friendly Date Range issue --- .../advanced-bonfires.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/challenges/01-front-end-development-certification/advanced-bonfires.json b/seed/challenges/01-front-end-development-certification/advanced-bonfires.json index f3cb326de9..d4dfdf0928 100644 --- a/seed/challenges/01-front-end-development-certification/advanced-bonfires.json +++ b/seed/challenges/01-front-end-development-certification/advanced-bonfires.json @@ -269,7 +269,7 @@ "Convert a date range consisting of two dates formatted as YYYY-MM-DD into a more readable format.", "The friendly display should use month names instead of numbers and ordinal dates instead of cardinal (1st instead of 1).", "Do not display information that is redundant or that can be inferred by the user: if the date range ends in less than a year from when it begins, do not display the ending year.", - "Additionally, if the date range begins in the current year and ends within one year, the year should not be displayed at the beginning of the friendly range.", + "Additionally, if the date range begins in the current year (i.e. it is currently the year 2016) and ends within one year, the year should not be displayed at the beginning of the friendly range.", "If the range ends in the same month that it begins, do not display the ending year or month.", "Examples:", "makeFriendlyDates([\"2016-07-01\", \"2016-07-04\"]) should return [\"July 1st\",\"4th\"]", From c8ce78112c3aa13767d397c03582a999f3ef7465 Mon Sep 17 00:00:00 2001 From: Russ Otto Date: Wed, 1 Jun 2016 12:24:29 -0500 Subject: [PATCH 16/21] Fixed issue #7015: Access Multi-Dimensional Arrays with Indexes This commit clarifies the explanation and example for the Basic Javascript challenge "Access Multi-Dimensional Arrays with Indexes." The implemented fix was proposed by @erictleung in https://github.com/FreeCodeCamp/FreeCodeCamp/issues/7015#issuecomment-222345491 --- .../basic-javascript.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/seed/challenges/01-front-end-development-certification/basic-javascript.json b/seed/challenges/01-front-end-development-certification/basic-javascript.json index 40f93ba7e1..94fab3ae13 100644 --- a/seed/challenges/01-front-end-development-certification/basic-javascript.json +++ b/seed/challenges/01-front-end-development-certification/basic-javascript.json @@ -1756,9 +1756,9 @@ "id": "56592a60ddddeae28f7aa8e1", "title": "Access Multi-Dimensional Arrays With Indexes", "description": [ - "One way to think of a multi-dimensional array, is as an array of arrays. When you use brackets to access your array, the first set of bracket refers to the entries in the outer-most array, and each subsequent level of brackets refers to the next level of entries inside.", + "One way to think of a multi-dimensional array, is as an array of arrays. When you use brackets to access your array, the first set of bracket refers to the entries in the outer-most (the first level) array, and each additional pair of brackets refers to the next level of entries inside.", "Example", - "
var arr = [
[1,2,3],
[4,5,6],
[7,8,9],
[[10,11,12], 13, 14]
];
arr[0]; // equals [1,2,3]
arr[1][2]; // equals 6
arr[3][0][1]; // equals 11
", + "
var arr = [
[1,2,3],
[4,5,6],
[7,8,9],
[[10,11,12], 13, 14]
];
arr[3]; // equals [[10,11,12], 13, 14]
arr[3][0]; // equals [10,11,12]
arr[3][0][1]; // equals 11
", "

Instructions

", "Using bracket notation select an element from myArray such that myData is equal to 8." ], From 4a4f71145ccfa699f899f812443849451190a6ab Mon Sep 17 00:00:00 2001 From: "Pietro F. Menna" Date: Wed, 1 Jun 2016 14:37:11 -0300 Subject: [PATCH 17/21] Updated CONTRIBUTING.md wiki links If applied, this commit will update the links from "Other resources" section from CONTRIBUTING.md file with the current wiki links. It will also remove [Contributions Guide - With a demo on fixing a typo] link from "Other resources" section. --- CONTRIBUTING.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8af4c5b33c..cd7cb1341c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -190,11 +190,10 @@ Be sure to post in the PR conversation that you have made the requested changes. ## Other resources -- [Searching for Your Issue on GitHub](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Searching-for-Your-Issue-on-GitHub) +- [Searching for Your Issue on GitHub](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Search-Existing-Issues) - [Creating a New GitHub Issue](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Creating-a-New-GitHub-Issue) -- [Select Issues for Contributing Using Labels](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Select-Issues-for-Contributing-Using-Labels) -- [How to clone the FreeCodeCamp website on a Windows pc](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/How-to-clone-the-FreeCodeCamp-website-on-a-Windows-pc) -- [How to log in to your local FCC site - using GitHub](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/How-To-Log-In-To-Your-Local-FCC-Site) -- [Contributions Guide - With a demo on fixing a typo](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Contributions-Guide---with-Typo-Demo) -- [Writing great git commit message](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Writing-great-git-commit-message) +- [Select Issues for Contributing Using Labels](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/FreeCodeCamp-Issue-Labels) +- [How to clone the FreeCodeCamp website on a Windows pc](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/FreeCodeCamp-Fork-Windows) +- [How to log in to your local FCC site - using GitHub](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/FreeCodeCamp-Log-In-To-Local-Instance) +- [Writing great git commit message](https://github.com/FreeCodeCamp/FreeCodeCamp/wiki/Git-Commit-Message) - [Contributor Chat Support - For the FCC Repositories, and running a local instance] (https://gitter.im/FreeCodeCamp/HelpContributors) From 5fb13fe4251f60520cf2536b8f83d40eba4a4d20 Mon Sep 17 00:00:00 2001 From: Ramon Carroll Date: Wed, 1 Jun 2016 22:23:05 -0500 Subject: [PATCH 18/21] Added further clarification to instructions regarding using templates. --- .../01-front-end-development-certification/basic-ziplines.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/seed/challenges/01-front-end-development-certification/basic-ziplines.json b/seed/challenges/01-front-end-development-certification/basic-ziplines.json index 6b0a610387..f80c162894 100644 --- a/seed/challenges/01-front-end-development-certification/basic-ziplines.json +++ b/seed/challenges/01-front-end-development-certification/basic-ziplines.json @@ -192,7 +192,7 @@ "User Story: I can see thumbnail images of different projects the portfolio creator has built (if you haven't built any websites before, use placeholders.)", "User Story: I navigate to different sections of the webpage by clicking buttons in the navigation.", "Don't worry if you don't have anything to showcase on your portfolio yet - you will build several apps on the next few CodePen challenges, and can come back and update your portfolio later.", - "There are many great portfolio templates out there, but for this challenge, you'll need to build a portfolio page yourself. Using Bootstrap will make this much easier for you.", + "There are many great portfolio templates out there already. However, you should consider building your portfolio page as much as you can from the ground up. Using Bootstrap can help make this process much easier for you.", "Note that CodePen.io overrides the Window.open() function, so if you want to open windows using jQuery, you will need to target invisible anchor elements like this one: <a target='_blank'>.", "Remember to use Read-Search-Ask if you get stuck.", "When you are finished, click the \"I've completed this challenge\" button and include a link to your CodePen. ", From 564f4c118051650aa8868a6c390b5f758774caf7 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Thu, 2 Jun 2016 13:01:32 -0700 Subject: [PATCH 19/21] Update email info copy --- common/models/user.js | 32 +++++-------- server/utils/middleware.js | 15 ++---- server/views/emails/a-extend-user-welcome.ejs | 47 +++++++++---------- server/views/emails/user-email-verify.ejs | 29 ++++++------ 4 files changed, 53 insertions(+), 70 deletions(-) diff --git a/common/models/user.js b/common/models/user.js index 246a2984d1..77d9874df2 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -366,27 +366,19 @@ module.exports = function(User) { 'user-email-verify.ejs' ) }; - return this.verify(mailOptions).then( - (data) => { - if (data) { - return Promise.resolve( - dedent` - Your email has been updated successfully, Please - follow the link we sent you, to confirm. - `); - } - return Promise.reject( - 'Oops, something went wrong, please try again later' - ); - }, - (error) => { - debug(error); - return Promise.reject( - 'Oops, something went wrong, please try again later' - ); - } + return this.verify(mailOptions); + }) + .map(() => dedent` + Please check your email. + We sent you a link that you can click to verify your email address. + `) + .catch(error => { + debug(error); + return Observable.throw( + 'Oops, something went wrong, please try again later' ); - }).toPromise(); + }) + .toPromise(); }); }; diff --git a/server/utils/middleware.js b/server/utils/middleware.js index 51fe4e45ae..7ec17defdf 100644 --- a/server/utils/middleware.js +++ b/server/utils/middleware.js @@ -1,5 +1,3 @@ -import dedent from 'dedent'; - export function ifNoUserRedirectTo(url, message, type = 'errors') { return function(req, res, next) { const { path } = req; @@ -38,15 +36,10 @@ export function flashIfNotVerified(req, res, next) { } const email = req.user.email; const emailVerified = req.user.emailVerified; - if (!email) { - req.flash('info', { msg: - dedent `Please update your email address when you get a moment in - your Settings Page.` - }); - } else if (!emailVerified) { - req.flash('info', { msg: - dedent `We have your email address with us, but its not yet verified. - Please follow the link we sent you, when you get a moment.` + if (!email || !emailVerified) { + req.flash('info', { + msg: 'Please verify your email address ' + + 'here.' }); } return next(); diff --git a/server/views/emails/a-extend-user-welcome.ejs b/server/views/emails/a-extend-user-welcome.ejs index be361302e2..97b0f159cd 100644 --- a/server/views/emails/a-extend-user-welcome.ejs +++ b/server/views/emails/a-extend-user-welcome.ejs @@ -1,24 +1,23 @@ -

- Greetings from San Francisco! -

-

- Thank you for joining our community. -

-

- Please verify your email by following the link below: -

-

- <%= verifyHref %> -

-

- Feel free to email us at this address if you have any questions about Free Code Camp. -

-

- And if you have a moment, check out our blog: https://medium.freecodecamp.com. -

-

- Good luck with the challenges! -

-

- - the Free Code Camp Team. -

+Greetings from San Francisco! +
+
+Thank you for joining our community. +
+
+Please verify your email by following the link below: +
+
+<%= verifyHref %> +
+
+Feel free to email us at this address if you have any questions about Free Code Camp. +
+
+And if you have a moment, check out our blog: https://medium.freecodecamp.com. +
+
+Good luck with the challenges! +
+
+
+- the Free Code Camp Team. diff --git a/server/views/emails/user-email-verify.ejs b/server/views/emails/user-email-verify.ejs index 1df3b84627..6263a89b1f 100644 --- a/server/views/emails/user-email-verify.ejs +++ b/server/views/emails/user-email-verify.ejs @@ -1,15 +1,14 @@ -

- Thank you, for updating you contact details. -

-

- Please verify your email by following the link below: -

-

- <%= verifyHref %> -

-

- Feel free to email us at this address if you have any questions about Free Code Camp. -

-

- - the Free Code Camp Team. -

+Thank you, for updating you contact details. +
+
+Please verify your email by following the link below: +
+
+<%= verifyHref %> +
+
+Feel free to email us at this address if you have any questions about Free Code Camp. +
+
+
+- the Free Code Camp Team. From 942962fa0f95b2873c660b37e4a52d30964cc5ac Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Thu, 2 Jun 2016 13:49:34 -0700 Subject: [PATCH 20/21] Add prod urls to verify emails --- common/models/user.js | 4 ++++ server/boot/a-extendUser.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/common/models/user.js b/common/models/user.js index 77d9874df2..f75dea051a 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -11,6 +11,7 @@ import { blacklistedUsernames } from '../../server/utils/constants'; const debug = debugFactory('fcc:user:remote'); const BROWNIEPOINTS_TIMEOUT = [1, 'hour']; +const isDev = process.env.NODE_ENV !== 'production'; function getAboutProfile({ username, @@ -356,6 +357,9 @@ module.exports = function(User) { to: email, from: 'Team@freecodecamp.com', subject: 'Welcome to Free Code Camp!', + protocol: isDev ? null : 'https', + host: isDev ? 'localhost' : 'freecodecamp.com', + port: isDev ? null : 443, template: path.join( __dirname, '..', diff --git a/server/boot/a-extendUser.js b/server/boot/a-extendUser.js index 9d62c62e0f..3e9dbf0305 100644 --- a/server/boot/a-extendUser.js +++ b/server/boot/a-extendUser.js @@ -4,6 +4,7 @@ import { isEmail } from 'validator'; import path from 'path'; const debug = debugFactory('fcc:user:remote'); +const isDev = process.env.NODE_ENV !== 'production'; function destroyAllRelated(id, Model) { return Observable.fromNodeCallback( @@ -70,6 +71,9 @@ module.exports = function(app) { to: user.email, from: 'Team@freecodecamp.com', subject: 'Welcome to Free Code Camp!', + protocol: isDev ? null : 'https', + host: isDev ? 'localhost' : 'freecodecamp.com', + port: isDev ? null : 443, template: path.join( __dirname, '..', From e29a2a97169176703e8f43f418fc9f0cf6d26a0f Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Thu, 2 Jun 2016 15:23:49 -0700 Subject: [PATCH 21/21] feature(email): add ability to verify current email --- common/models/user.js | 36 ++++++++++++++++++++++---- common/models/user.json | 3 +++ server/views/account/update-email.jade | 3 +-- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/common/models/user.js b/common/models/user.js index f75dea051a..cb3ec80932 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -325,19 +325,42 @@ module.exports = function(User) { ); User.prototype.updateEmail = function updateEmail(email) { + const fiveMinutesAgo = moment().subtract(5, 'minutes'); + const lastEmailSentAt = moment(new Date(this.emailVerifyTTL || null)); + const ownEmail = email === this.email; + const isWaitPeriodOver = this.emailVerifyTTL ? + lastEmailSentAt.isBefore(fiveMinutesAgo) : + true; + if (!isEmail(email)) { return Promise.reject( - new Error('The submitted email not valid') + new Error('The submitted email not valid.') ); } - if (this.email && this.email === email) { + // email is already associated and verified with this account + if (ownEmail && this.emailVerified) { return Promise.reject(new Error( `${email} is already associated with this account.` )); } + + if (ownEmail && !isWaitPeriodOver) { + const minutesLeft = 5 - + (moment().minutes() - lastEmailSentAt.minutes()); + + const timeToWait = minutesLeft ? + `${minutesLeft} minute${minutesLeft > 1 ? 's' : ''}` : + 'a few seconds'; + + return Promise.reject(new Error( + `Please wait ${timeToWait} to resend email verification.` + )); + } + return User.doesExist(null, email) .then(exists => { - if (exists) { + // not associated with this account, but is associated with another + if (!ownEmail && exists) { return Promise.reject( new Error(`${email} is already associated with another account.`) ); @@ -345,11 +368,14 @@ module.exports = function(User) { const emailVerified = false; return this.update$({ - email, emailVerified + email, + emailVerified, + emailVerifyTTL: new Date() }) .do(() => { this.email = email; this.emailVerified = emailVerified; + this.emailVerifyTTL = new Date(); }) .flatMap(() => { var mailOptions = { @@ -379,7 +405,7 @@ module.exports = function(User) { .catch(error => { debug(error); return Observable.throw( - 'Oops, something went wrong, please try again later' + 'Oops, something went wrong, please try again later.' ); }) .toPromise(); diff --git a/common/models/user.json b/common/models/user.json index cdbff24c58..225f58a78c 100644 --- a/common/models/user.json +++ b/common/models/user.json @@ -16,6 +16,9 @@ } } }, + "emailVerifyTTL": { + "type": "date" + }, "password": { "type": "string" }, diff --git a/server/views/account/update-email.jade b/server/views/account/update-email.jade index c7dce563aa..cb45c9d937 100644 --- a/server/views/account/update-email.jade +++ b/server/views/account/update-email.jade @@ -15,8 +15,7 @@ block content .form-group input.input-lg.form-control(type='email', name='email', id='email', value=user.email || '', placeholder=user.email || 'Enter your new email', autofocus, required, autocomplete="off") .form-group - button.btn.btn-lg.btn-primary.btn-block(type='submit') - | Update my Email + button.btn.btn-lg.btn-primary.btn-block(type='submit')= !user.email || user.emailVerified ? 'Update my Email' : 'Verify Email' a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/settings') | Go back to Settings