From 29bade1914d712862705aac7abf78f740c01a7f5 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 25 Apr 2017 20:22:18 -0400 Subject: [PATCH 01/21] Prepared for v0.6.0 release --- README.md | 12 ++++++++---- config/sockets.js | 4 ++-- server.js | 2 +- static/js/settings.js | 3 --- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index cfe5605..6938e9f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Tracman ###### v 0.6.0 -node.js application to display a map with user's location. +node.js application to display a sharable map with user's location. ## Installation @@ -25,15 +25,19 @@ $ npm run nodemon ## Contributing -Tracman will be updated according to [this branching model](http://nvie.com/posts/a-successful-git-branching-model). +Tracman will be updated according to [this branching model](http://nvie.com/posts/a-successful-git-branching-model)... more or less. ## Changelog #### v0.6.0 -* Added more login options -* Replaced some callbacks with promises +* [#32](https://github.com/Tracman-org/Server/issues/32), [#57](https://github.com/Tracman-org/Server/issues/57), [#58](https://github.com/Tracman-org/Server/issues/58), [#60](https://github.com/Tracman-org/Server/issues/60) Added more login options +* [#50](https://github.com/Tracman-org/Server/issues/50) Replaced some callbacks with promises * Minified static files +* [#51](https://github.com/Tracman-org/Server/issues/51), [#52](https://github.com/Tracman-org/Server/issues/52) Added settings validations +* [#54](https://github.com/Tracman-org/Server/issues/54), [#55](https://github.com/Tracman-org/Server/issues/55) Made map work better +* [#61](https://github.com/Tracman-org/Server/issues/61) New MongoDB security +* [#62](https://github.com/Tracman-org/Server/issues/62) Fixed error handling #### v0.5.1 diff --git a/config/sockets.js b/config/sockets.js index 6d0f849..563af44 100644 --- a/config/sockets.js +++ b/config/sockets.js @@ -29,7 +29,7 @@ module.exports = { init: (io)=>{ io.on('connection', (socket)=>{ - // console.log(`${socket.id} connected.`); + //console.log(`${socket.id} connected.`); // Set a few variables socket.ip = socket.client.request.headers['x-real-ip']; @@ -61,7 +61,7 @@ module.exports = { // Set location socket.on('set', (loc)=>{ - // console.log(`${socket.id} set location for ${loc.usr}`); + //console.log(`${socket.id} set location for ${loc.usr}`); loc.time = Date.now(); // Check for user and sk32 token diff --git a/server.js b/server.js index 7beffee..eda5e8c 100755 --- a/server.js +++ b/server.js @@ -79,7 +79,7 @@ const // Path for redirects let nextPath = ( req.path.substring(0, req.path.indexOf('#')) || req.path ); if ( nextPath.substring(0,6)!=='/login' && nextPath.substring(0,7)!=='/logout' ){ - // console.log(`Setting redirect path to ${nextPath}#`); + //console.log(`Setting redirect path to ${nextPath}#`); req.session.next = nextPath+'#'; } diff --git a/static/js/settings.js b/static/js/settings.js index ce3c0a5..931b26d 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -57,9 +57,6 @@ $(function(){ $.get('/validate?'+input+'='+$('#'+input+'-input').val()) .fail(function(data,status){ - console.log(typeof status); - console.log(status); - // Input is not unique if (status===400) { $('#'+input+'-help').show().text("That "+input+" is already in use by another user. "); From 55b3859512d67ab7345c3638d6d2a5aa04eb37b0 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 26 Apr 2017 15:10:07 -0400 Subject: [PATCH 02/21] Removed code from error page title --- config/models.js | 15 +++++++++------ views/error.html | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/config/models.js b/config/models.js index 3da2b55..46ab877 100644 --- a/config/models.js +++ b/config/models.js @@ -47,17 +47,20 @@ const userSchema = new mongoose.Schema({ /* User methods */ { //TODO: Return promises instead of taking callbacks + // See https://gist.github.com/7h1b0/5154fda207e68ad1cefc#file-random-js + // For an example // Create email confirmation token userSchema.methods.createEmailToken = function(next){ var user = this; - crypto.randomBytes(16) - .then( (buf)=>{ - user.emailToken = buf.toString('hex'); - user.save(); - }) - .catch( (err)=>{ next(err,null); }); + crypto.randomBytes(16, (err,buf)=>{ + if (err){ next(err,null); } + if (buf){ + user.emailToken = buf.toString('hex'); + user.save(); + } + }); }; diff --git a/views/error.html b/views/error.html index e4df486..f8c18e0 100644 --- a/views/error.html +++ b/views/error.html @@ -3,7 +3,7 @@ {% block main %}
-

❌️ {% if code %}{{code}} {% endif %}{% if message %}{{message}}{% endif %}

+ {% if message %}

❌️ {{message}}

{% endif %} {% if stack %}

{{stack}}

{% else %} {% if code == '404' %}

This page does not exist. Maybe you followed a dead link here.

{% else %}

Would you please report this error?

{% endif %} From dc11bdd271eb9db751e50c714487bb4f04e4fdf7 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 26 Apr 2017 22:47:23 -0400 Subject: [PATCH 03/21] Added some debug logging --- config/models.js | 9 +++++++-- config/routes/settings.js | 8 +++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/config/models.js b/config/models.js index 46ab877..d82bf14 100644 --- a/config/models.js +++ b/config/models.js @@ -52,13 +52,17 @@ const userSchema = new mongoose.Schema({ // Create email confirmation token userSchema.methods.createEmailToken = function(next){ + // next(err,hash); + console.log('user.createEmailToken() called'); var user = this; crypto.randomBytes(16, (err,buf)=>{ if (err){ next(err,null); } - if (buf){ + if (buf){ + //console.log(`Buffer ${buf.toString('hex')} created`); user.emailToken = buf.toString('hex'); user.save(); + next(null,user.emailToken); } }); @@ -66,11 +70,12 @@ const userSchema = new mongoose.Schema({ // Generate hash for new password userSchema.methods.generateHash = function(password,next){ + // next(err,hash); bcrypt.genSalt(8) .then( (salt)=>{ bcrypt.hash(password, salt, null, next); }) - .catch( (err)=>{ return next(err); }); + .catch( (err)=>{ return next(err,null); }); }; // Create password reset token diff --git a/config/routes/settings.js b/config/routes/settings.js index ae7b4ed..c72dc65 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -30,6 +30,7 @@ router.route('/') .post( (req,res,next)=>{ function setSettings(){ + //console.log('setSettings() called'); // Set values req.user.name = xss(req.body.name); @@ -43,10 +44,12 @@ router.route('/') showAlt: (req.body.showAlt)?true:false, showStreetview: (req.body.showStreet)?true:false }; - + // Save user and send response + //console.log(`Saving new settings for user ${req.user.name}...`); req.user.save() .then( ()=>{ + //console.log(`DONE! Redirecting user...`); req.flash('success', 'Settings updated. '); res.redirect('/settings'); }) @@ -71,9 +74,11 @@ router.route('/') // Email changed if (req.user.email!==req.body.email) { + //console.log(`Email changed to ${req.body.email}`); req.user.newEmail = req.body.email; // Create token + //console.log(`Creating email token...`); req.user.createEmailToken((err,token)=>{ if (err){ mw.throwErr(err,req); @@ -81,6 +86,7 @@ router.route('/') } // Send token to user by email + //console.log(`Mailing new email token to ${req.body.email}...`); mail.send({ to: `"${req.user.name}" <${req.body.email}>`, from: mail.from, From 4421b35ba6329fea65d465bec1572dcd2cffd150 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 26 Apr 2017 23:00:50 -0400 Subject: [PATCH 04/21] Fixed update time/speed/altitude not updating --- static/js/map.js | 55 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/static/js/map.js b/static/js/map.js index 636c486..4dbdcb6 100644 --- a/static/js/map.js +++ b/static/js/map.js @@ -23,7 +23,7 @@ function onConnect(socket,userid,mapuserid) { console.log("🚹 Receiving updates for",mapuserid); // Can set location too - if (mapuserid==userid) { + if (mapuserid===userid) { socket.emit('can-set', userid ); console.log("🚹 Sending updates for",userid); } @@ -75,9 +75,10 @@ $(function() { // Google maps API callback window.gmapsCb = function() { + //console.log("gmapsCb() called"); + // Make sure everything's ready... waitForElements([mapuser,disp,noHeader], function() { - //console.log("gmapsCb() called"); // Create map if (disp!=='1') { @@ -145,8 +146,8 @@ window.gmapsCb = function() { // Create altitude block if (mapuser.settings.showAlt) { //console.log("Creating altitude sign..."); - var elevator = new google.maps.ElevationService; - const altitudeSign = document.createElement('div'), + const elevator = new google.maps.ElevationService, + altitudeSign = document.createElement('div'), altitudeLabel = document.createElement('div'), altitudeText = document.createElement('div'), altitudeUnit = document.createElement('div'); @@ -175,29 +176,53 @@ window.gmapsCb = function() { } }); + }; -// Get location +// Got location socket.on('get', function(loc) { + console.log("🌐️ Got location:",loc.lat+", "+loc.lon); + // Parse location loc = parseLoc(loc); - // Update street view - if (disp!=='0' && mapuser.settings.showStreetview) { - $('.tim').text('location updated '+loc.time); - if (mapuser.settings.showSpeed) { $('.spd').text(loc.spd.toFixed()); } + // Update map + if (disp!=='1') { + + // Update time + $('#timestamp').text('location updated '+loc.time); + + // Show or hide map + toggleMaps(loc); + + // Update marker and map center + map.setCenter({ lat:loc.lat, lng:loc.lon }); + marker.setPosition({ lat:loc.lat, lng:loc.lon }); + + // Update speed + if (mapuser.settings.showSpeed) { + $('#spd').text( loc.spd.toFixed() ); + } + + // Update altitude if (mapuser.settings.showAlt) { - getAltitude({lat:loc.lat,lng:loc.lon}, elevator, function(alt) { - if (alt) { $('.alt').text((mapuser.settings.units=='standard')?(alt*3.28084).toFixed():alt.toFixed()); } + getAltitude({ + lat:loc.lat, + lng:loc.lon + }, elevator, function(alt) { + if (alt) { + $('#alt').text( (mapuser.settings.units=='standard')?(alt*3.28084).toFixed():alt.toFixed() ); + } }); } - toggleMaps(loc); - map.setCenter({lat:loc.lat,lng:loc.lon}); - marker.setPosition({lat:loc.lat,lng:loc.lon}); + + } + + // Update street view + if (disp!=='0' && mapuser.settings.showStreetview) { updateStreetView(loc,10); } - console.log("🌐️ Got location:",loc.lat+", "+loc.lon); }); // Check altitude From 2516708cd03899cc084039276b2d1902f971f0a2 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 26 Apr 2017 23:13:14 -0400 Subject: [PATCH 05/21] Moved environment files to own folder --- .gitignore | 4 ++-- README.md | 11 ++++++++++- config/{env-sample.js => env/sample.js} | 0 config/mail.js | 2 +- config/middleware.js | 2 +- config/passport.js | 2 +- config/routes/auth.js | 2 +- config/routes/map.js | 2 +- config/routes/settings.js | 2 +- server.js | 2 +- 10 files changed, 19 insertions(+), 10 deletions(-) rename config/{env-sample.js => env/sample.js} (100%) diff --git a/.gitignore b/.gitignore index b3dca0c..585443d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,8 @@ node_modules # Secret stuff -config/env* -!config/env-sample.js +config/env/* +!config/env/sample.js # Minified static files (can be built with `npm run minify`) static/**/*.min.* diff --git a/README.md b/README.md index 6938e9f..b443e6e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,16 @@ node.js application to display a sharable map with user's location. $ git clone https://github.com/Tracman-org/Server.git && (cd Server && exec npm install) ``` -You will need to set up a configuration file at `config/env.js`. Use `config/env-sample.js` for an example. You can get API keys at the [google developer's console](https://console.developers.google.com/apis/credentials). You will need to set up approved hosts and auth callbacks. There is more information in [their documentation](https://support.google.com/googleapi/answer/6158857?hl=en). +You will need to set up a configuration file at `config/env/env.js`. Use `config/env/sample.js` for an example. You can get API keys at the [google developer's console](https://console.developers.google.com/apis/credentials). You will need to set up approved hosts and auth callbacks. There is more information in [their documentation](https://support.google.com/googleapi/answer/6158857?hl=en). + +A good method is to simply copy the sample configuration and point `config/env/env.js` to the new version: + +```sh +$ cp config/env/sample.js config/env/my-config.js +$ echo "module.exports = require('./my-config.js');" > config/env/env.js +``` + +Then edit `config/env/my-config.js` to match your local environment. ## Running diff --git a/config/env-sample.js b/config/env/sample.js similarity index 100% rename from config/env-sample.js rename to config/env/sample.js diff --git a/config/mail.js b/config/mail.js index 4f44fa4..fe4a578 100644 --- a/config/mail.js +++ b/config/mail.js @@ -1,7 +1,7 @@ 'use strict'; const nodemailer = require('nodemailer'), - env = require('./env.js'); + env = require('./env/env.js'); let transporter = nodemailer.createTransport({ host: 'keithirwin.us', diff --git a/config/middleware.js b/config/middleware.js index 0c19bc9..12192be 100644 --- a/config/middleware.js +++ b/config/middleware.js @@ -1,6 +1,6 @@ 'use strict'; -const env = require('./env.js'); +const env = require('./env/env.js'); module.exports = { diff --git a/config/passport.js b/config/passport.js index c8f8a67..ccf4c04 100644 --- a/config/passport.js +++ b/config/passport.js @@ -8,7 +8,7 @@ const GoogleTokenStrategy = require('passport-google-id-token'), FacebookTokenStrategy = require('passport-facebook-token'), TwitterTokenStrategy = require('passport-twitter-token'), - env = require('./env.js'), + env = require('./env/env.js'), mw = require('./middleware.js'), User = require('./models.js').user; diff --git a/config/routes/auth.js b/config/routes/auth.js index d0137d0..d40eefb 100644 --- a/config/routes/auth.js +++ b/config/routes/auth.js @@ -5,7 +5,7 @@ const mail = require('../mail.js'), User = require('../models.js').user, crypto = require('crypto'), - env = require('../env.js'); + env = require('../env/env.js'); module.exports = (app, passport) => { diff --git a/config/routes/map.js b/config/routes/map.js index d5a20f1..7c3e8bd 100644 --- a/config/routes/map.js +++ b/config/routes/map.js @@ -3,7 +3,7 @@ const router = require('express').Router(), mw = require('../middleware.js'), - env = require('../env.js'), + env = require('../env/env.js'), User = require('../models.js').user; // Redirect to real slug diff --git a/config/routes/settings.js b/config/routes/settings.js index c72dc65..c3564dd 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -6,7 +6,7 @@ const slug = require('slug'), mw = require('../middleware.js'), User = require('../models.js').user, mail = require('../mail.js'), - env = require('../env.js'), + env = require('../env/env.js'), router = require('express').Router(); // Validate email addresses diff --git a/server.js b/server.js index eda5e8c..7b165ca 100755 --- a/server.js +++ b/server.js @@ -11,7 +11,7 @@ const nunjucks = require('nunjucks'), passport = require('passport'), flash = require('connect-flash-plus'), - env = require('./config/env.js'), + env = require('./config/env/env.js'), User = require('./config/models.js').user, app = express(), http = require('http').Server(app), From 10a8f2c62e3af4837c8f04e02677f8e8951cf4b8 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 26 Apr 2017 23:26:46 -0400 Subject: [PATCH 06/21] Added info to README --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b443e6e..520f541 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +![Logo](https://tracman.org/static/img/icon/by/72.png "The Tracman Logo") # Tracman ###### v 0.6.0 @@ -20,21 +21,25 @@ $ echo "module.exports = require('./my-config.js');" > config/env/env.js Then edit `config/env/my-config.js` to match your local environment. -## Running +## Usage + +Run Tracman with npm: ```sh $ npm run minify && npm start ``` -or, using [nodemon](https://nodemon.io/): +...or with [nodemon](https://nodemon.io/): ```sh $ npm run nodemon ``` +Nodemon will automatically minify files and restart the app when you make changes. Check out the `nodemon.json` configuration. + ## Contributing -Tracman will be updated according to [this branching model](http://nvie.com/posts/a-successful-git-branching-model)... more or less. +Tracman will be updated according to [this branching model](http://nvie.com/posts/a-successful-git-branching-model)... more or less. If you know anything about programming Android, [the Tracman android app](https://github.com/Tracman-org/Android) is more desperate for help. ## Changelog From 77920a0bfc9762529c85bece5f0c0c7660dbe58e Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 26 Apr 2017 23:29:43 -0400 Subject: [PATCH 07/21] Added style to logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 520f541..dddca30 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Logo](https://tracman.org/static/img/icon/by/72.png "The Tracman Logo") +![Logo](https://tracman.org/static/img/icon/by/72.png "The Tracman Logo"){:style="float:left;} # Tracman ###### v 0.6.0 From a353e547f1319607824a4925f78e5a7096fd15db Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 26 Apr 2017 23:30:54 -0400 Subject: [PATCH 08/21] Fixed style to logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dddca30..1b80e11 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Logo](https://tracman.org/static/img/icon/by/72.png "The Tracman Logo"){:style="float:left;} +![Logo](https://tracman.org/static/img/icon/by/72.png "The Tracman Logo"){:style="float:left;"} # Tracman ###### v 0.6.0 From aca939257b8e7cff9460acbfd43335ffc286e88b Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 26 Apr 2017 23:31:53 -0400 Subject: [PATCH 09/21] Fixed style to logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b80e11..a7f37c3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Logo](https://tracman.org/static/img/icon/by/72.png "The Tracman Logo"){:style="float:left;"} +![Logo](https://tracman.org/static/img/icon/by/72.png "The Tracman Logo"){:align="left"} # Tracman ###### v 0.6.0 From d469ae4da20122d6693b889f638db27396321a21 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 26 Apr 2017 23:33:11 -0400 Subject: [PATCH 10/21] Fixed style to logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a7f37c3..10468c6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Logo](https://tracman.org/static/img/icon/by/72.png "The Tracman Logo"){:align="left"} +[] # Tracman ###### v 0.6.0 From c4f9279444e8ace5f9753654f82008d5c4814198 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 26 Apr 2017 23:33:58 -0400 Subject: [PATCH 11/21] Fixed style to logo --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 10468c6..20c145d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -[] -# Tracman +# [] Tracman ###### v 0.6.0 node.js application to display a sharable map with user's location. From 5d38691a8e2db549adc853a180a1b0fa81d89d2d Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 26 Apr 2017 23:34:54 -0400 Subject: [PATCH 12/21] Fixed style to logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 20c145d..efbd416 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# [] Tracman +# [] Tracman ###### v 0.6.0 node.js application to display a sharable map with user's location. From b539b4fb8318adcc3382b28fdc3b239d52b0e4e1 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 26 Apr 2017 23:55:26 -0400 Subject: [PATCH 13/21] Final readme updates --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index efbd416..1daafb0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# [] Tracman +# []Tracman ###### v 0.6.0 node.js application to display a sharable map with user's location. From 0662741cd2af84c56c22bc8c2c4860c2b642f938 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Thu, 27 Apr 2017 00:49:11 -0400 Subject: [PATCH 14/21] Fixed control button font sizes --- static/css/map.css | 106 +++++++++++++++++++++++--------------- static/js/map-controls.js | 4 +- views/map.html | 2 +- 3 files changed, 68 insertions(+), 44 deletions(-) diff --git a/static/css/map.css b/static/css/map.css index c8438eb..cd4e9d0 100644 --- a/static/css/map.css +++ b/static/css/map.css @@ -61,15 +61,56 @@ main { padding: 2%; border-radius: 3px; margin: 3%; -} #spd-sign { +} +#spd-sign { color: #000; background-color: #FFF; border: 2px solid #000; -} #alt-sign { +} +#alt-sign { color: #FFF; background-color: #009800; border: 2px solid #FFF; } +@media (max-width:300px) { + #spd, #alt { + height: 20px; + font-size: 18px; + } + #alt-unit, #spd-unit { + font-size: 8px; + } + #alt-label, #spd-label { + font-size: 9px; + height: 9px; + } +} +@media (min-width:300px) and (max-width:350px) { + #spd, #alt { + height: 22px; + font-size: 20px; + } + #alt-unit, #spd-unit { + font-size: 9px; + } + #alt-label, #spd-label { + font-size: 11px; + height: 11px; + } +} +@media (min-width:350px) and (max-width:400px) { + #spd, #alt { + height: 30px; + font-size: 28px; + } + #alt-unit, #spd-unit { + font-size: 10px; + } + #alt-label, #spd-label { + font-size: 14px; + height: 14px; + } +} @media (min-width:400px) { #spd, #alt { height: 40px; @@ -82,44 +123,9 @@ main { font-size: 18px; height: 18px; } -} @media (min-width:350px) and (max-width:400px) { - #spd, #alt { - height: 30px; - font-size: 28px; - } - #alt-unit, #spd-unit { - font-size: 10px; - } - #alt-label, #spd-label { - font-size: 14px; - height: 14px; - } -} @media (min-width:300px) and (max-width:350px) { - #spd, #alt { - height: 22px; - font-size: 20px; - } - #alt-unit, #spd-unit { - font-size: 9px; - } - #alt-label, #spd-label { - font-size: 11px; - height: 11px; - } -} @media (min-width:0px) and (max-width:300px) { - #spd, #alt { - height: 20px; - font-size: 18px; - } - #alt-unit, #spd-unit { - font-size: 8px; - } - #alt-label, #spd-label { - font-size: 9px; - height: 9px; - } } + /* Control buttons */ #controls { width: 100vw; @@ -127,16 +133,34 @@ main { bottom: 50px; display: flex; justify-content: space-around; -} #controls .btn { +} +#controls .btn { z-index: 50; background: #222; height: 10vh; padding: 2vh 0; -} #controls .btn:hover { +} +#controls .btn .fa { + margin: 0 2vw; +} +#controls .btn:hover { background: #333; } #controls .btn.set, #controls .btn.clear { width: 30vw; -} #controls .btn.track { +} +#controls .btn.track { width: 35vw; +} +@media (max-width:250px) { + #controls .btn { font-size:.8em; } +} +@media (min-width:250px) and (max-width:350px) { + #controls .btn { font-size:1em; } +} +@media (min-width:350px) and (max-width:450px) { + #controls .btn { font-size:1.15em; } +} +@media (min-width:450px) { + #controls .btn { font-size:1.3em; } } \ No newline at end of file diff --git a/static/js/map-controls.js b/static/js/map-controls.js index ef2f4a9..6542466 100644 --- a/static/js/map-controls.js +++ b/static/js/map-controls.js @@ -48,7 +48,7 @@ $(function(){ if (!wpid) { if (!navigator.geolocation) { alert('Unable to track location. '); } else { - $('#track-loc').html(' Stop').prop('title',"Click here to stop tracking your location. "); + $('#track-loc').html('Stop').prop('title',"Click here to stop tracking your location. "); wpid = navigator.geolocation.watchPosition( // Success callback @@ -80,7 +80,7 @@ $(function(){ // Stop tracking else { - $('#track-loc').html(' Track').prop('title',"Click here to track your location. "); + $('#track-loc').html('Track').prop('title',"Click here to track your location. "); navigator.geolocation.clearWatch(wpid); wpid = undefined; } diff --git a/views/map.html b/views/map.html index c2e4daf..890af57 100644 --- a/views/map.html +++ b/views/map.html @@ -66,7 +66,7 @@ {% endif %} - + From a43568a009387a9528c290f8256825d65a237d43 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Thu, 27 Apr 2017 15:52:24 -0400 Subject: [PATCH 15/21] Fixed changing password, added expiration time string, fixed #51 clientside validation --- config/models.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/config/models.js b/config/models.js index d82bf14..e698f04 100644 --- a/config/models.js +++ b/config/models.js @@ -51,8 +51,7 @@ const userSchema = new mongoose.Schema({ // For an example // Create email confirmation token - userSchema.methods.createEmailToken = function(next){ - // next(err,hash); + userSchema.methods.createEmailToken = function(next){// next(err,hash) console.log('user.createEmailToken() called'); var user = this; @@ -62,7 +61,7 @@ const userSchema = new mongoose.Schema({ //console.log(`Buffer ${buf.toString('hex')} created`); user.emailToken = buf.toString('hex'); user.save(); - next(null,user.emailToken); + return next(null,user.emailToken); } }); @@ -96,13 +95,15 @@ const userSchema = new mongoose.Schema({ // Create new token else { - crypto.randomBytes(16) - .then( (buf)=>{ - user.auth.passToken = buf.toString('hex'); - user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour - user.save(); - }) - .catch( (err)=>{ return next(err,null); }); + crypto.randomBytes(16, (err,buf)=>{ + if (err){ return next(err,null); } + if (buf) { + user.auth.passToken = buf.toString('hex'); + user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour + user.save(); + return next(null,user.passToken); + } + }); } }; From 3bdaf1b03493270baa4ec9aba318bd25bb6df6bd Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Thu, 27 Apr 2017 16:44:49 -0400 Subject: [PATCH 16/21] #52 Added server-side uniqueness checks --- config/models.js | 83 ++++++++------- config/routes/index.js | 16 ++- config/routes/map.js | 1 - config/routes/settings.js | 139 ++++++++++++++++++------- config/sockets.js | 17 +-- server.js | 2 +- static/css/settings.css | 1 - static/js/settings.js | 214 +++++++++++++++++++++++--------------- 8 files changed, 302 insertions(+), 171 deletions(-) diff --git a/config/models.js b/config/models.js index e698f04..9edb081 100644 --- a/config/models.js +++ b/config/models.js @@ -51,8 +51,8 @@ const userSchema = new mongoose.Schema({ // For an example // Create email confirmation token - userSchema.methods.createEmailToken = function(next){// next(err,hash) - console.log('user.createEmailToken() called'); + userSchema.methods.createEmailToken = function(next){ // next(err,token) + //console.log('user.createEmailToken() called'); var user = this; crypto.randomBytes(16, (err,buf)=>{ @@ -60,12 +60,56 @@ const userSchema = new mongoose.Schema({ if (buf){ //console.log(`Buffer ${buf.toString('hex')} created`); user.emailToken = buf.toString('hex'); - user.save(); - return next(null,user.emailToken); + user.save() + .then( ()=>{ + return next(null,user.emailToken); + }) + .catch( (err)=>{ + return next(err,null); + }); + } }); }; + + // Create password reset token + userSchema.methods.createPassToken = function(next){ // next(err,token,expires) + var user = this; + + // Reuse old token, resetting clock + if ( user.auth.passTokenExpires >= Date.now() ){ + console.log(`Reusing old password token...`); + user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour + user.save() + .then( ()=>{ + return next(null,user.auth.passToken,user.auth.passTokenExpires); + }) + .catch( (err)=>{ + return next(err,null,null); + }); + } + + // Create new token + else { + console.log(`Creating new password token...`); + crypto.randomBytes(16, (err,buf)=>{ + if (err){ return next(err,null,null); } + if (buf) { + user.auth.passToken = buf.toString('hex'); + user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour + user.save() + .then( ()=>{ + return next(null,user.auth.passToken,user.auth.passTokenExpires); + }) + .catch( (err)=>{ + return next(err,null,null); + }); + } + }); + } + + }; // Generate hash for new password userSchema.methods.generateHash = function(password,next){ @@ -77,37 +121,6 @@ const userSchema = new mongoose.Schema({ .catch( (err)=>{ return next(err,null); }); }; - // Create password reset token - userSchema.methods.createPassToken = function(next){ - var user = this; - - // Reuse old token, resetting clock - if ( user.auth.passTokenExpires <= Date.now() ){ - user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour - user.save() - .then( ()=>{ - return next(null,user.auth.passToken); - }) - .catch( (err)=>{ - return next(err,user.auth.passToken); - }); - } - - // Create new token - else { - crypto.randomBytes(16, (err,buf)=>{ - if (err){ return next(err,null); } - if (buf) { - user.auth.passToken = buf.toString('hex'); - user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour - user.save(); - return next(null,user.passToken); - } - }); - } - - }; - // Check for valid password userSchema.methods.validPassword = function(password,next){ bcrypt.compare(password, this.auth.password, next); diff --git a/config/routes/index.js b/config/routes/index.js index 4f4f589..285dd6a 100644 --- a/config/routes/index.js +++ b/config/routes/index.js @@ -40,7 +40,7 @@ module.exports = router }) // Endpoint to validate forms - .get('/validate', (req,res)=>{ + .get('/validate', (req,res,next)=>{ // Validate unique slug if (req.query.slug) { @@ -51,7 +51,10 @@ module.exports = router } else { res.sendStatus(200); } }) - .catch( (err)=>{ mw.throwErr(err,req); }); + .catch( (err)=>{ + console.error(err); + res.sendStatus(500); + }); } // Validate unique email @@ -63,7 +66,10 @@ module.exports = router } else { res.sendStatus(200); } }) - .catch( (err)=>{ mw.throwErr(err,req); }); + .catch( (err)=>{ + console.error(err); + res.sendStatus(500); + }); } // Create slug @@ -71,10 +77,14 @@ module.exports = router res.send(slug(xss(req.query.slugify))); } + // Sanitize for XSS else if (req.query.xss) { res.send(xss(req.query.xss)); } + // 404 + else { next(); } + }) // Link to androidapp in play store diff --git a/config/routes/map.js b/config/routes/map.js index 7c3e8bd..3c657b3 100644 --- a/config/routes/map.js +++ b/config/routes/map.js @@ -1,5 +1,4 @@ 'use strict'; -//TODO: Use promises const router = require('express').Router(), mw = require('../middleware.js'), diff --git a/config/routes/settings.js b/config/routes/settings.js index c3564dd..5ce7f6c 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -3,6 +3,7 @@ const slug = require('slug'), xss = require('xss'), mellt = require('mellt'), + moment = require('moment'), mw = require('../middleware.js'), User = require('../models.js').user, mail = require('../mail.js'), @@ -34,7 +35,6 @@ router.route('/') // Set values req.user.name = xss(req.body.name); - req.user.slug = slug(xss(req.body.slug)); req.user.settings = { units: req.body.units, defaultMap: req.body.map, @@ -72,41 +72,101 @@ router.route('/') else { - // Email changed - if (req.user.email!==req.body.email) { - //console.log(`Email changed to ${req.body.email}`); - req.user.newEmail = req.body.email; + // Check if email changed + let checkEmailChanged = new Promise( (resolve,reject)=>{ - // Create token - //console.log(`Creating email token...`); - req.user.createEmailToken((err,token)=>{ - if (err){ - mw.throwErr(err,req); - res.redirect(req.session.next||'/settings'); - } + // Email changed + if (req.user.email!==req.body.email) { + //console.log(`Email changed to ${req.body.email}`); - // Send token to user by email - //console.log(`Mailing new email token to ${req.body.email}...`); - mail.send({ - to: `"${req.user.name}" <${req.body.email}>`, - from: mail.from, - subject: 'Confirm your new email address for Tracman', - text: mail.text(`A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it. \n\nTo confirm your email, follow this link:\n${env.url}/settings/email/${token}. `), - html: mail.html(`

A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it.

To confirm your email, follow this link:
${env.url}/settings/email/${token}.

`) + // Check uniqueness + User.findOne({ email: req.body.email }) + .then( (existingUser)=>{ + + // Not unique! + if (existingUser && existingUser.id!==req.user.id) { + //console.log("Email not unique!"); + req.flash('warning', `That email, ${req.body.email}, is already in use by another user! `); + } + + // It's unique + else { + //console.log("Email is unique"); + req.user.newEmail = req.body.email; + + // Create token + //console.log(`Creating email token...`); + req.user.createEmailToken((err,token)=>{ + if (err){ reject(err); } + + // Send token to user by email + //console.log(`Mailing new email token to ${req.body.email}...`); + mail.send({ + to: `"${req.user.name}" <${req.body.email}>`, + from: mail.from, + subject: 'Confirm your new email address for Tracman', + text: mail.text(`A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it. \n\nTo confirm your email, follow this link:\n${env.url}/settings/email/${token}. `), + html: mail.html(`

A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it.

To confirm your email, follow this link:
${env.url}/settings/email/${token}.

`) + }) + .then( ()=>{ + req.flash('warning',`An email has been sent to ${req.body.email}. Check your inbox to confirm your new email address. `); + }) + .catch( (err)=>{ + reject(err); + }); + + }); + + } + }) - .then( ()=>{ - req.flash('warning',`An email has been sent to ${req.body.email}. Check your inbox to confirm your new email address. `); - setSettings(); - }) - .catch( (err)=>{ + .then(resolve) + .catch( (err)=>{ mw.throwErr(err,req); + res.redirect('/settings'); }); - }); - } + } else { resolve(); } + }); - // Email not changed - else { setSettings(); } + // Check if slug changed + let checkSlugChanged = new Promise( (resolve,reject)=>{ + + // Slug changed + if (req.user.slug!==req.body.slug) { + + // Check uniqueness + User.findOne({ slug: req.body.slug }) + .then( (existingUser)=>{ + + // Not unique! + if (existingUser && existingUser.id!==req.user.id) { + req.flash('warning', `That slug, ${req.body.slug}, is already in use by another user! `); + } + + // It's unique + else { + req.user.slug = slug(xss(req.body.slug)); + } + + }) + .then(resolve) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/settings'); + }); + + } else { resolve(); } + + }); + + // Set settings when done + Promise.all([checkEmailChanged, checkSlugChanged]) + .then(setSettings) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/settings'); + }); } @@ -115,8 +175,6 @@ router.route('/') // Delete user account .delete( (req,res,next)=>{ - //TODO: Reenter password? - User.findByIdAndRemove(req.user) .then( ()=>{ req.flash('success', 'Your account has been deleted. '); @@ -174,28 +232,33 @@ router.route('/password') .get( (req,res,next)=>{ // Create token for password change - req.user.createPassToken( (err,token)=>{ + req.user.createPassToken( (err,token,expires)=>{ if (err){ mw.throwErr(err,req); - res.redirect(req.session.next||'/settings'); + res.redirect((req.user)?'/settings':'/login'); } + // Figure out expiration time + let expirationTimeString = (req.query.tz)? + moment(expires).utcOffset(req.query.tz).toDate().toLocaleTimeString(req.acceptsLanguages[0]): + moment(expires).toDate().toLocaleTimeString(req.acceptsLanguages[0])+" UTC"; + // Confirm password change request by email. mail.send({ to: mail.to(req.user), from: mail.from, subject: 'Request to change your Tracman password', - text: mail.text(`A request has been made to change your tracman password. If you did not initiate this request, please contact support at keith@tracman.org. \n\nTo change your password, follow this link:\n${env.url}/settings/password/${token}. \n\nThis request will expire in 1 hour. `), - html: mail.html(`

A request has been made to change your tracman password. If you did not initiate this request, please contact support at keith@tracman.org.

To change your password, follow this link:
${env.url}/settings/password/${token}.

This request will expire in 1 hour.

`) + text: mail.text(`A request has been made to change your tracman password. If you did not initiate this request, please contact support at keith@tracman.org. \n\nTo change your password, follow this link:\n${env.url}/settings/password/${token}. \n\nThis request will expire at ${expirationTimeString}. `), + html: mail.html(`

A request has been made to change your tracman password. If you did not initiate this request, please contact support at keith@tracman.org.

To change your password, follow this link:
${env.url}/settings/password/${token}.

This request will expire at ${expirationTimeString}.

`) }) .then( ()=>{ // Alert user to check email. - req.flash('success',`An email has been sent to ${req.user.email}. Check your inbox to complete your password change. `); - res.redirect('/login#login'); + req.flash('success',`An link has been sent to ${req.user.email}. Click on the link to complete your password change. This link will expire in one hour (${expirationTimeString}). `); + res.redirect((req.user)?'/settings':'/login'); }) .catch( (err)=>{ mw.throwErr(err,req); - res.redirect('/login#login'); + res.redirect((req.user)?'/settings':'/login'); }); }); diff --git a/config/sockets.js b/config/sockets.js index 563af44..1150edf 100644 --- a/config/sockets.js +++ b/config/sockets.js @@ -1,8 +1,7 @@ 'use strict'; // Imports -const mw = require('./middleware.js'), - User = require('./models.js').user; +const User = require('./models.js').user; // Check for tracking clients function checkForUsers(io, user) { @@ -10,10 +9,14 @@ function checkForUsers(io, user) { // Checks if any sockets are getting updates for this user //TODO: Use Object.values() after upgrading to node v7 - if (Object.keys(io.sockets.connected).map( (id)=>{ - return io.sockets.connected[id]; + /* if (Object.values(io.sockets.connected).some( (socket)=>{ + * return socket.gets==user; + * })) { + */ + if (Object.keys(io.sockets.connected).map( (key)=>{ + return io.sockets.connected[key]; }).some( (socket)=>{ - return socket.gets==user; + return socket.gets===user; })) { //console.log(`Activating updates for ${user}.`); io.to(user).emit('activate','true'); @@ -32,8 +35,8 @@ module.exports = { //console.log(`${socket.id} connected.`); // Set a few variables - socket.ip = socket.client.request.headers['x-real-ip']; - socket.ua = socket.client.request.headers['user-agent']; + //socket.ip = socket.client.request.headers['x-real-ip']; + //socket.ua = socket.client.request.headers['user-agent']; /* Log */ //socket.on('log', (text)=>{ diff --git a/server.js b/server.js index 7b165ca..40b33c9 100755 --- a/server.js +++ b/server.js @@ -79,8 +79,8 @@ const // Path for redirects let nextPath = ( req.path.substring(0, req.path.indexOf('#')) || req.path ); if ( nextPath.substring(0,6)!=='/login' && nextPath.substring(0,7)!=='/logout' ){ - //console.log(`Setting redirect path to ${nextPath}#`); req.session.next = nextPath+'#'; + //console.log(`Set redirect path to ${nextPath}#`); } // User account diff --git a/static/css/settings.css b/static/css/settings.css index 6206095..50c4a5f 100644 --- a/static/css/settings.css +++ b/static/css/settings.css @@ -54,7 +54,6 @@ /* Submit buttons */ #submit-group { - padding: 0 0 60px; justify-content: space-around; } #submit-group .main { diff --git a/static/js/settings.js b/static/js/settings.js index 931b26d..77be716 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -8,7 +8,7 @@ function validateEmail(email) { } // Replace inputed value with response -function validateFromEndpoint(type, selector, cb) { +function replaceFromEndpoint(type, selector, cb) { $.get('/validate?'+type+'='+$(selector).val()) .done(function(data){ $(selector).val(data); @@ -18,95 +18,17 @@ function validateFromEndpoint(type, selector, cb) { // On page load $(function(){ - - function validateForm(input) { - - // Everything passed - make sure no help texts are visible - function recheckInputs() { - if ($('#email-help').is(":visible")) { - $('#submit-group .main').prop('disabled',true).prop('title',"You need to supply a different email address. "); - } - else if ($('#slug-help').is(":visible")) { - $('#submit-group .main').prop('disabled',true).prop('title',"You need to supply a different slug. "); - } - else { - $('#submit-group .main').prop('disabled',false).prop('title',"Click here to save your changes. "); - } - } - - // Empty fields - if ($('#slug-input').val()===''){ - $('#slug-help').show().text("A slug is required. "); - $('#submit-group .main').prop('disabled',true).prop('title',"You need to enter a slug. "); - } - else if ($('#email-input').val()===''){ - $('#email-help').show().text("An email is required. "); - $('#submit-group .main').prop('disabled',true).prop('title',"You need to enter an email address. "); - } - - // Is email - else if (!validateEmail($('#email-input').val())) { - $('#email-help').show().text("You must enter a valid email address. "); - $('#submit-group .main').prop('disabled',true).prop('title',"You need to enter a valid email address. "); - } - - // Validate unique fields with server - else if (input) { - - // Make AJAX request - $.get('/validate?'+input+'='+$('#'+input+'-input').val()) - .fail(function(data,status){ - - // Input is not unique - if (status===400) { - $('#'+input+'-help').show().text("That "+input+" is already in use by another user. "); - $('#submit-group .main').prop('disabled',true).prop('title',"You need to supply a different "+input+". "); - } - - // Server error - else { - $('#'+input+'-help').show().text("Unable to confirm unique "+input+" with the server. This might not work... "); - recheckInputs(); - } - - }) - - // Input is unique - .done(function(){ - $('#'+input+'-help').hide(); - recheckInputs(); - }); - - } - - // All passed - else { recheckInputs(); } - - } - - // Validate slug - $('#slug-input').change(function(){ - validateFromEndpoint('slugify','#slug-input',function(){ - validateForm(slug); - }); - }); - - // Validate email - $('#email-input').change(function(){ - validateForm('email'); - }); - - // Validate name - $('#name-input').change(function(){ - validateFromEndpoint('xss','#name-input',validateForm); - }); + var slugNotUnique, emailNotUnique; + // Set timezone in password change link + $('#password').attr('href',"/settings/password?tz="+new Date().getTimezoneOffset()); + // Delete account $('#delete').click(function(){ if (confirm("Are you sure you want to delete your account? This CANNOT be undone! ")) { $.ajax({ - url: "/settings", - type: "DELETE", + url: '/settings', + type: 'DELETE', success: function(){ location.reload(); }, @@ -117,4 +39,126 @@ $(function(){ } }); + function validateForm(input) { + + // Perform basic check, then validate uniqueness + basicCheck(function(){ validateUniqueness(input); }); + + function basicCheck(cb){ + var checkedCount = 0; + + // Check slug + if (!$('#slug-input').val()){ + $('#slug-help').show().text("A slug is required. "); + $('#submit-group .main').prop('disabled',true).prop('title',"You need to enter a slug. "); + if (checkedCount>0) {cb();} else {checkedCount++;} + } + else { + if (!slugNotUnique){ $('#slug-help').hide(); } + if (checkedCount>0) {cb();} else {checkedCount++;} + } + + // Check email + if (!$('#email-input').val()){ + $('#email-help').show().text("An email is required. "); + $('#submit-group .main').prop('disabled',true).prop('title',"You need to enter an email address. "); + if (checkedCount>0) {cb();} else {checkedCount++;} + } + else if (!validateEmail($('#email-input').val())) { + $('#email-help').show().text("You must enter a valid email address. "); + $('#submit-group .main').prop('disabled',true).prop('title',"You need to enter a valid email address. "); + if (checkedCount>0) {cb();} else {checkedCount++;} + } + else { + if (!emailNotUnique){ $('#email-help').hide(); } + if (checkedCount>0) {cb();} else {checkedCount++;} + } + } + + function validateUniqueness(input){ + + function recheckBasic(){ + if ($('#email-help').is(":visible") && $('#email-help').text().substring(0,25)!=="Unable to confirm unique ") { + $('#submit-group .main').prop('disabled',true).prop('title',"You need to supply a different email address. "); + } + else if ($('#slug-help').is(":visible") && $('#slug-help').text().substring(0,25)!=="Unable to confirm unique ") { + $('#submit-group .main').prop('disabled',true).prop('title',"You need to supply a different slug. "); + } + else if ( $('#slug-help').text().substring(0,25)==="Unable to confirm unique " ) { + $('#submit-group .main').prop('title',"Unable to confirm unique slug with the server. This might not work... "); + } + else if ( $('#email-help').text().substring(0,25)==="Unable to confirm unique " ) { + $('#submit-group .main').prop('title',"Unable to confirm unique email with the server. This might not work... "); + } + else { + $('#submit-group .main').prop('disabled',false).prop('title',"Click here to save your changes. "); + } + } + + // Should server be queried for unique values? + if (input && $('#'+input+'-input').val()) { + if (input==='email' && !validateEmail($('#email-input').val())) {} + + // Query server for unique values + else { + $.ajax({ + url: '/validate?'+input+'='+$('#'+input+'-input').val(), + type: 'GET', + statusCode: { + + // Is unique + 200: function(){ + $('#'+input+'-help').hide(); + if (input==='slug'){ slugNotUnique=false; } + else if (input==='email'){ emailNotUnique=false; } + recheckBasic(); + }, + + // Isn't unique + 400: function(){ + if (input==='slug'){ slugNotUnique=true; } + else if (input==='email'){ emailNotUnique=true; } + $('#'+input+'-help').show().text("That "+input+" is already in use by another user. "); + $('#submit-group .main').prop('disabled',true).prop('title',"You need to supply a different "+input+". "); + } + + } }) + + // Server error + .error( function(){ + if (input==='slug'){ slugNotUnique=undefined; } + else if (input==='email'){ emailNotUnique=undefined; } + $('#'+input+'-help').show().text("Unable to confirm unique "+input+". This might not work... "); + recheckBasic(); + }); + + } } + + // Nothing changed. Recheck basic validations + else { recheckBasic(); } + + } + + } + + // Input change listeners + $('#slug-input').change(function(){ + if (!$('#slug-input').val()){ + $('#slug-help').show().text("A slug is required. "); + $('#submit-group .main').prop('disabled',true).prop('title',"You need to enter a slug. "); + } + else { + $('#slug-help').hide(); + replaceFromEndpoint('slugify','#slug-input',function(){ + validateForm('slug'); + }); + } + }); + $('#email-input').change(function(){ + validateForm('email'); + }); + $('#name-input').change(function(){ + replaceFromEndpoint('xss','#name-input',validateForm); + }); + }); From 1216127add9575dfc9507f1e51bc0dbb144c4627 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Thu, 27 Apr 2017 17:25:16 -0400 Subject: [PATCH 17/21] Fixed promises --- config/routes/auth.js | 4 +- config/routes/settings.js | 219 +++++++++++++++++++------------------- 2 files changed, 109 insertions(+), 114 deletions(-) diff --git a/config/routes/auth.js b/config/routes/auth.js index d40eefb..f028cde 100644 --- a/config/routes/auth.js +++ b/config/routes/auth.js @@ -115,7 +115,7 @@ module.exports = (app, passport) => { user.slug = slug(user.email.substring(0, user.email.indexOf('@'))); // Generate unique slug - let slug = new Promise((resolve,reject) => { + const slug = new Promise((resolve,reject) => { (function checkSlug(s,cb){ User.findOne({slug:s}) @@ -150,7 +150,7 @@ module.exports = (app, passport) => { }); // Generate sk32 - let sk32 = new Promise((resolve,reject) => { + const sk32 = new Promise((resolve,reject) => { crypto.randomBytes(32) .then( (buf)=>{ user.sk32 = buf.toString('hex'); diff --git a/config/routes/settings.js b/config/routes/settings.js index 5ce7f6c..ad5d147 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -30,8 +30,109 @@ router.route('/') // Set new settings .post( (req,res,next)=>{ - function setSettings(){ - //console.log('setSettings() called'); + // Validate email + const checkEmail = new Promise( (resolve,reject)=>{ + + // Check validity + if (!validateEmail(req.body.email)) { + req.flash('warning', `${req.body.email} is not a valid email address. `); + resolve(); + } + + // Check if unchanged + else if (req.user.email===req.body.email) { + resolve(); + } + + // Check uniqueness + else { + User.findOne({ email: req.body.email }) + .then( (existingUser)=>{ + + // Not unique! + if (existingUser && existingUser.id!==req.user.id) { + //console.log("Email not unique!"); + req.flash('warning', `That email, ${req.body.email}, is already in use by another user! `); + resolve(); + } + + // It's unique + else { + //console.log("Email is unique"); + req.user.newEmail = req.body.email; + + // Create token + //console.log(`Creating email token...`); + req.user.createEmailToken((err,token)=>{ + if (err){ reject(err); } + + // Send token to user by email + //console.log(`Mailing new email token to ${req.body.email}...`); + mail.send({ + to: `"${req.user.name}" <${req.body.email}>`, + from: mail.from, + subject: 'Confirm your new email address for Tracman', + text: mail.text(`A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it. \n\nTo confirm your email, follow this link:\n${env.url}/settings/email/${token}. `), + html: mail.html(`

A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it.

To confirm your email, follow this link:
${env.url}/settings/email/${token}.

`) + }) + .then( ()=>{ + req.flash('warning',`An email has been sent to ${req.body.email}. Check your inbox to confirm your new email address. `); + resolve(); + }) + .catch(reject); + + }); + + } + + }) + .catch(reject); + } + + }); + + // Validate slug + const checkSlug = new Promise( (resolve,reject)=>{ + + // Check existence + if (req.body.slug==='') { + req.flash('warning', `You must supply a slug. `); + resolve(); + } + + // Check if unchanged + else if (req.user.slug===slug(xss(req.body.slug))) { + resolve(); + } + + // Check uniqueness + else { + + User.findOne({ slug: req.body.slug }) + .then( (existingUser)=>{ + + // Not unique! + if (existingUser && existingUser.id!==req.user.id) { + req.flash('warning', `That slug, ${req.body.slug}, is already in use by another user! `); + } + + // It's unique + else { + req.user.slug = slug(xss(req.body.slug)); + } + + }) + .then(resolve) + .catch(reject); + + } + + }); + + // Set settings when done + Promise.all([checkEmail, checkSlug]) + .then( ()=>{ + //console.log('Setting settings... '); // Set values req.user.name = xss(req.body.name); @@ -58,117 +159,11 @@ router.route('/') res.redirect('/settings'); }); - } - - // Validations - if (req.body.slug==='') { - req.flash('warning', `You must supply a slug. `); + }) + .catch( (err)=>{ + mw.throwErr(err,req); res.redirect('/settings'); - } - else if (!validateEmail(req.body.email)) { - req.flash('warning', `${req.body.email} is not a valid email address. `); - res.redirect('/settings'); - } - - else { - - // Check if email changed - let checkEmailChanged = new Promise( (resolve,reject)=>{ - - // Email changed - if (req.user.email!==req.body.email) { - //console.log(`Email changed to ${req.body.email}`); - - // Check uniqueness - User.findOne({ email: req.body.email }) - .then( (existingUser)=>{ - - // Not unique! - if (existingUser && existingUser.id!==req.user.id) { - //console.log("Email not unique!"); - req.flash('warning', `That email, ${req.body.email}, is already in use by another user! `); - } - - // It's unique - else { - //console.log("Email is unique"); - req.user.newEmail = req.body.email; - - // Create token - //console.log(`Creating email token...`); - req.user.createEmailToken((err,token)=>{ - if (err){ reject(err); } - - // Send token to user by email - //console.log(`Mailing new email token to ${req.body.email}...`); - mail.send({ - to: `"${req.user.name}" <${req.body.email}>`, - from: mail.from, - subject: 'Confirm your new email address for Tracman', - text: mail.text(`A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it. \n\nTo confirm your email, follow this link:\n${env.url}/settings/email/${token}. `), - html: mail.html(`

A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it.

To confirm your email, follow this link:
${env.url}/settings/email/${token}.

`) - }) - .then( ()=>{ - req.flash('warning',`An email has been sent to ${req.body.email}. Check your inbox to confirm your new email address. `); - }) - .catch( (err)=>{ - reject(err); - }); - - }); - - } - - }) - .then(resolve) - .catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/settings'); - }); - - } else { resolve(); } - }); - - // Check if slug changed - let checkSlugChanged = new Promise( (resolve,reject)=>{ - - // Slug changed - if (req.user.slug!==req.body.slug) { - - // Check uniqueness - User.findOne({ slug: req.body.slug }) - .then( (existingUser)=>{ - - // Not unique! - if (existingUser && existingUser.id!==req.user.id) { - req.flash('warning', `That slug, ${req.body.slug}, is already in use by another user! `); - } - - // It's unique - else { - req.user.slug = slug(xss(req.body.slug)); - } - - }) - .then(resolve) - .catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/settings'); - }); - - } else { resolve(); } - - }); - - // Set settings when done - Promise.all([checkEmailChanged, checkSlugChanged]) - .then(setSettings) - .catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/settings'); - }); - - } + }); } ) From dc2a833c20c5bb10b5b3f7818e6bb473c53a5f07 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 28 Apr 2017 03:21:16 -0400 Subject: [PATCH 18/21] Login not needed to view /help --- config/routes/index.js | 2 +- views/templates/header.html | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config/routes/index.js b/config/routes/index.js index 285dd6a..44fa002 100644 --- a/config/routes/index.js +++ b/config/routes/index.js @@ -14,7 +14,7 @@ module.exports = router }) // Help - .get('/help', mw.ensureAuth, (req,res)=>{ + .get('/help', (req,res)=>{ res.render('help'); }) diff --git a/views/templates/header.html b/views/templates/header.html index cf46b34..d2c6a1a 100644 --- a/views/templates/header.html +++ b/views/templates/header.html @@ -16,10 +16,12 @@
  • Map
  • Settings
  • {% if user.isAdmin %}
  • Admin
  • {% endif %} + {% endif %} +
  • About
  • Help
  • + {% if user %}
  • Logout
  • {% else %} -
  • About
  • Demo
  • Login
  • Join
  • From 692f647143b8f7a82af1bd39bfb3bb9d2c989585 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 28 Apr 2017 03:24:03 -0400 Subject: [PATCH 19/21] Fixed margins, rearranged base.css --- static/css/base.css | 108 ++++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/static/css/base.css b/static/css/base.css index de43f40..83e4d07 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -31,60 +31,6 @@ body { } /* Elements */ -h1, h2, h3 { - margin: 0 0 5% 0; - position: relative; - z-index: 6; -} -h1, h2, h3, h4 { font-weight: 600; } -h1 { - font-size: 48px; - line-height: 46px; } -h2 { - font-size: 40px; - line-height: 36px; } -h3 { font-size: 28px; } -h4 { font-size: 20px; } -p { - margin-top: 0; - margin-bottom: 10vh; -} - -a { - color: #fbc93d; - text-decoration: none; -} -main a:hover:not(.btn) { - color: #fbc93d; - text-decoration: underline; -} -a.underline { - text-decoration: underline; -} -a.underline:hover:not(.btn) { - text-decoration: none; -} - -hr { - width: 90%; - margin: 10% auto; -} -img { - max-width: 100%; -} -p img { - display: block; - margin: auto; -} -pre { - white-space: pre-wrap; - white-space: -moz-pre-wrap; - white-space: -pre-wrap; - white-space: -o-pre-wrap; - word-wrap: break-word; -} - - main { top: 59px; position: absolute; @@ -108,6 +54,60 @@ section { padding: 10vh 0 5vh; } +h1, h2, h3 { + margin: 0 0 5% 0; + position: relative; + z-index: 6; +} +/* Font sizes */ +h1, h2, h3, h4 { font-weight: 600; } +h1 { + font-size: 48px; + line-height: 46px; } +h2 { + font-size: 40px; + line-height: 36px; } +h3 { font-size: 28px; } +h4 { font-size: 20px; } + +p, main ul { + margin-top: 0; + margin-bottom: 10vh; +} +hr { + width: 90%; + margin: 10% auto; +} +img { + max-width: 100%; +} +p img { + display: block; + margin: auto; +} +pre { + white-space: pre-wrap; + white-space: -moz-pre-wrap; + white-space: -pre-wrap; + white-space: -o-pre-wrap; + word-wrap: break-word; +} + +a { + color: #fbc93d; + text-decoration: none; +} +main a:hover:not(.btn) { + color: #fbc93d; + text-decoration: underline; +} +a.underline { + text-decoration: underline; +} +a.underline:hover:not(.btn) { + text-decoration: none; +} + /* Modifiers */ .hide { display: none !important; } .red, .red:hover { color: #fb6e3d !important; } From a6e18ed0e3c5da922b992d73bdbaeb7cca9cadea Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 28 Apr 2017 03:26:09 -0400 Subject: [PATCH 20/21] Removed map button --- views/help.html | 2 -- 1 file changed, 2 deletions(-) diff --git a/views/help.html b/views/help.html index 051f87e..dc4b398 100644 --- a/views/help.html +++ b/views/help.html @@ -96,7 +96,5 @@
  • iOS apps can only be built using a mac.
  • - Go to map -
    {% endblock %} \ No newline at end of file From ae22b3f59aae02af5840a3bfe6adc5a956042bd1 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 28 Apr 2017 03:32:42 -0400 Subject: [PATCH 21/21] Removed share buttons, added privacy/terms to footer --- views/templates/footer.html | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/views/templates/footer.html b/views/templates/footer.html index c731aa9..1beecbe 100644 --- a/views/templates/footer.html +++ b/views/templates/footer.html @@ -4,14 +4,12 @@
    Design by Fraser Boag.

    - Share: - - - -
    Contribute: + Contribute: +
    + Privacy Policy ▪️ Terms of Service