From f17541cb7515a1a0697af4cf68674badc7a329d8 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 15 Mar 2017 07:24:48 -0400 Subject: [PATCH 001/143] Updated version --- README.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 27027f5..5408345 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,9 @@ Tracman will be updated according to [this branching model](http://nvie.com/post ## Changelog +#### v0.5.1 + + #### v0.5.0 * Updated libraries diff --git a/package.json b/package.json index 4d8398c..6989ed1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tracman", - "version": "0.5.0", + "version": "0.5.1", "description": "Tracks user's GPS location", "main": "server.js", "dependencies": { From 66f5e4660060d0b0ba87c85ae253a127ed1d1c8b Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 15 Mar 2017 10:38:42 -0400 Subject: [PATCH 002/143] Added deploy script --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 6989ed1..3f6bf24 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "cookie-parser": "^1.4.1", "cookie-session": "^2.0.0-alpha.1", "express": "^4.15.2", + "firebase": "^3.7.2", "kerberos": "0.0.17", "moment": "^2.12.0", "mongodb": "^2.1.4", @@ -37,6 +38,7 @@ "test": "mocha test.js", "start": "node server.js", "dev": "nodemon server.js", + "deploy": "ssh khp deploy-tracman", "update": "sudo n stable && sudo npm update --save && sudo npm prune" }, "repository": { From 8d9e6bc809ab0e692d0fec6ba05f856e3da0e3a0 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 18 Mar 2017 09:11:54 -0400 Subject: [PATCH 003/143] 'Tests' sounds better --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f6bf24..c6550c3 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "supertest": "^1.2.0" }, "scripts": { - "test": "mocha test.js", + "tests": "mocha test.js", "start": "node server.js", "dev": "nodemon server.js", "deploy": "ssh khp deploy-tracman", From e3162736f1bf777bae9e642bfba20d45c52a1e34 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 18 Mar 2017 10:07:36 -0400 Subject: [PATCH 004/143] Moved CSS to file and made buttons prettier --- static/css/map.css | 20 ++++++++++++++++---- views/map.html | 29 +++++++++-------------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/static/css/map.css b/static/css/map.css index 3782ba5..c8036a3 100644 --- a/static/css/map.css +++ b/static/css/map.css @@ -42,8 +42,6 @@ img#panoImg { width:100%; height:100%; } background-color: rgba(255,255,255,.7); } -/*TODO: Make signs smaller on mobile */ - /* Speed sign */ .spd { font-size: 32px; @@ -57,6 +55,7 @@ img#panoImg { width:100%; height:100%; } margin: 10px; background-color: #FFF; } + /* Altitude sign */ .alt-unit, .spd-unit { font-size:12px; } .alt-label, .spd-label { @@ -76,9 +75,22 @@ img#panoImg { width:100%; height:100%; } } /* Control buttons */ -.btn { +#controls { + width: 100vw; + position: absolute; + bottom: 50px; + display: flex; + justify-content: space-around; +} #controls .btn { z-index: 50; background: #222; -} .btn:hover { + height: 10vh; + padding: 2vh 0; +} #controls .btn:hover { background: #333; +} +#controls .btn.set, #controls .btn.clear { + width: 30vw; +} #controls .btn.track { + width: 35vw; } \ No newline at end of file diff --git a/views/map.html b/views/map.html index 55fb0bc..71be565 100644 --- a/views/map.html +++ b/views/map.html @@ -57,29 +57,18 @@ {% if user.id == mapuser.id %}
- + {% if mapuser.settings.showStreetview and disp!='0' %} + + {% endif %} + +
{% endif %} From 98fa595738d6d955742408a17a019e13be331814 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 18 Mar 2017 10:22:26 -0400 Subject: [PATCH 005/143] Rearranged CSS --- static/css/map.css | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/static/css/map.css b/static/css/map.css index c8036a3..dd970ea 100644 --- a/static/css/map.css +++ b/static/css/map.css @@ -42,36 +42,35 @@ img#panoImg { width:100%; height:100%; } background-color: rgba(255,255,255,.7); } -/* Speed sign */ -.spd { - font-size: 32px; - height: 40px;} +/* Signs */ +.spd-sign, .alt-sign { + text-align: center; +} .spd-sign { color: #000; - text-align: center; - padding: 5px; - border: 2px solid #000; - border-radius: 3px; - margin: 10px; background-color: #FFF; + border: 2px solid #000; } - -/* Altitude sign */ -.alt-unit, .spd-unit { font-size:12px; } -.alt-label, .spd-label { - font-size:18px; - height:18px;} -.alt { - font-size: 32px; - height: 40px;} .alt-sign { color: #FFF; - text-align: center; - padding: 5px; + background-color: #009800; border: 2px solid #FFF; +} +.spd, .alt { + height: 40px; + font-size: 32px; +} +.alt-unit, .spd-unit { + font-size: 12px; +} +.alt-label, .spd-label { + font-size: 18px; + height: 18px; +} +.spd-sign, .alt-sign { + padding: 5px; border-radius: 3px; margin: 10px; - background-color: #009800; } /* Control buttons */ From f020e1eafa36f467f136c89d80c9c8841da5a669 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 18 Mar 2017 10:23:42 -0400 Subject: [PATCH 006/143] Added media query --- static/css/map.css | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/static/css/map.css b/static/css/map.css index dd970ea..20637e1 100644 --- a/static/css/map.css +++ b/static/css/map.css @@ -56,6 +56,7 @@ img#panoImg { width:100%; height:100%; } background-color: #009800; border: 2px solid #FFF; } + .spd, .alt { height: 40px; font-size: 32px; @@ -73,6 +74,25 @@ img#panoImg { width:100%; height:100%; } margin: 10px; } +@media (max-width:300px) { + .spd, .alt { + height: 40px; + font-size: 32px; + } + .alt-unit, .spd-unit { + font-size: 12px; + } + .alt-label, .spd-label { + font-size: 18px; + height: 18px; + } + .spd-sign, .alt-sign { + padding: 5px; + border-radius: 3px; + margin: 10px; + } +} + /* Control buttons */ #controls { width: 100vw; From 2532833be95a026f7ca38024280b30fe56f5f428 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 18 Mar 2017 11:01:19 -0400 Subject: [PATCH 007/143] #12 Fixed speed/altitude sign size --- static/css/map.css | 68 ++++++++++++++++++++++++++++------------------ views/map.html | 6 ++-- 2 files changed, 44 insertions(+), 30 deletions(-) diff --git a/static/css/map.css b/static/css/map.css index 20637e1..f1886c1 100644 --- a/static/css/map.css +++ b/static/css/map.css @@ -45,36 +45,19 @@ img#panoImg { width:100%; height:100%; } /* Signs */ .spd-sign, .alt-sign { text-align: center; -} -.spd-sign { + padding: 2%; + border-radius: 3px; + margin: 3%; +} .spd-sign { color: #000; background-color: #FFF; border: 2px solid #000; -} -.alt-sign { +} .alt-sign { color: #FFF; background-color: #009800; border: 2px solid #FFF; } - -.spd, .alt { - height: 40px; - font-size: 32px; -} -.alt-unit, .spd-unit { - font-size: 12px; -} -.alt-label, .spd-label { - font-size: 18px; - height: 18px; -} -.spd-sign, .alt-sign { - padding: 5px; - border-radius: 3px; - margin: 10px; -} - -@media (max-width:300px) { +@media (min-width:400px) { .spd, .alt { height: 40px; font-size: 32px; @@ -86,10 +69,41 @@ img#panoImg { width:100%; height:100%; } font-size: 18px; height: 18px; } - .spd-sign, .alt-sign { - padding: 5px; - border-radius: 3px; - margin: 10px; +} @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; } } diff --git a/views/map.html b/views/map.html index 71be565..ec0d63f 100644 --- a/views/map.html +++ b/views/map.html @@ -188,7 +188,7 @@ // Create speed block if (settings.showSpeed) { - var speedSign = document.createElement('div'), + const speedSign = document.createElement('div'), speedLabel = document.createElement('div'), speedText = document.createElement('div'), speedUnit = document.createElement('div'); @@ -208,7 +208,7 @@ // Create altitude block if (settings.showAlt) { var elevator = new google.maps.ElevationService; - var altitudeSign = document.createElement('div'), + const altitudeSign = document.createElement('div'), altitudeLabel = document.createElement('div'), altitudeText = document.createElement('div'), altitudeUnit = document.createElement('div'); @@ -221,7 +221,7 @@ getAltitude(new google.maps.LatLng(last.lat,last.lon), elevator, function(alt) { if (alt) { altitudeText.innerHTML = (settings.units=='standard')?(alt*3.28084).toFixed():alt.toFixed(); } }); - altitudeUnit.innerHTML = (settings.units=='standard')?'feet above sea level':'meters above sea level'; + altitudeUnit.innerHTML = (settings.units=='standard')?'feet':'meters'; altitudeSign.appendChild(altitudeLabel); altitudeSign.appendChild(altitudeText); altitudeSign.appendChild(altitudeUnit); From 69ee6d3bf8ae4e6ae22e9f73d19c023838be589c Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 18 Mar 2017 11:40:12 -0400 Subject: [PATCH 008/143] Removed licene, added terms --- config/routes/misc.js | 4 ++-- views/license.html | 31 ------------------------------- views/terms.html | 12 ++++++++++++ 3 files changed, 14 insertions(+), 33 deletions(-) delete mode 100644 views/license.html create mode 100644 views/terms.html diff --git a/config/routes/misc.js b/config/routes/misc.js index 15aafe6..8a4faf0 100644 --- a/config/routes/misc.js +++ b/config/routes/misc.js @@ -26,8 +26,8 @@ router.get('/android', function(req,res){ res.redirect('https://play.google.com/store/apps/details?id=us.keithirwin.tracman'); }); -router.get('/license', function(req,res){ - res.render('license.html', {user:req.user}); +router.get('/terms', function(req,res){ + res.render('terms.html', {user:req.user}); }); router.route('/pro') diff --git a/views/license.html b/views/license.html deleted file mode 100644 index da645d3..0000000 --- a/views/license.html +++ /dev/null @@ -1,31 +0,0 @@ -{% extends 'templates/base.html' %} -{% block title %}{{ super() }} | License{% endblock %} - -{% block main %} -
- -
-

The MIT License (MIT)

-

Copyright © 2016 Keith Irwin

- -

Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions:

- -

The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software.

- -

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE.

-
- -
-{% endblock %} diff --git a/views/terms.html b/views/terms.html new file mode 100644 index 0000000..f04fbec --- /dev/null +++ b/views/terms.html @@ -0,0 +1,12 @@ +{% extends 'templates/base.html' %} +{% block title %}{{super()}} | Terms of Service{% endblock %} + +{% block main %} +
+ +

Terms of Service

+ +

The terms of service haven't been written yet. (#48)

+ +
+{% endblock %} \ No newline at end of file From ee7c874d3cfb638989af53be123e0d5784300364 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 18 Mar 2017 11:41:51 -0400 Subject: [PATCH 009/143] Fixed license discrepency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c6550c3..4b9c29c 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "map" ], "author": "Keith Irwin", - "license": "MIT", + "license": "GPL-3.0", "README": "README.md", "bugs": { "url": "https://github.com/Tracman-org/Server/issues" From 94a718afab8b7be74731d1cb04610bfdc94d25c1 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 18 Mar 2017 13:21:48 -0400 Subject: [PATCH 010/143] Various updates --- config/routes/index.js | 41 ++++++++++++++++++++++++++++++++--------- config/routes/misc.js | 35 +++++++++-------------------------- views/error.html | 1 + views/help.html | 40 ++++++++++++++++++++++++++++++++++------ views/pro.html | 21 ++++++--------------- 5 files changed, 82 insertions(+), 56 deletions(-) diff --git a/config/routes/index.js b/config/routes/index.js index 3b42446..aa2fe76 100644 --- a/config/routes/index.js +++ b/config/routes/index.js @@ -5,11 +5,6 @@ const slug = require('slug'), User = require('../models/user.js'), router = require('express').Router(); -// Shortcut to favicon.ico -router.get('/favicon.ico', function(req,res){ - res.redirect('/static/img/icon/by/16-32-48.ico'); -}); - // Index route router.route('/') .get(function(req,res,next){ @@ -87,10 +82,38 @@ router.route('/settings') } ); }); - -router.route('/help') - .get(mw.ensureAuth, function(req,res){ - res.render('help.html', {user:req.session.passport.user}); + + +// Tracman pro +router.route('/pro') + .all(mw.ensureAuth, function(req,res,next){ + next(); + }).get(function(req,res,next){ + User.findById(req.session.passport.user, function(err, user){ + if (err){ mw.throwErr(req,err); } + if (!user){ next(); } + else { res.render('pro.html', {user:user}); } + }); + }).post(function(req,res){ + User.findByIdAndUpdate(req.session.passport.user, + {$set:{ isPro:true }}, + function(err, user){ + if (err){ mw.throwErr(req,err); } + else { req.flash('success','You have been signed up for pro. '); } + res.redirect('/map'); + } + ); }); +// Help +router.route('/help') + .get(mw.ensureAuth, function(req,res){ + res.render('help.html', {user:req.user}); + }); + +// Terms of Service +router.get('/terms', function(req,res){ + res.render('terms.html', {user:req.user}); +}); + module.exports = router; \ No newline at end of file diff --git a/config/routes/misc.js b/config/routes/misc.js index 8a4faf0..b1d93da 100644 --- a/config/routes/misc.js +++ b/config/routes/misc.js @@ -1,17 +1,23 @@ 'use strict'; const router = require('express').Router(), - mw = require('../middleware.js'), slug = require('slug'), User = require('../models/user.js'); +// robots.txt router.get('/robots.txt', function(req,res){ res.type('text/plain'); res.send("User-agent: *\n"+ - "Disallow: /map\n" + "Disallow: /map/*\n" ); }); +// favicon.ico +router.get('/favicon.ico', function(req,res){ + res.redirect('/static/img/icon/by/16-32-48.ico'); +}); + +// Endpoint to validate forms router.get('/validate', function(req,res){ if (req.query.slug) { // validate unique slug User.findOne({slug:slug(req.query.slug)}, function(err, existingUser){ @@ -22,32 +28,9 @@ router.get('/validate', function(req,res){ } }); +// Link to android app in play store router.get('/android', function(req,res){ res.redirect('https://play.google.com/store/apps/details?id=us.keithirwin.tracman'); }); -router.get('/terms', function(req,res){ - res.render('terms.html', {user:req.user}); -}); - -router.route('/pro') - .all(mw.ensureAuth, function(req,res,next){ - next(); - }).get(function(req,res,next){ - User.findById(req.session.passport.user, function(err, user){ - if (err){ mw.throwErr(req,err); } - if (!user){ next(); } - else { res.render('pro.html', {user:user}); } - }); - }).post(function(req,res){ - User.findByIdAndUpdate(req.session.passport.user, - {$set:{ isPro:true }}, - function(err, user){ - if (err){ mw.throwErr(req,err); } - else { req.flash('success','You have been signed up for pro. '); } - res.redirect('/map'); - } - ); - }); - module.exports = router; \ No newline at end of file diff --git a/views/error.html b/views/error.html index 06461a5..ed036b6 100644 --- a/views/error.html +++ b/views/error.html @@ -7,6 +7,7 @@ {% if code %}

{{code}}

{% endif %} {% if message %}

{{message}}

{% endif %} {% if error %}

{{error}}

{% endif %} +

I would really appreciate it if you would report this error.

{% if code %}{% endif %} diff --git a/views/help.html b/views/help.html index 055bace..5341151 100644 --- a/views/help.html +++ b/views/help.html @@ -4,17 +4,45 @@ {% block main %}
-

Help

+

Help

-

Welcome to Tracman! Here's how to get started.

+

Welcome to Tracman! Here's how to get started.

-

Set sets your location once using this device's geolocation. On a GPS-enabled phone, the location will be set to its coordinates.

+

Website map controls

-

Track sets your location as above, but continues to track your location as long as you keep this window open. On a phone, this can drain the battery fast, so be careful!

+

Set sets your location once using this device's geolocation. On a GPS-enabled phone, the location will be set to its coordinates.

+ +

Track sets your location as above, but continues to track your location as long as you keep this window open. To track your location in the background, check out the android app.

+ +

Clear clears your location instantly. Anyone looking at your map will see a blank screen instead. Use this to hide your location.

-

Clear clears your location instantly. Anyone looking at your map will see a blank screen instead. Use this to hide your location.

+

Website settings

-

Share your location by sending the URL to anyone. They won't need an account to view the map.

+

+ +

Android app

+ +

+ +

FAQ

+ +
    +
  • How do I share my location?
  • +
  • How accurate is the location?
  • +
  • Can I contribute to Tracman?
  • +
+ +

How do I share my location?

+ +

You can share your map's url with anyone. The URL is {% if user %}https://tracman.org/map/{{user.slug}}{% else %}https://tracman.org/map/>your-slug<{% endif %}.

+ +

How accurate is the location?

+ +

+ +

Can I contribute to Tracman?

+ +

Go to map diff --git a/views/pro.html b/views/pro.html index 21e90ba..e5945a5 100644 --- a/views/pro.html +++ b/views/pro.html @@ -3,28 +3,18 @@ {% block main %}
-
+

Tracman Pro

A word from the developer

Hi Folks,

-

Glad you're enjoying my website and app. I made the whole thing, from front to backend, - and I'm really proud of it! However, I'm a long-haul trucker by day and coding is just a hobby. - I don't make any money off this website, and I pay the server fees out of my own pocket. Do you - pity me enough to donate some money by paypal - or bitcoin?

+

Glad you're enjoying my website and app. I made the whole thing, from front to backend, and I'm really proud of it! However, I'm a long-haul trucker by day and coding is just a hobby. I don't make any money off this website, and I pay the server fees out of my own pocket. Do you pity me enough to donate some money or bitcoin?

-

To make a little money off this service, I'm going to be offering a pro version with more - features. It'll be cheap, probably $1 or $2 per month. However, while Tracman is in beta, - you can beta test the pro version too. Be sure to inform me about any bugs - you encounter or suggestions you have. And keep in mind that at some - point, when we launch out of beta, Tracman Pro will not be free and you will - lose your pro membership unless start paying for it. +

To make a little money off this service, I'm going to be offering a pro version with more features. It'll be cheap, probably $1 or $2 per month. However, while Tracman is in beta, you can beta test the pro version too. Be sure to inform me about any bugs you encounter. And keep in mind that at some point, when we launch out of beta, Tracman Pro will not be free and you will lose your pro membership unless start paying for it. -

That said, just click the button below to test out the pro features. Keep in mind, they are - as unstable as the rest of this product. +

That said, just click the button below to test out the pro features. Keep in mind, they are as unstable as the rest of this product.

Cheers,
Keith Irwin

@@ -32,10 +22,11 @@
{% if user.isPro %}
You are already pro!
+ go to map {% else %} + go home {% endif %} - go home
From de17e1734770f8938966bfafb90174916ce5c940 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 18 Mar 2017 14:27:32 -0400 Subject: [PATCH 011/143] Various changes, updates to help --- config/middleware.js | 10 ++--- config/routes/index.js | 78 ++++++++++++++++----------------- server.js | 23 +++++++++- views/help.html | 97 ++++++++++++++++++++++++++++++------------ 4 files changed, 133 insertions(+), 75 deletions(-) diff --git a/config/middleware.js b/config/middleware.js index 4d6b03e..1f473f4 100644 --- a/config/middleware.js +++ b/config/middleware.js @@ -3,14 +3,12 @@ const secret = require('./secrets.js'); var throwErr = function(req,err){ - console.log('middleware.js:5 '+typeof err); - console.log('Middleware error:'+err+'\nfor request:\n'+req); + console.error('middleware.js:5 '+typeof err); + console.error('Middleware error:'+err+'\nfor request:\n'+req); if (secret.env==='production') { - req.flash('error', 'An error occured.
Would you like to report it?'); - req.flash('error-message',err); + req.flash('danger', 'An error occured.
Would you like to report it?'); } else { // development - req.flash('error',err); - req.flash('error-message',err); + req.flash('danger', err); } }; diff --git a/config/routes/index.js b/config/routes/index.js index aa2fe76..ed30081 100644 --- a/config/routes/index.js +++ b/config/routes/index.js @@ -8,47 +8,43 @@ const slug = require('slug'), // Index route router.route('/') .get(function(req,res,next){ - - // Logged in - if ( req.session.passport && req.session.passport.user ){ - // Get user - User.findById(req.session.passport.user, function(err, user){ - if (err){ mw.throwErr(req,err); } - if (!user){ console.log('Already logged in user not found:', req.session.passport); next(); } - // If user found: - else { - // Open index - res.render('index.html', { - user: user, - error: req.flash('error')[0], - success: req.flash('succcess')[0] - }); - } - }); - } - - // Not logged in - else { - res.render('index.html', { - error: req.flash('error')[0], - success: req.flash('success')[0] - }); - } - -}); + + // Logged in + if ( req.session.passport && req.session.passport.user ){ + // Get user + User.findById(req.session.passport.user, function(err, user){ + if (err){ mw.throwErr(req,err); } + if (!user){ console.log('Already logged in user not found:', req.session.passport); next(); } + // If user found: + else { + // Open index + res.render('index.html'); + } + }); + } + + // Not logged in + else { + res.render('index.html'); + } + + }); // Settings -router.route('/settings') +router.route('/settings').all(mw.ensureAuth, function(req,res,next){ + next(); + }) // Get settings form - .get(mw.ensureAuth, function(req,res,next){ + .get(function(req,res,next){ User.findById(req.session.passport.user, function(err,user){ if (err){ console.log('Error finding settings for user:',err); mw.throwErr(req,err); } res.render('settings.html', {user:user}); }); - + }) + // Set new settings - }).post(mw.ensureAuth, function(req,res,next){ + .post(function(req,res,next){ User.findByIdAndUpdate(req.session.passport.user, {$set:{ name: req.body.name, slug: slug(req.body.slug), @@ -69,7 +65,7 @@ router.route('/settings') }) // Delete user account - .delete(mw.ensureAuth, function(req,res,next){ + .delete(function(req,res,next){ User.findByIdAndRemove( req.session.passport.user, function(err) { if (err) { @@ -85,16 +81,21 @@ router.route('/settings') // Tracman pro -router.route('/pro') - .all(mw.ensureAuth, function(req,res,next){ +router.route('/pro').all(mw.ensureAuth, function(req,res,next){ next(); - }).get(function(req,res,next){ + }) + + // Get info about pro + .get(function(req,res,next){ User.findById(req.session.passport.user, function(err, user){ if (err){ mw.throwErr(req,err); } if (!user){ next(); } else { res.render('pro.html', {user:user}); } }); - }).post(function(req,res){ + }) + + // Join Tracman pro + .post(function(req,res){ User.findByIdAndUpdate(req.session.passport.user, {$set:{ isPro:true }}, function(err, user){ @@ -106,8 +107,7 @@ router.route('/pro') }); // Help -router.route('/help') - .get(mw.ensureAuth, function(req,res){ +router.route('/help').get(mw.ensureAuth, function(req,res){ res.render('help.html', {user:req.user}); }); diff --git a/server.js b/server.js index fc7ca0c..5bd0ea0 100755 --- a/server.js +++ b/server.js @@ -63,16 +63,35 @@ const } /* Routes */ { - app.get('/favicon.ico', function(req,res){ - res.redirect('/static/img/icon/by/16-32-48.ico'); + + // Default locals + app.get('*', function(req,res,next){ + + // User account + res.locals.user = req.user; + + // Flash messages + res.locals.successes = req.flash('success'); + res.locals.dangers = req.flash('danger'); + res.locals.warnings = req.flash('warning'); + + next(); }); + + // Main routes app.use('/', require('./config/routes/index.js'), require('./config/routes/auth.js'), require('./config/routes/misc.js') ); + + // Map app.use(['/map','/trac'], require('./config/routes/map.js')); + + // Admin app.use('/admin', require('./config/routes/admin.js')); + + // Static files app.use('/static', express.static(__dirname+'/static')); } diff --git a/views/help.html b/views/help.html index 5341151..1d86f5a 100644 --- a/views/help.html +++ b/views/help.html @@ -5,45 +5,86 @@

Help

- -

Welcome to Tracman! Here's how to get started.

- -

Website map controls

- -

Set sets your location once using this device's geolocation. On a GPS-enabled phone, the location will be set to its coordinates.

-

Track sets your location as above, but continues to track your location as long as you keep this window open. To track your location in the background, check out the android app.

+

Welcome to Tracman!

-

Clear clears your location instantly. Anyone looking at your map will see a blank screen instead. Use this to hide your location.

- -

Website settings

- -

+

Website map controls

-

Android app

- -

- -

FAQ

-
    -
  • How do I share my location?
  • -
  • How accurate is the location?
  • -
  • Can I contribute to Tracman?
  • +
  • Set sets your location once using this device's geolocation. On a GPS-enabled phone, the location will be set to its coordinates.
  • +
  • Track sets your location as above, but continues to track your location as long as you keep this window open. To track your location in the background, check out the android app.
  • +
  • Clear clears your location instantly. Anyone looking at your map will see a blank screen instead. Use this to hide your location.
-

How do I share my location?

+

Website settings

-

You can share your map's url with anyone. The URL is {% if user %}https://tracman.org/map/{{user.slug}}{% else %}https://tracman.org/map/>your-slug<{% endif %}.

+
    +
  • Name is your name. It appears at the title of your browsing window and when you hover over the marker on your map. You can put whatever you want, really.
  • +
  • Email is your email, used for account recovery and (really) important information, like if Tracman shuts down permanently. Tracman will never send bulk emails. For more information, check out the Terms of Service.
  • +
  • URL lets you determine the slug your map can be accessed. See How do I share my location? for more info.
  • +
  • Units let you choose metric (meters and kilometers-per-hour) or standard American units (feet and miles-per-hour).
  • +
  • Default Map is the default map type that visitors to your map will see. It's either a standard google map or a satellite image. Users will be able to change this.
  • +
  • Default Zoom is the default zoom level that visitors to your map will see. It ranges from 1-20, where 1 shows the whole world and 20 shows the most detail. Users will be able to change this.
  • +
  • Show speed puts a sign in the top-right corner of the map, which shows your speed.
  • +
  • Show altitude puts a sign in the top-right corner of the map, which shows your altitude. See How is the altitude determined? in the FAQ.
  • +
  • Show street view shows a Google street view image of your location. See What is the street view image? in the FAQ.
  • +
-

How accurate is the location?

+

Android

-

+

The android app is especially buggy, so be careful!

-

Can I contribute to Tracman?

+

Settings

+ +
    +
  • Start service on boot determines whether the Tracman app will start in the background when you turn on your phone.
  • +
  • Enable updates turns on and off updates. Your location will be updated in the background, as long as the app is running. A notification will show whether updates are enabled or not.
  • +
  • Update interval determines how often location updates will be sent to the map. See the location services documentation for more information.
  • +
  • Update priority determines how accurate your updates will be, and how much battery the app will use. See the location services documentation for more information.
  • +
+ +

The notification

+ +

While tracman is running in the background, the update interval and priority are lessened while nobody's looking at the map. This is to save battery life. The notification will show whether its sending realtime or occasional updates. This is an informal way of seeing if anyone is viewing your location.

+ +

FAQ

+ + + +

How do I share my location?

+ +

You can simply share your map's url with anyone. {% if user %}Your URL is https://tracman.org/map/{{user.slug}}{% else %}The URL is https://tracman.org/map/>your-slug<{% endif %}.

+ +

How accurate is the location?

+ +

When using the web app, the location will be as accurate as the underlying geolocation data. It can be pretty accurate if opened on a mobile phone browser, since the device's GPS will be used. On a desktop, the location will be estimated based on your IP address, which can be very inaccurate. Check out the API Specification for way more information.

+ +

On android, Tracman uses Google Play Services location APIs to set your phone's location. This can use your phone's GPS data, or nearby WiFi and cellular towers. Sometimes this can be pretty inaccurate.

+ +

How is the altitude determined?

+ +

The altitude is not determined using your GPS, because this is notoriously inaccurate. Instead, the Google Maps Elevation API resolves the altitude of the ground at your coordinates. This means that if you are flying, Tracman will show the altitude of the ground beneath you.

+ +

What is the street view image?

+ +

While you are stationary, Tracman will display the google street view panorama closest to your location. Users will be able to pan the image to look around. By default, the image will be oriented towards you. If you are in a building, and there is a street view image of the street outside the building, the panorama will be pointed at the building.

+ +

While you are moving, Tracman will display a plain google street view image closest the image cannot be panned. This allows for it to be loaded faster. The image will be oriented to your direction of travel, so if you are driving down a road, it will show the view in the direction you're driving. This can sometimes appear like a slow-frame-rate dashcam video.

+ +

The images come from Google street view. They are not live images from your location. Mostly, the photos were taken during the daytime when the weather was good.

+ +

Can I contribute to Tracman?

+ +

Sure! Tracman has some github repositories you can clone.

+ +

I also accept donations to help with development and server fees. You can pay with cash or bitcoin.

-

- Go to map
From ccacbbf5c418b336396b68b682d30c6614c7c66d Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 18 Mar 2017 14:46:02 -0400 Subject: [PATCH 012/143] Various fixes --- config/routes/index.js | 39 +++++++++------------------------------ 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/config/routes/index.js b/config/routes/index.js index ed30081..5b661ed 100644 --- a/config/routes/index.js +++ b/config/routes/index.js @@ -5,30 +5,10 @@ const slug = require('slug'), User = require('../models/user.js'), router = require('express').Router(); -// Index route -router.route('/') - .get(function(req,res,next){ - - // Logged in - if ( req.session.passport && req.session.passport.user ){ - // Get user - User.findById(req.session.passport.user, function(err, user){ - if (err){ mw.throwErr(req,err); } - if (!user){ console.log('Already logged in user not found:', req.session.passport); next(); } - // If user found: - else { - // Open index - res.render('index.html'); - } - }); - } - - // Not logged in - else { - res.render('index.html'); - } - - }); +// Index +router.get('/', function(req,res,next){ + res.render('index.html'); +}); // Settings router.route('/settings').all(mw.ensureAuth, function(req,res,next){ @@ -39,7 +19,7 @@ router.route('/settings').all(mw.ensureAuth, function(req,res,next){ .get(function(req,res,next){ User.findById(req.session.passport.user, function(err,user){ if (err){ console.log('Error finding settings for user:',err); mw.throwErr(req,err); } - res.render('settings.html', {user:user}); + res.render('settings.html'); }); }) @@ -60,7 +40,7 @@ router.route('/settings').all(mw.ensureAuth, function(req,res,next){ }}, function(err, user){ if (err) { console.log('Error updating user settings:',err); mw.throwErr(req,err); } else { req.flash('success', 'Settings updated. '); } - res.redirect('/map#'); + res.redirect('/settings'); }); }) @@ -79,7 +59,6 @@ router.route('/settings').all(mw.ensureAuth, function(req,res,next){ ); }); - // Tracman pro router.route('/pro').all(mw.ensureAuth, function(req,res,next){ next(); @@ -90,7 +69,7 @@ router.route('/pro').all(mw.ensureAuth, function(req,res,next){ User.findById(req.session.passport.user, function(err, user){ if (err){ mw.throwErr(req,err); } if (!user){ next(); } - else { res.render('pro.html', {user:user}); } + else { res.render('pro.html'); } }); }) @@ -108,12 +87,12 @@ router.route('/pro').all(mw.ensureAuth, function(req,res,next){ // Help router.route('/help').get(mw.ensureAuth, function(req,res){ - res.render('help.html', {user:req.user}); + res.render('help.html'); }); // Terms of Service router.get('/terms', function(req,res){ - res.render('terms.html', {user:req.user}); + res.render('terms.html'); }); module.exports = router; \ No newline at end of file From d652c3ba15914f9fc5fbad4ef0ac1feb6f50c4a9 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 18 Mar 2017 14:58:18 -0400 Subject: [PATCH 013/143] #38 Sanatized user input --- config/routes/index.js | 5 +++-- package.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/config/routes/index.js b/config/routes/index.js index 5b661ed..8648731 100644 --- a/config/routes/index.js +++ b/config/routes/index.js @@ -1,6 +1,7 @@ 'use strict'; const slug = require('slug'), + xss = require('xss'), mw = require('../middleware.js'), User = require('../models/user.js'), router = require('express').Router(); @@ -26,8 +27,8 @@ router.route('/settings').all(mw.ensureAuth, function(req,res,next){ // Set new settings .post(function(req,res,next){ User.findByIdAndUpdate(req.session.passport.user, {$set:{ - name: req.body.name, - slug: slug(req.body.slug), + name: xss(req.body.name), + slug: slug(xss(req.body.slug)), email: req.body.email, settings: { units: req.body.units, diff --git a/package.json b/package.json index 4b9c29c..fe8f59a 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "supertest": "^1.2.0" }, "scripts": { - "tests": "mocha test.js", + "test": "mocha test.js", "start": "node server.js", "dev": "nodemon server.js", "deploy": "ssh khp deploy-tracman", From 187797a5874e8deb2066da39c2faf2b6fcf4fcd4 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 18 Mar 2017 15:40:03 -0400 Subject: [PATCH 014/143] #49 Fixed flash messages --- config/routes/auth.js | 4 ++++ config/routes/index.js | 2 +- server.js | 30 ++++++++++++++----------- static/css/base.css | 21 +++++++++++++++++ static/css/bootstrap.css | 1 - views/templates/base.html | 7 ------ views/templates/header.html | 45 ++++++++++++++++++++++++------------- 7 files changed, 73 insertions(+), 37 deletions(-) diff --git a/config/routes/auth.js b/config/routes/auth.js index 72fd4f6..1d1c587 100644 --- a/config/routes/auth.js +++ b/config/routes/auth.js @@ -3,14 +3,17 @@ const router = require('express').Router(), passport = require('passport'); +// Routes router.get('/login', function(req,res){ res.redirect('/auth/google'); }); router.get('/logout', function(req,res){ req.logout(); // Needs to clear cookies? + req.flash('success', 'You have been logged out. '); res.redirect('/'); }); +// Web app auth router.get('/auth/google', passport.authenticate('google', { scope: [ 'https://www.googleapis.com/auth/plus.login', 'https://www.googleapis.com/auth/plus.profile.emails.read' @@ -22,6 +25,7 @@ router.get('/auth/google/callback', passport.authenticate('google', { successFlash: true } )); +// Android auth router.get('/auth/google/idtoken', passport.authenticate('google-id-token'), function (req,res) { if (!req.user) { res.sendStatus(401); } else { res.send(req.user); } diff --git a/config/routes/index.js b/config/routes/index.js index 8648731..2f57ccb 100644 --- a/config/routes/index.js +++ b/config/routes/index.js @@ -7,7 +7,7 @@ const slug = require('slug'), router = require('express').Router(); // Index -router.get('/', function(req,res,next){ +router.get('/', function(req,res,next) { res.render('index.html'); }); diff --git a/server.js b/server.js index 5bd0ea0..19cc182 100755 --- a/server.js +++ b/server.js @@ -64,18 +64,24 @@ const /* Routes */ { - // Default locals - app.get('*', function(req,res,next){ + // Static files (keep this before setting default locals) + app.use('/static', express.static(__dirname+'/static')); - // User account - res.locals.user = req.user; - - // Flash messages - res.locals.successes = req.flash('success'); - res.locals.dangers = req.flash('danger'); - res.locals.warnings = req.flash('warning'); - - next(); + // Set default locals (keep this after static files) + app.get('/*', function(req,res,next){ + // console.log(`Setting local variables for request to ${req.path}.`); + + // User account + res.locals.user = req.user; + // console.log(`User set as ${res.locals.user}. `); + + // Flash messages + res.locals.successes = req.flash('success'); + res.locals.dangers = req.flash('danger'); + res.locals.warnings = req.flash('warning'); + // console.log(`Flash messages set as:\nSuccesses: ${res.locals.successes}\nWarnings: ${res.locals.warnings}\nDangers: ${res.locals.dangers}`); + + next(); }); // Main routes @@ -91,8 +97,6 @@ const // Admin app.use('/admin', require('./config/routes/admin.js')); - // Static files - app.use('/static', express.static(__dirname+'/static')); } /* Errors */ { diff --git a/static/css/base.css b/static/css/base.css index e06b8c5..e436145 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -64,9 +64,20 @@ input[type="checkbox"] { display: inline-block; } .help-block {margin-top:-20px;} + .alert { z-index:10; } +.alert-header { + position: relative; + top: 58px; +} .alert-header.alert-danger { + z-index: 103; +} .alert-header.alert-warning { + z-index: 102; +} .alert-header.alert-success { + z-index: 101; +} .alert:not(.alert-dismissible) { text-align: center; } @@ -78,6 +89,7 @@ input[type="checkbox"] { color: inherit; text-decoration: none; } + input:focus, textarea:focus { outline: 0; } @@ -99,6 +111,15 @@ h3 { font-size: 28px; } h4 { font-size: 20px; } .red { color: #fb6e3d; } +.shadow { + -moz-box-shadow: .18vw .18vw .36vw #000; + -webkit-box-shadow: .18vw .18vw .36vw #000; + box-shadow: .18vw .18vw .36vw #000; +} .shadow:active { + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} a { color: #fbc93d; diff --git a/static/css/bootstrap.css b/static/css/bootstrap.css index 8875db2..ddad873 100644 --- a/static/css/bootstrap.css +++ b/static/css/bootstrap.css @@ -4337,7 +4337,6 @@ a.label:focus, a.label:hover { .alert { padding: 15px; border: 1px solid transparent; - border-radius: .25rem; } .alert > p, diff --git a/views/templates/base.html b/views/templates/base.html index f42d6ab..3e8f4ee 100644 --- a/views/templates/base.html +++ b/views/templates/base.html @@ -39,13 +39,6 @@ - - {% if not noHeader %}{% include 'templates/header.html' %}{% endif %} {% block main %}Loading... {% endblock %} {% if not noFooter %}{% include 'templates/footer.html' %}{% endif %} diff --git a/views/templates/header.html b/views/templates/header.html index c89238d..5de9750 100644 --- a/views/templates/header.html +++ b/views/templates/header.html @@ -1,12 +1,18 @@ -
+
+ + + +
+ + +
- - - -{% if error %} -
- ERROR: {{error|safe}} - + +
@@ -51,22 +51,22 @@ Mobile phone

Setting your location

-

You can track your GPS location from your phone's web browsers. There's also has an android app which can run in the background. With the app, you can:

+

You can track your GPS location from your phone's web browsers. There's also has an android app which can run in the background. With the app, you can:

  • Turn off tracking

    -

    If you need to go undercover, just turn tracman off with the flip of a switch. 

    +

    If you need to go undercover, just turn tracman off with the flip of a switch.

  • Change settings

    -

    Change your settings to show a less accurate location, if you want an air of mystery. 

    +

    Change your settings to show a less accurate location, if you want an air of mystery.

  • Save energy

    -

    If nobody's tracking you, tracman won't needlessly drain your battery. 

    +

    If nobody's tracking you, tracman won't needlessly drain your battery.

@@ -78,22 +78,22 @@ Laptop

The Map

-

You'll get a simple webpage with a map to send to friends. It'll look like this. 

+

You'll get a simple webpage with a map to send to friends. It'll look like this.

  • Easy

    -

    Just send a link to whomever you want. Bam, now they know where you are. 

    +

    Just send a link to whomever you want. Bam, now they know where you are.

  • Precise

    -

    Map updates in realtime with websockets. 

    +

    Map updates in realtime with websockets.

  • Customizable

    -

    Change the map default type and zoom level. You can also show speed, altitude, and streetview. 

    +

    Change the map default type and zoom level. You can also show speed, altitude, and streetview.

@@ -104,7 +104,7 @@

Warning!

This is beta software, so there are still kinks to be worked out.

-

Also keep in mind that publishing your location online could be a bad idea.

+

Keep in mind that publishing your location online could be a bad idea.

@@ -112,8 +112,8 @@

Hook me up!

-

Right now, Tracman is in beta testing. Things may break.

- Join Tracman +

Just click that there button to create an account.

+ Join Tracman
{% endif %} From 14b586e14653007af08209683e8d3409e102aafc Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 12 Apr 2017 15:24:38 -0400 Subject: [PATCH 038/143] Fixed index links, removed data-scroll effects --- static/js/index.js | 20 -------------------- views/index.html | 7 ++----- 2 files changed, 2 insertions(+), 25 deletions(-) delete mode 100644 static/js/index.js diff --git a/static/js/index.js b/static/js/index.js deleted file mode 100644 index 27c0a71..0000000 --- a/static/js/index.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -jQuery.extend(jQuery.easing,{ - easeInOutExpo: function(x, t, b, c, d){ - if (t==0) return b; - if (t==d) return b+c; - if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; - return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; - } -}); - -$(document).ready(function(){ - - $('a[href=#]').click(function(e){ - e.preventDefault(); - $('nav').removeClass('visible'); - $('html,body').stop().animate({scrollTop: $('.'+$(this).data('scrollto')).offset().top-65 }, 700, 'easeInOutExpo', function(){}); - }); - -}); diff --git a/views/index.html b/views/index.html index 8381378..0a9370b 100644 --- a/views/index.html +++ b/views/index.html @@ -6,13 +6,10 @@ {% block main %} - -

Tracman

Display your realtime GPS location on a map

- {% if user %} Map {% if user.isAdmin %} @@ -20,8 +17,8 @@ {% endif %} {% else %} View example - Join - Login + Join + Login {% endif %}
From e8afae268ee733bce504e647c29c96b03851711b Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 12 Apr 2017 19:39:39 -0400 Subject: [PATCH 039/143] Various changes --- config/auth.js | 13 ++++---- server.js | 4 +-- static/css/base.css | 64 +----------------------------------- static/css/header.css | 75 ++++++++++++++++++++++++++++++++++++++++--- static/css/index.css | 6 ++++ views/index.html | 6 ++-- 6 files changed, 89 insertions(+), 79 deletions(-) diff --git a/config/auth.js b/config/auth.js index 75cb268..a7d34ac 100644 --- a/config/auth.js +++ b/config/auth.js @@ -11,13 +11,13 @@ module.exports = function(app, passport) { // Methods for success and failure const loginOutcome = { - failureRedirect: '/login', - failureFlash: true - }, + failureRedirect: '/login', + failureFlash: true + }, connectOutcome = { - failureRedirect: '/settings', - failureFlash: true - }, + failureRedirect: '/settings', + failureFlash: true + }, loginCallback = function(req,res){ res.redirect( req.session.next || '/settings' ); delete req.session.next; @@ -33,6 +33,7 @@ module.exports = function(app, passport) { .post( passport.authenticate('local',loginOutcome), loginCallback ); app.get('/logout', function(req,res){ req.logout(); + req.flash('success',`You have been logged out.`); res.redirect('/'); }); diff --git a/server.js b/server.js index 4fff89f..68c0e00 100755 --- a/server.js +++ b/server.js @@ -74,12 +74,12 @@ const // Static files (keep this before setting default locals) app.use('/static', express.static(__dirname+'/static')); - // Set default locals (keep this after static files) + // Set default locals available to all views (keep this after static files) app.get('/*', function(req,res,next){ // console.log(`Setting local variables for request to ${req.path}.`); // User account - // res.locals.user = req.user; + res.locals.user = req.user; // console.log(`User set as ${res.locals.user}. `); // Flash messages diff --git a/static/css/base.css b/static/css/base.css index d276740..04be7a7 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -129,70 +129,8 @@ section { padding: 10vh 0 5vh; } -/* Alerts */ -.alert { - z-index: 10; - padding: 15px; - border: 1px solid transparent; - border-radius: 4px; -} -.alert a { - z-index: 10; - color: inherit; - font-weight: bold; - text-decoration: underline; -} -.alert a:hover { - color: inherit; - text-decoration: none; -} -.alert h4 { - margin-top: 0; - color: inherit; -} -.alert > p, -.alert > ul { - margin-bottom: 0; -} -.alert > p + p { - margin-top: 5px; -} -.alert-dismissable { - padding-right: 35px; -} -.alert .close, -.alert-dismissible .close { - cursor: pointer; - float: right; - color: inherit; -} -.alert-success { - color: #dff0d8; - background-color: #3c763d; -} -.alert-info { - color: #d9edf7; - background-color: #31708f; -} -.alert-warning { - color: #fcf8e3; - background-color: #8a6d3b; -} -.alert-danger { - color: #f2dede; - background-color: #a94442; -} -.alert.alert-header { - position: relative; - border-radius: 0; - top: 58px; - width: 100%; -} - /* Buttons */ .btn { - text-decoration: none; - text-align: center; font-weight:600; display: inline-block; padding: 15px 30px; @@ -200,7 +138,7 @@ section { cursor: pointer; background: rgba(255,255,255,0.1); color: #eee; - border: 1px solid #999; + border: 1px solid #666; border-radius: .5vw; } .btn:not(.disabled) { -moz-box-shadow: diff --git a/static/css/header.css b/static/css/header.css index 25b88c2..4ca92dd 100644 --- a/static/css/header.css +++ b/static/css/header.css @@ -1,3 +1,4 @@ +/* Main */ header { background: #222; padding: 0; @@ -8,6 +9,8 @@ header { } header a:hover, header a:focus { color: #fbc93d; } + +/* Logo */ header .logo { float: left; font-family: 'Open Sans', sans-serif; @@ -29,6 +32,8 @@ header .logo { text-decoration: none; background: rgba(255,255,255,0.1); } + +/* Navigation */ header nav { float: right; } header nav ul { @@ -46,10 +51,9 @@ header nav { } header nav ul li a:hover, header nav ul li a:focus, header nav ul li a.active, -header .logo:hover { - text-decoration: none; - background: rgba(255,255,255,0.1); -} + + +/* Hamburger */ header .hamburger { display: none; padding: 5px; @@ -151,4 +155,65 @@ header .hamburger-inner::after { right: 10px; top: 13px; } -} \ No newline at end of file +} + +/* Alerts */ +.alert { + padding: 15px; + border: 1px solid transparent; + border-radius: 4px; +} +noscript .alert-danger { + z-index: 40; +} +.alert-danger { + z-index: 30; + color: #f2dede; + background-color: #a94442; +} +.alert-warning { + z-index: 20; + color: #fcf8e3; + background-color: #8a6d3b; +} +.alert-success { + z-index: 10; + color: #dff0d8; + background-color: #3c763d; +} +.alert.alert-header { + position: relative; + border-radius: 0; + top: 58px; + width: 100%; +} +.alert a { + z-index: 10; + color: inherit; + font-weight: bold; + text-decoration: underline; +} +.alert a:hover { + color: inherit; + text-decoration: none; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable { + padding-right: 35px; +} +.alert .close, +.alert-dismissible .close { + cursor: pointer; + float: right; + color: inherit; +} diff --git a/static/css/index.css b/static/css/index.css index d536271..0b78d2a 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -15,6 +15,7 @@ /* End Animations */ .btn { border-radius: 50px; } +.container > p { margin-bottom: 5vh; } .splash { background: #090909; @@ -165,7 +166,12 @@ .light h2 { margin-bottom: 40px; } +.light .btn { + color: #111; + background: rgba(0,0,0,0.1); +} .light .btn:hover:not(.disabled) { + cursor: pointer; text-decoration: none; background: rgba(0,0,0,0.2); } diff --git a/views/index.html b/views/index.html index 0a9370b..44e4dac 100644 --- a/views/index.html +++ b/views/index.html @@ -97,7 +97,7 @@ -
+

Warning!

This is beta software, so there are still kinks to be worked out.

@@ -109,8 +109,8 @@

Hook me up!

-

Just click that there button to create an account.

- Join Tracman +

Just click that there button to create an account.

+ Join Tracman
{% endif %} From 40838f7717559e3c29199545583eda318fc0e4d3 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 12 Apr 2017 19:44:54 -0400 Subject: [PATCH 040/143] Added .gitignore --- .gitignore | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..743f41b --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +# Nodemon config +nodemon.json + +# Ignore node modules +node_modules + +# Secret stuff +config/env* +!config/env-sample.js + +# Ignore docs files +_gh_pages +_site + +# Numerous always-ignore extensions +*.diff +*.err +*.orig +*.log +*.rej +*.swo +*.swp +*.zip +*.vi +*~ + +# OS or Editor folders +.DS_Store +._* +Thumbs.db +.cache +.project +.settings +.tmproj +*.esproj +nbproject +*.sublime-project +*.sublime-workspace +.idea + +# Komodo +*.komodoproject +.komodotools + +# grunt-html-validation +validation-status.json +validation-report.json From df2d11f3ca83cb3e8b915480eb9fba4d9f45166a Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 12 Apr 2017 19:50:28 -0400 Subject: [PATCH 041/143] Modified .gitignore --- .gitignore | 7 +++---- nodemon.json | 4 ++++ 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 nodemon.json diff --git a/.gitignore b/.gitignore index 743f41b..785ddef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,4 @@ -# Nodemon config -nodemon.json - -# Ignore node modules +# npm packages node_modules # Secret stuff @@ -37,6 +34,8 @@ nbproject *.sublime-project *.sublime-workspace .idea +.c9 +c9d # Komodo *.komodoproject diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..ab722a2 --- /dev/null +++ b/nodemon.json @@ -0,0 +1,4 @@ +{ + "verbose": true, + "ext": "html, js, json, css" +} \ No newline at end of file From 80c71a7082e4f87f7f03e53d85e4662db18098e6 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 12 Apr 2017 20:06:34 -0400 Subject: [PATCH 042/143] Minified static files --- package.json | 4 +- static/css/base.css | 8 -- static/css/base.min.css | 2 + static/css/footer.min.css | 1 + static/css/form.min.css | 1 + static/css/header.min.css | 2 + static/css/index.min.css | 2 + static/css/login.min.css | 1 + static/css/map.min.css | 1 + static/js/footer.min.js | 3 + static/js/header.min.js | 3 + static/js/moment.min.min.js | 210 ++++++++++++++++++++++++++++++++++++ views/admin.html | 1 - views/index.html | 6 +- views/login.html | 4 +- views/map.html | 2 +- views/password.html | 2 +- views/settings.html | 2 +- views/templates/base.html | 4 +- views/templates/footer.html | 2 +- views/templates/header.html | 2 +- 21 files changed, 240 insertions(+), 23 deletions(-) create mode 100644 static/css/base.min.css create mode 100644 static/css/footer.min.css create mode 100644 static/css/form.min.css create mode 100644 static/css/header.min.css create mode 100644 static/css/index.min.css create mode 100644 static/css/login.min.css create mode 100644 static/css/map.min.css create mode 100644 static/js/footer.min.js create mode 100644 static/js/header.min.js create mode 100644 static/js/moment.min.min.js diff --git a/package.json b/package.json index e5d4636..1956112 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "karma-chrome-launcher": "^1.0.1", "karma-firefox-launcher": "^1.0.0", "karma-mocha": "^1.1.1", + "minifier": "^0.8.1", "mocha": "^2.5.3", "nodemon": "^1.10.2", "supertest": "^1.2.0" @@ -44,7 +45,8 @@ "scripts": { "test": "mocha test.js", "start": "node server.js", - "dev": "nodemon server.js", + "nodemon": "nodemon server.js", + "minify": "minify static", "update": "sudo n stable && sudo npm update --save && sudo npm prune" }, "repository": { diff --git a/static/css/base.css b/static/css/base.css index 04be7a7..1c7d1e5 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -175,11 +175,3 @@ section { .btn .fa { margin-left: 10px; } - -.group { - width: 100%; -} -.group div { - display: flex; - margin-bottom: 10vh; -} \ No newline at end of file diff --git a/static/css/base.min.css b/static/css/base.min.css new file mode 100644 index 0000000..a64c0cc --- /dev/null +++ b/static/css/base.min.css @@ -0,0 +1,2 @@ +/* Global */ +div,footer,.fa,.container,.container:before,.container:after{box-sizing:border-box}body,input,textarea{padding:0;margin:0;font-family:'Open Sans',sans-serif;font-size:18px;color:#eee}body{background-color:#080808}::-webkit-scrollbar{width:5vw;min-width:10px;max-width:40px}::-webkit-scrollbar-track{background-color:#080808;background-color:rgba(8,8,8,0)}::-webkit-scrollbar-thumb{border-radius:.2vw;background:#333}::selection{background:#999}::-moz-selection{background:#999}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}.hide{display:none!important}.red,.red:hover{color:#fb6e3d!important}.yellow,.yellow:hover{color:#fbc93d!important}.shadow{-moz-box-shadow:.18vw .18vw .36vw #000;-webkit-box-shadow:.18vw .18vw .36vw #000;box-shadow:.18vw .18vw .36vw #000}.shadow:active{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none}.inline{display:inline-block}.flex{width:100%;display:flex;justify-content:space-around}.flex.stretch{justify-content:space-between}.left{float:left}.right{float:right}main{top:60px;position:absolute;left:0;right:0;bottom:0;overflow-y:auto}.container{padding-right:5%;padding-left:5%;width:100%;margin:0 auto}.container:after{content:"";display:block;clear:both}section{padding:10vh 0 5vh}.btn{font-weight:600;display:inline-block;padding:15px 30px;transition:100ms;cursor:pointer;background:rgba(255,255,255,0.1);color:#eee;border:1px solid #666;border-radius:.5vw}.btn:not(.disabled){-moz-box-shadow:inset .11vw .18vw .52vw rgba(255,255,255,.2),inset -.11vw -.18vw .52vw rgba(0,0,0,.4),.11vw .18vw .52vw #000;-webkit-box-shadow:inset .11vw .18vw .52vw rgba(255,255,255,.2),inset -.11vw -.18vw .52vw rgba(0,0,0,.4),.18vw .18vw .36vw #000;box-shadow:inset .11vw .18vw .52vw rgba(255,255,255,.2),inset -.11vw -.18vw .52vw rgba(0,0,0,.4),.18vw .18vw .36vw #000}.btn:hover:not(.disabled){text-decoration:none;background:rgba(255,255,255,0.2)}.btn:active:not(.disabled){-moz-box-shadow:inset .11vw .18vw .52vw rgba(0,0,0,.4),inset -.11vw -.18vw .52vw rgba(255,255,255,.2);-webkit-box-shadow:inset .11vw .18vw .52vw rgba(0,0,0,.4),inset -.11vw -.18vw .52vw rgba(255,255,255,.2);box-shadow:inset .11vw .18vw .52vw rgba(0,0,0,.4),inset -.11vw -.18vw .52vw rgba(255,255,255,.2)}.btn:focus:not(.disabled){border:1px solid #fbc93d}.btn.main{color:#fbc93d}.btn .fa{margin-left:10px} \ No newline at end of file diff --git a/static/css/footer.min.css b/static/css/footer.min.css new file mode 100644 index 0000000..ae64481 --- /dev/null +++ b/static/css/footer.min.css @@ -0,0 +1 @@ +footer{font-weight:300;width:100%;overflow:auto;background:#111;color:#ccc;padding:0 20px;-moz-box-shadow:inset 0 .25vw 1vw #222;-webkit-box-shadow:inset 0 .25vw 1vw #222;box-shadow:inset 0 .25vw 1vw #222}footer .left{float:left;padding:15px 0}footer .left p{margin:0}footer a{font-weight:600;color:#fff}footer a:hover{text-decoration:none}footer .right{text-align:right;float:right;padding:15px 0}footer a .fa{margin-left:5px;font-size:20px;color:inherit}footer .fa a:hover,footer .fa a:focus{color:inherit}@media (max-width:800px){footer{padding:0 10px}}@media (max-width:600px){footer{text-align:center}footer .left,footer .right{float:none}footer .right{padding-top:0}} \ No newline at end of file diff --git a/static/css/form.min.css b/static/css/form.min.css new file mode 100644 index 0000000..a813046 --- /dev/null +++ b/static/css/form.min.css @@ -0,0 +1 @@ +form{margin:auto;max-width:800px}.form-group{display:flex;justify-content:space-between;margin:8% 0}form label{font-size:1.2em;margin-right:3%}form input,form textarea,form select{-moz-box-shadow:inset .11vw .18vw .25vw rgba(0,0,0,.5);-webkit-box-shadow:inset .11vw .18vw .25vw rgba(0,0,0,.5);box-shadow:inset .11vw .18vw .25vw rgba(0,0,0,.5);color:#eee;background-color:#202020;background-color:rgba(255,255,255,0.1);padding:1% 1.5%;border:1px solid #666;border-radius:.3vw}form input:not(.input-addon):not(.input-with-addon):not([type="radio"]):not([type="checkbox"]),form .input-with-addon-group{min-width:50%}form input:active:not(.input-addon),form textarea:active,form select:active,form input:focus:not(.input-addon),form textarea:focus,form select:focus{outline:none;border:1px solid #fbc93d}form .input-with-addon-group{display:flex}form .input-addon{padding:1% 0 1% 1.5%;border-right-color:#202020;border-right-color:rgba(102,102,102,0);border-top-right-radius:0;border-bottom-right-radius:0;-moz-box-shadow:inset .11vw .18vw .25vw rgba(0,0,0,.5);-webkit-box-shadow:inset .11vw .18vw .25vw rgba(0,0,0,.5);box-shadow:inset .11vw .18vw .25vw rgba(0,0,0,.5)}form .input-with-addon{padding:1% 1.5% 1% 0;border-left-color:#202020;border-left-color:rgba(102,102,102,0);border-top-left-radius:0;border-bottom-left-radius:0;-moz-box-shadow:inset 0 .18vw .25vw rgba(0,0,0,.5);-webkit-box-shadow:inset 0 .18vw .25vw rgba(0,0,0,.5);box-shadow:inset 0 .18vw .25vw rgba(0,0,0,.5)}::-webkit-input-placeholder{color:#666}:-moz-placeholder{color:#666;opacity:1}::-moz-placeholder{color:#666;opacity:1}:-ms-input-placeholder{color:#666}form select{-moz-box-shadow:inset 0 1px 6px rgba(255,255,255,.2),inset -.11vw -.18vw .52vw rgba(0,0,0,.4);-webkit-box-shadow:inset -.11vw -.18vw .52vw rgba(255,255,255,.2),inset -.11vw -.18vw .52vw rgba(0,0,0,.4);box-shadow:inset 0 .11vw .52vw rgba(255,255,255,.2),inset 0 .11vw .52vw rgba(0,0,0,.4)}form select > option{background:#222;color:inherit}form .radio{min-width:150px;display:flex;justify-content:space-between}form input[type="checkbox"],form input[type="radio"]{width:auto;margin:8px}form input[type="checkbox"]:active,form input[type="radio"]:active,form input[type="checkbox"]:focus,form input[type="radio"]:focus{outline:1px solid #fbc93d}form .btn{font-size:1.5em} \ No newline at end of file diff --git a/static/css/header.min.css b/static/css/header.min.css new file mode 100644 index 0000000..55c7762 --- /dev/null +++ b/static/css/header.min.css @@ -0,0 +1,2 @@ +/* Main */ +header{background:#222;padding:0;position:fixed;top:0;left:0;width:100%;z-index:200}header a:hover,header a:focus{color:#fbc93d}header .logo{float:left;font-family:'Open Sans',sans-serif;padding:13px 23px;color:#fbc93d;font-weight:800;font-size:22px;line-height:30px;margin:0}header .logo a{color:inherit;font:inherit;text-decoration:inherit;cursor:pointer}header .logo img{margin-right:10px;vertical-align:middle}header .logo:hover{text-decoration:none;background:rgba(255,255,255,0.1)}header nav{float:right}header nav ul{padding:0;margin:0}header nav ul li{display:inline-block;float:left}header nav ul li a,header nav ul li span{text-decoration:inherit;display:inline-block;padding:15px 20px;color:#fff;transition:100ms}header nav ul li a:hover,header nav ul li a:focus,header nav ul li a.active,header .hamburger{display:none;padding:5px;cursor:pointer;transition-property:opacity,-webkit-filter;transition-property:opacity,filter;transition-property:opacity,filter,-webkit-filter;transition-duration:150ms;transition-timing-function:linear}header .hamburger:hover{opacity:0.7}header .hamburger-box{width:40px;height:24px;position:relative}header .hamburger-inner{top:50%;margin-top:-2px}header .hamburger-inner,header .hamburger-inner::before,header .hamburger-inner::after{width:40px;height:4px;background-color:#fff;border-radius:4px;position:absolute;transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:150ms;transition-timing-function:ease}header .hamburger-inner::before,header .hamburger-inner::after{content:"";display:block}header .hamburger-inner::before{top:-10px}header .hamburger-inner::after{bottom:-10px}header .hamburger--slider .hamburger-inner{top:0}header .hamburger--slider .hamburger-inner::before{top:10px;transition-property:opacity,-webkit-transform;transition-property:transform,opacity;transition-property:transform,opacity,-webkit-transform;transition-timing-function:ease;transition-duration:200ms}header .hamburger--slider .hamburger-inner::after{top:20px}header .hamburger--slider.is-active .hamburger-inner{-webkit-transform:translate3d(0,10px,0) rotate(45deg);-moz-transform:translate3d(0,10px,0) rotate(45deg);-md-transform:translate3d(0,10px,0) rotate(45deg);-o-transform:translate3d(0,10px,0) rotate(45deg);transform:translate3d(0,10px,0) rotate(45deg)}header .hamburger--slider.is-active .hamburger-inner::before{-webkit-transform:rotate(-45deg) translate3d(-5.71429px,-6px,0);-moz-transform:rotate(-45deg) translate3d(-5.71429px,-6px,0);-ms-transform:rotate(-45deg) translate3d(-5.71429px,-6px,0);-o-transform:rotate(-45deg) translate3d(-5.71429px,-6px,0);transform:rotate(-45deg) translate3d(-5.71429px,-6px,0);opacity:0}header .hamburger--slider.is-active .hamburger-inner::after{-webkit-transform:translate3d(0,-20px,0) rotate(-90deg);-moz-transform:translate3d(0,-20px,0) rotate(-90deg);-ms-transform:translate3d(0,-20px,0) rotate(-90deg);-o-transform:translate3d(0,-20px,0) rotate(-90deg);transform:translate3d(0,-20px,0) rotate(-90deg)}@media (max-width:800px){header nav ul li a{padding:15px}}@media (max-width:600px){header nav{float:none;position:fixed;top:56px;right:-300px;bottom:0;width:100%;max-width:300px;background:#333;transition:100ms}header nav.visible{right:0}header nav ul li{display:block;float:none;width:100%}header nav ul li a{display:block;width:100%;border-bottom:1px solid rgba(255,255,255,0.1)}header .hamburger{display:inline-block;color:#fff;position:absolute;right:10px;top:13px}}.alert{padding:15px;border:1px solid transparent;border-radius:4px}noscript .alert-danger{z-index:40}.alert-danger{z-index:30;color:#f2dede;background-color:#a94442}.alert-warning{z-index:20;color:#fcf8e3;background-color:#8a6d3b}.alert-success{z-index:10;color:#dff0d8;background-color:#3c763d}.alert.alert-header{position:relative;border-radius:0;top:58px;width:100%}.alert a{z-index:10;color:inherit;font-weight:bold;text-decoration:underline}.alert a:hover{color:inherit;text-decoration:none}.alert h4{margin-top:0;color:inherit}.alert > p,.alert > ul{margin-bottom:0}.alert > p + p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert .close,.alert-dismissible .close{cursor:pointer;float:right;color:inherit} \ No newline at end of file diff --git a/static/css/index.min.css b/static/css/index.min.css new file mode 100644 index 0000000..bc2a443 --- /dev/null +++ b/static/css/index.min.css @@ -0,0 +1,2 @@ +/* Animations */ +@keyframes pulse{0%{transform:scale(1)}50%{transform:scale(0.8)}100%{transform:scale(1)}}@keyframes spin{0%{transform:rotate(30deg)}100%{transform:rotate(210deg)}}@keyframes spin2{0%{transform:rotate(150deg)}100%{transform:rotate(330deg)}}.btn{border-radius:50px}.container > p{margin-bottom:5vh}.splash{background:#090909;background-image:url(/static/img/style/map.jpg);background-size:cover;color:#FFF;height:100vh;overflow:hidden;position:relative}.splash:after,.splash:before{content:"";display:block;position:absolute;top:-40px;right:-40px;bottom:-40px;left:-40px}.splash:after{background:rgba(255,255,255,0.05);transform:rotate(30deg);animation:spin 60s infinite linear}.splash:before{background:rgba(0,0,0,0.5);transform:rotate(150deg);animation:spin2 50s infinite linear}.splash .container{position:relative;top:48%;transform:translateY(-50%);z-index:5}.splash h1{color:#fbc93d}.splash h2{margin-bottom:40px}.splash .btn{margin:0 20px 10px 0}.splash .btn:hover{color:#fff;background:rgba(0,0,0,0.5)}.overview{text-align:center}.overview > div > div{float:left;width:33%;padding:0 40px 0 40px}.overview .fa{display:inline-block;color:#222;font-size:50px;width:100px;height:100px;border-radius:50px;background:#f6f6f6;margin-bottom:20px;padding-top:25px}.overview p{margin-bottom:0}.feature.app{background:#111}.feature{position:relative;overflow:hidden}.feature img{position:absolute;top:100px;right:55%}.feature:nth-of-type(even) img{right:auto;left:55%}.feature > div > div{width:50%;float:right}.feature > div > div > p{margin-bottom:40px}.feature:nth-of-type(even) > div > div{float:left}.feature ul{margin:0;padding:0}.feature ul li{display:block;margin-bottom:20px;padding-bottom:20px;border-bottom:1px solid #eee}.feature ul li:last-child{margin-bottom:0;padding-bottom:0;border-bottom:0}.feature ul li h3{margin:0 0 5px 0}.feature ul li p:last-child{margin:0}.feature ul li .fa{float:left;font-size:30px;background:#fbc93d;color:#000;width:50px;height:50px;display:inline-block;text-align:center;padding-top:10px;border-radius:25px;margin-right:20px;margin-top:7px}.feature ul li p{overflow:hidden}.light{color:#222;position:relative;overflow:hidden}.light:after{content:"";display:block;position:absolute;top:-40px;right:-40px;bottom:-40px;left:-40px;background:rgba(255,255,255,0.1);transform:rotate(30deg)}.light h2{margin-bottom:40px}.light .btn{color:#111;background:rgba(0,0,0,0.1)}.light .btn:hover:not(.disabled){cursor:pointer;text-decoration:none;background:rgba(0,0,0,0.2)}.disclaimer{color:#fb6e3d;background:#000}.disclaimer .container{position:relative;z-index:10}.disclaimer a,.disclaimer a:hover{color:#fb6e3d}.join{background:#fbc93d}.join input,.join textarea{color:#111}.join .input{width:47%;float:left}.join .submit{width:47%;float:right}.join .input:nth-of-type(odd){margin-right:6%}.join .message{display:block;clear:both;float:none;padding-top:10px}.join .input input{display:inline-block;float:left;width:100%;background:rgba(255,255,255,0.3);border:0;padding:10px 15px}.join .message textarea{display:block;width:100%;height:200px;background:rgba(255,255,255,0.3);border:0;padding:10px 15px;resize:vertical}.join label{position:relative;z-index:10}.join label.input span,.join label.message span{display:inline-block;float:left}.join .submit{text-align:center;padding-top:10px}.join .submit .btn,.join .submit .alert{position:static;float:right}@media (max-width:800px){section{padding:80px 10px}.splash{height:auto;padding:150px 10px 80px 10px;text-align:center}.splash .container{position:relative;top:0;transform:none}.overview > div > div{padding:0 20px}.feature img{right:65%}.feature:nth-of-type(even) img{left:65%}.feature > div > div{width:60%}}@media (max-width:600px){section{padding:40px 10px}.splash{padding:100px 10px 40px 10px}.overview > div > div{float:none;width:100%;margin-bottom:40px;padding:0}.overview > div > div:last-child{margin-bottom:0}.overview p{overflow:hidden}.feature img{display:none}.feature > div > div{width:100%;float:none}.join .input{display:block;width:100%;float:none}.join .input:nth-of-type(odd){margin-right:0}.join label{padding-top:10px}.join label:first-of-type{padding-top:0}} \ No newline at end of file diff --git a/static/css/login.min.css b/static/css/login.min.css new file mode 100644 index 0000000..4fa9c29 --- /dev/null +++ b/static/css/login.min.css @@ -0,0 +1 @@ +section > .flex > div{width:50%;padding:0 2%}form input{width:96%}form input.btn,form #social-login{width:100%}#social-login .btn{padding:0;font-size:1.3em;height:60px;text-align:center;width:60px;margin:0 3%;color:#FFF}#social-login .btn .fa{margin:0;position:relative;padding-top:20px}#social-login .btn.gp{background:rgb(206,77,57)}#social-login .btn.gp:hover{background:rgb(251,122,102)}#social-login .btn.fb{background:rgb(48,88,145)}#social-login .btn.fb:hover{background:rgb(93,133,190)}#social-login .btn.tw{background:rgb(44,168,210)}#social-login .btn.tw:hover{background:rgb(89,213,255)}@media (max-width:800px){section > .flex{flex-direction:column}section > .flex > div{width:100%}hr.hide{display:block}} \ No newline at end of file diff --git a/static/css/map.min.css b/static/css/map.min.css new file mode 100644 index 0000000..651d5bf --- /dev/null +++ b/static/css/map.min.css @@ -0,0 +1 @@ +body{color:#fff;width:100%;height:100%;background:#000}.wrap{position:absolute;bottom:0;width:100%}.centered.alert{text-align:center;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}#map,#pano{position:relative}#pano{float:right}img#panoImg{width:100%;height:100%}#notset{display:none}.map-logo{margin-left:-75px;background:rgba(0,0,0,.7);padding:0 10px 0 75px;font-size:2em}.map-logo a{color:#fbc93d}.tim{color:#000;font-size:12px;padding-left:5px;padding-right:5px;background-color:rgba(255,255,255,.7)}.spd-sign,.alt-sign{text-align:center;padding:2%;border-radius:3px;margin:3%}.spd-sign{color:#000;background-color:#FFF;border:2px solid #000}.alt-sign{color:#FFF;background-color:#009800;border:2px solid #FFF}@media (min-width:400px){.spd,.alt{height:40px;font-size:32px}.alt-unit,.spd-unit{font-size:12px}.alt-label,.spd-label{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:0) 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}}#controls{width:100vw;position:absolute;bottom:50px;display:flex;justify-content:space-around}#controls .btn{z-index:50;background:#222;height:10vh;padding:2vh 0}#controls .btn:hover{background:#333}#controls .btn.set,#controls .btn.clear{width:30vw}#controls .btn.track{width:35vw} \ No newline at end of file diff --git a/static/js/footer.min.js b/static/js/footer.min.js new file mode 100644 index 0000000..d8dbe51 --- /dev/null +++ b/static/js/footer.min.js @@ -0,0 +1,3 @@ +"use strict" +function setFooter(){var o=$(window).height(),t=$("footer").offset().top+$("footer").height() +o>t&&$("footer").css("margin-top",o-t)}$(function(){setFooter()}),$(window).resize(function(){setFooter()}) diff --git a/static/js/header.min.js b/static/js/header.min.js new file mode 100644 index 0000000..03d1fef --- /dev/null +++ b/static/js/header.min.js @@ -0,0 +1,3 @@ +/* global $ */ +"use strict" +$(document).ready(function(){$(".hamburger").click(function(){$(".hamburger").toggleClass("is-active"),$("nav").toggleClass("visible")}),$("nav").click(function(){$(".hamburger").removeClass("is-active"),$("nav").removeClass("visible")}),$(".wrap, section").click(function(){$(".hamburger").removeClass("is-active"),$("nav").removeClass("visible")}),$(".alert-dismissible .close").click(function(){$(this).parent().slideUp(500)})}) diff --git a/static/js/moment.min.min.js b/static/js/moment.min.min.js new file mode 100644 index 0000000..e7b3172 --- /dev/null +++ b/static/js/moment.min.min.js @@ -0,0 +1,210 @@ +//! moment.js +//! version : 2.12.0 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.moment=e()}(this,function(){"use strict" +function t(){return Qn.apply(null,arguments)}function e(t){return t instanceof Array||"[object Array]"===Object.prototype.toString.call(t)}function n(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function i(t,e){var n,i=[] +for(n=0;n0)for(n in Xn)i=Xn[n],s=e[i],h(s)||(t[i]=s) +return t}function f(e){c(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),!1===Kn&&(Kn=!0,t.updateOffset(this),Kn=!1)}function m(t){return t instanceof f||null!=t&&null!=t._isAMomentObject}function _(t){return 0>t?Math.ceil(t):Math.floor(t)}function y(t){var e=+t,n=0 +return 0!==e&&isFinite(e)&&(n=_(e)),n}function g(t,e,n){var i,s=Math.min(t.length,e.length),r=Math.abs(t.length-e.length),a=0 +for(i=0;s>i;i++)(n&&t[i]!==e[i]||!n&&y(t[i])!==y(e[i]))&&a++ +return a+r}function p(e){!1===t.suppressDeprecationWarnings&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+e)}function v(t,e){var n=!0 +return r(function(){return n&&(p(t+"\nArguments: "+Array.prototype.slice.call(arguments).join(", ")+"\n"+(new Error).stack),n=!1),e.apply(this,arguments)},e)}function D(t,e){ti[t]||(p(e),ti[t]=!0)}function M(t){return t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)}function S(t){return"[object Object]"===Object.prototype.toString.call(t)}function Y(t){var e,n +for(n in t)e=t[n],M(e)?this[n]=e:this["_"+n]=e +this._config=t,this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function w(t,e){var n,i=r({},t) +for(n in e)s(e,n)&&(S(t[n])&&S(e[n])?(i[n]={},r(i[n],t[n]),r(i[n],e[n])):null!=e[n]?i[n]=e[n]:delete i[n]) +return i}function k(t){null!=t&&this.set(t)}function T(t){return t?t.toLowerCase().replace("_","-"):t}function b(t){for(var e,n,i,s,r=0;r0;){if(i=O(s.slice(0,e).join("-")))return i +if(n&&n.length>=e&&g(s,n,!0)>=e-1)break +e--}r++}return null}function O(t){var e=null +if(!ni[t]&&"undefined"!=typeof module&&module&&module.exports)try{e=ei._abbr,require("./locale/"+t),W(e)}catch(t){}return ni[t]}function W(t,e){var n +return t&&(n=h(e)?G(t):x(t,e))&&(ei=n),ei._abbr}function x(t,e){return null!==e?(e.abbr=t,null!=ni[t]?(D("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale"),e=w(ni[t]._config,e)):null!=e.parentLocale&&(null!=ni[e.parentLocale]?e=w(ni[e.parentLocale]._config,e):D("parentLocaleUndefined","specified parentLocale is not defined yet")),ni[t]=new k(e),W(t),ni[t]):(delete ni[t],null)}function U(t,e){if(null!=e){var n +null!=ni[t]&&(e=w(ni[t]._config,e)),n=new k(e),n.parentLocale=ni[t],ni[t]=n,W(t)}else null!=ni[t]&&(null!=ni[t].parentLocale?ni[t]=ni[t].parentLocale:null!=ni[t]&&delete ni[t]) +return ni[t]}function G(t){var n +if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return ei +if(!e(t)){if(n=O(t))return n +t=[t]}return b(t)}function P(){return Object.keys(ni)}function C(t,e){var n=t.toLowerCase() +ii[n]=ii[n+"s"]=ii[e]=t}function F(t){return"string"==typeof t?ii[t]||ii[t.toLowerCase()]:void 0}function H(t){var e,n,i={} +for(n in t)s(t,n)&&(e=F(n))&&(i[e]=t[n]) +return i}function L(e,n){return function(i){return null!=i?(N(this,e,i),t.updateOffset(this,n),this):V(this,e)}}function V(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function N(t,e,n){t.isValid()&&t._d["set"+(t._isUTC?"UTC":"")+e](n)}function I(t,e){var n +if("object"==typeof t)for(n in t)this.set(n,t[n]) +else if(t=F(t),M(this[t]))return this[t](e) +return this}function A(t,e,n){var i=""+Math.abs(t),s=e-i.length +return(t>=0?n?"+":"":"-")+Math.pow(10,Math.max(0,s)).toString().substr(1)+i}function R(t,e,n,i){var s=i +"string"==typeof i&&(s=function(){return this[i]()}),t&&(oi[t]=s),e&&(oi[e[0]]=function(){return A(s.apply(this,arguments),e[1],e[2])}),n&&(oi[n]=function(){return this.localeData().ordinal(s.apply(this,arguments),t)})}function E(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function j(t){var e,n,i=t.match(si) +for(e=0,n=i.length;n>e;e++)oi[i[e]]?i[e]=oi[i[e]]:i[e]=E(i[e]) +return function(s){var r="" +for(e=0;n>e;e++)r+=i[e]instanceof Function?i[e].call(s,t):i[e] +return r}}function z(t,e){return t.isValid()?(e=Z(e,t.localeData()),ai[e]=ai[e]||j(e),ai[e](t)):t.localeData().invalidDate()}function Z(t,e){function n(t){return e.longDateFormat(t)||t}var i=5 +for(ri.lastIndex=0;i>=0&&ri.test(t);)t=t.replace(ri,n),ri.lastIndex=0,i-=1 +return t}function $(t,e,n){Mi[t]=M(e)?e:function(t,i){return t&&n?n:e}}function q(t,e){return s(Mi,t)?Mi[t](e._strict,e._locale):new RegExp(J(t))}function J(t){return B(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,n,i,s){return e||n||i||s}))}function B(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function Q(t,e){var n,i=e +for("string"==typeof t&&(t=[t]),"number"==typeof e&&(i=function(t,n){n[e]=y(t)}),n=0;ni;i++){if(s=a([2e3,i]),n&&!this._longMonthsParse[i]&&(this._longMonthsParse[i]=new RegExp("^"+this.months(s,"").replace(".","")+"$","i"),this._shortMonthsParse[i]=new RegExp("^"+this.monthsShort(s,"").replace(".","")+"$","i")),n||this._monthsParse[i]||(r="^"+this.months(s,"")+"|^"+this.monthsShort(s,""),this._monthsParse[i]=new RegExp(r.replace(".",""),"i")),n&&"MMMM"===e&&this._longMonthsParse[i].test(t))return i +if(n&&"MMM"===e&&this._shortMonthsParse[i].test(t))return i +if(!n&&this._monthsParse[i].test(t))return i}}function st(t,e){var n +if(!t.isValid())return t +if("string"==typeof e)if(/^\d+$/.test(e))e=y(e) +else if("number"!=typeof(e=t.localeData().monthsParse(e)))return t +return n=Math.min(t.date(),tt(t.year(),e)),t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,n),t}function rt(e){return null!=e?(st(this,e),t.updateOffset(this,!0),this):V(this,"Month")}function at(){return tt(this.year(),this.month())}function ot(t){return this._monthsParseExact?(s(this,"_monthsRegex")||dt.call(this),t?this._monthsShortStrictRegex:this._monthsShortRegex):this._monthsShortStrictRegex&&t?this._monthsShortStrictRegex:this._monthsShortRegex}function ut(t){return this._monthsParseExact?(s(this,"_monthsRegex")||dt.call(this),t?this._monthsStrictRegex:this._monthsRegex):this._monthsStrictRegex&&t?this._monthsStrictRegex:this._monthsRegex}function dt(){function t(t,e){return e.length-t.length}var e,n,i=[],s=[],r=[] +for(e=0;12>e;e++)n=a([2e3,e]),i.push(this.monthsShort(n,"")),s.push(this.months(n,"")),r.push(this.months(n,"")),r.push(this.monthsShort(n,"")) +for(i.sort(t),s.sort(t),r.sort(t),e=0;12>e;e++)i[e]=B(i[e]),s[e]=B(s[e]),r[e]=B(r[e]) +this._monthsRegex=new RegExp("^("+r.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+s.join("|")+")$","i"),this._monthsShortStrictRegex=new RegExp("^("+i.join("|")+")$","i")}function lt(t){var e,n=t._a +return n&&-2===u(t).overflow&&(e=n[wi]<0||n[wi]>11?wi:n[ki]<1||n[ki]>tt(n[Yi],n[wi])?ki:n[Ti]<0||n[Ti]>24||24===n[Ti]&&(0!==n[bi]||0!==n[Oi]||0!==n[Wi])?Ti:n[bi]<0||n[bi]>59?bi:n[Oi]<0||n[Oi]>59?Oi:n[Wi]<0||n[Wi]>999?Wi:-1,u(t)._overflowDayOfYear&&(Yi>e||e>ki)&&(e=ki),u(t)._overflowWeeks&&-1===e&&(e=xi),u(t)._overflowWeekday&&-1===e&&(e=Ui),u(t).overflow=e),t}function ht(t){var e,n,i,s,r,a,o=t._i,d=Fi.exec(o)||Hi.exec(o) +if(d){for(u(t).iso=!0,e=0,n=Vi.length;n>e;e++)if(Vi[e][1].exec(d[1])){s=Vi[e][0],i=!1!==Vi[e][2] +break}if(null==s)return void(t._isValid=!1) +if(d[3]){for(e=0,n=Ni.length;n>e;e++)if(Ni[e][1].exec(d[3])){r=(d[2]||" ")+Ni[e][0] +break}if(null==r)return void(t._isValid=!1)}if(!i&&null!=r)return void(t._isValid=!1) +if(d[4]){if(!Li.exec(d[4]))return void(t._isValid=!1) +a="Z"}t._f=s+(r||"")+(a||""),Tt(t)}else t._isValid=!1}function ct(e){var n=Ii.exec(e._i) +return null!==n?void(e._d=new Date(+n[1])):(ht(e),void(!1===e._isValid&&(delete e._isValid,t.createFromInputFallback(e))))}function ft(t,e,n,i,s,r,a){var o=new Date(t,e,n,i,s,r,a) +return 100>t&&t>=0&&isFinite(o.getFullYear())&&o.setFullYear(t),o}function mt(t){var e=new Date(Date.UTC.apply(null,arguments)) +return 100>t&&t>=0&&isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t),e}function _t(t){return yt(t)?366:365}function yt(t){return t%4==0&&t%100!=0||t%400==0}function gt(){return yt(this.year())}function pt(t,e,n){var i=7+e-n +return-(7+mt(t,0,i).getUTCDay()-e)%7+i-1}function vt(t,e,n,i,s){var r,a,o=(7+n-i)%7,u=pt(t,i,s),d=1+7*(e-1)+o+u +return 0>=d?(r=t-1,a=_t(r)+d):d>_t(t)?(r=t+1,a=d-_t(t)):(r=t,a=d),{year:r,dayOfYear:a}}function Dt(t,e,n){var i,s,r=pt(t.year(),e,n),a=Math.floor((t.dayOfYear()-r-1)/7)+1 +return 1>a?(s=t.year()-1,i=a+Mt(s,e,n)):a>Mt(t.year(),e,n)?(i=a-Mt(t.year(),e,n),s=t.year()+1):(s=t.year(),i=a),{week:i,year:s}}function Mt(t,e,n){var i=pt(t,e,n),s=pt(t+1,e,n) +return(_t(t)-i+s)/7}function St(t,e,n){return null!=t?t:null!=e?e:n}function Yt(e){var n=new Date(t.now()) +return e._useUTC?[n.getUTCFullYear(),n.getUTCMonth(),n.getUTCDate()]:[n.getFullYear(),n.getMonth(),n.getDate()]}function wt(t){var e,n,i,s,r=[] +if(!t._d){for(i=Yt(t),t._w&&null==t._a[ki]&&null==t._a[wi]&&kt(t),t._dayOfYear&&(s=St(t._a[Yi],i[Yi]),t._dayOfYear>_t(s)&&(u(t)._overflowDayOfYear=!0),n=mt(s,0,t._dayOfYear),t._a[wi]=n.getUTCMonth(),t._a[ki]=n.getUTCDate()),e=0;3>e&&null==t._a[e];++e)t._a[e]=r[e]=i[e] +for(;7>e;e++)t._a[e]=r[e]=null==t._a[e]?2===e?1:0:t._a[e] +24===t._a[Ti]&&0===t._a[bi]&&0===t._a[Oi]&&0===t._a[Wi]&&(t._nextDay=!0,t._a[Ti]=0),t._d=(t._useUTC?mt:ft).apply(null,r),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[Ti]=24)}}function kt(t){var e,n,i,s,r,a,o,d +e=t._w,null!=e.GG||null!=e.W||null!=e.E?(r=1,a=4,n=St(e.GG,t._a[Yi],Dt(Ct(),1,4).year),i=St(e.W,1),(1>(s=St(e.E,1))||s>7)&&(d=!0)):(r=t._locale._week.dow,a=t._locale._week.doy,n=St(e.gg,t._a[Yi],Dt(Ct(),r,a).year),i=St(e.w,1),null!=e.d?(0>(s=e.d)||s>6)&&(d=!0):null!=e.e?(s=e.e+r,(e.e<0||e.e>6)&&(d=!0)):s=r),1>i||i>Mt(n,r,a)?u(t)._overflowWeeks=!0:null!=d?u(t)._overflowWeekday=!0:(o=vt(n,i,s,r,a),t._a[Yi]=o.year,t._dayOfYear=o.dayOfYear)}function Tt(e){if(e._f===t.ISO_8601)return void ht(e) +e._a=[],u(e).empty=!0 +var n,i,s,r,a,o=""+e._i,d=o.length,l=0 +for(s=Z(e._f,e._locale).match(si)||[],n=0;n0&&u(e).unusedInput.push(a),o=o.slice(o.indexOf(i)+i.length),l+=i.length),oi[r]?(i?u(e).empty=!1:u(e).unusedTokens.push(r),K(r,i,e)):e._strict&&!i&&u(e).unusedTokens.push(r) +u(e).charsLeftOver=d-l,o.length>0&&u(e).unusedInput.push(o),!0===u(e).bigHour&&e._a[Ti]<=12&&e._a[Ti]>0&&(u(e).bigHour=void 0),e._a[Ti]=bt(e._locale,e._a[Ti],e._meridiem),wt(e),lt(e)}function bt(t,e,n){var i +return null==n?e:null!=t.meridiemHour?t.meridiemHour(e,n):null!=t.isPM?(i=t.isPM(n),i&&12>e&&(e+=12),i||12!==e||(e=0),e):e}function Ot(t){var e,n,i,s,a +if(0===t._f.length)return u(t).invalidFormat=!0,void(t._d=new Date(NaN)) +for(s=0;sa)&&(i=a,n=e)) +r(t,n||e)}function Wt(t){if(!t._d){var e=H(t._i) +t._a=i([e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],function(t){return t&&parseInt(t,10)}),wt(t)}}function xt(t){var e=new f(lt(Ut(t))) +return e._nextDay&&(e.add(1,"d"),e._nextDay=void 0),e}function Ut(t){var i=t._i,s=t._f +return t._locale=t._locale||G(t._l),null===i||void 0===s&&""===i?l({nullInput:!0}):("string"==typeof i&&(t._i=i=t._locale.preparse(i)),m(i)?new f(lt(i)):(e(s)?Ot(t):s?Tt(t):n(i)?t._d=i:Gt(t),d(t)||(t._d=null),t))}function Gt(s){var r=s._i +void 0===r?s._d=new Date(t.now()):n(r)?s._d=new Date(+r):"string"==typeof r?ct(s):e(r)?(s._a=i(r.slice(0),function(t){return parseInt(t,10)}),wt(s)):"object"==typeof r?Wt(s):"number"==typeof r?s._d=new Date(r):t.createFromInputFallback(s)}function Pt(t,e,n,i,s){var r={} +return"boolean"==typeof n&&(i=n,n=void 0),r._isAMomentObject=!0,r._useUTC=r._isUTC=s,r._l=n,r._i=t,r._f=e,r._strict=i,xt(r)}function Ct(t,e,n,i){return Pt(t,e,n,i,!1)}function Ft(t,n){var i,s +if(1===n.length&&e(n[0])&&(n=n[0]),!n.length)return Ct() +for(i=n[0],s=1;st&&(t=-t,n="-"),n+A(~~(t/60),2)+e+A(~~t%60,2)})}function At(t,e){var n=(e||"").match(t)||[],i=n[n.length-1]||[],s=(i+"").match(zi)||["-",0,0],r=60*s[1]+y(s[2]) +return"+"===s[0]?r:-r}function Rt(e,i){var s,r +return i._isUTC?(s=i.clone(),r=(m(e)||n(e)?+e:+Ct(e))-+s,s._d.setTime(+s._d+r),t.updateOffset(s,!1),s):Ct(e).local()}function Et(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function jt(e,n){var i,s=this._offset||0 +return this.isValid()?null!=e?("string"==typeof e?e=At(vi,e):Math.abs(e)<16&&(e*=60),!this._isUTC&&n&&(i=Et(this)),this._offset=e,this._isUTC=!0,null!=i&&this.add(i,"m"),s!==e&&(!n||this._changeInProgress?oe(this,ee(e-s,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,t.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?s:Et(this):null!=e?this:NaN}function zt(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()}function Zt(t){return this.utcOffset(0,t)}function $t(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(Et(this),"m")),this}function qt(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(At(pi,this._i)),this}function Jt(t){return!!this.isValid()&&(t=t?Ct(t).utcOffset():0,(this.utcOffset()-t)%60==0)}function Bt(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Qt(){if(!h(this._isDSTShifted))return this._isDSTShifted +var t={} +if(c(t,this),t=Ut(t),t._a){var e=t._isUTC?a(t._a):Ct(t._a) +this._isDSTShifted=this.isValid()&&g(t._a,e.toArray())>0}else this._isDSTShifted=!1 +return this._isDSTShifted}function Xt(){return!!this.isValid()&&!this._isUTC}function Kt(){return!!this.isValid()&&this._isUTC}function te(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function ee(t,e){var n,i,r,a=t,o=null +return Nt(t)?a={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(a={},e?a[e]=t:a.milliseconds=t):(o=Zi.exec(t))?(n="-"===o[1]?-1:1,a={y:0,d:y(o[ki])*n,h:y(o[Ti])*n,m:y(o[bi])*n,s:y(o[Oi])*n,ms:y(o[Wi])*n}):(o=$i.exec(t))?(n="-"===o[1]?-1:1,a={y:ne(o[2],n),M:ne(o[3],n),w:ne(o[4],n),d:ne(o[5],n),h:ne(o[6],n),m:ne(o[7],n),s:ne(o[8],n)}):null==a?a={}:"object"==typeof a&&("from"in a||"to"in a)&&(r=se(Ct(a.from),Ct(a.to)),a={},a.ms=r.milliseconds,a.M=r.months),i=new Vt(a),Nt(t)&&s(t,"_locale")&&(i._locale=t._locale),i}function ne(t,e){var n=t&&parseFloat(t.replace(",",".")) +return(isNaN(n)?0:n)*e}function ie(t,e){var n={milliseconds:0,months:0} +return n.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(n.months,"M").isAfter(e)&&--n.months,n.milliseconds=+e-+t.clone().add(n.months,"M"),n}function se(t,e){var n +return t.isValid()&&e.isValid()?(e=Rt(e,t),t.isBefore(e)?n=ie(t,e):(n=ie(e,t),n.milliseconds=-n.milliseconds,n.months=-n.months),n):{milliseconds:0,months:0}}function re(t){return 0>t?-1*Math.round(-1*t):Math.round(t)}function ae(t,e){return function(n,i){var s,r +return null===i||isNaN(+i)||(D(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period)."),r=n,n=i,i=r),n="string"==typeof n?+n:n,s=ee(n,i),oe(this,s,t),this}}function oe(e,n,i,s){var r=n._milliseconds,a=re(n._days),o=re(n._months) +e.isValid()&&(s=null==s||s,r&&e._d.setTime(+e._d+r*i),a&&N(e,"Date",V(e,"Date")+a*i),o&&st(e,V(e,"Month")+o*i),s&&t.updateOffset(e,a||o))}function ue(t,e){var n=t||Ct(),i=Rt(n,this).startOf("day"),s=this.diff(i,"days",!0),r=-6>s?"sameElse":-1>s?"lastWeek":0>s?"lastDay":1>s?"sameDay":2>s?"nextDay":7>s?"nextWeek":"sameElse",a=e&&(M(e[r])?e[r]():e[r]) +return this.format(a||this.localeData().calendar(r,this,Ct(n)))}function de(){return new f(this)}function le(t,e){var n=m(t)?t:Ct(t) +return!(!this.isValid()||!n.isValid())&&(e=F(h(e)?"millisecond":e),"millisecond"===e?+this>+n:+n<+this.clone().startOf(e))}function he(t,e){var n=m(t)?t:Ct(t) +return!(!this.isValid()||!n.isValid())&&(e=F(h(e)?"millisecond":e),"millisecond"===e?+n>+this:+this.clone().endOf(e)<+n)}function ce(t,e,n){return this.isAfter(t,n)&&this.isBefore(e,n)}function fe(t,e){var n,i=m(t)?t:Ct(t) +return!(!this.isValid()||!i.isValid())&&(e=F(e||"millisecond"),"millisecond"===e?+this==+i:(n=+i,+this.clone().startOf(e)<=n&&n<=+this.clone().endOf(e)))}function me(t,e){return this.isSame(t,e)||this.isAfter(t,e)}function _e(t,e){return this.isSame(t,e)||this.isBefore(t,e)}function ye(t,e,n){var i,s,r,a +return this.isValid()?(i=Rt(t,this),i.isValid()?(s=6e4*(i.utcOffset()-this.utcOffset()),e=F(e),"year"===e||"month"===e||"quarter"===e?(a=ge(this,i),"quarter"===e?a/=3:"year"===e&&(a/=12)):(r=this-i,a="second"===e?r/1e3:"minute"===e?r/6e4:"hour"===e?r/36e5:"day"===e?(r-s)/864e5:"week"===e?(r-s)/6048e5:r),n?a:_(a)):NaN):NaN}function ge(t,e){var n,i,s=12*(e.year()-t.year())+(e.month()-t.month()),r=t.clone().add(s,"months") +return 0>e-r?(n=t.clone().add(s-1,"months"),i=(e-r)/(r-n)):(n=t.clone().add(s+1,"months"),i=(e-r)/(n-r)),-(s+i)}function pe(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function ve(){var t=this.clone().utc() +return 0r&&(e=r),ze.call(this,t,e,n,i,s))}function ze(t,e,n,i,s){var r=vt(t,e,n,i,s),a=mt(r.year,0,r.dayOfYear) +return this.year(a.getUTCFullYear()),this.month(a.getUTCMonth()),this.date(a.getUTCDate()),this}function Ze(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)}function $e(t){return Dt(t,this._week.dow,this._week.doy).week}function qe(){return this._week.dow}function Je(){return this._week.doy}function Be(t){var e=this.localeData().week(this) +return null==t?e:this.add(7*(t-e),"d")}function Qe(t){var e=Dt(this,1,4).week +return null==t?e:this.add(7*(t-e),"d")}function Xe(t,e){return"string"!=typeof t?t:isNaN(t)?(t=e.weekdaysParse(t),"number"==typeof t?t:null):parseInt(t,10)}function Ke(t,n){return e(this._weekdays)?this._weekdays[t.day()]:this._weekdays[this._weekdays.isFormat.test(n)?"format":"standalone"][t.day()]}function tn(t){return this._weekdaysShort[t.day()]}function en(t){return this._weekdaysMin[t.day()]}function nn(t,e,n){var i,s,r +for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),i=0;7>i;i++){if(s=Ct([2e3,1]).day(i),n&&!this._fullWeekdaysParse[i]&&(this._fullWeekdaysParse[i]=new RegExp("^"+this.weekdays(s,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[i]=new RegExp("^"+this.weekdaysShort(s,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[i]=new RegExp("^"+this.weekdaysMin(s,"").replace(".",".?")+"$","i")),this._weekdaysParse[i]||(r="^"+this.weekdays(s,"")+"|^"+this.weekdaysShort(s,"")+"|^"+this.weekdaysMin(s,""),this._weekdaysParse[i]=new RegExp(r.replace(".",""),"i")),n&&"dddd"===e&&this._fullWeekdaysParse[i].test(t))return i +if(n&&"ddd"===e&&this._shortWeekdaysParse[i].test(t))return i +if(n&&"dd"===e&&this._minWeekdaysParse[i].test(t))return i +if(!n&&this._weekdaysParse[i].test(t))return i}}function sn(t){if(!this.isValid())return null!=t?this:NaN +var e=this._isUTC?this._d.getUTCDay():this._d.getDay() +return null!=t?(t=Xe(t,this.localeData()),this.add(t-e,"d")):e}function rn(t){if(!this.isValid())return null!=t?this:NaN +var e=(this.day()+7-this.localeData()._week.dow)%7 +return null==t?e:this.add(t-e,"d")}function an(t){return this.isValid()?null==t?this.day()||7:this.day(this.day()%7?t:t-7):null!=t?this:NaN}function on(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1 +return null==t?e:this.add(t-e,"d")}function un(){return this.hours()%12||12}function dn(t,e){R(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function ln(t,e){return e._meridiemParse}function hn(t){return"p"===(t+"").toLowerCase().charAt(0)}function cn(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"}function fn(t,e){e[Wi]=y(1e3*("0."+t))}function mn(){return this._isUTC?"UTC":""}function _n(){return this._isUTC?"Coordinated Universal Time":""}function yn(t){return Ct(1e3*t)}function gn(){return Ct.apply(null,arguments).parseZone()}function pn(t,e,n){var i=this._calendar[t] +return M(i)?i.call(e,n):i}function vn(t){var e=this._longDateFormat[t],n=this._longDateFormat[t.toUpperCase()] +return e||!n?e:(this._longDateFormat[t]=n.replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t])}function Dn(){return this._invalidDate}function Mn(t){return this._ordinal.replace("%d",t)}function Sn(t){return t}function Yn(t,e,n,i){var s=this._relativeTime[n] +return M(s)?s(t,e,n,i):s.replace(/%d/i,t)}function wn(t,e){var n=this._relativeTime[t>0?"future":"past"] +return M(n)?n(e):n.replace(/%s/i,e)}function kn(t,e,n,i){var s=G(),r=a().set(i,e) +return s[n](r,t)}function Tn(t,e,n,i,s){if("number"==typeof t&&(e=t,t=void 0),t=t||"",null!=e)return kn(t,e,n,s) +var r,a=[] +for(r=0;i>r;r++)a[r]=kn(t,r,n,s) +return a}function bn(t,e){return Tn(t,e,"months",12,"month")}function On(t,e){return Tn(t,e,"monthsShort",12,"month")}function Wn(t,e){return Tn(t,e,"weekdays",7,"day")}function xn(t,e){return Tn(t,e,"weekdaysShort",7,"day")}function Un(t,e){return Tn(t,e,"weekdaysMin",7,"day")}function Gn(){var t=this._data +return this._milliseconds=fs(this._milliseconds),this._days=fs(this._days),this._months=fs(this._months),t.milliseconds=fs(t.milliseconds),t.seconds=fs(t.seconds),t.minutes=fs(t.minutes),t.hours=fs(t.hours),t.months=fs(t.months),t.years=fs(t.years),this}function Pn(t,e,n,i){var s=ee(e,n) +return t._milliseconds+=i*s._milliseconds,t._days+=i*s._days,t._months+=i*s._months,t._bubble()}function Cn(t,e){return Pn(this,t,e,1)}function Fn(t,e){return Pn(this,t,e,-1)}function Hn(t){return 0>t?Math.floor(t):Math.ceil(t)}function Ln(){var t,e,n,i,s,r=this._milliseconds,a=this._days,o=this._months,u=this._data +return r>=0&&a>=0&&o>=0||0>=r&&0>=a&&0>=o||(r+=864e5*Hn(Nn(o)+a),a=0,o=0),u.milliseconds=r%1e3,t=_(r/1e3),u.seconds=t%60,e=_(t/60),u.minutes=e%60,n=_(e/60),u.hours=n%24,a+=_(n/24),s=_(Vn(a)),o+=s,a-=Hn(Nn(s)),i=_(o/12),o%=12,u.days=a,u.months=o,u.years=i,this}function Vn(t){return 4800*t/146097}function Nn(t){return 146097*t/4800}function In(t){var e,n,i=this._milliseconds +if("month"===(t=F(t))||"year"===t)return e=this._days+i/864e5,n=this._months+Vn(e),"month"===t?n:n/12 +switch(e=this._days+Math.round(Nn(this._months)),t){case"week":return e/7+i/6048e5 +case"day":return e+i/864e5 +case"hour":return 24*e+i/36e5 +case"minute":return 1440*e+i/6e4 +case"second":return 86400*e+i/1e3 +case"millisecond":return Math.floor(864e5*e)+i +default:throw new Error("Unknown unit "+t)}}function An(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*y(this._months/12)}function Rn(t){return function(){return this.as(t)}}function En(t){return t=F(t),this[t+"s"]()}function jn(t){return function(){return this._data[t]}}function zn(){return _(this.days()/7)}function Zn(t,e,n,i,s){return s.relativeTime(e||1,!!n,t,i)}function $n(t,e,n){var i=ee(t).abs(),s=Ws(i.as("s")),r=Ws(i.as("m")),a=Ws(i.as("h")),o=Ws(i.as("d")),u=Ws(i.as("M")),d=Ws(i.as("y")),l=s=r&&["m"]||r=a&&["h"]||a=o&&["d"]||o=u&&["M"]||u=d&&["y"]||["yy",d] +return l[2]=e,l[3]=+t>0,l[4]=n,Zn.apply(null,l)}function qn(t,e){return void 0!==xs[t]&&(void 0===e?xs[t]:(xs[t]=e,!0))}function Jn(t){var e=this.localeData(),n=$n(this,!t,e) +return t&&(n=e.pastFuture(+this,n)),e.postformat(n)}function Bn(){var t,e,n,i=Us(this._milliseconds)/1e3,s=Us(this._days),r=Us(this._months) +t=_(i/60),e=_(t/60),i%=60,t%=60,n=_(r/12),r%=12 +var a=n,o=r,u=s,d=e,l=t,h=i,c=this.asSeconds() +return c?(0>c?"-":"")+"P"+(a?a+"Y":"")+(o?o+"M":"")+(u?u+"D":"")+(d||l||h?"T":"")+(d?d+"H":"")+(l?l+"M":"")+(h?h+"S":""):"P0D"}var Qn,Xn=t.momentProperties=[],Kn=!1,ti={} +t.suppressDeprecationWarnings=!1 +var ei,ni={},ii={},si=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,ri=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,ai={},oi={},ui=/\d\d/,di=/\d{4}/,li=/[+-]?\d{6}/,hi=/\d\d?/,ci=/\d\d\d\d?/,fi=/\d\d\d\d\d\d?/,mi=/\d{1,3}/,_i=/\d{1,4}/,yi=/[+-]?\d{1,6}/,gi=/[+-]?\d+/,pi=/Z|[+-]\d\d:?\d\d/gi,vi=/Z|[+-]\d\d(?::?\d\d)?/gi,Di=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Mi={},Si={},Yi=0,wi=1,ki=2,Ti=3,bi=4,Oi=5,Wi=6,xi=7,Ui=8 +R("M",["MM",2],"Mo",function(){return this.month()+1}),R("MMM",0,0,function(t){return this.localeData().monthsShort(this,t)}),R("MMMM",0,0,function(t){return this.localeData().months(this,t)}),C("month","M"),$("M",hi),$("MM",hi,ui),$("MMM",function(t,e){return e.monthsShortRegex(t)}),$("MMMM",function(t,e){return e.monthsRegex(t)}),Q(["M","MM"],function(t,e){e[wi]=y(t)-1}),Q(["MMM","MMMM"],function(t,e,n,i){var s=n._locale.monthsParse(t,i,n._strict) +null!=s?e[wi]=s:u(n).invalidMonth=t}) +var Gi=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/,Pi="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Ci="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),Fi=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Hi=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Li=/Z|[+-]\d\d(?::?\d\d)?/,Vi=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Ni=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Ii=/^\/?Date\((\-?\d+)/i +t.createFromInputFallback=v("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))}),R("Y",0,0,function(){var t=this.year() +return 9999>=t?""+t:"+"+t}),R(0,["YY",2],0,function(){return this.year()%100}),R(0,["YYYY",4],0,"year"),R(0,["YYYYY",5],0,"year"),R(0,["YYYYYY",6,!0],0,"year"),C("year","y"),$("Y",gi),$("YY",hi,ui),$("YYYY",_i,di),$("YYYYY",yi,li),$("YYYYYY",yi,li),Q(["YYYYY","YYYYYY"],Yi),Q("YYYY",function(e,n){n[Yi]=2===e.length?t.parseTwoDigitYear(e):y(e)}),Q("YY",function(e,n){n[Yi]=t.parseTwoDigitYear(e)}),Q("Y",function(t,e){e[Yi]=parseInt(t,10)}),t.parseTwoDigitYear=function(t){return y(t)+(y(t)>68?1900:2e3)} +var Ai=L("FullYear",!1) +t.ISO_8601=function(){} +var Ri=v("moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var t=Ct.apply(null,arguments) +return this.isValid()&&t.isValid()?this>t?this:t:l()}),Ei=v("moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var t=Ct.apply(null,arguments) +return this.isValid()&&t.isValid()?t>this?this:t:l()}),ji=function(){return Date.now?Date.now():+new Date} +It("Z",":"),It("ZZ",""),$("Z",vi),$("ZZ",vi),Q(["Z","ZZ"],function(t,e,n){n._useUTC=!0,n._tzm=At(vi,t)}) +var zi=/([\+\-]|\d\d)/gi +t.updateOffset=function(){} +var Zi=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/,$i=/^(-)?P(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)W)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?$/ +ee.fn=Vt.prototype +var qi=ae(1,"add"),Ji=ae(-1,"subtract") +t.defaultFormat="YYYY-MM-DDTHH:mm:ssZ" +var Bi=v("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)}) +R(0,["gg",2],0,function(){return this.weekYear()%100}),R(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ne("gggg","weekYear"),Ne("ggggg","weekYear"),Ne("GGGG","isoWeekYear"),Ne("GGGGG","isoWeekYear"),C("weekYear","gg"),C("isoWeekYear","GG"),$("G",gi),$("g",gi),$("GG",hi,ui),$("gg",hi,ui),$("GGGG",_i,di),$("gggg",_i,di),$("GGGGG",yi,li),$("ggggg",yi,li),X(["gggg","ggggg","GGGG","GGGGG"],function(t,e,n,i){e[i.substr(0,2)]=y(t)}),X(["gg","GG"],function(e,n,i,s){n[s]=t.parseTwoDigitYear(e)}),R("Q",0,"Qo","quarter"),C("quarter","Q"),$("Q",/\d/),Q("Q",function(t,e){e[wi]=3*(y(t)-1)}),R("w",["ww",2],"wo","week"),R("W",["WW",2],"Wo","isoWeek"),C("week","w"),C("isoWeek","W"),$("w",hi),$("ww",hi,ui),$("W",hi),$("WW",hi,ui),X(["w","ww","W","WW"],function(t,e,n,i){e[i.substr(0,1)]=y(t)}) +var Qi={dow:0,doy:6} +R("D",["DD",2],"Do","date"),C("date","D"),$("D",hi),$("DD",hi,ui),$("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),Q(["D","DD"],ki),Q("Do",function(t,e){e[ki]=y(t.match(hi)[0],10)}) +var Xi=L("Date",!0) +R("d",0,"do","day"),R("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),R("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),R("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),R("e",0,0,"weekday"),R("E",0,0,"isoWeekday"),C("day","d"),C("weekday","e"),C("isoWeekday","E"),$("d",hi),$("e",hi),$("E",hi),$("dd",Di),$("ddd",Di),$("dddd",Di),X(["dd","ddd","dddd"],function(t,e,n,i){var s=n._locale.weekdaysParse(t,i,n._strict) +null!=s?e.d=s:u(n).invalidWeekday=t}),X(["d","e","E"],function(t,e,n,i){e[i]=y(t)}) +var Ki="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),ts="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),es="Su_Mo_Tu_We_Th_Fr_Sa".split("_") +R("DDD",["DDDD",3],"DDDo","dayOfYear"),C("dayOfYear","DDD"),$("DDD",mi),$("DDDD",/\d{3}/),Q(["DDD","DDDD"],function(t,e,n){n._dayOfYear=y(t)}),R("H",["HH",2],0,"hour"),R("h",["hh",2],0,un),R("hmm",0,0,function(){return""+un.apply(this)+A(this.minutes(),2)}),R("hmmss",0,0,function(){return""+un.apply(this)+A(this.minutes(),2)+A(this.seconds(),2)}),R("Hmm",0,0,function(){return""+this.hours()+A(this.minutes(),2)}),R("Hmmss",0,0,function(){return""+this.hours()+A(this.minutes(),2)+A(this.seconds(),2)}),dn("a",!0),dn("A",!1),C("hour","h"),$("a",ln),$("A",ln),$("H",hi),$("h",hi),$("HH",hi,ui),$("hh",hi,ui),$("hmm",ci),$("hmmss",fi),$("Hmm",ci),$("Hmmss",fi),Q(["H","HH"],Ti),Q(["a","A"],function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t}),Q(["h","hh"],function(t,e,n){e[Ti]=y(t),u(n).bigHour=!0}),Q("hmm",function(t,e,n){var i=t.length-2 +e[Ti]=y(t.substr(0,i)),e[bi]=y(t.substr(i)),u(n).bigHour=!0}),Q("hmmss",function(t,e,n){var i=t.length-4,s=t.length-2 +e[Ti]=y(t.substr(0,i)),e[bi]=y(t.substr(i,2)),e[Oi]=y(t.substr(s)),u(n).bigHour=!0}),Q("Hmm",function(t,e,n){var i=t.length-2 +e[Ti]=y(t.substr(0,i)),e[bi]=y(t.substr(i))}),Q("Hmmss",function(t,e,n){var i=t.length-4,s=t.length-2 +e[Ti]=y(t.substr(0,i)),e[bi]=y(t.substr(i,2)),e[Oi]=y(t.substr(s))}) +var ns=L("Hours",!0) +R("m",["mm",2],0,"minute"),C("minute","m"),$("m",hi),$("mm",hi,ui),Q(["m","mm"],bi) +var is=L("Minutes",!1) +R("s",["ss",2],0,"second"),C("second","s"),$("s",hi),$("ss",hi,ui),Q(["s","ss"],Oi) +var ss=L("Seconds",!1) +R("S",0,0,function(){return~~(this.millisecond()/100)}),R(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),R(0,["SSS",3],0,"millisecond"),R(0,["SSSS",4],0,function(){return 10*this.millisecond()}),R(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),R(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),R(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),R(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),R(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),C("millisecond","ms"),$("S",mi,/\d/),$("SS",mi,ui),$("SSS",mi,/\d{3}/) +var rs +for(rs="SSSS";rs.length<=9;rs+="S")$(rs,/\d+/) +for(rs="S";rs.length<=9;rs+="S")Q(rs,fn) +var as=L("Milliseconds",!1) +R("z",0,0,"zoneAbbr"),R("zz",0,0,"zoneName") +var os=f.prototype +os.add=qi,os.calendar=ue,os.clone=de,os.diff=ye,os.endOf=Oe,os.format=De,os.from=Me,os.fromNow=Se,os.to=Ye,os.toNow=we,os.get=I,os.invalidAt=Le,os.isAfter=le,os.isBefore=he,os.isBetween=ce,os.isSame=fe,os.isSameOrAfter=me,os.isSameOrBefore=_e,os.isValid=Fe,os.lang=Bi,os.locale=ke,os.localeData=Te,os.max=Ei,os.min=Ri,os.parsingFlags=He,os.set=I,os.startOf=be,os.subtract=Ji,os.toArray=Ge,os.toObject=Pe,os.toDate=Ue,os.toISOString=ve,os.toJSON=Ce,os.toString=pe,os.unix=xe,os.valueOf=We,os.creationData=Ve,os.year=Ai,os.isLeapYear=gt,os.weekYear=Ie,os.isoWeekYear=Ae,os.quarter=os.quarters=Ze,os.month=rt,os.daysInMonth=at,os.week=os.weeks=Be,os.isoWeek=os.isoWeeks=Qe,os.weeksInYear=Ee,os.isoWeeksInYear=Re,os.date=Xi,os.day=os.days=sn,os.weekday=rn,os.isoWeekday=an,os.dayOfYear=on,os.hour=os.hours=ns,os.minute=os.minutes=is,os.second=os.seconds=ss,os.millisecond=os.milliseconds=as,os.utcOffset=jt,os.utc=Zt,os.local=$t,os.parseZone=qt,os.hasAlignedHourOffset=Jt,os.isDST=Bt,os.isDSTShifted=Qt,os.isLocal=Xt,os.isUtcOffset=Kt,os.isUtc=te,os.isUTC=te,os.zoneAbbr=mn,os.zoneName=_n,os.dates=v("dates accessor is deprecated. Use date instead.",Xi),os.months=v("months accessor is deprecated. Use month instead",rt),os.years=v("years accessor is deprecated. Use year instead",Ai),os.zone=v("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",zt) +var us=os,ds={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},ls={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},hs={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},cs=k.prototype +cs._calendar=ds,cs.calendar=pn,cs._longDateFormat=ls,cs.longDateFormat=vn,cs._invalidDate="Invalid date",cs.invalidDate=Dn,cs._ordinal="%d",cs.ordinal=Mn,cs._ordinalParse=/\d{1,2}/,cs.preparse=Sn,cs.postformat=Sn,cs._relativeTime=hs,cs.relativeTime=Yn,cs.pastFuture=wn,cs.set=Y,cs.months=et,cs._months=Pi,cs.monthsShort=nt,cs._monthsShort=Ci,cs.monthsParse=it,cs._monthsRegex=Di,cs.monthsRegex=ut,cs._monthsShortRegex=Di,cs.monthsShortRegex=ot,cs.week=$e,cs._week=Qi,cs.firstDayOfYear=Je,cs.firstDayOfWeek=qe,cs.weekdays=Ke,cs._weekdays=Ki,cs.weekdaysMin=en,cs._weekdaysMin=es,cs.weekdaysShort=tn,cs._weekdaysShort=ts,cs.weekdaysParse=nn,cs.isPM=hn,cs._meridiemParse=/[ap]\.?m?\.?/i,cs.meridiem=cn,W("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10 +return t+(1===y(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th")}}),t.lang=v("moment.lang is deprecated. Use moment.locale instead.",W),t.langData=v("moment.langData is deprecated. Use moment.localeData instead.",G) +var fs=Math.abs,ms=Rn("ms"),_s=Rn("s"),ys=Rn("m"),gs=Rn("h"),ps=Rn("d"),vs=Rn("w"),Ds=Rn("M"),Ms=Rn("y"),Ss=jn("milliseconds"),Ys=jn("seconds"),ws=jn("minutes"),ks=jn("hours"),Ts=jn("days"),bs=jn("months"),Os=jn("years"),Ws=Math.round,xs={s:45,m:45,h:22,d:26,M:11},Us=Math.abs,Gs=Vt.prototype +return Gs.abs=Gn,Gs.add=Cn,Gs.subtract=Fn,Gs.as=In,Gs.asMilliseconds=ms,Gs.asSeconds=_s,Gs.asMinutes=ys,Gs.asHours=gs,Gs.asDays=ps,Gs.asWeeks=vs,Gs.asMonths=Ds,Gs.asYears=Ms,Gs.valueOf=An,Gs._bubble=Ln,Gs.get=En,Gs.milliseconds=Ss,Gs.seconds=Ys,Gs.minutes=ws,Gs.hours=ks,Gs.days=Ts,Gs.weeks=zn,Gs.months=bs,Gs.years=Os,Gs.humanize=Jn,Gs.toISOString=Bn,Gs.toString=Bn,Gs.toJSON=Bn,Gs.locale=ke,Gs.localeData=Te,Gs.toIsoString=v("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Bn),Gs.lang=Bi,R("X",0,0,"unix"),R("x",0,0,"valueOf"),$("x",gi),$("X",/[+-]?\d+(\.\d{1,3})?/),Q("X",function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))}),Q("x",function(t,e,n){n._d=new Date(y(t))}),t.version="2.12.0",function(t){Qn=t}(Ct),t.fn=us,t.min=Ht,t.max=Lt,t.now=ji,t.utc=a,t.unix=yn,t.months=bn,t.isDate=n,t.locale=W,t.invalid=l,t.duration=ee,t.isMoment=m,t.weekdays=Wn,t.parseZone=gn,t.localeData=G,t.isDuration=Nt,t.monthsShort=On,t.weekdaysMin=Un,t.defineLocale=x,t.updateLocale=U,t.locales=P,t.weekdaysShort=xn,t.normalizeUnits=F,t.relativeTimeThreshold=qn,t.prototype=us,t}) diff --git a/views/admin.html b/views/admin.html index d04f60e..c803c65 100644 --- a/views/admin.html +++ b/views/admin.html @@ -3,7 +3,6 @@ {% block head %} {{ super() }} - diff --git a/views/index.html b/views/index.html index 44e4dac..c2cbb8e 100644 --- a/views/index.html +++ b/views/index.html @@ -1,7 +1,7 @@ {% extends 'templates/base.html' %} {% block head %} - {{ super() }} - +{{super()}} + {% endblock %} {% block main %} @@ -22,7 +22,7 @@ {% endif %}
- +
diff --git a/views/login.html b/views/login.html index 5d35475..d650579 100644 --- a/views/login.html +++ b/views/login.html @@ -2,8 +2,8 @@ {% block title %}{{ super() }} | Login{% endblock %} {% block head %} {{super()}} - - + + - -
- - -
- -
- - -
- -
- -

Click save to save your name/email as entered above. If you change your email, an email will be sent to both the old address and the new address to confirm the change. To change your password, click "Change password". You will be sent an email with a link to a page where you can reset your password. These precautions are for security and to prevent mistakes.

- -
- - - - Change password -
- -

You can connect to social network accounts for faster login. This information is only used for logging in. On some authorization screens, you will be told that Trackmap will be granted access to profile information and will be able to post to your profile. Only your account ID, which is needed for login, will be saved in our database. Trackmap will never post to your profile or sell information about you. For more information, see our privacy policy.

- -
- - - - - {% if user.auth.google %}Disconnect{% else %}Connect{% endif %} Google - - - - {% if user.auth.facebook %}Disconnect{% else %}Connect{% endif %} Facebook - - - - {% if user.auth.twitter %}Disconnect{% else %}Connect{% endif %} Twitter - - -
- - - -

If you're satisfied with your settings, you can create and edit maps on the maps page.

- -{% endblock %} - -{% block javascript %} -{{super()}} - -{% endblock %} \ No newline at end of file diff --git a/static/js/moment.min.js b/static/js/moment.min.js deleted file mode 100644 index ba38995..0000000 --- a/static/js/moment.min.js +++ /dev/null @@ -1,7 +0,0 @@ -//! moment.js -//! version : 2.12.0 -//! authors : Tim Wood, Iskren Chernev, Moment.js contributors -//! license : MIT -//! momentjs.com -!function(a,b){"object"==typeof exports&&"undefined"!=typeof module?module.exports=b():"function"==typeof define&&define.amd?define(b):a.moment=b()}(this,function(){"use strict";function a(){return Zc.apply(null,arguments)}function b(a){Zc=a}function c(a){return a instanceof Array||"[object Array]"===Object.prototype.toString.call(a)}function d(a){return a instanceof Date||"[object Date]"===Object.prototype.toString.call(a)}function e(a,b){var c,d=[];for(c=0;c0)for(c in $c)d=$c[c],e=b[d],m(e)||(a[d]=e);return a}function o(b){n(this,b),this._d=new Date(null!=b._d?b._d.getTime():NaN),_c===!1&&(_c=!0,a.updateOffset(this),_c=!1)}function p(a){return a instanceof o||null!=a&&null!=a._isAMomentObject}function q(a){return 0>a?Math.ceil(a):Math.floor(a)}function r(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=q(b)),c}function s(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&r(a[d])!==r(b[d]))&&g++;return g+f}function t(b){a.suppressDeprecationWarnings===!1&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+b)}function u(a,b){var c=!0;return g(function(){return c&&(t(a+"\nArguments: "+Array.prototype.slice.call(arguments).join(", ")+"\n"+(new Error).stack),c=!1),b.apply(this,arguments)},b)}function v(a,b){ad[a]||(t(b),ad[a]=!0)}function w(a){return a instanceof Function||"[object Function]"===Object.prototype.toString.call(a)}function x(a){return"[object Object]"===Object.prototype.toString.call(a)}function y(a){var b,c;for(c in a)b=a[c],w(b)?this[c]=b:this["_"+c]=b;this._config=a,this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function z(a,b){var c,d=g({},a);for(c in b)f(b,c)&&(x(a[c])&&x(b[c])?(d[c]={},g(d[c],a[c]),g(d[c],b[c])):null!=b[c]?d[c]=b[c]:delete d[c]);return d}function A(a){null!=a&&this.set(a)}function B(a){return a?a.toLowerCase().replace("_","-"):a}function C(a){for(var b,c,d,e,f=0;f0;){if(d=D(e.slice(0,b).join("-")))return d;if(c&&c.length>=b&&s(e,c,!0)>=b-1)break;b--}f++}return null}function D(a){var b=null;if(!cd[a]&&"undefined"!=typeof module&&module&&module.exports)try{b=bd._abbr,require("./locale/"+a),E(b)}catch(c){}return cd[a]}function E(a,b){var c;return a&&(c=m(b)?H(a):F(a,b),c&&(bd=c)),bd._abbr}function F(a,b){return null!==b?(b.abbr=a,null!=cd[a]?(v("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale"),b=z(cd[a]._config,b)):null!=b.parentLocale&&(null!=cd[b.parentLocale]?b=z(cd[b.parentLocale]._config,b):v("parentLocaleUndefined","specified parentLocale is not defined yet")),cd[a]=new A(b),E(a),cd[a]):(delete cd[a],null)}function G(a,b){if(null!=b){var c;null!=cd[a]&&(b=z(cd[a]._config,b)),c=new A(b),c.parentLocale=cd[a],cd[a]=c,E(a)}else null!=cd[a]&&(null!=cd[a].parentLocale?cd[a]=cd[a].parentLocale:null!=cd[a]&&delete cd[a]);return cd[a]}function H(a){var b;if(a&&a._locale&&a._locale._abbr&&(a=a._locale._abbr),!a)return bd;if(!c(a)){if(b=D(a))return b;a=[a]}return C(a)}function I(){return Object.keys(cd)}function J(a,b){var c=a.toLowerCase();dd[c]=dd[c+"s"]=dd[b]=a}function K(a){return"string"==typeof a?dd[a]||dd[a.toLowerCase()]:void 0}function L(a){var b,c,d={};for(c in a)f(a,c)&&(b=K(c),b&&(d[b]=a[c]));return d}function M(b,c){return function(d){return null!=d?(O(this,b,d),a.updateOffset(this,c),this):N(this,b)}}function N(a,b){return a.isValid()?a._d["get"+(a._isUTC?"UTC":"")+b]():NaN}function O(a,b,c){a.isValid()&&a._d["set"+(a._isUTC?"UTC":"")+b](c)}function P(a,b){var c;if("object"==typeof a)for(c in a)this.set(c,a[c]);else if(a=K(a),w(this[a]))return this[a](b);return this}function Q(a,b,c){var d=""+Math.abs(a),e=b-d.length,f=a>=0;return(f?c?"+":"":"-")+Math.pow(10,Math.max(0,e)).toString().substr(1)+d}function R(a,b,c,d){var e=d;"string"==typeof d&&(e=function(){return this[d]()}),a&&(hd[a]=e),b&&(hd[b[0]]=function(){return Q(e.apply(this,arguments),b[1],b[2])}),c&&(hd[c]=function(){return this.localeData().ordinal(e.apply(this,arguments),a)})}function S(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function T(a){var b,c,d=a.match(ed);for(b=0,c=d.length;c>b;b++)hd[d[b]]?d[b]=hd[d[b]]:d[b]=S(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function U(a,b){return a.isValid()?(b=V(b,a.localeData()),gd[b]=gd[b]||T(b),gd[b](a)):a.localeData().invalidDate()}function V(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(fd.lastIndex=0;d>=0&&fd.test(a);)a=a.replace(fd,c),fd.lastIndex=0,d-=1;return a}function W(a,b,c){zd[a]=w(b)?b:function(a,d){return a&&c?c:b}}function X(a,b){return f(zd,a)?zd[a](b._strict,b._locale):new RegExp(Y(a))}function Y(a){return Z(a.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e}))}function Z(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function $(a,b){var c,d=b;for("string"==typeof a&&(a=[a]),"number"==typeof b&&(d=function(a,c){c[b]=r(a)}),c=0;cd;d++){if(e=h([2e3,d]),c&&!this._longMonthsParse[d]&&(this._longMonthsParse[d]=new RegExp("^"+this.months(e,"").replace(".","")+"$","i"),this._shortMonthsParse[d]=new RegExp("^"+this.monthsShort(e,"").replace(".","")+"$","i")),c||this._monthsParse[d]||(f="^"+this.months(e,"")+"|^"+this.monthsShort(e,""),this._monthsParse[d]=new RegExp(f.replace(".",""),"i")),c&&"MMMM"===b&&this._longMonthsParse[d].test(a))return d;if(c&&"MMM"===b&&this._shortMonthsParse[d].test(a))return d;if(!c&&this._monthsParse[d].test(a))return d}}function fa(a,b){var c;if(!a.isValid())return a;if("string"==typeof b)if(/^\d+$/.test(b))b=r(b);else if(b=a.localeData().monthsParse(b),"number"!=typeof b)return a;return c=Math.min(a.date(),ba(a.year(),b)),a._d["set"+(a._isUTC?"UTC":"")+"Month"](b,c),a}function ga(b){return null!=b?(fa(this,b),a.updateOffset(this,!0),this):N(this,"Month")}function ha(){return ba(this.year(),this.month())}function ia(a){return this._monthsParseExact?(f(this,"_monthsRegex")||ka.call(this),a?this._monthsShortStrictRegex:this._monthsShortRegex):this._monthsShortStrictRegex&&a?this._monthsShortStrictRegex:this._monthsShortRegex}function ja(a){return this._monthsParseExact?(f(this,"_monthsRegex")||ka.call(this),a?this._monthsStrictRegex:this._monthsRegex):this._monthsStrictRegex&&a?this._monthsStrictRegex:this._monthsRegex}function ka(){function a(a,b){return b.length-a.length}var b,c,d=[],e=[],f=[];for(b=0;12>b;b++)c=h([2e3,b]),d.push(this.monthsShort(c,"")),e.push(this.months(c,"")),f.push(this.months(c,"")),f.push(this.monthsShort(c,""));for(d.sort(a),e.sort(a),f.sort(a),b=0;12>b;b++)d[b]=Z(d[b]),e[b]=Z(e[b]),f[b]=Z(f[b]);this._monthsRegex=new RegExp("^("+f.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+e.join("|")+")$","i"),this._monthsShortStrictRegex=new RegExp("^("+d.join("|")+")$","i")}function la(a){var b,c=a._a;return c&&-2===j(a).overflow&&(b=c[Cd]<0||c[Cd]>11?Cd:c[Dd]<1||c[Dd]>ba(c[Bd],c[Cd])?Dd:c[Ed]<0||c[Ed]>24||24===c[Ed]&&(0!==c[Fd]||0!==c[Gd]||0!==c[Hd])?Ed:c[Fd]<0||c[Fd]>59?Fd:c[Gd]<0||c[Gd]>59?Gd:c[Hd]<0||c[Hd]>999?Hd:-1,j(a)._overflowDayOfYear&&(Bd>b||b>Dd)&&(b=Dd),j(a)._overflowWeeks&&-1===b&&(b=Id),j(a)._overflowWeekday&&-1===b&&(b=Jd),j(a).overflow=b),a}function ma(a){var b,c,d,e,f,g,h=a._i,i=Pd.exec(h)||Qd.exec(h);if(i){for(j(a).iso=!0,b=0,c=Sd.length;c>b;b++)if(Sd[b][1].exec(i[1])){e=Sd[b][0],d=Sd[b][2]!==!1;break}if(null==e)return void(a._isValid=!1);if(i[3]){for(b=0,c=Td.length;c>b;b++)if(Td[b][1].exec(i[3])){f=(i[2]||" ")+Td[b][0];break}if(null==f)return void(a._isValid=!1)}if(!d&&null!=f)return void(a._isValid=!1);if(i[4]){if(!Rd.exec(i[4]))return void(a._isValid=!1);g="Z"}a._f=e+(f||"")+(g||""),Ba(a)}else a._isValid=!1}function na(b){var c=Ud.exec(b._i);return null!==c?void(b._d=new Date(+c[1])):(ma(b),void(b._isValid===!1&&(delete b._isValid,a.createFromInputFallback(b))))}function oa(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 100>a&&a>=0&&isFinite(h.getFullYear())&&h.setFullYear(a),h}function pa(a){var b=new Date(Date.UTC.apply(null,arguments));return 100>a&&a>=0&&isFinite(b.getUTCFullYear())&&b.setUTCFullYear(a),b}function qa(a){return ra(a)?366:365}function ra(a){return a%4===0&&a%100!==0||a%400===0}function sa(){return ra(this.year())}function ta(a,b,c){var d=7+b-c,e=(7+pa(a,0,d).getUTCDay()-b)%7;return-e+d-1}function ua(a,b,c,d,e){var f,g,h=(7+c-d)%7,i=ta(a,d,e),j=1+7*(b-1)+h+i;return 0>=j?(f=a-1,g=qa(f)+j):j>qa(a)?(f=a+1,g=j-qa(a)):(f=a,g=j),{year:f,dayOfYear:g}}function va(a,b,c){var d,e,f=ta(a.year(),b,c),g=Math.floor((a.dayOfYear()-f-1)/7)+1;return 1>g?(e=a.year()-1,d=g+wa(e,b,c)):g>wa(a.year(),b,c)?(d=g-wa(a.year(),b,c),e=a.year()+1):(e=a.year(),d=g),{week:d,year:e}}function wa(a,b,c){var d=ta(a,b,c),e=ta(a+1,b,c);return(qa(a)-d+e)/7}function xa(a,b,c){return null!=a?a:null!=b?b:c}function ya(b){var c=new Date(a.now());return b._useUTC?[c.getUTCFullYear(),c.getUTCMonth(),c.getUTCDate()]:[c.getFullYear(),c.getMonth(),c.getDate()]}function za(a){var b,c,d,e,f=[];if(!a._d){for(d=ya(a),a._w&&null==a._a[Dd]&&null==a._a[Cd]&&Aa(a),a._dayOfYear&&(e=xa(a._a[Bd],d[Bd]),a._dayOfYear>qa(e)&&(j(a)._overflowDayOfYear=!0),c=pa(e,0,a._dayOfYear),a._a[Cd]=c.getUTCMonth(),a._a[Dd]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=f[b]=d[b];for(;7>b;b++)a._a[b]=f[b]=null==a._a[b]?2===b?1:0:a._a[b];24===a._a[Ed]&&0===a._a[Fd]&&0===a._a[Gd]&&0===a._a[Hd]&&(a._nextDay=!0,a._a[Ed]=0),a._d=(a._useUTC?pa:oa).apply(null,f),null!=a._tzm&&a._d.setUTCMinutes(a._d.getUTCMinutes()-a._tzm),a._nextDay&&(a._a[Ed]=24)}}function Aa(a){var b,c,d,e,f,g,h,i;b=a._w,null!=b.GG||null!=b.W||null!=b.E?(f=1,g=4,c=xa(b.GG,a._a[Bd],va(Ja(),1,4).year),d=xa(b.W,1),e=xa(b.E,1),(1>e||e>7)&&(i=!0)):(f=a._locale._week.dow,g=a._locale._week.doy,c=xa(b.gg,a._a[Bd],va(Ja(),f,g).year),d=xa(b.w,1),null!=b.d?(e=b.d,(0>e||e>6)&&(i=!0)):null!=b.e?(e=b.e+f,(b.e<0||b.e>6)&&(i=!0)):e=f),1>d||d>wa(c,f,g)?j(a)._overflowWeeks=!0:null!=i?j(a)._overflowWeekday=!0:(h=ua(c,d,e,f,g),a._a[Bd]=h.year,a._dayOfYear=h.dayOfYear)}function Ba(b){if(b._f===a.ISO_8601)return void ma(b);b._a=[],j(b).empty=!0;var c,d,e,f,g,h=""+b._i,i=h.length,k=0;for(e=V(b._f,b._locale).match(ed)||[],c=0;c0&&j(b).unusedInput.push(g),h=h.slice(h.indexOf(d)+d.length),k+=d.length),hd[f]?(d?j(b).empty=!1:j(b).unusedTokens.push(f),aa(f,d,b)):b._strict&&!d&&j(b).unusedTokens.push(f);j(b).charsLeftOver=i-k,h.length>0&&j(b).unusedInput.push(h),j(b).bigHour===!0&&b._a[Ed]<=12&&b._a[Ed]>0&&(j(b).bigHour=void 0),b._a[Ed]=Ca(b._locale,b._a[Ed],b._meridiem),za(b),la(b)}function Ca(a,b,c){var d;return null==c?b:null!=a.meridiemHour?a.meridiemHour(b,c):null!=a.isPM?(d=a.isPM(c),d&&12>b&&(b+=12),d||12!==b||(b=0),b):b}function Da(a){var b,c,d,e,f;if(0===a._f.length)return j(a).invalidFormat=!0,void(a._d=new Date(NaN));for(e=0;ef)&&(d=f,c=b));g(a,c||b)}function Ea(a){if(!a._d){var b=L(a._i);a._a=e([b.year,b.month,b.day||b.date,b.hour,b.minute,b.second,b.millisecond],function(a){return a&&parseInt(a,10)}),za(a)}}function Fa(a){var b=new o(la(Ga(a)));return b._nextDay&&(b.add(1,"d"),b._nextDay=void 0),b}function Ga(a){var b=a._i,e=a._f;return a._locale=a._locale||H(a._l),null===b||void 0===e&&""===b?l({nullInput:!0}):("string"==typeof b&&(a._i=b=a._locale.preparse(b)),p(b)?new o(la(b)):(c(e)?Da(a):e?Ba(a):d(b)?a._d=b:Ha(a),k(a)||(a._d=null),a))}function Ha(b){var f=b._i;void 0===f?b._d=new Date(a.now()):d(f)?b._d=new Date(+f):"string"==typeof f?na(b):c(f)?(b._a=e(f.slice(0),function(a){return parseInt(a,10)}),za(b)):"object"==typeof f?Ea(b):"number"==typeof f?b._d=new Date(f):a.createFromInputFallback(b)}function Ia(a,b,c,d,e){var f={};return"boolean"==typeof c&&(d=c,c=void 0),f._isAMomentObject=!0,f._useUTC=f._isUTC=e,f._l=c,f._i=a,f._f=b,f._strict=d,Fa(f)}function Ja(a,b,c,d){return Ia(a,b,c,d,!1)}function Ka(a,b){var d,e;if(1===b.length&&c(b[0])&&(b=b[0]),!b.length)return Ja();for(d=b[0],e=1;ea&&(a=-a,c="-"),c+Q(~~(a/60),2)+b+Q(~~a%60,2)})}function Qa(a,b){var c=(b||"").match(a)||[],d=c[c.length-1]||[],e=(d+"").match(Zd)||["-",0,0],f=+(60*e[1])+r(e[2]);return"+"===e[0]?f:-f}function Ra(b,c){var e,f;return c._isUTC?(e=c.clone(),f=(p(b)||d(b)?+b:+Ja(b))-+e,e._d.setTime(+e._d+f),a.updateOffset(e,!1),e):Ja(b).local()}function Sa(a){return 15*-Math.round(a._d.getTimezoneOffset()/15)}function Ta(b,c){var d,e=this._offset||0;return this.isValid()?null!=b?("string"==typeof b?b=Qa(wd,b):Math.abs(b)<16&&(b=60*b),!this._isUTC&&c&&(d=Sa(this)),this._offset=b,this._isUTC=!0,null!=d&&this.add(d,"m"),e!==b&&(!c||this._changeInProgress?ib(this,cb(b-e,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?e:Sa(this):null!=b?this:NaN}function Ua(a,b){return null!=a?("string"!=typeof a&&(a=-a),this.utcOffset(a,b),this):-this.utcOffset()}function Va(a){return this.utcOffset(0,a)}function Wa(a){return this._isUTC&&(this.utcOffset(0,a),this._isUTC=!1,a&&this.subtract(Sa(this),"m")),this}function Xa(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(Qa(vd,this._i)),this}function Ya(a){return this.isValid()?(a=a?Ja(a).utcOffset():0,(this.utcOffset()-a)%60===0):!1}function Za(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function $a(){if(!m(this._isDSTShifted))return this._isDSTShifted;var a={};if(n(a,this),a=Ga(a),a._a){var b=a._isUTC?h(a._a):Ja(a._a);this._isDSTShifted=this.isValid()&&s(a._a,b.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}function _a(){return this.isValid()?!this._isUTC:!1}function ab(){return this.isValid()?this._isUTC:!1}function bb(){return this.isValid()?this._isUTC&&0===this._offset:!1}function cb(a,b){var c,d,e,g=a,h=null;return Oa(a)?g={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(g={},b?g[b]=a:g.milliseconds=a):(h=$d.exec(a))?(c="-"===h[1]?-1:1,g={y:0,d:r(h[Dd])*c,h:r(h[Ed])*c,m:r(h[Fd])*c,s:r(h[Gd])*c,ms:r(h[Hd])*c}):(h=_d.exec(a))?(c="-"===h[1]?-1:1,g={y:db(h[2],c),M:db(h[3],c),w:db(h[4],c),d:db(h[5],c),h:db(h[6],c),m:db(h[7],c),s:db(h[8],c)}):null==g?g={}:"object"==typeof g&&("from"in g||"to"in g)&&(e=fb(Ja(g.from),Ja(g.to)),g={},g.ms=e.milliseconds,g.M=e.months),d=new Na(g),Oa(a)&&f(a,"_locale")&&(d._locale=a._locale),d}function db(a,b){var c=a&&parseFloat(a.replace(",","."));return(isNaN(c)?0:c)*b}function eb(a,b){var c={milliseconds:0,months:0};return c.months=b.month()-a.month()+12*(b.year()-a.year()),a.clone().add(c.months,"M").isAfter(b)&&--c.months,c.milliseconds=+b-+a.clone().add(c.months,"M"),c}function fb(a,b){var c;return a.isValid()&&b.isValid()?(b=Ra(b,a),a.isBefore(b)?c=eb(a,b):(c=eb(b,a),c.milliseconds=-c.milliseconds,c.months=-c.months),c):{milliseconds:0,months:0}}function gb(a){return 0>a?-1*Math.round(-1*a):Math.round(a)}function hb(a,b){return function(c,d){var e,f;return null===d||isNaN(+d)||(v(b,"moment()."+b+"(period, number) is deprecated. Please use moment()."+b+"(number, period)."),f=c,c=d,d=f),c="string"==typeof c?+c:c,e=cb(c,d),ib(this,e,a),this}}function ib(b,c,d,e){var f=c._milliseconds,g=gb(c._days),h=gb(c._months);b.isValid()&&(e=null==e?!0:e,f&&b._d.setTime(+b._d+f*d),g&&O(b,"Date",N(b,"Date")+g*d),h&&fa(b,N(b,"Month")+h*d),e&&a.updateOffset(b,g||h))}function jb(a,b){var c=a||Ja(),d=Ra(c,this).startOf("day"),e=this.diff(d,"days",!0),f=-6>e?"sameElse":-1>e?"lastWeek":0>e?"lastDay":1>e?"sameDay":2>e?"nextDay":7>e?"nextWeek":"sameElse",g=b&&(w(b[f])?b[f]():b[f]);return this.format(g||this.localeData().calendar(f,this,Ja(c)))}function kb(){return new o(this)}function lb(a,b){var c=p(a)?a:Ja(a);return this.isValid()&&c.isValid()?(b=K(m(b)?"millisecond":b),"millisecond"===b?+this>+c:+c<+this.clone().startOf(b)):!1}function mb(a,b){var c=p(a)?a:Ja(a);return this.isValid()&&c.isValid()?(b=K(m(b)?"millisecond":b),"millisecond"===b?+c>+this:+this.clone().endOf(b)<+c):!1}function nb(a,b,c){return this.isAfter(a,c)&&this.isBefore(b,c)}function ob(a,b){var c,d=p(a)?a:Ja(a);return this.isValid()&&d.isValid()?(b=K(b||"millisecond"),"millisecond"===b?+this===+d:(c=+d,+this.clone().startOf(b)<=c&&c<=+this.clone().endOf(b))):!1}function pb(a,b){return this.isSame(a,b)||this.isAfter(a,b)}function qb(a,b){return this.isSame(a,b)||this.isBefore(a,b)}function rb(a,b,c){var d,e,f,g;return this.isValid()?(d=Ra(a,this),d.isValid()?(e=6e4*(d.utcOffset()-this.utcOffset()),b=K(b),"year"===b||"month"===b||"quarter"===b?(g=sb(this,d),"quarter"===b?g/=3:"year"===b&&(g/=12)):(f=this-d,g="second"===b?f/1e3:"minute"===b?f/6e4:"hour"===b?f/36e5:"day"===b?(f-e)/864e5:"week"===b?(f-e)/6048e5:f),c?g:q(g)):NaN):NaN}function sb(a,b){var c,d,e=12*(b.year()-a.year())+(b.month()-a.month()),f=a.clone().add(e,"months");return 0>b-f?(c=a.clone().add(e-1,"months"),d=(b-f)/(f-c)):(c=a.clone().add(e+1,"months"),d=(b-f)/(c-f)),-(e+d)}function tb(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function ub(){var a=this.clone().utc();return 0f&&(b=f),Ub.call(this,a,b,c,d,e))}function Ub(a,b,c,d,e){var f=ua(a,b,c,d,e),g=pa(f.year,0,f.dayOfYear);return this.year(g.getUTCFullYear()),this.month(g.getUTCMonth()),this.date(g.getUTCDate()),this}function Vb(a){return null==a?Math.ceil((this.month()+1)/3):this.month(3*(a-1)+this.month()%3)}function Wb(a){return va(a,this._week.dow,this._week.doy).week}function Xb(){return this._week.dow}function Yb(){return this._week.doy}function Zb(a){var b=this.localeData().week(this);return null==a?b:this.add(7*(a-b),"d")}function $b(a){var b=va(this,1,4).week;return null==a?b:this.add(7*(a-b),"d")}function _b(a,b){return"string"!=typeof a?a:isNaN(a)?(a=b.weekdaysParse(a),"number"==typeof a?a:null):parseInt(a,10)}function ac(a,b){return c(this._weekdays)?this._weekdays[a.day()]:this._weekdays[this._weekdays.isFormat.test(b)?"format":"standalone"][a.day()]}function bc(a){return this._weekdaysShort[a.day()]}function cc(a){return this._weekdaysMin[a.day()]}function dc(a,b,c){var d,e,f;for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),d=0;7>d;d++){if(e=Ja([2e3,1]).day(d),c&&!this._fullWeekdaysParse[d]&&(this._fullWeekdaysParse[d]=new RegExp("^"+this.weekdays(e,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[d]=new RegExp("^"+this.weekdaysShort(e,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[d]=new RegExp("^"+this.weekdaysMin(e,"").replace(".",".?")+"$","i")),this._weekdaysParse[d]||(f="^"+this.weekdays(e,"")+"|^"+this.weekdaysShort(e,"")+"|^"+this.weekdaysMin(e,""),this._weekdaysParse[d]=new RegExp(f.replace(".",""),"i")),c&&"dddd"===b&&this._fullWeekdaysParse[d].test(a))return d;if(c&&"ddd"===b&&this._shortWeekdaysParse[d].test(a))return d;if(c&&"dd"===b&&this._minWeekdaysParse[d].test(a))return d;if(!c&&this._weekdaysParse[d].test(a))return d}}function ec(a){if(!this.isValid())return null!=a?this:NaN;var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=_b(a,this.localeData()),this.add(a-b,"d")):b}function fc(a){if(!this.isValid())return null!=a?this:NaN;var b=(this.day()+7-this.localeData()._week.dow)%7;return null==a?b:this.add(a-b,"d")}function gc(a){return this.isValid()?null==a?this.day()||7:this.day(this.day()%7?a:a-7):null!=a?this:NaN}function hc(a){var b=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==a?b:this.add(a-b,"d")}function ic(){return this.hours()%12||12}function jc(a,b){R(a,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),b)})}function kc(a,b){return b._meridiemParse}function lc(a){return"p"===(a+"").toLowerCase().charAt(0)}function mc(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"}function nc(a,b){b[Hd]=r(1e3*("0."+a))}function oc(){return this._isUTC?"UTC":""}function pc(){return this._isUTC?"Coordinated Universal Time":""}function qc(a){return Ja(1e3*a)}function rc(){return Ja.apply(null,arguments).parseZone()}function sc(a,b,c){var d=this._calendar[a];return w(d)?d.call(b,c):d}function tc(a){var b=this._longDateFormat[a],c=this._longDateFormat[a.toUpperCase()];return b||!c?b:(this._longDateFormat[a]=c.replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a])}function uc(){return this._invalidDate}function vc(a){return this._ordinal.replace("%d",a)}function wc(a){return a}function xc(a,b,c,d){var e=this._relativeTime[c];return w(e)?e(a,b,c,d):e.replace(/%d/i,a)}function yc(a,b){var c=this._relativeTime[a>0?"future":"past"];return w(c)?c(b):c.replace(/%s/i,b)}function zc(a,b,c,d){var e=H(),f=h().set(d,b);return e[c](f,a)}function Ac(a,b,c,d,e){if("number"==typeof a&&(b=a,a=void 0),a=a||"",null!=b)return zc(a,b,c,e);var f,g=[];for(f=0;d>f;f++)g[f]=zc(a,f,c,e);return g}function Bc(a,b){return Ac(a,b,"months",12,"month")}function Cc(a,b){return Ac(a,b,"monthsShort",12,"month")}function Dc(a,b){return Ac(a,b,"weekdays",7,"day")}function Ec(a,b){return Ac(a,b,"weekdaysShort",7,"day")}function Fc(a,b){return Ac(a,b,"weekdaysMin",7,"day")}function Gc(){var a=this._data;return this._milliseconds=xe(this._milliseconds),this._days=xe(this._days),this._months=xe(this._months),a.milliseconds=xe(a.milliseconds),a.seconds=xe(a.seconds),a.minutes=xe(a.minutes),a.hours=xe(a.hours),a.months=xe(a.months),a.years=xe(a.years),this}function Hc(a,b,c,d){var e=cb(b,c);return a._milliseconds+=d*e._milliseconds,a._days+=d*e._days,a._months+=d*e._months,a._bubble()}function Ic(a,b){return Hc(this,a,b,1)}function Jc(a,b){return Hc(this,a,b,-1)}function Kc(a){return 0>a?Math.floor(a):Math.ceil(a)}function Lc(){var a,b,c,d,e,f=this._milliseconds,g=this._days,h=this._months,i=this._data;return f>=0&&g>=0&&h>=0||0>=f&&0>=g&&0>=h||(f+=864e5*Kc(Nc(h)+g),g=0,h=0),i.milliseconds=f%1e3,a=q(f/1e3),i.seconds=a%60,b=q(a/60),i.minutes=b%60,c=q(b/60),i.hours=c%24,g+=q(c/24),e=q(Mc(g)),h+=e,g-=Kc(Nc(e)),d=q(h/12),h%=12,i.days=g,i.months=h,i.years=d,this}function Mc(a){return 4800*a/146097}function Nc(a){return 146097*a/4800}function Oc(a){var b,c,d=this._milliseconds;if(a=K(a),"month"===a||"year"===a)return b=this._days+d/864e5,c=this._months+Mc(b),"month"===a?c:c/12;switch(b=this._days+Math.round(Nc(this._months)),a){case"week":return b/7+d/6048e5;case"day":return b+d/864e5;case"hour":return 24*b+d/36e5;case"minute":return 1440*b+d/6e4;case"second":return 86400*b+d/1e3;case"millisecond":return Math.floor(864e5*b)+d;default:throw new Error("Unknown unit "+a)}}function Pc(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*r(this._months/12)}function Qc(a){return function(){return this.as(a)}}function Rc(a){return a=K(a),this[a+"s"]()}function Sc(a){return function(){return this._data[a]}}function Tc(){return q(this.days()/7)}function Uc(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function Vc(a,b,c){var d=cb(a).abs(),e=Ne(d.as("s")),f=Ne(d.as("m")),g=Ne(d.as("h")),h=Ne(d.as("d")),i=Ne(d.as("M")),j=Ne(d.as("y")),k=e=f&&["m"]||f=g&&["h"]||g=h&&["d"]||h=i&&["M"]||i=j&&["y"]||["yy",j];return k[2]=b,k[3]=+a>0,k[4]=c,Uc.apply(null,k)}function Wc(a,b){return void 0===Oe[a]?!1:void 0===b?Oe[a]:(Oe[a]=b,!0)}function Xc(a){var b=this.localeData(),c=Vc(this,!a,b);return a&&(c=b.pastFuture(+this,c)),b.postformat(c)}function Yc(){var a,b,c,d=Pe(this._milliseconds)/1e3,e=Pe(this._days),f=Pe(this._months);a=q(d/60),b=q(a/60),d%=60,a%=60,c=q(f/12),f%=12;var g=c,h=f,i=e,j=b,k=a,l=d,m=this.asSeconds();return m?(0>m?"-":"")+"P"+(g?g+"Y":"")+(h?h+"M":"")+(i?i+"D":"")+(j||k||l?"T":"")+(j?j+"H":"")+(k?k+"M":"")+(l?l+"S":""):"P0D"}var Zc,$c=a.momentProperties=[],_c=!1,ad={};a.suppressDeprecationWarnings=!1;var bd,cd={},dd={},ed=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,fd=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,gd={},hd={},id=/\d/,jd=/\d\d/,kd=/\d{3}/,ld=/\d{4}/,md=/[+-]?\d{6}/,nd=/\d\d?/,od=/\d\d\d\d?/,pd=/\d\d\d\d\d\d?/,qd=/\d{1,3}/,rd=/\d{1,4}/,sd=/[+-]?\d{1,6}/,td=/\d+/,ud=/[+-]?\d+/,vd=/Z|[+-]\d\d:?\d\d/gi,wd=/Z|[+-]\d\d(?::?\d\d)?/gi,xd=/[+-]?\d+(\.\d{1,3})?/,yd=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,zd={},Ad={},Bd=0,Cd=1,Dd=2,Ed=3,Fd=4,Gd=5,Hd=6,Id=7,Jd=8;R("M",["MM",2],"Mo",function(){return this.month()+1}),R("MMM",0,0,function(a){return this.localeData().monthsShort(this,a)}),R("MMMM",0,0,function(a){return this.localeData().months(this,a)}),J("month","M"),W("M",nd),W("MM",nd,jd),W("MMM",function(a,b){return b.monthsShortRegex(a)}),W("MMMM",function(a,b){return b.monthsRegex(a)}),$(["M","MM"],function(a,b){b[Cd]=r(a)-1}),$(["MMM","MMMM"],function(a,b,c,d){var e=c._locale.monthsParse(a,d,c._strict);null!=e?b[Cd]=e:j(c).invalidMonth=a});var Kd=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/,Ld="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Md="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),Nd=yd,Od=yd,Pd=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Qd=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Rd=/Z|[+-]\d\d(?::?\d\d)?/,Sd=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Td=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Ud=/^\/?Date\((\-?\d+)/i;a.createFromInputFallback=u("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(a){a._d=new Date(a._i+(a._useUTC?" UTC":""))}),R("Y",0,0,function(){var a=this.year();return 9999>=a?""+a:"+"+a}),R(0,["YY",2],0,function(){return this.year()%100}),R(0,["YYYY",4],0,"year"),R(0,["YYYYY",5],0,"year"),R(0,["YYYYYY",6,!0],0,"year"),J("year","y"),W("Y",ud),W("YY",nd,jd),W("YYYY",rd,ld),W("YYYYY",sd,md),W("YYYYYY",sd,md),$(["YYYYY","YYYYYY"],Bd),$("YYYY",function(b,c){c[Bd]=2===b.length?a.parseTwoDigitYear(b):r(b); -}),$("YY",function(b,c){c[Bd]=a.parseTwoDigitYear(b)}),$("Y",function(a,b){b[Bd]=parseInt(a,10)}),a.parseTwoDigitYear=function(a){return r(a)+(r(a)>68?1900:2e3)};var Vd=M("FullYear",!1);a.ISO_8601=function(){};var Wd=u("moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var a=Ja.apply(null,arguments);return this.isValid()&&a.isValid()?this>a?this:a:l()}),Xd=u("moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var a=Ja.apply(null,arguments);return this.isValid()&&a.isValid()?a>this?this:a:l()}),Yd=function(){return Date.now?Date.now():+new Date};Pa("Z",":"),Pa("ZZ",""),W("Z",wd),W("ZZ",wd),$(["Z","ZZ"],function(a,b,c){c._useUTC=!0,c._tzm=Qa(wd,a)});var Zd=/([\+\-]|\d\d)/gi;a.updateOffset=function(){};var $d=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/,_d=/^(-)?P(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)W)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?$/;cb.fn=Na.prototype;var ae=hb(1,"add"),be=hb(-1,"subtract");a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ";var ce=u("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(a){return void 0===a?this.localeData():this.locale(a)});R(0,["gg",2],0,function(){return this.weekYear()%100}),R(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ob("gggg","weekYear"),Ob("ggggg","weekYear"),Ob("GGGG","isoWeekYear"),Ob("GGGGG","isoWeekYear"),J("weekYear","gg"),J("isoWeekYear","GG"),W("G",ud),W("g",ud),W("GG",nd,jd),W("gg",nd,jd),W("GGGG",rd,ld),W("gggg",rd,ld),W("GGGGG",sd,md),W("ggggg",sd,md),_(["gggg","ggggg","GGGG","GGGGG"],function(a,b,c,d){b[d.substr(0,2)]=r(a)}),_(["gg","GG"],function(b,c,d,e){c[e]=a.parseTwoDigitYear(b)}),R("Q",0,"Qo","quarter"),J("quarter","Q"),W("Q",id),$("Q",function(a,b){b[Cd]=3*(r(a)-1)}),R("w",["ww",2],"wo","week"),R("W",["WW",2],"Wo","isoWeek"),J("week","w"),J("isoWeek","W"),W("w",nd),W("ww",nd,jd),W("W",nd),W("WW",nd,jd),_(["w","ww","W","WW"],function(a,b,c,d){b[d.substr(0,1)]=r(a)});var de={dow:0,doy:6};R("D",["DD",2],"Do","date"),J("date","D"),W("D",nd),W("DD",nd,jd),W("Do",function(a,b){return a?b._ordinalParse:b._ordinalParseLenient}),$(["D","DD"],Dd),$("Do",function(a,b){b[Dd]=r(a.match(nd)[0],10)});var ee=M("Date",!0);R("d",0,"do","day"),R("dd",0,0,function(a){return this.localeData().weekdaysMin(this,a)}),R("ddd",0,0,function(a){return this.localeData().weekdaysShort(this,a)}),R("dddd",0,0,function(a){return this.localeData().weekdays(this,a)}),R("e",0,0,"weekday"),R("E",0,0,"isoWeekday"),J("day","d"),J("weekday","e"),J("isoWeekday","E"),W("d",nd),W("e",nd),W("E",nd),W("dd",yd),W("ddd",yd),W("dddd",yd),_(["dd","ddd","dddd"],function(a,b,c,d){var e=c._locale.weekdaysParse(a,d,c._strict);null!=e?b.d=e:j(c).invalidWeekday=a}),_(["d","e","E"],function(a,b,c,d){b[d]=r(a)});var fe="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),ge="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),he="Su_Mo_Tu_We_Th_Fr_Sa".split("_");R("DDD",["DDDD",3],"DDDo","dayOfYear"),J("dayOfYear","DDD"),W("DDD",qd),W("DDDD",kd),$(["DDD","DDDD"],function(a,b,c){c._dayOfYear=r(a)}),R("H",["HH",2],0,"hour"),R("h",["hh",2],0,ic),R("hmm",0,0,function(){return""+ic.apply(this)+Q(this.minutes(),2)}),R("hmmss",0,0,function(){return""+ic.apply(this)+Q(this.minutes(),2)+Q(this.seconds(),2)}),R("Hmm",0,0,function(){return""+this.hours()+Q(this.minutes(),2)}),R("Hmmss",0,0,function(){return""+this.hours()+Q(this.minutes(),2)+Q(this.seconds(),2)}),jc("a",!0),jc("A",!1),J("hour","h"),W("a",kc),W("A",kc),W("H",nd),W("h",nd),W("HH",nd,jd),W("hh",nd,jd),W("hmm",od),W("hmmss",pd),W("Hmm",od),W("Hmmss",pd),$(["H","HH"],Ed),$(["a","A"],function(a,b,c){c._isPm=c._locale.isPM(a),c._meridiem=a}),$(["h","hh"],function(a,b,c){b[Ed]=r(a),j(c).bigHour=!0}),$("hmm",function(a,b,c){var d=a.length-2;b[Ed]=r(a.substr(0,d)),b[Fd]=r(a.substr(d)),j(c).bigHour=!0}),$("hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[Ed]=r(a.substr(0,d)),b[Fd]=r(a.substr(d,2)),b[Gd]=r(a.substr(e)),j(c).bigHour=!0}),$("Hmm",function(a,b,c){var d=a.length-2;b[Ed]=r(a.substr(0,d)),b[Fd]=r(a.substr(d))}),$("Hmmss",function(a,b,c){var d=a.length-4,e=a.length-2;b[Ed]=r(a.substr(0,d)),b[Fd]=r(a.substr(d,2)),b[Gd]=r(a.substr(e))});var ie=/[ap]\.?m?\.?/i,je=M("Hours",!0);R("m",["mm",2],0,"minute"),J("minute","m"),W("m",nd),W("mm",nd,jd),$(["m","mm"],Fd);var ke=M("Minutes",!1);R("s",["ss",2],0,"second"),J("second","s"),W("s",nd),W("ss",nd,jd),$(["s","ss"],Gd);var le=M("Seconds",!1);R("S",0,0,function(){return~~(this.millisecond()/100)}),R(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),R(0,["SSS",3],0,"millisecond"),R(0,["SSSS",4],0,function(){return 10*this.millisecond()}),R(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),R(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),R(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),R(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),R(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),J("millisecond","ms"),W("S",qd,id),W("SS",qd,jd),W("SSS",qd,kd);var me;for(me="SSSS";me.length<=9;me+="S")W(me,td);for(me="S";me.length<=9;me+="S")$(me,nc);var ne=M("Milliseconds",!1);R("z",0,0,"zoneAbbr"),R("zz",0,0,"zoneName");var oe=o.prototype;oe.add=ae,oe.calendar=jb,oe.clone=kb,oe.diff=rb,oe.endOf=Db,oe.format=vb,oe.from=wb,oe.fromNow=xb,oe.to=yb,oe.toNow=zb,oe.get=P,oe.invalidAt=Mb,oe.isAfter=lb,oe.isBefore=mb,oe.isBetween=nb,oe.isSame=ob,oe.isSameOrAfter=pb,oe.isSameOrBefore=qb,oe.isValid=Kb,oe.lang=ce,oe.locale=Ab,oe.localeData=Bb,oe.max=Xd,oe.min=Wd,oe.parsingFlags=Lb,oe.set=P,oe.startOf=Cb,oe.subtract=be,oe.toArray=Hb,oe.toObject=Ib,oe.toDate=Gb,oe.toISOString=ub,oe.toJSON=Jb,oe.toString=tb,oe.unix=Fb,oe.valueOf=Eb,oe.creationData=Nb,oe.year=Vd,oe.isLeapYear=sa,oe.weekYear=Pb,oe.isoWeekYear=Qb,oe.quarter=oe.quarters=Vb,oe.month=ga,oe.daysInMonth=ha,oe.week=oe.weeks=Zb,oe.isoWeek=oe.isoWeeks=$b,oe.weeksInYear=Sb,oe.isoWeeksInYear=Rb,oe.date=ee,oe.day=oe.days=ec,oe.weekday=fc,oe.isoWeekday=gc,oe.dayOfYear=hc,oe.hour=oe.hours=je,oe.minute=oe.minutes=ke,oe.second=oe.seconds=le,oe.millisecond=oe.milliseconds=ne,oe.utcOffset=Ta,oe.utc=Va,oe.local=Wa,oe.parseZone=Xa,oe.hasAlignedHourOffset=Ya,oe.isDST=Za,oe.isDSTShifted=$a,oe.isLocal=_a,oe.isUtcOffset=ab,oe.isUtc=bb,oe.isUTC=bb,oe.zoneAbbr=oc,oe.zoneName=pc,oe.dates=u("dates accessor is deprecated. Use date instead.",ee),oe.months=u("months accessor is deprecated. Use month instead",ga),oe.years=u("years accessor is deprecated. Use year instead",Vd),oe.zone=u("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",Ua);var pe=oe,qe={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},re={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},se="Invalid date",te="%d",ue=/\d{1,2}/,ve={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},we=A.prototype;we._calendar=qe,we.calendar=sc,we._longDateFormat=re,we.longDateFormat=tc,we._invalidDate=se,we.invalidDate=uc,we._ordinal=te,we.ordinal=vc,we._ordinalParse=ue,we.preparse=wc,we.postformat=wc,we._relativeTime=ve,we.relativeTime=xc,we.pastFuture=yc,we.set=y,we.months=ca,we._months=Ld,we.monthsShort=da,we._monthsShort=Md,we.monthsParse=ea,we._monthsRegex=Od,we.monthsRegex=ja,we._monthsShortRegex=Nd,we.monthsShortRegex=ia,we.week=Wb,we._week=de,we.firstDayOfYear=Yb,we.firstDayOfWeek=Xb,we.weekdays=ac,we._weekdays=fe,we.weekdaysMin=cc,we._weekdaysMin=he,we.weekdaysShort=bc,we._weekdaysShort=ge,we.weekdaysParse=dc,we.isPM=lc,we._meridiemParse=ie,we.meridiem=mc,E("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(a){var b=a%10,c=1===r(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),a.lang=u("moment.lang is deprecated. Use moment.locale instead.",E),a.langData=u("moment.langData is deprecated. Use moment.localeData instead.",H);var xe=Math.abs,ye=Qc("ms"),ze=Qc("s"),Ae=Qc("m"),Be=Qc("h"),Ce=Qc("d"),De=Qc("w"),Ee=Qc("M"),Fe=Qc("y"),Ge=Sc("milliseconds"),He=Sc("seconds"),Ie=Sc("minutes"),Je=Sc("hours"),Ke=Sc("days"),Le=Sc("months"),Me=Sc("years"),Ne=Math.round,Oe={s:45,m:45,h:22,d:26,M:11},Pe=Math.abs,Qe=Na.prototype;Qe.abs=Gc,Qe.add=Ic,Qe.subtract=Jc,Qe.as=Oc,Qe.asMilliseconds=ye,Qe.asSeconds=ze,Qe.asMinutes=Ae,Qe.asHours=Be,Qe.asDays=Ce,Qe.asWeeks=De,Qe.asMonths=Ee,Qe.asYears=Fe,Qe.valueOf=Pc,Qe._bubble=Lc,Qe.get=Rc,Qe.milliseconds=Ge,Qe.seconds=He,Qe.minutes=Ie,Qe.hours=Je,Qe.days=Ke,Qe.weeks=Tc,Qe.months=Le,Qe.years=Me,Qe.humanize=Xc,Qe.toISOString=Yc,Qe.toString=Yc,Qe.toJSON=Yc,Qe.locale=Ab,Qe.localeData=Bb,Qe.toIsoString=u("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Yc),Qe.lang=ce,R("X",0,0,"unix"),R("x",0,0,"valueOf"),W("x",ud),W("X",xd),$("X",function(a,b,c){c._d=new Date(1e3*parseFloat(a,10))}),$("x",function(a,b,c){c._d=new Date(r(a))}),a.version="2.12.0",b(Ja),a.fn=pe,a.min=La,a.max=Ma,a.now=Yd,a.utc=h,a.unix=qc,a.months=Bc,a.isDate=d,a.locale=E,a.invalid=l,a.duration=cb,a.isMoment=p,a.weekdays=Dc,a.parseZone=rc,a.localeData=H,a.isDuration=Oa,a.monthsShort=Cc,a.weekdaysMin=Fc,a.defineLocale=F,a.updateLocale=G,a.locales=I,a.weekdaysShort=Ec,a.normalizeUnits=K,a.relativeTimeThreshold=Wc,a.prototype=pe;var Re=a;return Re}); \ No newline at end of file diff --git a/static/js/moment.min.min.js b/static/js/moment.min.min.js deleted file mode 100644 index e7b3172..0000000 --- a/static/js/moment.min.min.js +++ /dev/null @@ -1,210 +0,0 @@ -//! moment.js -//! version : 2.12.0 -//! authors : Tim Wood, Iskren Chernev, Moment.js contributors -//! license : MIT -//! momentjs.com -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.moment=e()}(this,function(){"use strict" -function t(){return Qn.apply(null,arguments)}function e(t){return t instanceof Array||"[object Array]"===Object.prototype.toString.call(t)}function n(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function i(t,e){var n,i=[] -for(n=0;n0)for(n in Xn)i=Xn[n],s=e[i],h(s)||(t[i]=s) -return t}function f(e){c(this,e),this._d=new Date(null!=e._d?e._d.getTime():NaN),!1===Kn&&(Kn=!0,t.updateOffset(this),Kn=!1)}function m(t){return t instanceof f||null!=t&&null!=t._isAMomentObject}function _(t){return 0>t?Math.ceil(t):Math.floor(t)}function y(t){var e=+t,n=0 -return 0!==e&&isFinite(e)&&(n=_(e)),n}function g(t,e,n){var i,s=Math.min(t.length,e.length),r=Math.abs(t.length-e.length),a=0 -for(i=0;s>i;i++)(n&&t[i]!==e[i]||!n&&y(t[i])!==y(e[i]))&&a++ -return a+r}function p(e){!1===t.suppressDeprecationWarnings&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+e)}function v(t,e){var n=!0 -return r(function(){return n&&(p(t+"\nArguments: "+Array.prototype.slice.call(arguments).join(", ")+"\n"+(new Error).stack),n=!1),e.apply(this,arguments)},e)}function D(t,e){ti[t]||(p(e),ti[t]=!0)}function M(t){return t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)}function S(t){return"[object Object]"===Object.prototype.toString.call(t)}function Y(t){var e,n -for(n in t)e=t[n],M(e)?this[n]=e:this["_"+n]=e -this._config=t,this._ordinalParseLenient=new RegExp(this._ordinalParse.source+"|"+/\d{1,2}/.source)}function w(t,e){var n,i=r({},t) -for(n in e)s(e,n)&&(S(t[n])&&S(e[n])?(i[n]={},r(i[n],t[n]),r(i[n],e[n])):null!=e[n]?i[n]=e[n]:delete i[n]) -return i}function k(t){null!=t&&this.set(t)}function T(t){return t?t.toLowerCase().replace("_","-"):t}function b(t){for(var e,n,i,s,r=0;r0;){if(i=O(s.slice(0,e).join("-")))return i -if(n&&n.length>=e&&g(s,n,!0)>=e-1)break -e--}r++}return null}function O(t){var e=null -if(!ni[t]&&"undefined"!=typeof module&&module&&module.exports)try{e=ei._abbr,require("./locale/"+t),W(e)}catch(t){}return ni[t]}function W(t,e){var n -return t&&(n=h(e)?G(t):x(t,e))&&(ei=n),ei._abbr}function x(t,e){return null!==e?(e.abbr=t,null!=ni[t]?(D("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale"),e=w(ni[t]._config,e)):null!=e.parentLocale&&(null!=ni[e.parentLocale]?e=w(ni[e.parentLocale]._config,e):D("parentLocaleUndefined","specified parentLocale is not defined yet")),ni[t]=new k(e),W(t),ni[t]):(delete ni[t],null)}function U(t,e){if(null!=e){var n -null!=ni[t]&&(e=w(ni[t]._config,e)),n=new k(e),n.parentLocale=ni[t],ni[t]=n,W(t)}else null!=ni[t]&&(null!=ni[t].parentLocale?ni[t]=ni[t].parentLocale:null!=ni[t]&&delete ni[t]) -return ni[t]}function G(t){var n -if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return ei -if(!e(t)){if(n=O(t))return n -t=[t]}return b(t)}function P(){return Object.keys(ni)}function C(t,e){var n=t.toLowerCase() -ii[n]=ii[n+"s"]=ii[e]=t}function F(t){return"string"==typeof t?ii[t]||ii[t.toLowerCase()]:void 0}function H(t){var e,n,i={} -for(n in t)s(t,n)&&(e=F(n))&&(i[e]=t[n]) -return i}function L(e,n){return function(i){return null!=i?(N(this,e,i),t.updateOffset(this,n),this):V(this,e)}}function V(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function N(t,e,n){t.isValid()&&t._d["set"+(t._isUTC?"UTC":"")+e](n)}function I(t,e){var n -if("object"==typeof t)for(n in t)this.set(n,t[n]) -else if(t=F(t),M(this[t]))return this[t](e) -return this}function A(t,e,n){var i=""+Math.abs(t),s=e-i.length -return(t>=0?n?"+":"":"-")+Math.pow(10,Math.max(0,s)).toString().substr(1)+i}function R(t,e,n,i){var s=i -"string"==typeof i&&(s=function(){return this[i]()}),t&&(oi[t]=s),e&&(oi[e[0]]=function(){return A(s.apply(this,arguments),e[1],e[2])}),n&&(oi[n]=function(){return this.localeData().ordinal(s.apply(this,arguments),t)})}function E(t){return t.match(/\[[\s\S]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function j(t){var e,n,i=t.match(si) -for(e=0,n=i.length;n>e;e++)oi[i[e]]?i[e]=oi[i[e]]:i[e]=E(i[e]) -return function(s){var r="" -for(e=0;n>e;e++)r+=i[e]instanceof Function?i[e].call(s,t):i[e] -return r}}function z(t,e){return t.isValid()?(e=Z(e,t.localeData()),ai[e]=ai[e]||j(e),ai[e](t)):t.localeData().invalidDate()}function Z(t,e){function n(t){return e.longDateFormat(t)||t}var i=5 -for(ri.lastIndex=0;i>=0&&ri.test(t);)t=t.replace(ri,n),ri.lastIndex=0,i-=1 -return t}function $(t,e,n){Mi[t]=M(e)?e:function(t,i){return t&&n?n:e}}function q(t,e){return s(Mi,t)?Mi[t](e._strict,e._locale):new RegExp(J(t))}function J(t){return B(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(t,e,n,i,s){return e||n||i||s}))}function B(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function Q(t,e){var n,i=e -for("string"==typeof t&&(t=[t]),"number"==typeof e&&(i=function(t,n){n[e]=y(t)}),n=0;ni;i++){if(s=a([2e3,i]),n&&!this._longMonthsParse[i]&&(this._longMonthsParse[i]=new RegExp("^"+this.months(s,"").replace(".","")+"$","i"),this._shortMonthsParse[i]=new RegExp("^"+this.monthsShort(s,"").replace(".","")+"$","i")),n||this._monthsParse[i]||(r="^"+this.months(s,"")+"|^"+this.monthsShort(s,""),this._monthsParse[i]=new RegExp(r.replace(".",""),"i")),n&&"MMMM"===e&&this._longMonthsParse[i].test(t))return i -if(n&&"MMM"===e&&this._shortMonthsParse[i].test(t))return i -if(!n&&this._monthsParse[i].test(t))return i}}function st(t,e){var n -if(!t.isValid())return t -if("string"==typeof e)if(/^\d+$/.test(e))e=y(e) -else if("number"!=typeof(e=t.localeData().monthsParse(e)))return t -return n=Math.min(t.date(),tt(t.year(),e)),t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,n),t}function rt(e){return null!=e?(st(this,e),t.updateOffset(this,!0),this):V(this,"Month")}function at(){return tt(this.year(),this.month())}function ot(t){return this._monthsParseExact?(s(this,"_monthsRegex")||dt.call(this),t?this._monthsShortStrictRegex:this._monthsShortRegex):this._monthsShortStrictRegex&&t?this._monthsShortStrictRegex:this._monthsShortRegex}function ut(t){return this._monthsParseExact?(s(this,"_monthsRegex")||dt.call(this),t?this._monthsStrictRegex:this._monthsRegex):this._monthsStrictRegex&&t?this._monthsStrictRegex:this._monthsRegex}function dt(){function t(t,e){return e.length-t.length}var e,n,i=[],s=[],r=[] -for(e=0;12>e;e++)n=a([2e3,e]),i.push(this.monthsShort(n,"")),s.push(this.months(n,"")),r.push(this.months(n,"")),r.push(this.monthsShort(n,"")) -for(i.sort(t),s.sort(t),r.sort(t),e=0;12>e;e++)i[e]=B(i[e]),s[e]=B(s[e]),r[e]=B(r[e]) -this._monthsRegex=new RegExp("^("+r.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+s.join("|")+")$","i"),this._monthsShortStrictRegex=new RegExp("^("+i.join("|")+")$","i")}function lt(t){var e,n=t._a -return n&&-2===u(t).overflow&&(e=n[wi]<0||n[wi]>11?wi:n[ki]<1||n[ki]>tt(n[Yi],n[wi])?ki:n[Ti]<0||n[Ti]>24||24===n[Ti]&&(0!==n[bi]||0!==n[Oi]||0!==n[Wi])?Ti:n[bi]<0||n[bi]>59?bi:n[Oi]<0||n[Oi]>59?Oi:n[Wi]<0||n[Wi]>999?Wi:-1,u(t)._overflowDayOfYear&&(Yi>e||e>ki)&&(e=ki),u(t)._overflowWeeks&&-1===e&&(e=xi),u(t)._overflowWeekday&&-1===e&&(e=Ui),u(t).overflow=e),t}function ht(t){var e,n,i,s,r,a,o=t._i,d=Fi.exec(o)||Hi.exec(o) -if(d){for(u(t).iso=!0,e=0,n=Vi.length;n>e;e++)if(Vi[e][1].exec(d[1])){s=Vi[e][0],i=!1!==Vi[e][2] -break}if(null==s)return void(t._isValid=!1) -if(d[3]){for(e=0,n=Ni.length;n>e;e++)if(Ni[e][1].exec(d[3])){r=(d[2]||" ")+Ni[e][0] -break}if(null==r)return void(t._isValid=!1)}if(!i&&null!=r)return void(t._isValid=!1) -if(d[4]){if(!Li.exec(d[4]))return void(t._isValid=!1) -a="Z"}t._f=s+(r||"")+(a||""),Tt(t)}else t._isValid=!1}function ct(e){var n=Ii.exec(e._i) -return null!==n?void(e._d=new Date(+n[1])):(ht(e),void(!1===e._isValid&&(delete e._isValid,t.createFromInputFallback(e))))}function ft(t,e,n,i,s,r,a){var o=new Date(t,e,n,i,s,r,a) -return 100>t&&t>=0&&isFinite(o.getFullYear())&&o.setFullYear(t),o}function mt(t){var e=new Date(Date.UTC.apply(null,arguments)) -return 100>t&&t>=0&&isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t),e}function _t(t){return yt(t)?366:365}function yt(t){return t%4==0&&t%100!=0||t%400==0}function gt(){return yt(this.year())}function pt(t,e,n){var i=7+e-n -return-(7+mt(t,0,i).getUTCDay()-e)%7+i-1}function vt(t,e,n,i,s){var r,a,o=(7+n-i)%7,u=pt(t,i,s),d=1+7*(e-1)+o+u -return 0>=d?(r=t-1,a=_t(r)+d):d>_t(t)?(r=t+1,a=d-_t(t)):(r=t,a=d),{year:r,dayOfYear:a}}function Dt(t,e,n){var i,s,r=pt(t.year(),e,n),a=Math.floor((t.dayOfYear()-r-1)/7)+1 -return 1>a?(s=t.year()-1,i=a+Mt(s,e,n)):a>Mt(t.year(),e,n)?(i=a-Mt(t.year(),e,n),s=t.year()+1):(s=t.year(),i=a),{week:i,year:s}}function Mt(t,e,n){var i=pt(t,e,n),s=pt(t+1,e,n) -return(_t(t)-i+s)/7}function St(t,e,n){return null!=t?t:null!=e?e:n}function Yt(e){var n=new Date(t.now()) -return e._useUTC?[n.getUTCFullYear(),n.getUTCMonth(),n.getUTCDate()]:[n.getFullYear(),n.getMonth(),n.getDate()]}function wt(t){var e,n,i,s,r=[] -if(!t._d){for(i=Yt(t),t._w&&null==t._a[ki]&&null==t._a[wi]&&kt(t),t._dayOfYear&&(s=St(t._a[Yi],i[Yi]),t._dayOfYear>_t(s)&&(u(t)._overflowDayOfYear=!0),n=mt(s,0,t._dayOfYear),t._a[wi]=n.getUTCMonth(),t._a[ki]=n.getUTCDate()),e=0;3>e&&null==t._a[e];++e)t._a[e]=r[e]=i[e] -for(;7>e;e++)t._a[e]=r[e]=null==t._a[e]?2===e?1:0:t._a[e] -24===t._a[Ti]&&0===t._a[bi]&&0===t._a[Oi]&&0===t._a[Wi]&&(t._nextDay=!0,t._a[Ti]=0),t._d=(t._useUTC?mt:ft).apply(null,r),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[Ti]=24)}}function kt(t){var e,n,i,s,r,a,o,d -e=t._w,null!=e.GG||null!=e.W||null!=e.E?(r=1,a=4,n=St(e.GG,t._a[Yi],Dt(Ct(),1,4).year),i=St(e.W,1),(1>(s=St(e.E,1))||s>7)&&(d=!0)):(r=t._locale._week.dow,a=t._locale._week.doy,n=St(e.gg,t._a[Yi],Dt(Ct(),r,a).year),i=St(e.w,1),null!=e.d?(0>(s=e.d)||s>6)&&(d=!0):null!=e.e?(s=e.e+r,(e.e<0||e.e>6)&&(d=!0)):s=r),1>i||i>Mt(n,r,a)?u(t)._overflowWeeks=!0:null!=d?u(t)._overflowWeekday=!0:(o=vt(n,i,s,r,a),t._a[Yi]=o.year,t._dayOfYear=o.dayOfYear)}function Tt(e){if(e._f===t.ISO_8601)return void ht(e) -e._a=[],u(e).empty=!0 -var n,i,s,r,a,o=""+e._i,d=o.length,l=0 -for(s=Z(e._f,e._locale).match(si)||[],n=0;n0&&u(e).unusedInput.push(a),o=o.slice(o.indexOf(i)+i.length),l+=i.length),oi[r]?(i?u(e).empty=!1:u(e).unusedTokens.push(r),K(r,i,e)):e._strict&&!i&&u(e).unusedTokens.push(r) -u(e).charsLeftOver=d-l,o.length>0&&u(e).unusedInput.push(o),!0===u(e).bigHour&&e._a[Ti]<=12&&e._a[Ti]>0&&(u(e).bigHour=void 0),e._a[Ti]=bt(e._locale,e._a[Ti],e._meridiem),wt(e),lt(e)}function bt(t,e,n){var i -return null==n?e:null!=t.meridiemHour?t.meridiemHour(e,n):null!=t.isPM?(i=t.isPM(n),i&&12>e&&(e+=12),i||12!==e||(e=0),e):e}function Ot(t){var e,n,i,s,a -if(0===t._f.length)return u(t).invalidFormat=!0,void(t._d=new Date(NaN)) -for(s=0;sa)&&(i=a,n=e)) -r(t,n||e)}function Wt(t){if(!t._d){var e=H(t._i) -t._a=i([e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],function(t){return t&&parseInt(t,10)}),wt(t)}}function xt(t){var e=new f(lt(Ut(t))) -return e._nextDay&&(e.add(1,"d"),e._nextDay=void 0),e}function Ut(t){var i=t._i,s=t._f -return t._locale=t._locale||G(t._l),null===i||void 0===s&&""===i?l({nullInput:!0}):("string"==typeof i&&(t._i=i=t._locale.preparse(i)),m(i)?new f(lt(i)):(e(s)?Ot(t):s?Tt(t):n(i)?t._d=i:Gt(t),d(t)||(t._d=null),t))}function Gt(s){var r=s._i -void 0===r?s._d=new Date(t.now()):n(r)?s._d=new Date(+r):"string"==typeof r?ct(s):e(r)?(s._a=i(r.slice(0),function(t){return parseInt(t,10)}),wt(s)):"object"==typeof r?Wt(s):"number"==typeof r?s._d=new Date(r):t.createFromInputFallback(s)}function Pt(t,e,n,i,s){var r={} -return"boolean"==typeof n&&(i=n,n=void 0),r._isAMomentObject=!0,r._useUTC=r._isUTC=s,r._l=n,r._i=t,r._f=e,r._strict=i,xt(r)}function Ct(t,e,n,i){return Pt(t,e,n,i,!1)}function Ft(t,n){var i,s -if(1===n.length&&e(n[0])&&(n=n[0]),!n.length)return Ct() -for(i=n[0],s=1;st&&(t=-t,n="-"),n+A(~~(t/60),2)+e+A(~~t%60,2)})}function At(t,e){var n=(e||"").match(t)||[],i=n[n.length-1]||[],s=(i+"").match(zi)||["-",0,0],r=60*s[1]+y(s[2]) -return"+"===s[0]?r:-r}function Rt(e,i){var s,r -return i._isUTC?(s=i.clone(),r=(m(e)||n(e)?+e:+Ct(e))-+s,s._d.setTime(+s._d+r),t.updateOffset(s,!1),s):Ct(e).local()}function Et(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function jt(e,n){var i,s=this._offset||0 -return this.isValid()?null!=e?("string"==typeof e?e=At(vi,e):Math.abs(e)<16&&(e*=60),!this._isUTC&&n&&(i=Et(this)),this._offset=e,this._isUTC=!0,null!=i&&this.add(i,"m"),s!==e&&(!n||this._changeInProgress?oe(this,ee(e-s,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,t.updateOffset(this,!0),this._changeInProgress=null)),this):this._isUTC?s:Et(this):null!=e?this:NaN}function zt(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()}function Zt(t){return this.utcOffset(0,t)}function $t(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(Et(this),"m")),this}function qt(){return this._tzm?this.utcOffset(this._tzm):"string"==typeof this._i&&this.utcOffset(At(pi,this._i)),this}function Jt(t){return!!this.isValid()&&(t=t?Ct(t).utcOffset():0,(this.utcOffset()-t)%60==0)}function Bt(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Qt(){if(!h(this._isDSTShifted))return this._isDSTShifted -var t={} -if(c(t,this),t=Ut(t),t._a){var e=t._isUTC?a(t._a):Ct(t._a) -this._isDSTShifted=this.isValid()&&g(t._a,e.toArray())>0}else this._isDSTShifted=!1 -return this._isDSTShifted}function Xt(){return!!this.isValid()&&!this._isUTC}function Kt(){return!!this.isValid()&&this._isUTC}function te(){return!!this.isValid()&&(this._isUTC&&0===this._offset)}function ee(t,e){var n,i,r,a=t,o=null -return Nt(t)?a={ms:t._milliseconds,d:t._days,M:t._months}:"number"==typeof t?(a={},e?a[e]=t:a.milliseconds=t):(o=Zi.exec(t))?(n="-"===o[1]?-1:1,a={y:0,d:y(o[ki])*n,h:y(o[Ti])*n,m:y(o[bi])*n,s:y(o[Oi])*n,ms:y(o[Wi])*n}):(o=$i.exec(t))?(n="-"===o[1]?-1:1,a={y:ne(o[2],n),M:ne(o[3],n),w:ne(o[4],n),d:ne(o[5],n),h:ne(o[6],n),m:ne(o[7],n),s:ne(o[8],n)}):null==a?a={}:"object"==typeof a&&("from"in a||"to"in a)&&(r=se(Ct(a.from),Ct(a.to)),a={},a.ms=r.milliseconds,a.M=r.months),i=new Vt(a),Nt(t)&&s(t,"_locale")&&(i._locale=t._locale),i}function ne(t,e){var n=t&&parseFloat(t.replace(",",".")) -return(isNaN(n)?0:n)*e}function ie(t,e){var n={milliseconds:0,months:0} -return n.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(n.months,"M").isAfter(e)&&--n.months,n.milliseconds=+e-+t.clone().add(n.months,"M"),n}function se(t,e){var n -return t.isValid()&&e.isValid()?(e=Rt(e,t),t.isBefore(e)?n=ie(t,e):(n=ie(e,t),n.milliseconds=-n.milliseconds,n.months=-n.months),n):{milliseconds:0,months:0}}function re(t){return 0>t?-1*Math.round(-1*t):Math.round(t)}function ae(t,e){return function(n,i){var s,r -return null===i||isNaN(+i)||(D(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period)."),r=n,n=i,i=r),n="string"==typeof n?+n:n,s=ee(n,i),oe(this,s,t),this}}function oe(e,n,i,s){var r=n._milliseconds,a=re(n._days),o=re(n._months) -e.isValid()&&(s=null==s||s,r&&e._d.setTime(+e._d+r*i),a&&N(e,"Date",V(e,"Date")+a*i),o&&st(e,V(e,"Month")+o*i),s&&t.updateOffset(e,a||o))}function ue(t,e){var n=t||Ct(),i=Rt(n,this).startOf("day"),s=this.diff(i,"days",!0),r=-6>s?"sameElse":-1>s?"lastWeek":0>s?"lastDay":1>s?"sameDay":2>s?"nextDay":7>s?"nextWeek":"sameElse",a=e&&(M(e[r])?e[r]():e[r]) -return this.format(a||this.localeData().calendar(r,this,Ct(n)))}function de(){return new f(this)}function le(t,e){var n=m(t)?t:Ct(t) -return!(!this.isValid()||!n.isValid())&&(e=F(h(e)?"millisecond":e),"millisecond"===e?+this>+n:+n<+this.clone().startOf(e))}function he(t,e){var n=m(t)?t:Ct(t) -return!(!this.isValid()||!n.isValid())&&(e=F(h(e)?"millisecond":e),"millisecond"===e?+n>+this:+this.clone().endOf(e)<+n)}function ce(t,e,n){return this.isAfter(t,n)&&this.isBefore(e,n)}function fe(t,e){var n,i=m(t)?t:Ct(t) -return!(!this.isValid()||!i.isValid())&&(e=F(e||"millisecond"),"millisecond"===e?+this==+i:(n=+i,+this.clone().startOf(e)<=n&&n<=+this.clone().endOf(e)))}function me(t,e){return this.isSame(t,e)||this.isAfter(t,e)}function _e(t,e){return this.isSame(t,e)||this.isBefore(t,e)}function ye(t,e,n){var i,s,r,a -return this.isValid()?(i=Rt(t,this),i.isValid()?(s=6e4*(i.utcOffset()-this.utcOffset()),e=F(e),"year"===e||"month"===e||"quarter"===e?(a=ge(this,i),"quarter"===e?a/=3:"year"===e&&(a/=12)):(r=this-i,a="second"===e?r/1e3:"minute"===e?r/6e4:"hour"===e?r/36e5:"day"===e?(r-s)/864e5:"week"===e?(r-s)/6048e5:r),n?a:_(a)):NaN):NaN}function ge(t,e){var n,i,s=12*(e.year()-t.year())+(e.month()-t.month()),r=t.clone().add(s,"months") -return 0>e-r?(n=t.clone().add(s-1,"months"),i=(e-r)/(r-n)):(n=t.clone().add(s+1,"months"),i=(e-r)/(n-r)),-(s+i)}function pe(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")}function ve(){var t=this.clone().utc() -return 0r&&(e=r),ze.call(this,t,e,n,i,s))}function ze(t,e,n,i,s){var r=vt(t,e,n,i,s),a=mt(r.year,0,r.dayOfYear) -return this.year(a.getUTCFullYear()),this.month(a.getUTCMonth()),this.date(a.getUTCDate()),this}function Ze(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)}function $e(t){return Dt(t,this._week.dow,this._week.doy).week}function qe(){return this._week.dow}function Je(){return this._week.doy}function Be(t){var e=this.localeData().week(this) -return null==t?e:this.add(7*(t-e),"d")}function Qe(t){var e=Dt(this,1,4).week -return null==t?e:this.add(7*(t-e),"d")}function Xe(t,e){return"string"!=typeof t?t:isNaN(t)?(t=e.weekdaysParse(t),"number"==typeof t?t:null):parseInt(t,10)}function Ke(t,n){return e(this._weekdays)?this._weekdays[t.day()]:this._weekdays[this._weekdays.isFormat.test(n)?"format":"standalone"][t.day()]}function tn(t){return this._weekdaysShort[t.day()]}function en(t){return this._weekdaysMin[t.day()]}function nn(t,e,n){var i,s,r -for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),i=0;7>i;i++){if(s=Ct([2e3,1]).day(i),n&&!this._fullWeekdaysParse[i]&&(this._fullWeekdaysParse[i]=new RegExp("^"+this.weekdays(s,"").replace(".",".?")+"$","i"),this._shortWeekdaysParse[i]=new RegExp("^"+this.weekdaysShort(s,"").replace(".",".?")+"$","i"),this._minWeekdaysParse[i]=new RegExp("^"+this.weekdaysMin(s,"").replace(".",".?")+"$","i")),this._weekdaysParse[i]||(r="^"+this.weekdays(s,"")+"|^"+this.weekdaysShort(s,"")+"|^"+this.weekdaysMin(s,""),this._weekdaysParse[i]=new RegExp(r.replace(".",""),"i")),n&&"dddd"===e&&this._fullWeekdaysParse[i].test(t))return i -if(n&&"ddd"===e&&this._shortWeekdaysParse[i].test(t))return i -if(n&&"dd"===e&&this._minWeekdaysParse[i].test(t))return i -if(!n&&this._weekdaysParse[i].test(t))return i}}function sn(t){if(!this.isValid())return null!=t?this:NaN -var e=this._isUTC?this._d.getUTCDay():this._d.getDay() -return null!=t?(t=Xe(t,this.localeData()),this.add(t-e,"d")):e}function rn(t){if(!this.isValid())return null!=t?this:NaN -var e=(this.day()+7-this.localeData()._week.dow)%7 -return null==t?e:this.add(t-e,"d")}function an(t){return this.isValid()?null==t?this.day()||7:this.day(this.day()%7?t:t-7):null!=t?this:NaN}function on(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1 -return null==t?e:this.add(t-e,"d")}function un(){return this.hours()%12||12}function dn(t,e){R(t,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)})}function ln(t,e){return e._meridiemParse}function hn(t){return"p"===(t+"").toLowerCase().charAt(0)}function cn(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"}function fn(t,e){e[Wi]=y(1e3*("0."+t))}function mn(){return this._isUTC?"UTC":""}function _n(){return this._isUTC?"Coordinated Universal Time":""}function yn(t){return Ct(1e3*t)}function gn(){return Ct.apply(null,arguments).parseZone()}function pn(t,e,n){var i=this._calendar[t] -return M(i)?i.call(e,n):i}function vn(t){var e=this._longDateFormat[t],n=this._longDateFormat[t.toUpperCase()] -return e||!n?e:(this._longDateFormat[t]=n.replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t])}function Dn(){return this._invalidDate}function Mn(t){return this._ordinal.replace("%d",t)}function Sn(t){return t}function Yn(t,e,n,i){var s=this._relativeTime[n] -return M(s)?s(t,e,n,i):s.replace(/%d/i,t)}function wn(t,e){var n=this._relativeTime[t>0?"future":"past"] -return M(n)?n(e):n.replace(/%s/i,e)}function kn(t,e,n,i){var s=G(),r=a().set(i,e) -return s[n](r,t)}function Tn(t,e,n,i,s){if("number"==typeof t&&(e=t,t=void 0),t=t||"",null!=e)return kn(t,e,n,s) -var r,a=[] -for(r=0;i>r;r++)a[r]=kn(t,r,n,s) -return a}function bn(t,e){return Tn(t,e,"months",12,"month")}function On(t,e){return Tn(t,e,"monthsShort",12,"month")}function Wn(t,e){return Tn(t,e,"weekdays",7,"day")}function xn(t,e){return Tn(t,e,"weekdaysShort",7,"day")}function Un(t,e){return Tn(t,e,"weekdaysMin",7,"day")}function Gn(){var t=this._data -return this._milliseconds=fs(this._milliseconds),this._days=fs(this._days),this._months=fs(this._months),t.milliseconds=fs(t.milliseconds),t.seconds=fs(t.seconds),t.minutes=fs(t.minutes),t.hours=fs(t.hours),t.months=fs(t.months),t.years=fs(t.years),this}function Pn(t,e,n,i){var s=ee(e,n) -return t._milliseconds+=i*s._milliseconds,t._days+=i*s._days,t._months+=i*s._months,t._bubble()}function Cn(t,e){return Pn(this,t,e,1)}function Fn(t,e){return Pn(this,t,e,-1)}function Hn(t){return 0>t?Math.floor(t):Math.ceil(t)}function Ln(){var t,e,n,i,s,r=this._milliseconds,a=this._days,o=this._months,u=this._data -return r>=0&&a>=0&&o>=0||0>=r&&0>=a&&0>=o||(r+=864e5*Hn(Nn(o)+a),a=0,o=0),u.milliseconds=r%1e3,t=_(r/1e3),u.seconds=t%60,e=_(t/60),u.minutes=e%60,n=_(e/60),u.hours=n%24,a+=_(n/24),s=_(Vn(a)),o+=s,a-=Hn(Nn(s)),i=_(o/12),o%=12,u.days=a,u.months=o,u.years=i,this}function Vn(t){return 4800*t/146097}function Nn(t){return 146097*t/4800}function In(t){var e,n,i=this._milliseconds -if("month"===(t=F(t))||"year"===t)return e=this._days+i/864e5,n=this._months+Vn(e),"month"===t?n:n/12 -switch(e=this._days+Math.round(Nn(this._months)),t){case"week":return e/7+i/6048e5 -case"day":return e+i/864e5 -case"hour":return 24*e+i/36e5 -case"minute":return 1440*e+i/6e4 -case"second":return 86400*e+i/1e3 -case"millisecond":return Math.floor(864e5*e)+i -default:throw new Error("Unknown unit "+t)}}function An(){return this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*y(this._months/12)}function Rn(t){return function(){return this.as(t)}}function En(t){return t=F(t),this[t+"s"]()}function jn(t){return function(){return this._data[t]}}function zn(){return _(this.days()/7)}function Zn(t,e,n,i,s){return s.relativeTime(e||1,!!n,t,i)}function $n(t,e,n){var i=ee(t).abs(),s=Ws(i.as("s")),r=Ws(i.as("m")),a=Ws(i.as("h")),o=Ws(i.as("d")),u=Ws(i.as("M")),d=Ws(i.as("y")),l=s=r&&["m"]||r=a&&["h"]||a=o&&["d"]||o=u&&["M"]||u=d&&["y"]||["yy",d] -return l[2]=e,l[3]=+t>0,l[4]=n,Zn.apply(null,l)}function qn(t,e){return void 0!==xs[t]&&(void 0===e?xs[t]:(xs[t]=e,!0))}function Jn(t){var e=this.localeData(),n=$n(this,!t,e) -return t&&(n=e.pastFuture(+this,n)),e.postformat(n)}function Bn(){var t,e,n,i=Us(this._milliseconds)/1e3,s=Us(this._days),r=Us(this._months) -t=_(i/60),e=_(t/60),i%=60,t%=60,n=_(r/12),r%=12 -var a=n,o=r,u=s,d=e,l=t,h=i,c=this.asSeconds() -return c?(0>c?"-":"")+"P"+(a?a+"Y":"")+(o?o+"M":"")+(u?u+"D":"")+(d||l||h?"T":"")+(d?d+"H":"")+(l?l+"M":"")+(h?h+"S":""):"P0D"}var Qn,Xn=t.momentProperties=[],Kn=!1,ti={} -t.suppressDeprecationWarnings=!1 -var ei,ni={},ii={},si=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,ri=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,ai={},oi={},ui=/\d\d/,di=/\d{4}/,li=/[+-]?\d{6}/,hi=/\d\d?/,ci=/\d\d\d\d?/,fi=/\d\d\d\d\d\d?/,mi=/\d{1,3}/,_i=/\d{1,4}/,yi=/[+-]?\d{1,6}/,gi=/[+-]?\d+/,pi=/Z|[+-]\d\d:?\d\d/gi,vi=/Z|[+-]\d\d(?::?\d\d)?/gi,Di=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Mi={},Si={},Yi=0,wi=1,ki=2,Ti=3,bi=4,Oi=5,Wi=6,xi=7,Ui=8 -R("M",["MM",2],"Mo",function(){return this.month()+1}),R("MMM",0,0,function(t){return this.localeData().monthsShort(this,t)}),R("MMMM",0,0,function(t){return this.localeData().months(this,t)}),C("month","M"),$("M",hi),$("MM",hi,ui),$("MMM",function(t,e){return e.monthsShortRegex(t)}),$("MMMM",function(t,e){return e.monthsRegex(t)}),Q(["M","MM"],function(t,e){e[wi]=y(t)-1}),Q(["MMM","MMMM"],function(t,e,n,i){var s=n._locale.monthsParse(t,i,n._strict) -null!=s?e[wi]=s:u(n).invalidMonth=t}) -var Gi=/D[oD]?(\[[^\[\]]*\]|\s+)+MMMM?/,Pi="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Ci="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),Fi=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Hi=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?/,Li=/Z|[+-]\d\d(?::?\d\d)?/,Vi=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Ni=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Ii=/^\/?Date\((\-?\d+)/i -t.createFromInputFallback=v("moment construction falls back to js Date. This is discouraged and will be removed in upcoming major release. Please refer to https://github.com/moment/moment/issues/1407 for more info.",function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))}),R("Y",0,0,function(){var t=this.year() -return 9999>=t?""+t:"+"+t}),R(0,["YY",2],0,function(){return this.year()%100}),R(0,["YYYY",4],0,"year"),R(0,["YYYYY",5],0,"year"),R(0,["YYYYYY",6,!0],0,"year"),C("year","y"),$("Y",gi),$("YY",hi,ui),$("YYYY",_i,di),$("YYYYY",yi,li),$("YYYYYY",yi,li),Q(["YYYYY","YYYYYY"],Yi),Q("YYYY",function(e,n){n[Yi]=2===e.length?t.parseTwoDigitYear(e):y(e)}),Q("YY",function(e,n){n[Yi]=t.parseTwoDigitYear(e)}),Q("Y",function(t,e){e[Yi]=parseInt(t,10)}),t.parseTwoDigitYear=function(t){return y(t)+(y(t)>68?1900:2e3)} -var Ai=L("FullYear",!1) -t.ISO_8601=function(){} -var Ri=v("moment().min is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548",function(){var t=Ct.apply(null,arguments) -return this.isValid()&&t.isValid()?this>t?this:t:l()}),Ei=v("moment().max is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548",function(){var t=Ct.apply(null,arguments) -return this.isValid()&&t.isValid()?t>this?this:t:l()}),ji=function(){return Date.now?Date.now():+new Date} -It("Z",":"),It("ZZ",""),$("Z",vi),$("ZZ",vi),Q(["Z","ZZ"],function(t,e,n){n._useUTC=!0,n._tzm=At(vi,t)}) -var zi=/([\+\-]|\d\d)/gi -t.updateOffset=function(){} -var Zi=/^(\-)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?\d*)?$/,$i=/^(-)?P(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)W)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?$/ -ee.fn=Vt.prototype -var qi=ae(1,"add"),Ji=ae(-1,"subtract") -t.defaultFormat="YYYY-MM-DDTHH:mm:ssZ" -var Bi=v("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(t){return void 0===t?this.localeData():this.locale(t)}) -R(0,["gg",2],0,function(){return this.weekYear()%100}),R(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ne("gggg","weekYear"),Ne("ggggg","weekYear"),Ne("GGGG","isoWeekYear"),Ne("GGGGG","isoWeekYear"),C("weekYear","gg"),C("isoWeekYear","GG"),$("G",gi),$("g",gi),$("GG",hi,ui),$("gg",hi,ui),$("GGGG",_i,di),$("gggg",_i,di),$("GGGGG",yi,li),$("ggggg",yi,li),X(["gggg","ggggg","GGGG","GGGGG"],function(t,e,n,i){e[i.substr(0,2)]=y(t)}),X(["gg","GG"],function(e,n,i,s){n[s]=t.parseTwoDigitYear(e)}),R("Q",0,"Qo","quarter"),C("quarter","Q"),$("Q",/\d/),Q("Q",function(t,e){e[wi]=3*(y(t)-1)}),R("w",["ww",2],"wo","week"),R("W",["WW",2],"Wo","isoWeek"),C("week","w"),C("isoWeek","W"),$("w",hi),$("ww",hi,ui),$("W",hi),$("WW",hi,ui),X(["w","ww","W","WW"],function(t,e,n,i){e[i.substr(0,1)]=y(t)}) -var Qi={dow:0,doy:6} -R("D",["DD",2],"Do","date"),C("date","D"),$("D",hi),$("DD",hi,ui),$("Do",function(t,e){return t?e._ordinalParse:e._ordinalParseLenient}),Q(["D","DD"],ki),Q("Do",function(t,e){e[ki]=y(t.match(hi)[0],10)}) -var Xi=L("Date",!0) -R("d",0,"do","day"),R("dd",0,0,function(t){return this.localeData().weekdaysMin(this,t)}),R("ddd",0,0,function(t){return this.localeData().weekdaysShort(this,t)}),R("dddd",0,0,function(t){return this.localeData().weekdays(this,t)}),R("e",0,0,"weekday"),R("E",0,0,"isoWeekday"),C("day","d"),C("weekday","e"),C("isoWeekday","E"),$("d",hi),$("e",hi),$("E",hi),$("dd",Di),$("ddd",Di),$("dddd",Di),X(["dd","ddd","dddd"],function(t,e,n,i){var s=n._locale.weekdaysParse(t,i,n._strict) -null!=s?e.d=s:u(n).invalidWeekday=t}),X(["d","e","E"],function(t,e,n,i){e[i]=y(t)}) -var Ki="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),ts="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),es="Su_Mo_Tu_We_Th_Fr_Sa".split("_") -R("DDD",["DDDD",3],"DDDo","dayOfYear"),C("dayOfYear","DDD"),$("DDD",mi),$("DDDD",/\d{3}/),Q(["DDD","DDDD"],function(t,e,n){n._dayOfYear=y(t)}),R("H",["HH",2],0,"hour"),R("h",["hh",2],0,un),R("hmm",0,0,function(){return""+un.apply(this)+A(this.minutes(),2)}),R("hmmss",0,0,function(){return""+un.apply(this)+A(this.minutes(),2)+A(this.seconds(),2)}),R("Hmm",0,0,function(){return""+this.hours()+A(this.minutes(),2)}),R("Hmmss",0,0,function(){return""+this.hours()+A(this.minutes(),2)+A(this.seconds(),2)}),dn("a",!0),dn("A",!1),C("hour","h"),$("a",ln),$("A",ln),$("H",hi),$("h",hi),$("HH",hi,ui),$("hh",hi,ui),$("hmm",ci),$("hmmss",fi),$("Hmm",ci),$("Hmmss",fi),Q(["H","HH"],Ti),Q(["a","A"],function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t}),Q(["h","hh"],function(t,e,n){e[Ti]=y(t),u(n).bigHour=!0}),Q("hmm",function(t,e,n){var i=t.length-2 -e[Ti]=y(t.substr(0,i)),e[bi]=y(t.substr(i)),u(n).bigHour=!0}),Q("hmmss",function(t,e,n){var i=t.length-4,s=t.length-2 -e[Ti]=y(t.substr(0,i)),e[bi]=y(t.substr(i,2)),e[Oi]=y(t.substr(s)),u(n).bigHour=!0}),Q("Hmm",function(t,e,n){var i=t.length-2 -e[Ti]=y(t.substr(0,i)),e[bi]=y(t.substr(i))}),Q("Hmmss",function(t,e,n){var i=t.length-4,s=t.length-2 -e[Ti]=y(t.substr(0,i)),e[bi]=y(t.substr(i,2)),e[Oi]=y(t.substr(s))}) -var ns=L("Hours",!0) -R("m",["mm",2],0,"minute"),C("minute","m"),$("m",hi),$("mm",hi,ui),Q(["m","mm"],bi) -var is=L("Minutes",!1) -R("s",["ss",2],0,"second"),C("second","s"),$("s",hi),$("ss",hi,ui),Q(["s","ss"],Oi) -var ss=L("Seconds",!1) -R("S",0,0,function(){return~~(this.millisecond()/100)}),R(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),R(0,["SSS",3],0,"millisecond"),R(0,["SSSS",4],0,function(){return 10*this.millisecond()}),R(0,["SSSSS",5],0,function(){return 100*this.millisecond()}),R(0,["SSSSSS",6],0,function(){return 1e3*this.millisecond()}),R(0,["SSSSSSS",7],0,function(){return 1e4*this.millisecond()}),R(0,["SSSSSSSS",8],0,function(){return 1e5*this.millisecond()}),R(0,["SSSSSSSSS",9],0,function(){return 1e6*this.millisecond()}),C("millisecond","ms"),$("S",mi,/\d/),$("SS",mi,ui),$("SSS",mi,/\d{3}/) -var rs -for(rs="SSSS";rs.length<=9;rs+="S")$(rs,/\d+/) -for(rs="S";rs.length<=9;rs+="S")Q(rs,fn) -var as=L("Milliseconds",!1) -R("z",0,0,"zoneAbbr"),R("zz",0,0,"zoneName") -var os=f.prototype -os.add=qi,os.calendar=ue,os.clone=de,os.diff=ye,os.endOf=Oe,os.format=De,os.from=Me,os.fromNow=Se,os.to=Ye,os.toNow=we,os.get=I,os.invalidAt=Le,os.isAfter=le,os.isBefore=he,os.isBetween=ce,os.isSame=fe,os.isSameOrAfter=me,os.isSameOrBefore=_e,os.isValid=Fe,os.lang=Bi,os.locale=ke,os.localeData=Te,os.max=Ei,os.min=Ri,os.parsingFlags=He,os.set=I,os.startOf=be,os.subtract=Ji,os.toArray=Ge,os.toObject=Pe,os.toDate=Ue,os.toISOString=ve,os.toJSON=Ce,os.toString=pe,os.unix=xe,os.valueOf=We,os.creationData=Ve,os.year=Ai,os.isLeapYear=gt,os.weekYear=Ie,os.isoWeekYear=Ae,os.quarter=os.quarters=Ze,os.month=rt,os.daysInMonth=at,os.week=os.weeks=Be,os.isoWeek=os.isoWeeks=Qe,os.weeksInYear=Ee,os.isoWeeksInYear=Re,os.date=Xi,os.day=os.days=sn,os.weekday=rn,os.isoWeekday=an,os.dayOfYear=on,os.hour=os.hours=ns,os.minute=os.minutes=is,os.second=os.seconds=ss,os.millisecond=os.milliseconds=as,os.utcOffset=jt,os.utc=Zt,os.local=$t,os.parseZone=qt,os.hasAlignedHourOffset=Jt,os.isDST=Bt,os.isDSTShifted=Qt,os.isLocal=Xt,os.isUtcOffset=Kt,os.isUtc=te,os.isUTC=te,os.zoneAbbr=mn,os.zoneName=_n,os.dates=v("dates accessor is deprecated. Use date instead.",Xi),os.months=v("months accessor is deprecated. Use month instead",rt),os.years=v("years accessor is deprecated. Use year instead",Ai),os.zone=v("moment().zone is deprecated, use moment().utcOffset instead. https://github.com/moment/moment/issues/1779",zt) -var us=os,ds={sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},ls={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},hs={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},cs=k.prototype -cs._calendar=ds,cs.calendar=pn,cs._longDateFormat=ls,cs.longDateFormat=vn,cs._invalidDate="Invalid date",cs.invalidDate=Dn,cs._ordinal="%d",cs.ordinal=Mn,cs._ordinalParse=/\d{1,2}/,cs.preparse=Sn,cs.postformat=Sn,cs._relativeTime=hs,cs.relativeTime=Yn,cs.pastFuture=wn,cs.set=Y,cs.months=et,cs._months=Pi,cs.monthsShort=nt,cs._monthsShort=Ci,cs.monthsParse=it,cs._monthsRegex=Di,cs.monthsRegex=ut,cs._monthsShortRegex=Di,cs.monthsShortRegex=ot,cs.week=$e,cs._week=Qi,cs.firstDayOfYear=Je,cs.firstDayOfWeek=qe,cs.weekdays=Ke,cs._weekdays=Ki,cs.weekdaysMin=en,cs._weekdaysMin=es,cs.weekdaysShort=tn,cs._weekdaysShort=ts,cs.weekdaysParse=nn,cs.isPM=hn,cs._meridiemParse=/[ap]\.?m?\.?/i,cs.meridiem=cn,W("en",{ordinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10 -return t+(1===y(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th")}}),t.lang=v("moment.lang is deprecated. Use moment.locale instead.",W),t.langData=v("moment.langData is deprecated. Use moment.localeData instead.",G) -var fs=Math.abs,ms=Rn("ms"),_s=Rn("s"),ys=Rn("m"),gs=Rn("h"),ps=Rn("d"),vs=Rn("w"),Ds=Rn("M"),Ms=Rn("y"),Ss=jn("milliseconds"),Ys=jn("seconds"),ws=jn("minutes"),ks=jn("hours"),Ts=jn("days"),bs=jn("months"),Os=jn("years"),Ws=Math.round,xs={s:45,m:45,h:22,d:26,M:11},Us=Math.abs,Gs=Vt.prototype -return Gs.abs=Gn,Gs.add=Cn,Gs.subtract=Fn,Gs.as=In,Gs.asMilliseconds=ms,Gs.asSeconds=_s,Gs.asMinutes=ys,Gs.asHours=gs,Gs.asDays=ps,Gs.asWeeks=vs,Gs.asMonths=Ds,Gs.asYears=Ms,Gs.valueOf=An,Gs._bubble=Ln,Gs.get=En,Gs.milliseconds=Ss,Gs.seconds=Ys,Gs.minutes=ws,Gs.hours=ks,Gs.days=Ts,Gs.weeks=zn,Gs.months=bs,Gs.years=Os,Gs.humanize=Jn,Gs.toISOString=Bn,Gs.toString=Bn,Gs.toJSON=Bn,Gs.locale=ke,Gs.localeData=Te,Gs.toIsoString=v("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",Bn),Gs.lang=Bi,R("X",0,0,"unix"),R("x",0,0,"valueOf"),$("x",gi),$("X",/[+-]?\d+(\.\d{1,3})?/),Q("X",function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))}),Q("x",function(t,e,n){n._d=new Date(y(t))}),t.version="2.12.0",function(t){Qn=t}(Ct),t.fn=us,t.min=Ht,t.max=Lt,t.now=ji,t.utc=a,t.unix=yn,t.months=bn,t.isDate=n,t.locale=W,t.invalid=l,t.duration=ee,t.isMoment=m,t.weekdays=Wn,t.parseZone=gn,t.localeData=G,t.isDuration=Nt,t.monthsShort=On,t.weekdaysMin=Un,t.defineLocale=x,t.updateLocale=U,t.locales=P,t.weekdaysShort=xn,t.normalizeUnits=F,t.relativeTimeThreshold=qn,t.prototype=us,t}) diff --git a/static/js/validator.min.js b/static/js/validator.min.js deleted file mode 100644 index 9a49ff3..0000000 --- a/static/js/validator.min.js +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Validator v0.10.1 for Bootstrap 3, by @1000hz - * Copyright 2016 Cina Saffary - * Licensed under http://opensource.org/licenses/MIT - * - * https://github.com/1000hz/bootstrap-validator - */ - -+function(a){"use strict";function b(b){return b.is('[type="checkbox"]')?b.prop("checked"):b.is('[type="radio"]')?!!a('[name="'+b.attr("name")+'"]:checked').length:a.trim(b.val())}function c(b){return this.each(function(){var c=a(this),e=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b),f=c.data("bs.validator");(f||"destroy"!=b)&&(f||c.data("bs.validator",f=new d(this,e)),"string"==typeof b&&f[b]())})}var d=function(c,e){this.options=e,this.$element=a(c),this.$inputs=this.$element.find(d.INPUT_SELECTOR),this.$btn=a('button[type="submit"], input[type="submit"]').filter('[form="'+this.$element.attr("id")+'"]').add(this.$element.find('input[type="submit"], button[type="submit"]')),e.errors=a.extend({},d.DEFAULTS.errors,e.errors);for(var f in e.custom)if(!e.errors[f])throw new Error("Missing default error message for custom validator: "+f);a.extend(d.VALIDATORS,e.custom),this.$element.attr("novalidate",!0),this.toggleSubmit(),this.$element.on("input.bs.validator change.bs.validator focusout.bs.validator",d.INPUT_SELECTOR,a.proxy(this.onInput,this)),this.$element.on("submit.bs.validator",a.proxy(this.onSubmit,this)),this.$element.find("[data-match]").each(function(){var c=a(this),d=c.data("match");a(d).on("input.bs.validator",function(){b(c)&&c.trigger("input.bs.validator")})})};d.INPUT_SELECTOR=':input:not([type="submit"], button):enabled:visible',d.FOCUS_OFFSET=20,d.DEFAULTS={delay:500,html:!1,disable:!0,focus:!0,custom:{},errors:{match:"Does not match",minlength:"Not long enough"},feedback:{success:"glyphicon-ok",error:"glyphicon-remove"}},d.VALIDATORS={"native":function(a){var b=a[0];return b.checkValidity?b.checkValidity():!0},match:function(b){var c=b.data("match");return!b.val()||b.val()===a(c).val()},minlength:function(a){var b=a.data("minlength");return!a.val()||a.val().length>=b}},d.prototype.onInput=function(b){var c=this,d=a(b.target),e="focusout"!==b.type;this.validateInput(d,e).done(function(){c.toggleSubmit()})},d.prototype.validateInput=function(c,d){var e=b(c),f=c.data("bs.validator.previous"),g=c.data("bs.validator.errors");if(f===e)return a.Deferred().resolve();c.data("bs.validator.previous",e),c.is('[type="radio"]')&&(c=this.$element.find('input[name="'+c.attr("name")+'"]'));var h=a.Event("validate.bs.validator",{relatedTarget:c[0]});if(this.$element.trigger(h),!h.isDefaultPrevented()){var i=this;return this.runValidators(c).done(function(b){c.data("bs.validator.errors",b),b.length?d?i.defer(c,i.showErrors):i.showErrors(c):i.clearErrors(c),g&&b.toString()===g.toString()||(h=b.length?a.Event("invalid.bs.validator",{relatedTarget:c[0],detail:b}):a.Event("valid.bs.validator",{relatedTarget:c[0],detail:g}),i.$element.trigger(h)),i.toggleSubmit(),i.$element.trigger(a.Event("validated.bs.validator",{relatedTarget:c[0]}))})}},d.prototype.runValidators=function(c){function e(a){return c.data(a+"-error")||c.data("error")||"native"==a&&c[0].validationMessage||h.errors[a]}var f=[],g=a.Deferred(),h=this.options;return c.data("bs.validator.deferred")&&c.data("bs.validator.deferred").reject(),c.data("bs.validator.deferred",g),a.each(d.VALIDATORS,a.proxy(function(a,d){if((b(c)||c.attr("required"))&&(c.data(a)||"native"==a)&&!d.call(this,c)){var g=e(a);!~f.indexOf(g)&&f.push(g)}},this)),!f.length&&b(c)&&c.data("remote")?this.defer(c,function(){var d={};d[c.attr("name")]=b(c),a.get(c.data("remote"),d).fail(function(a,b,c){f.push(e("remote")||c)}).always(function(){g.resolve(f)})}):g.resolve(f),g.promise()},d.prototype.validate=function(){var b=this;return a.when(this.$inputs.map(function(){return b.validateInput(a(this),!1)})).then(function(){b.toggleSubmit(),b.$btn.hasClass("disabled")&&b.focusError()}),this},d.prototype.focusError=function(){if(this.options.focus){var b=a(".has-error:first :input");a(document.body).animate({scrollTop:b.offset().top-d.FOCUS_OFFSET},250),b.focus()}},d.prototype.showErrors=function(b){var c=this.options.html?"html":"text",d=b.data("bs.validator.errors"),e=b.closest(".form-group"),f=e.find(".help-block.with-errors"),g=e.find(".form-control-feedback");d.length&&(d=a("
    ").addClass("list-unstyled").append(a.map(d,function(b){return a("
  • ")[c](b)})),void 0===f.data("bs.validator.originalContent")&&f.data("bs.validator.originalContent",f.html()),f.empty().append(d),e.addClass("has-error has-danger"),e.hasClass("has-feedback")&&g.removeClass(this.options.feedback.success)&&g.addClass(this.options.feedback.error)&&e.removeClass("has-success"))},d.prototype.clearErrors=function(a){var c=a.closest(".form-group"),d=c.find(".help-block.with-errors"),e=c.find(".form-control-feedback");d.html(d.data("bs.validator.originalContent")),c.removeClass("has-error has-danger"),c.hasClass("has-feedback")&&e.removeClass(this.options.feedback.error)&&b(a)&&e.addClass(this.options.feedback.success)&&c.addClass("has-success")},d.prototype.hasErrors=function(){function b(){return!!(a(this).data("bs.validator.errors")||[]).length}return!!this.$inputs.filter(b).length},d.prototype.isIncomplete=function(){function c(){return!b(a(this))}return!!this.$inputs.filter("[required]").filter(c).length},d.prototype.onSubmit=function(a){this.validate(),this.$btn.hasClass("disabled")&&a.preventDefault()},d.prototype.toggleSubmit=function(){this.options.disable&&this.$btn.toggleClass("disabled",this.isIncomplete()||this.hasErrors())},d.prototype.defer=function(b,c){return c=a.proxy(c,this,b),this.options.delay?(window.clearTimeout(b.data("bs.validator.timeout")),void b.data("bs.validator.timeout",window.setTimeout(c,this.options.delay))):c()},d.prototype.destroy=function(){return this.$element.removeAttr("novalidate").removeData("bs.validator").off(".bs.validator").find(".form-control-feedback").removeClass([this.options.feedback.error,this.options.feedback.success].join(" ")),this.$inputs.off(".bs.validator").removeData(["bs.validator.errors","bs.validator.deferred","bs.validator.previous"]).each(function(){var b=a(this),c=b.data("bs.validator.timeout");window.clearTimeout(c)&&b.removeData("bs.validator.timeout")}),this.$element.find(".help-block.with-errors").each(function(){var b=a(this),c=b.data("bs.validator.originalContent");b.removeData("bs.validator.originalContent").html(c)}),this.$element.find('input[type="submit"], button[type="submit"]').removeClass("disabled"),this.$element.find(".has-error, .has-danger").removeClass("has-error has-danger"),this};var e=a.fn.validator;a.fn.validator=c,a.fn.validator.Constructor=d,a.fn.validator.noConflict=function(){return a.fn.validator=e,this},a(window).on("load",function(){a('form[data-toggle="validator"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery); \ No newline at end of file From 90398136156c56dbebca13381f6a702803186438 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 12 Apr 2017 20:20:17 -0400 Subject: [PATCH 048/143] Removed minified static files from repo --- static/css/base.min.css | 2 -- static/css/footer.min.css | 1 - static/css/form.min.css | 1 - static/css/header.min.css | 2 -- static/css/index.min.css | 2 -- static/css/login.min.css | 1 - static/css/map.min.css | 1 - static/js/footer.min.js | 3 --- static/js/header.min.js | 3 --- 9 files changed, 16 deletions(-) delete mode 100644 static/css/base.min.css delete mode 100644 static/css/footer.min.css delete mode 100644 static/css/form.min.css delete mode 100644 static/css/header.min.css delete mode 100644 static/css/index.min.css delete mode 100644 static/css/login.min.css delete mode 100644 static/css/map.min.css delete mode 100644 static/js/footer.min.js delete mode 100644 static/js/header.min.js diff --git a/static/css/base.min.css b/static/css/base.min.css deleted file mode 100644 index a64c0cc..0000000 --- a/static/css/base.min.css +++ /dev/null @@ -1,2 +0,0 @@ -/* Global */ -div,footer,.fa,.container,.container:before,.container:after{box-sizing:border-box}body,input,textarea{padding:0;margin:0;font-family:'Open Sans',sans-serif;font-size:18px;color:#eee}body{background-color:#080808}::-webkit-scrollbar{width:5vw;min-width:10px;max-width:40px}::-webkit-scrollbar-track{background-color:#080808;background-color:rgba(8,8,8,0)}::-webkit-scrollbar-thumb{border-radius:.2vw;background:#333}::selection{background:#999}::-moz-selection{background:#999}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}.hide{display:none!important}.red,.red:hover{color:#fb6e3d!important}.yellow,.yellow:hover{color:#fbc93d!important}.shadow{-moz-box-shadow:.18vw .18vw .36vw #000;-webkit-box-shadow:.18vw .18vw .36vw #000;box-shadow:.18vw .18vw .36vw #000}.shadow:active{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none}.inline{display:inline-block}.flex{width:100%;display:flex;justify-content:space-around}.flex.stretch{justify-content:space-between}.left{float:left}.right{float:right}main{top:60px;position:absolute;left:0;right:0;bottom:0;overflow-y:auto}.container{padding-right:5%;padding-left:5%;width:100%;margin:0 auto}.container:after{content:"";display:block;clear:both}section{padding:10vh 0 5vh}.btn{font-weight:600;display:inline-block;padding:15px 30px;transition:100ms;cursor:pointer;background:rgba(255,255,255,0.1);color:#eee;border:1px solid #666;border-radius:.5vw}.btn:not(.disabled){-moz-box-shadow:inset .11vw .18vw .52vw rgba(255,255,255,.2),inset -.11vw -.18vw .52vw rgba(0,0,0,.4),.11vw .18vw .52vw #000;-webkit-box-shadow:inset .11vw .18vw .52vw rgba(255,255,255,.2),inset -.11vw -.18vw .52vw rgba(0,0,0,.4),.18vw .18vw .36vw #000;box-shadow:inset .11vw .18vw .52vw rgba(255,255,255,.2),inset -.11vw -.18vw .52vw rgba(0,0,0,.4),.18vw .18vw .36vw #000}.btn:hover:not(.disabled){text-decoration:none;background:rgba(255,255,255,0.2)}.btn:active:not(.disabled){-moz-box-shadow:inset .11vw .18vw .52vw rgba(0,0,0,.4),inset -.11vw -.18vw .52vw rgba(255,255,255,.2);-webkit-box-shadow:inset .11vw .18vw .52vw rgba(0,0,0,.4),inset -.11vw -.18vw .52vw rgba(255,255,255,.2);box-shadow:inset .11vw .18vw .52vw rgba(0,0,0,.4),inset -.11vw -.18vw .52vw rgba(255,255,255,.2)}.btn:focus:not(.disabled){border:1px solid #fbc93d}.btn.main{color:#fbc93d}.btn .fa{margin-left:10px} \ No newline at end of file diff --git a/static/css/footer.min.css b/static/css/footer.min.css deleted file mode 100644 index ae64481..0000000 --- a/static/css/footer.min.css +++ /dev/null @@ -1 +0,0 @@ -footer{font-weight:300;width:100%;overflow:auto;background:#111;color:#ccc;padding:0 20px;-moz-box-shadow:inset 0 .25vw 1vw #222;-webkit-box-shadow:inset 0 .25vw 1vw #222;box-shadow:inset 0 .25vw 1vw #222}footer .left{float:left;padding:15px 0}footer .left p{margin:0}footer a{font-weight:600;color:#fff}footer a:hover{text-decoration:none}footer .right{text-align:right;float:right;padding:15px 0}footer a .fa{margin-left:5px;font-size:20px;color:inherit}footer .fa a:hover,footer .fa a:focus{color:inherit}@media (max-width:800px){footer{padding:0 10px}}@media (max-width:600px){footer{text-align:center}footer .left,footer .right{float:none}footer .right{padding-top:0}} \ No newline at end of file diff --git a/static/css/form.min.css b/static/css/form.min.css deleted file mode 100644 index a813046..0000000 --- a/static/css/form.min.css +++ /dev/null @@ -1 +0,0 @@ -form{margin:auto;max-width:800px}.form-group{display:flex;justify-content:space-between;margin:8% 0}form label{font-size:1.2em;margin-right:3%}form input,form textarea,form select{-moz-box-shadow:inset .11vw .18vw .25vw rgba(0,0,0,.5);-webkit-box-shadow:inset .11vw .18vw .25vw rgba(0,0,0,.5);box-shadow:inset .11vw .18vw .25vw rgba(0,0,0,.5);color:#eee;background-color:#202020;background-color:rgba(255,255,255,0.1);padding:1% 1.5%;border:1px solid #666;border-radius:.3vw}form input:not(.input-addon):not(.input-with-addon):not([type="radio"]):not([type="checkbox"]),form .input-with-addon-group{min-width:50%}form input:active:not(.input-addon),form textarea:active,form select:active,form input:focus:not(.input-addon),form textarea:focus,form select:focus{outline:none;border:1px solid #fbc93d}form .input-with-addon-group{display:flex}form .input-addon{padding:1% 0 1% 1.5%;border-right-color:#202020;border-right-color:rgba(102,102,102,0);border-top-right-radius:0;border-bottom-right-radius:0;-moz-box-shadow:inset .11vw .18vw .25vw rgba(0,0,0,.5);-webkit-box-shadow:inset .11vw .18vw .25vw rgba(0,0,0,.5);box-shadow:inset .11vw .18vw .25vw rgba(0,0,0,.5)}form .input-with-addon{padding:1% 1.5% 1% 0;border-left-color:#202020;border-left-color:rgba(102,102,102,0);border-top-left-radius:0;border-bottom-left-radius:0;-moz-box-shadow:inset 0 .18vw .25vw rgba(0,0,0,.5);-webkit-box-shadow:inset 0 .18vw .25vw rgba(0,0,0,.5);box-shadow:inset 0 .18vw .25vw rgba(0,0,0,.5)}::-webkit-input-placeholder{color:#666}:-moz-placeholder{color:#666;opacity:1}::-moz-placeholder{color:#666;opacity:1}:-ms-input-placeholder{color:#666}form select{-moz-box-shadow:inset 0 1px 6px rgba(255,255,255,.2),inset -.11vw -.18vw .52vw rgba(0,0,0,.4);-webkit-box-shadow:inset -.11vw -.18vw .52vw rgba(255,255,255,.2),inset -.11vw -.18vw .52vw rgba(0,0,0,.4);box-shadow:inset 0 .11vw .52vw rgba(255,255,255,.2),inset 0 .11vw .52vw rgba(0,0,0,.4)}form select > option{background:#222;color:inherit}form .radio{min-width:150px;display:flex;justify-content:space-between}form input[type="checkbox"],form input[type="radio"]{width:auto;margin:8px}form input[type="checkbox"]:active,form input[type="radio"]:active,form input[type="checkbox"]:focus,form input[type="radio"]:focus{outline:1px solid #fbc93d}form .btn{font-size:1.5em} \ No newline at end of file diff --git a/static/css/header.min.css b/static/css/header.min.css deleted file mode 100644 index 55c7762..0000000 --- a/static/css/header.min.css +++ /dev/null @@ -1,2 +0,0 @@ -/* Main */ -header{background:#222;padding:0;position:fixed;top:0;left:0;width:100%;z-index:200}header a:hover,header a:focus{color:#fbc93d}header .logo{float:left;font-family:'Open Sans',sans-serif;padding:13px 23px;color:#fbc93d;font-weight:800;font-size:22px;line-height:30px;margin:0}header .logo a{color:inherit;font:inherit;text-decoration:inherit;cursor:pointer}header .logo img{margin-right:10px;vertical-align:middle}header .logo:hover{text-decoration:none;background:rgba(255,255,255,0.1)}header nav{float:right}header nav ul{padding:0;margin:0}header nav ul li{display:inline-block;float:left}header nav ul li a,header nav ul li span{text-decoration:inherit;display:inline-block;padding:15px 20px;color:#fff;transition:100ms}header nav ul li a:hover,header nav ul li a:focus,header nav ul li a.active,header .hamburger{display:none;padding:5px;cursor:pointer;transition-property:opacity,-webkit-filter;transition-property:opacity,filter;transition-property:opacity,filter,-webkit-filter;transition-duration:150ms;transition-timing-function:linear}header .hamburger:hover{opacity:0.7}header .hamburger-box{width:40px;height:24px;position:relative}header .hamburger-inner{top:50%;margin-top:-2px}header .hamburger-inner,header .hamburger-inner::before,header .hamburger-inner::after{width:40px;height:4px;background-color:#fff;border-radius:4px;position:absolute;transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;transition-duration:150ms;transition-timing-function:ease}header .hamburger-inner::before,header .hamburger-inner::after{content:"";display:block}header .hamburger-inner::before{top:-10px}header .hamburger-inner::after{bottom:-10px}header .hamburger--slider .hamburger-inner{top:0}header .hamburger--slider .hamburger-inner::before{top:10px;transition-property:opacity,-webkit-transform;transition-property:transform,opacity;transition-property:transform,opacity,-webkit-transform;transition-timing-function:ease;transition-duration:200ms}header .hamburger--slider .hamburger-inner::after{top:20px}header .hamburger--slider.is-active .hamburger-inner{-webkit-transform:translate3d(0,10px,0) rotate(45deg);-moz-transform:translate3d(0,10px,0) rotate(45deg);-md-transform:translate3d(0,10px,0) rotate(45deg);-o-transform:translate3d(0,10px,0) rotate(45deg);transform:translate3d(0,10px,0) rotate(45deg)}header .hamburger--slider.is-active .hamburger-inner::before{-webkit-transform:rotate(-45deg) translate3d(-5.71429px,-6px,0);-moz-transform:rotate(-45deg) translate3d(-5.71429px,-6px,0);-ms-transform:rotate(-45deg) translate3d(-5.71429px,-6px,0);-o-transform:rotate(-45deg) translate3d(-5.71429px,-6px,0);transform:rotate(-45deg) translate3d(-5.71429px,-6px,0);opacity:0}header .hamburger--slider.is-active .hamburger-inner::after{-webkit-transform:translate3d(0,-20px,0) rotate(-90deg);-moz-transform:translate3d(0,-20px,0) rotate(-90deg);-ms-transform:translate3d(0,-20px,0) rotate(-90deg);-o-transform:translate3d(0,-20px,0) rotate(-90deg);transform:translate3d(0,-20px,0) rotate(-90deg)}@media (max-width:800px){header nav ul li a{padding:15px}}@media (max-width:600px){header nav{float:none;position:fixed;top:56px;right:-300px;bottom:0;width:100%;max-width:300px;background:#333;transition:100ms}header nav.visible{right:0}header nav ul li{display:block;float:none;width:100%}header nav ul li a{display:block;width:100%;border-bottom:1px solid rgba(255,255,255,0.1)}header .hamburger{display:inline-block;color:#fff;position:absolute;right:10px;top:13px}}.alert{padding:15px;border:1px solid transparent;border-radius:4px}noscript .alert-danger{z-index:40}.alert-danger{z-index:30;color:#f2dede;background-color:#a94442}.alert-warning{z-index:20;color:#fcf8e3;background-color:#8a6d3b}.alert-success{z-index:10;color:#dff0d8;background-color:#3c763d}.alert.alert-header{position:relative;border-radius:0;top:58px;width:100%}.alert a{z-index:10;color:inherit;font-weight:bold;text-decoration:underline}.alert a:hover{color:inherit;text-decoration:none}.alert h4{margin-top:0;color:inherit}.alert > p,.alert > ul{margin-bottom:0}.alert > p + p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert .close,.alert-dismissible .close{cursor:pointer;float:right;color:inherit} \ No newline at end of file diff --git a/static/css/index.min.css b/static/css/index.min.css deleted file mode 100644 index bc2a443..0000000 --- a/static/css/index.min.css +++ /dev/null @@ -1,2 +0,0 @@ -/* Animations */ -@keyframes pulse{0%{transform:scale(1)}50%{transform:scale(0.8)}100%{transform:scale(1)}}@keyframes spin{0%{transform:rotate(30deg)}100%{transform:rotate(210deg)}}@keyframes spin2{0%{transform:rotate(150deg)}100%{transform:rotate(330deg)}}.btn{border-radius:50px}.container > p{margin-bottom:5vh}.splash{background:#090909;background-image:url(/static/img/style/map.jpg);background-size:cover;color:#FFF;height:100vh;overflow:hidden;position:relative}.splash:after,.splash:before{content:"";display:block;position:absolute;top:-40px;right:-40px;bottom:-40px;left:-40px}.splash:after{background:rgba(255,255,255,0.05);transform:rotate(30deg);animation:spin 60s infinite linear}.splash:before{background:rgba(0,0,0,0.5);transform:rotate(150deg);animation:spin2 50s infinite linear}.splash .container{position:relative;top:48%;transform:translateY(-50%);z-index:5}.splash h1{color:#fbc93d}.splash h2{margin-bottom:40px}.splash .btn{margin:0 20px 10px 0}.splash .btn:hover{color:#fff;background:rgba(0,0,0,0.5)}.overview{text-align:center}.overview > div > div{float:left;width:33%;padding:0 40px 0 40px}.overview .fa{display:inline-block;color:#222;font-size:50px;width:100px;height:100px;border-radius:50px;background:#f6f6f6;margin-bottom:20px;padding-top:25px}.overview p{margin-bottom:0}.feature.app{background:#111}.feature{position:relative;overflow:hidden}.feature img{position:absolute;top:100px;right:55%}.feature:nth-of-type(even) img{right:auto;left:55%}.feature > div > div{width:50%;float:right}.feature > div > div > p{margin-bottom:40px}.feature:nth-of-type(even) > div > div{float:left}.feature ul{margin:0;padding:0}.feature ul li{display:block;margin-bottom:20px;padding-bottom:20px;border-bottom:1px solid #eee}.feature ul li:last-child{margin-bottom:0;padding-bottom:0;border-bottom:0}.feature ul li h3{margin:0 0 5px 0}.feature ul li p:last-child{margin:0}.feature ul li .fa{float:left;font-size:30px;background:#fbc93d;color:#000;width:50px;height:50px;display:inline-block;text-align:center;padding-top:10px;border-radius:25px;margin-right:20px;margin-top:7px}.feature ul li p{overflow:hidden}.light{color:#222;position:relative;overflow:hidden}.light:after{content:"";display:block;position:absolute;top:-40px;right:-40px;bottom:-40px;left:-40px;background:rgba(255,255,255,0.1);transform:rotate(30deg)}.light h2{margin-bottom:40px}.light .btn{color:#111;background:rgba(0,0,0,0.1)}.light .btn:hover:not(.disabled){cursor:pointer;text-decoration:none;background:rgba(0,0,0,0.2)}.disclaimer{color:#fb6e3d;background:#000}.disclaimer .container{position:relative;z-index:10}.disclaimer a,.disclaimer a:hover{color:#fb6e3d}.join{background:#fbc93d}.join input,.join textarea{color:#111}.join .input{width:47%;float:left}.join .submit{width:47%;float:right}.join .input:nth-of-type(odd){margin-right:6%}.join .message{display:block;clear:both;float:none;padding-top:10px}.join .input input{display:inline-block;float:left;width:100%;background:rgba(255,255,255,0.3);border:0;padding:10px 15px}.join .message textarea{display:block;width:100%;height:200px;background:rgba(255,255,255,0.3);border:0;padding:10px 15px;resize:vertical}.join label{position:relative;z-index:10}.join label.input span,.join label.message span{display:inline-block;float:left}.join .submit{text-align:center;padding-top:10px}.join .submit .btn,.join .submit .alert{position:static;float:right}@media (max-width:800px){section{padding:80px 10px}.splash{height:auto;padding:150px 10px 80px 10px;text-align:center}.splash .container{position:relative;top:0;transform:none}.overview > div > div{padding:0 20px}.feature img{right:65%}.feature:nth-of-type(even) img{left:65%}.feature > div > div{width:60%}}@media (max-width:600px){section{padding:40px 10px}.splash{padding:100px 10px 40px 10px}.overview > div > div{float:none;width:100%;margin-bottom:40px;padding:0}.overview > div > div:last-child{margin-bottom:0}.overview p{overflow:hidden}.feature img{display:none}.feature > div > div{width:100%;float:none}.join .input{display:block;width:100%;float:none}.join .input:nth-of-type(odd){margin-right:0}.join label{padding-top:10px}.join label:first-of-type{padding-top:0}} \ No newline at end of file diff --git a/static/css/login.min.css b/static/css/login.min.css deleted file mode 100644 index 4fa9c29..0000000 --- a/static/css/login.min.css +++ /dev/null @@ -1 +0,0 @@ -section > .flex > div{width:50%;padding:0 2%}form input{width:96%}form input.btn,form #social-login{width:100%}#social-login .btn{padding:0;font-size:1.3em;height:60px;text-align:center;width:60px;margin:0 3%;color:#FFF}#social-login .btn .fa{margin:0;position:relative;padding-top:20px}#social-login .btn.gp{background:rgb(206,77,57)}#social-login .btn.gp:hover{background:rgb(251,122,102)}#social-login .btn.fb{background:rgb(48,88,145)}#social-login .btn.fb:hover{background:rgb(93,133,190)}#social-login .btn.tw{background:rgb(44,168,210)}#social-login .btn.tw:hover{background:rgb(89,213,255)}@media (max-width:800px){section > .flex{flex-direction:column}section > .flex > div{width:100%}hr.hide{display:block}} \ No newline at end of file diff --git a/static/css/map.min.css b/static/css/map.min.css deleted file mode 100644 index 651d5bf..0000000 --- a/static/css/map.min.css +++ /dev/null @@ -1 +0,0 @@ -body{color:#fff;width:100%;height:100%;background:#000}.wrap{position:absolute;bottom:0;width:100%}.centered.alert{text-align:center;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}#map,#pano{position:relative}#pano{float:right}img#panoImg{width:100%;height:100%}#notset{display:none}.map-logo{margin-left:-75px;background:rgba(0,0,0,.7);padding:0 10px 0 75px;font-size:2em}.map-logo a{color:#fbc93d}.tim{color:#000;font-size:12px;padding-left:5px;padding-right:5px;background-color:rgba(255,255,255,.7)}.spd-sign,.alt-sign{text-align:center;padding:2%;border-radius:3px;margin:3%}.spd-sign{color:#000;background-color:#FFF;border:2px solid #000}.alt-sign{color:#FFF;background-color:#009800;border:2px solid #FFF}@media (min-width:400px){.spd,.alt{height:40px;font-size:32px}.alt-unit,.spd-unit{font-size:12px}.alt-label,.spd-label{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:0) 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}}#controls{width:100vw;position:absolute;bottom:50px;display:flex;justify-content:space-around}#controls .btn{z-index:50;background:#222;height:10vh;padding:2vh 0}#controls .btn:hover{background:#333}#controls .btn.set,#controls .btn.clear{width:30vw}#controls .btn.track{width:35vw} \ No newline at end of file diff --git a/static/js/footer.min.js b/static/js/footer.min.js deleted file mode 100644 index d8dbe51..0000000 --- a/static/js/footer.min.js +++ /dev/null @@ -1,3 +0,0 @@ -"use strict" -function setFooter(){var o=$(window).height(),t=$("footer").offset().top+$("footer").height() -o>t&&$("footer").css("margin-top",o-t)}$(function(){setFooter()}),$(window).resize(function(){setFooter()}) diff --git a/static/js/header.min.js b/static/js/header.min.js deleted file mode 100644 index 03d1fef..0000000 --- a/static/js/header.min.js +++ /dev/null @@ -1,3 +0,0 @@ -/* global $ */ -"use strict" -$(document).ready(function(){$(".hamburger").click(function(){$(".hamburger").toggleClass("is-active"),$("nav").toggleClass("visible")}),$("nav").click(function(){$(".hamburger").removeClass("is-active"),$("nav").removeClass("visible")}),$(".wrap, section").click(function(){$(".hamburger").removeClass("is-active"),$("nav").removeClass("visible")}),$(".alert-dismissible .close").click(function(){$(this).parent().slideUp(500)})}) From bae26295a4b0810afdc779cfb42322e5b45186a0 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 12 Apr 2017 20:22:48 -0400 Subject: [PATCH 049/143] Modified run instructions --- README.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index dbfa819..1b9b549 100644 --- a/README.md +++ b/README.md @@ -9,20 +9,12 @@ node.js application to display a 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. Be sure to block this file in your `.gitignore`, since it contains secrets. - -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.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). ## Running ```sh -$ node server.js -``` - -or - -```sh -$ npm start +$ npm run minify && npm start ``` ## Contributing From e84bb6ec3a3755ecf50cfc12299598e836988454 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 12 Apr 2017 20:38:06 -0400 Subject: [PATCH 050/143] Hid minified files --- nodemon.json | 2 +- package.json | 2 +- views/admin.html | 2 +- views/index.html | 2 +- views/login.html | 4 +-- views/map.html | 2 +- views/password.html | 2 +- views/settings.html | 54 ++++++++++++++++++------------------- views/templates/base.html | 6 ++--- views/templates/footer.html | 2 +- views/templates/header.html | 2 +- 11 files changed, 40 insertions(+), 40 deletions(-) diff --git a/nodemon.json b/nodemon.json index ab722a2..d730a0c 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,4 +1,4 @@ { "verbose": true, - "ext": "html, js, json, css" + "ext": "html, js, json, css", } \ No newline at end of file diff --git a/package.json b/package.json index be11c7f..3dd5d50 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "test": "mocha test.js", "start": "node server.js", "nodemon": "nodemon server.js", - "minify": "minify --clean static", + "minify": "minify --template .{{filename}}.min.{{ext}} --clean static", "update": "sudo n stable && sudo npm update --save && sudo npm prune" }, "repository": { diff --git a/views/admin.html b/views/admin.html index c803c65..8c6f461 100644 --- a/views/admin.html +++ b/views/admin.html @@ -49,7 +49,7 @@
- + {% endblock %} \ No newline at end of file diff --git a/views/templates/base.html b/views/templates/base.html index 83504e7..28ad474 100644 --- a/views/templates/base.html +++ b/views/templates/base.html @@ -41,15 +41,15 @@ - + {% endblock %} - {% if not noHeader %}{% endif %} - {% if not noFooter %}{% endif %} + {% if not noHeader %}{% endif %} + {% if not noFooter %}{% endif %} diff --git a/views/templates/footer.html b/views/templates/footer.html index f3650f7..037ee84 100644 --- a/views/templates/footer.html +++ b/views/templates/footer.html @@ -15,4 +15,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/views/templates/header.html b/views/templates/header.html index ca3ade3..04880d5 100644 --- a/views/templates/header.html +++ b/views/templates/header.html @@ -54,4 +54,4 @@ {% endfor %} - \ No newline at end of file + \ No newline at end of file From 449a813ab603167df9d92932c231c52d453a9a3b Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 12 Apr 2017 20:46:02 -0400 Subject: [PATCH 051/143] Now minifies static files with each nodemon restart --- nodemon.json | 3 +++ package.json | 2 +- static/css/base.css | 16 ++++++++-------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/nodemon.json b/nodemon.json index d730a0c..e0ecd7d 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,4 +1,7 @@ { "verbose": true, "ext": "html, js, json, css", + "events": { + "restart": "npm run minify" + } } \ No newline at end of file diff --git a/package.json b/package.json index 3dd5d50..a00ec09 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "scripts": { "test": "mocha test.js", "start": "node server.js", - "nodemon": "nodemon server.js", + "nodemon": "nodemon --ignore 'static/**/*.min.*' server.js", "minify": "minify --template .{{filename}}.min.{{ext}} --clean static", "update": "sudo n stable && sudo npm update --save && sudo npm prune" }, diff --git a/static/css/base.css b/static/css/base.css index 1c7d1e5..d378a54 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -45,7 +45,7 @@ h2 { line-height: 36px; } h3 { font-size: 28px; } h4 { font-size: 20px; } -p { +p { margin-top: 0; margin-bottom: 10vh; } @@ -112,7 +112,7 @@ main { left: 0px; right: 0px; bottom: 0px; - overflow-y: auto; + overflow-y: auto; } .container { padding-right: 5%; @@ -141,15 +141,15 @@ section { border: 1px solid #666; border-radius: .5vw; } .btn:not(.disabled) { - -moz-box-shadow: + -moz-box-shadow: inset .11vw .18vw .52vw rgba(255,255,255,.2), inset -.11vw -.18vw .52vw rgba(0,0,0,.4), .11vw .18vw .52vw #000; - -webkit-box-shadow: + -webkit-box-shadow: inset .11vw .18vw .52vw rgba(255,255,255,.2), inset -.11vw -.18vw .52vw rgba(0,0,0,.4), .18vw .18vw .36vw #000; - box-shadow: + box-shadow: inset .11vw .18vw .52vw rgba(255,255,255,.2), inset -.11vw -.18vw .52vw rgba(0,0,0,.4), .18vw .18vw .36vw #000; @@ -157,13 +157,13 @@ section { text-decoration: none; background: rgba(255,255,255,0.2); } .btn:active:not(.disabled) { - -moz-box-shadow: + -moz-box-shadow: inset .11vw .18vw .52vw rgba(0,0,0,.4), inset -.11vw -.18vw .52vw rgba(255,255,255,.2); - -webkit-box-shadow: + -webkit-box-shadow: inset .11vw .18vw .52vw rgba(0,0,0,.4), inset -.11vw -.18vw .52vw rgba(255,255,255,.2); - box-shadow: + box-shadow: inset .11vw .18vw .52vw rgba(0,0,0,.4), inset -.11vw -.18vw .52vw rgba(255,255,255,.2); } .btn:focus:not(.disabled){ From bfe4fca8bb9ebe06733908480b4822fafbc4b89f Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 12 Apr 2017 20:47:24 -0400 Subject: [PATCH 052/143] Minifies on first run too --- nodemon.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodemon.json b/nodemon.json index e0ecd7d..b80527a 100644 --- a/nodemon.json +++ b/nodemon.json @@ -2,6 +2,6 @@ "verbose": true, "ext": "html, js, json, css", "events": { - "restart": "npm run minify" + "start": "npm run minify" } } \ No newline at end of file From 3e08a462f20e73cfe11753f30830007cef007f19 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 12 Apr 2017 20:57:05 -0400 Subject: [PATCH 053/143] Added nodemon instructions to README --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1b9b549..928020e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Tracman ###### v 0.5.1 -node.js application to display a map with user's location. +node.js application to display a map with user's location. ## Installation @@ -9,7 +9,7 @@ node.js application to display a 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.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). ## Running @@ -17,9 +17,15 @@ You will need to set up a configuration file at `config/env.js`. Use `config/en $ npm run minify && npm start ``` +or, using [nodemon](https://nodemon.io/): + +```sh +$ 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). ## Changelog @@ -31,7 +37,7 @@ Tracman will be updated according to [this branching model](http://nvie.com/post * Updated libraries * Fixed recognition of attached clients [#34](https://github.com/Tracman-org/Server/issues/21) -* Moved socket.io code to own file. +* Moved socket.io code to own file. * Many minor fixes #### v0.4.3 From 67061fd99c2f505820c33001c48f46aeb6857776 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Thu, 13 Apr 2017 18:53:18 -0400 Subject: [PATCH 054/143] Added account creation, switched to arrow functions --- config/auth.js | 386 +++++++++++++++++------------------- config/mail.js | 23 +-- config/middleware.js | 26 +-- config/models.js | 52 ++--- config/passport.js | 120 +++++------ config/routes/admin.js | 80 +++----- config/routes/index.js | 27 +-- config/routes/map.js | 5 +- config/routes/settings.js | 179 ++++++++--------- config/routes/test.js | 10 +- config/sockets.js | 49 ++--- server.js | 103 +++++----- static/css/header.css | 9 +- test.js | 81 +++----- views/admin.html | 2 +- views/error.html | 16 +- views/templates/header.html | 8 +- 17 files changed, 529 insertions(+), 647 deletions(-) diff --git a/config/auth.js b/config/auth.js index a7d34ac..eee797c 100644 --- a/config/auth.js +++ b/config/auth.js @@ -4,10 +4,12 @@ const mw = require('./middleware.js'), mail = require('./mail.js'), User = require('./models.js').user, + slug = require('slug'), + crypto = require('crypto'), env = require('./env.js'); -module.exports = function(app, passport) { - +module.exports = (app, passport) => { + // Methods for success and failure const loginOutcome = { @@ -18,130 +20,209 @@ module.exports = function(app, passport) { failureRedirect: '/settings', failureFlash: true }, - loginCallback = function(req,res){ + loginCallback = (req,res)=>{ res.redirect( req.session.next || '/settings' ); delete req.session.next; }; - + // Login/-out app.route('/login') - .get( function(req,res){ - if (req.isAuthenticated()){ - res.redirect('/settings'); } + .get( (req,res)=>{ + req.session.next = req.header('Referer'); + if (req.isAuthenticated()){ + res.redirect(req.session.next||'/settings'); } else { res.render('login'); } }) .post( passport.authenticate('local',loginOutcome), loginCallback ); - app.get('/logout', function(req,res){ + app.get('/logout', (req,res)=>{ req.logout(); req.flash('success',`You have been logged out.`); res.redirect('/'); }); - + // Signup - app.post('/signup', function(req,res,next){ - User.findOne({'email':req.body.email}, function(err,user){ - if (err){ next(err); } - - // User already exists - else if (user){ - req.flash('warning','A user with that email already exists! If you forgot your password, use this form.'); - res.redirect('/login'); - } else { - - // Create user - var newUser = new User(); - newUser.email = req.body.email; - newUser.created = Date.now(); - - newUser.createToken(function(err,token){ - if (err){ next(err); } - mail({ - from: '"Tracman" ', - to: req.body.email, - subject: 'Complete your Tracman registration', - text: `Welcome to Tracman! \n\nTo complete your registration, follow this link and set your password:\n${env.url}/settings/password/${token}`, - html: `

Welcome to Tracman!

To complete your registration, follow this link and set your password:
${env.url}/settings/password/${token}

` - }).then(function(){ - req.flash('success',`An email has been sent to ${req.body.email}. Check your inbox to complete your registration.`); - res.redirect('/'); - }).catch(function(err){ - next(err); - }); + app.get('/signup', (req,res)=>{ + res.redirect('/login#signup'); + }).post('/signup', (req,res,next)=>{ + + // Send token and alert user + function sendToken(user){ + + // Create a password token + user.createToken(function(err,token){ + if (err){ mw.throwErr(err,req); } + + // Email the instructions to continue + mail.send({ + from: mail.from, + to: `<${user.email}>`, + subject: 'Complete your Tracman registration', + text: mail.text(`Welcome to Tracman! \n\nTo complete your registration, follow this link and set your password:\n${env.url}/settings/password/${token}`), + html: mail.html(`

Welcome to Tracman!

To complete your registration, follow this link and set your password:
${env.url}/settings/password/${token}

`) + }).catch(function(err){ + mw.throwErr(err,req); + res.redirect('/login#signup'); + }).then(function(){ + req.flash('success', `An email has been sent to ${user.email}. Check your inbox to complete your registration. `); + res.redirect('/'); }); - - } - }); - }); - - // Forgot password - app.route('/login/forgot') - .all( function(req,res,next){ - if (req.isAuthenticated()){ res.redirect('/settings'); } - else { next(); } - }) - .get( function(req,res,next){ - res.render('forgot'); - }) - .post( function(req,res,next){ - - //TODO: Validate and sanitize email - // req.assert('email', 'Please enter a valid email address.').isEmail(); - // req.sanitize('email').normalizeEmail({ remove_dots: false }); - - User.findOne({'email':req.body.email},function(err,user){ - if (err){ next(err); } - else if (!user) { - req.flash('danger', `No user has ${req.body.email} set as their email address. `); - res.redirect('/login/forgot'); - } else { - - // Set reset token to user - user.createToken( function(err,token){ - if (err){ next(err); } - - // Email reset link - mail({ - from: '"Tracman" ', - to: `"${user.name}"" <${user.email}>`, - subject: 'Reset your Tracman password', - text: `Hi, \n\nDid you request to reset your Tracman password? If so, follow this link to do so:\n${env.url}/settings/password/${token}\n\nIf you didn't initiate this request, just ignore this email. `, - html: `

Hi,

Did you request to reset your Tracman password? If so, follow this link to do so:
${env.url}/settings/password/${token}

If you didn't initiate this request, just ignore this email.

` - }).then(function(){ - req.flash('success', `An email has been sent to ${req.body.email}. Check your email for instructions to reset your password. `); - res.redirect('/'); - }).catch(function(err){ - next(err); - }); - - }); - } }); + } + + // Check if somebody already has that email + User.findOne({'email':req.body.email}, (err,user)=>{ + if (err){ mw.throwErr(err,req); } + + // User already exists + if (user && user.auth.password) { + req.flash('warning','A user with that email already exists! If you forgot your password, you can reset it here.'); + res.redirect('/login#login'); + next(); + } + + // User exists but hasn't created a password yet + else if (user) { + // Send another token (or the same one if it hasn't expired) + sendToken(user); + } + + // Create user + else { + + user = new User(); + user.created = Date.now(); + user.email = req.body.email; + user.slug = slug(user.email.substring(0, user.email.indexOf('@'))); + + // Generate unique slug + var generateSlug = new Promise((resolve,reject) => { + (function checkSlug(s,cb){ + User.findOne({slug:s}, function(err, existingUser){ + if (err) { mw.throwErr(err,req); } + + // Slug in use: generate a random one and retry + if (existingUser){ + s = ''; + while (s.length<6) { + s+='abcdefghijkmnpqrtuvwxy346789'.charAt(Math.floor(Math.random()*28)); + } + checkSlug(s,cb); + + // Unique slug: proceed + } else { cb(s); } + }); + })(user.slug, function(newSlug){ + user.slug = newSlug; + resolve(); + }); + }); + + // Generate sk32 + var generateSk32 = new Promise((resolve,reject) => { + crypto.randomBytes(32, (err,buf)=>{ + if (err) { mw.throwErr(err,req); } + user.sk32 = buf.toString('hex'); + resolve(); + }); + }); + + // Save user and send the token by email + Promise.all([generateSlug, generateSk32]) + .catch(err => { + mw.throwErr(err,req); + }).then(() => { + user.save( (err)=>{ + if (err){ mw.throwErr(err,req); } + sendToken(user); + }); + }); + + } + }); - + }); + + // Forgot password + // app.route('/login/forgot') + // .all( (req,res,next)=>{ + // if (req.isAuthenticated()){ res.redirect('/settings'); } + // else { next(); } + // }) + // .get( (req,res,next)=>{ + // res.render('forgot'); + // }) + // .post( (req,res,next)=>{ + + // //TODO: Validate and sanitize email + // // req.assert('email', 'Please enter a valid email address.').isEmail(); + // // req.sanitize('email').normalizeEmail({ remove_dots: false }); + + // User.findOne( {'email':req.body.email}, (err,user)=>{ + // if (err){ next(err); } + // else if (!user) { + // req.flash('danger', `No user has ${req.body.email} set as their email address. `); + // res.redirect('/login/forgot'); + // } else { + + // // Set reset token to user + // user.createToken( (err,token)=>{ + // if (err){ next(err); } + + // // Email reset link + // mail({ + // from: '"Tracman" ', + // to: `"${user.name}"" <${user.email}>`, + // subject: 'Reset your Tracman password', + // text: `Hi, \n\nDid you request to reset your Tracman password? If so, follow this link to do so:\n${env.url}/settings/password/${token}\n\nIf you didn't initiate this request, just ignore this email. `, + // html: `

Hi,

Did you request to reset your Tracman password? If so, follow this link to do so:
${env.url}/settings/password/${token}

If you didn't initiate this request, just ignore this email.

` + // }).then(()=>{ + // req.flash('success', `An email has been sent to ${req.body.email}. Check your email for instructions to reset your password. `); + // res.redirect('/'); + // }).catch((err)=>{ + // next(err); + // }); + + // }); + // } + // }); + + // }); + // Social - app.get('/login/:service', function(req,res,next){ + app.get('/login/:service', (req,res,next)=>{ var service = req.params.service; if (service==='google'){ var sendParams = {scope:['profile']}; } - if (!req.user) { // Social login + + // Social login + if (!req.user) { passport.authenticate(service, sendParams)(req,res,next); - } else if (!req.user.auth[service]) { // Connect social account + } + + // Connect social account + else if (!req.user.auth[service]) { passport.authorize(service, sendParams)(req,res,next); - } else { // Disconnect social account + } + + // Disconnect social account + else { req.user.auth[service] = undefined; - req.user.save(function(err){ - if (err){ mw.throwErr(err,req); } + req.user.save( (err)=>{ + if (err) { + mw.throwErr(err,req); + res.redirect('/settings'); + } else { req.flash('success', `${mw.capitalize(service)} account disconnected. `); + res.redirect('/settings'); } - res.redirect('/settings'); }); } }); - app.get('/login/:service/cb', function(req,res,next){ + app.get('/login/:service/cb', (req,res,next)=>{ var service = req.params.service; if (!req.user) { passport.authenticate(service, loginOutcome)(req,res,next); @@ -151,123 +232,12 @@ module.exports = function(app, passport) { passport.authenticate(service, connectOutcome)(req,res,next); } }, loginCallback); - - // Old google auth - // app.get('/auth/google', passport.authenticate('google', { scope: [ - // 'https://www.googleapis.com/auth/plus.login', - // 'https://www.googleapis.com/auth/plus.profile.emails.read' - // ] })); - // app.get('/auth/google/callback', passport.authenticate('google', { - // failureRedirect: '/', - // failureFlash: true, - // successRedirect: '/', - // successFlash: true - // } )); - + // Android auth //TODO: See if there's a better method - app.get('/auth/google/idtoken', passport.authenticate('google-id-token'), function (req,res) { + app.get('/auth/google/idtoken', passport.authenticate('google-id-token'), (req,res)=>{ if (!req.user) { res.sendStatus(401); } else { res.send(req.user); } } ); - + }; - -// passport.use(new GoogleStrategy({ -// clientID: env.googleClientId, -// clientSecret: env.googleClientSecret, -// callbackURL: env.url+'/auth/google/callback', -// passReqToCallback: true -// }, function(req, accessToken, refreshToken, profile, done) { - -// // Check for user -// User.findOne({googleID: profile.id}, function(err, user){ - -// // Error -// if (err) { console.log('Error finding user with google ID: '+profile.id+'\n'+err); } - -// // User found -// if (!err && user !== null) /* Log user in */ { -// if (!user.name) { user.name=profile.displayName; } -// user.lastLogin = Date.now(); -// user.save(function (err, raw) { -// if (err) { throwErr(err,req); } -// }); done(null, user); -// } - -// // User not found -// else /* create user */ { -// user = new User(); -// user.googleID = profile.id; -// user.name = profile.displayName; -// user.email = profile.emails[0].value; -// user.slug = slug(profile.displayName).toLowerCase(); -// user.created = Date.now(); -// user.lastLogin = Date.now(); -// // user.settings = { units:'standard', defaultMap:'road', defaultZoom:11, showSpeed:false, showTemp:false, showAlt:false, showStreetview:false }, -// // user.last = { lat:0, lon:0, dir:0, alt:0, spd:0 }, -// // user.isPro = false; -// // user.isAdmin = false; -// var cbc = 2; -// var successMessage, failMessage; - -// // Generate slug -// (function checkSlug(s,cb) { -// //console.log('checking ',s); -// User.findOne({slug:s}, function(err, existingUser){ -// if (err) { console.log('No user found for ',slug,':',err); } -// if (existingUser){ -// s = ''; -// while (s.length<6) { -// s+='abcdefghijkmnpqrtuvwxy346789'.charAt(Math.floor(Math.random()*28)); -// } -// checkSlug(s,cb); -// } else { cb(s); } -// }); -// })(user.slug, function(newSlug){ -// user.slug = newSlug; -// if (cbc>1) /* waiting on other calls */ { cbc--; } -// else { done(null, user, { success:successMessage, failure:failMessage }); } -// }); - -// // Generate sk32 -// crypto.randomBytes(32, function(err,buf) { -// if (err) {console.log('Unable to get random bytes:',err);} -// if (!buf) {console.log('Unable to get random buffer');} -// else { -// user.sk32 = buf.toString('hex'); -// user.save(function(err) { -// if (err) { -// console.log('Error saving new user '+err); -// var failMessage = 'Something went wrong creating your account. Would you like to report this error?'; -// } else { successMessage = 'Your account has been created. Next maybe you should download the android app. ' } -// if (cbc>1) /* waiting on other calls */ { cbc--; } -// else { done(null, user, { success:successMessage, failure:failMessage }); } -// }); -// } -// }); - -// } - -// }); - -// })); - -// passport.use(new GoogleTokenStrategy({ -// clientID: env.googleClientId -// }, function(parsedToken, googleId, done) { -// User.findOne({googleID:googleId}, function(err, user) { -// if (err) { -// console.log('Error finding user for gToken login with google profile ID: '+googleId+'\n'+err); } -// if (!err && user !== null) { // Log in -// user.lastLogin = Date.now(); -// user.save(function (err) { -// if (err) { -// console.log('Error saving user\'s lastLogin for gToken login with google profile ID: '+googleId+'\n'+err); } -// }); -// return done(err, user); -// } else { // No such user -// done(null, false); -// } -// }); -// })); diff --git a/config/mail.js b/config/mail.js index 66b4d57..4f44fa4 100644 --- a/config/mail.js +++ b/config/mail.js @@ -17,39 +17,26 @@ let transporter = nodemailer.createTransport({ }); /* Confirm login */ -// transporter.verify(function(err, success) { +// transporter.verify( (err,success)=>{ // if (err){ console.error(`SMTP Error: ${err}`); } // console.log(`SMTP ${!success?'not ':''}ready...`); -// }); - -/* Send test email */ -// transporter.sendMail({ -// to: `"Keith Irwin" `, -// from: '"Tracman" ', -// subject: 'Test email', -// text: "Looks like everything's working", -// html: "" -// }).then(function(){ -// console.log("Email should have sent..."); -// }).catch(function(err){ -// console.error(err); -// }); +// } ); module.exports = { send: transporter.sendMail.bind(transporter), - text: function(text) { + text: (text)=>{ return `Tracman\n\n${text}\n\nDo not reply to this email\nFor information about why you received this email, see the privacy policy at ${env.url}/privacyy#email`; }, - html: function(text) { + html: (text)=>{ return `

+Tracman

${text}

Do not reply to this email. For information about why you recieved this email, see our privacy policy.

`; }, from: `"Tracman" `, - to: function(user) { + to: (user)=>{ return `"${user.name}" <${user.email}>`; } diff --git a/config/middleware.js b/config/middleware.js index cdb6721..82ab95b 100644 --- a/config/middleware.js +++ b/config/middleware.js @@ -5,28 +5,30 @@ const env = require('./env.js'); module.exports = { // Throw error - throwErr: function(err,req=null){ - console.error('Middleware error:'+err.message+'\nfor request:\n'+req); - if (env.mode==='production') { - req.flash('danger', 'An error occured.
Would you like to report it?'); - } else { // development - req.flash('danger', err); + throwErr: (err,req=null)=>{ + console.error(`⛔️ ${err.stack}`); + if (req){ + if (env.mode==='production') { + req.flash('danger', 'An error occured.
Would you like to report it?'); + } else { // development + req.flash('danger', err.message); + } } }, - + // Capitalize the first letter of a string - capitalize: function(str){ 'use strict'; + capitalize: (str)=>{ return str.charAt(0).toUpperCase() + str.slice(1); }, - + // Ensure authentication - ensureAuth: function(req,res,next){ + ensureAuth: (req,res,next)=>{ if (req.isAuthenticated()) { return next(); } else { res.redirect('/login'); } }, - + // Ensure administrator - ensureAdmin: function(req,res,next){ + ensureAdmin: (req,res,next)=>{ if (req.user.isAdmin){ return next(); } else { res.sendStatus(401); } //TODO: test this by logging in as !isAdmin and go to /admin diff --git a/config/models.js b/config/models.js index 77cf2f9..c5a02f4 100644 --- a/config/models.js +++ b/config/models.js @@ -6,7 +6,7 @@ const mongoose = require('mongoose'), crypto = require('crypto'); const userSchema = new mongoose.Schema({ - name: {type:String, required:true}, + name: {type:String}, email: {type:String, required:true}, slug: {type:String, required:true, unique:true}, auth: { @@ -19,52 +19,52 @@ const userSchema = new mongoose.Schema({ }, isAdmin: {type:Boolean, required:true, default:false}, isPro: {type:Boolean, required:true, default:false}, - created: Date, + created: {type:Date, required:true}, lastLogin: Date, settings: { - units: {type:String, default:'standard'}, - defaultMap: {type:String, default:'road'}, - defaultZoom: {type:Number, default:11}, - showSpeed: {type:Boolean, default:false}, - showTemp: {type:Boolean, default:false}, - showAlt: {type:Boolean, default:false}, - showStreetview: {type:Boolean, default:false} + units: {type:String, required:true, default:'standard'}, + defaultMap: {type:String, required:true, default:'road'}, + defaultZoom: {type:Number, required:true, default:11}, + showSpeed: {type:Boolean, required:true, default:false}, + showTemp: {type:Boolean, required:true, default:false}, + showAlt: {type:Boolean, required:true, default:false}, + showStreetview: {type:Boolean, required:true, default:false} }, last: { time: Date, - lat: {type:Number, default:0}, - lon: {type:Number, default:0}, - dir: {type:Number, default:0}, - alt: {type:Number, default:0}, - spd: {type:Number, default:0} + lat: {type:Number, required:true, default:0}, + lon: {type:Number, required:true, default:0}, + dir: {type:Number, required:true, default:0}, + alt: {type:Number, required:true, default:0}, + spd: {type:Number, required:true, default:0} }, sk32: {type:String, required:true, unique:true} }).plugin(unique); /* User methods */ { - + // Generate hash for new password - userSchema.methods.generateHash = function(password, next) { - bcrypt.genSalt(8, function(err,salt){ + userSchema.methods.generateHash = (password,next)=>{ + bcrypt.genSalt(8, (err,salt)=>{ if (err){ return next(err); } bcrypt.hash(password, salt, null, next); }); }; // Create password reset token - userSchema.methods.createToken = function(next){ + userSchema.methods.createToken = (next)=>{ var user = this; if ( user.auth.tokenExpires <= Date.now() ){ - + // Reuse old token, resetting clock user.auth.tokenExpires = Date.now() + 3600000; // 1 hour user.save(); return next(null.user.auth.passToken); - + } else { - + // Create new token - crypto.randomBytes(16, function(err,buf){ + crypto.randomBytes(16, (err,buf)=>{ if (err){ next(err,null); } else { user.auth.passToken = buf.toString('hex'); @@ -73,15 +73,15 @@ const userSchema = new mongoose.Schema({ return next(null,user.auth.passToken); } }); - + } }; - + // Check for valid password - userSchema.methods.validPassword = function(password, next) { + userSchema.methods.validPassword = (password,next)=>{ bcrypt.compare(password, this.auth.password, next); }; - + } module.exports = { diff --git a/config/passport.js b/config/passport.js index 1b6ebf4..2d7829c 100644 --- a/config/passport.js +++ b/config/passport.js @@ -9,81 +9,55 @@ const mw = require('./middleware.js'), User = require('./models.js').user; -module.exports = function(passport) { +module.exports = (passport)=>{ // Serialize/deserialize users - passport.serializeUser(function(user,done) { + passport.serializeUser((user,done)=>{ done(null, user.id); }); - passport.deserializeUser(function(id,done) { - User.findById(id, function(err, user) { + passport.deserializeUser((id,done)=>{ + User.findById(id, (err,user)=>{ if(!err){ done(null, user); } else { done(err, null); } }); }); - // Signup - // passport.use('signup', new LocalStrategy({ - // usernameField: 'email', - // passwordField: 'password', - // passReqToCallback : true - // }, function(req, email, password, done) { - // process.nextTick(function() { - // User.findOne({'email':email }, function(err, user) { - // if (err){ return done(err); } - - // // Check for existing user - // if (user) { - // return done( null, false, req.flash('warning','That email is already in use. Try logging in below.') ); - - // // Create user - // } else { - // var newUser = new User(); - // newUser.email = email; - // newUser.created = Date.now(); - // newUser.lastLogin = Date.now(); - // newUser.generateHash(password, function(err, hash){ - // if (err){ return done(err); } - // newUser.auth.password = hash; - // newUser.save(function(err) { - // if (err){ return done(err); } - // return done( null, newUser ); - // }); - // }); - // } - - // }); - // }); - // }) - // ); - // Local passport.use('local', new LocalStrategy({ usernameField: 'email', passwordField: 'password', - passReqToCallback : true - }, function(req, email, password, done) { - User.findOne({ 'email':email }, function (err, user) { + passReqToCallback: true + }, (req,email,password,done)=>{ + User.findOne( {'email':email}, (err,user)=>{ if (err){ return done(err); } - // Wrong username + // No user with that email if (!user) { - return done( null, false, req.flash('danger','No account exists for that email.') ); - // Username correct, password incorrect - } else { + return done( null, false, req.flash('danger','Incorrect email or password.') ); + } + + // User exists + else { + // Check password - user.validPassword(password, function(err,res){ - if (err){ console.log('Passport error:\n',err); } - if (!res) { // Password incorrect - return done( null, false, req.flash('danger','Incorrect password.') ); - } else { // Successful login + user.validPassword( password, (err,res)=>{ + if (err){ return done(err); } + + // Password incorrect + if (!res) { + return done( null, false, req.flash('danger','Incorrect email or password.') ); + } + + // Successful login + else { user.lastLogin = Date.now(); user.save(); - return done( null, user ); + return done(null,user); } - }); + + } ); } - }); + } ); } )); @@ -96,35 +70,39 @@ module.exports = function(passport) { var query = {}; query['auth.'+service] = profileId; - User.findOne(query, function (err, user) { + User.findOne(query, (err,user)=>{ if (err){ return done(err); } + + // Can't find user else if (!user){ - // console.log('User not found.'); // Lazy update from old googleId field if (service==='google') { - User.findOne({'googleID':parseInt(profileId)}, function(err,user){ - // console.log(`searched for user with googleID ${profileId}`); - if (err){ mw.throwErr(err,req); } + User.findOne( {'googleID':parseInt(profileId)}, (err,user)=>{ + if (err){ return done(err); } if (user) { - // console.log(`Lazily updating schema for ${user.name}.`); user.auth.google = profileId; user.googleId = null; - user.save(function(err){ + user.save( (err)=>{ if (err){ mw.throwErr(err,req); } + else { console.info(`🗂️ Lazily updated schema for ${user.name}.`); } return done(null, user); - }); + } ); } else { req.flash('danger',`There's no user for that ${service} account. `); return done(); } - }); - } else { - + } ); + } + + // No googleId either + else { req.flash('danger',`There's no user for that ${service} account. `); return done(); } } + + // Successfull social login else { // console.log(`Found user: ${user}`); return done(null, user); @@ -134,12 +112,12 @@ module.exports = function(passport) { // Connect account else { - console.log(`Connecting ${service} account.`); + // console.log(`Connecting ${service} account.`); req.user.auth[service] = profileId; - req.user.save(function(err){ + req.user.save( (err)=>{ if (err){ return done(err); } else { return done(null, req.user); } - }); + } ); } } @@ -150,7 +128,7 @@ module.exports = function(passport) { clientSecret: env.googleClientSecret, callbackURL: env.url+'/login/google/cb', passReqToCallback: true - }, function(req, accessToken, refreshToken, profile, done) { + }, (req, accessToken, refreshToken, profile, done)=>{ socialLogin(req, 'google', profile.id, done); } )); @@ -161,7 +139,7 @@ module.exports = function(passport) { clientSecret: env.facebookAppSecret, callbackURL: env.url+'/login/facebook/cb', passReqToCallback: true - }, function(req, accessToken, refreshToken, profile, done) { + }, (req, accessToken, refreshToken, profile, done)=>{ socialLogin(req, 'facebook', profile.id, done); } )); @@ -172,7 +150,7 @@ module.exports = function(passport) { consumerSecret: env.twitterConsumerSecret, callbackURL: env.url+'/login/twitter/cb', passReqToCallback: true - }, function(req, token, tokenSecret, profile, done) { + }, (req, token, tokenSecret, profile, done)=>{ socialLogin(req, 'twitter', profile.id, done); } )); diff --git a/config/routes/admin.js b/config/routes/admin.js index eab9379..9374140 100644 --- a/config/routes/admin.js +++ b/config/routes/admin.js @@ -2,69 +2,35 @@ const router = require('express').Router(), mw = require('../middleware.js'), - User = require('../models.js').user, - mail = require('../mail.js'); + User = require('../models.js').user; router.route('/') - .all(mw.ensureAdmin, function(req,res,next){ + .all(mw.ensureAdmin, (req,res,next)=>{ next(); - }).get(function(req,res){ - - var cbc = 0; - var checkCBC = function(req,res,err){ - if (err) { - req.flash('error', err.message); - console.error(err); - } - if (cbc<1){ cbc++; } - else { // done - res.render('admin', { - noFooter: '1', - success:req.flash('success')[0], - error:req.flash('error')[0] - }); - } - }; - - User.findById(req.user, function(err, found) { - res.locals.user = found; - checkCBC(req,res,err); - }); - - User.find({}).sort({lastLogin:-1}).exec(function(err, found){ - res.locals.users = found; - checkCBC(req,res,err); - }); - - }); + } ) -router.route('/users') - .all(mw.ensureAdmin, function(req,res,next){ - next(); - }).post(function(req,res,next){ + .get( (req,res)=>{ + + User.find({}).sort({lastLogin:-1}) + .catch( (err)=>{ + mw.throwErr(err); + }).then( (found)=>{ + res.render('admin', { + noFooter: '1', + users: found + }); + }); + + } ) + + .post( (req,res,next)=>{ if (req.body.delete) { - User.findOneAndRemove({'_id':req.body.delete}, function(err,user){ + User.findOneAndRemove( {'_id':req.body.delete}, (err,user)=>{ if (err){ req.flash('error', err.message); } else { req.flash('success', ''+user.name+' deleted.'); } res.redirect('/admin#users'); - }); - } else { console.error('ERROR! POST without action sent. '); next(); } - }); - -router.route('/testmail').get(function(req,res,next){ - mail.send({ - to: `"Keith Irwin" `, - from: mail.from, - subject: 'Test email', - text: mail.text("Looks like everything's working! "), - html: mail.html("

Looks like everything's working!

") - }).then(function(){ - console.log("Test email should have sent..."); - res.sendStatus(200); - }).catch(function(err){ - mw.throwErr(err,req); - next(); - }); -}); - + } ); + } else { console.error(new Error('POST without action sent. ')); next(); } + } ); + module.exports = router; \ No newline at end of file diff --git a/config/routes/index.js b/config/routes/index.js index 6c5c502..132c687 100644 --- a/config/routes/index.js +++ b/config/routes/index.js @@ -6,24 +6,25 @@ const mw = require('../middleware.js'), User = require('../models.js').user; // Index -router.get('/', function(req,res,next) { +router.get('/', (req,res,next)=>{ res.render('index'); }); // Help -router.get('/help', mw.ensureAuth, function(req,res){ - res.render('help'); - }); +router.get('/help', mw.ensureAuth, (req,res)=>{ + res.render('help'); +}); // Terms of Service and Privacy Policy -router.get('/terms', function(req,res){ +router.get('/terms', (req,res)=>{ res.render('terms'); -}).get('/privacy', function(req,res){ +}) +.get('/privacy', (req,res)=>{ res.render('privacy'); }); // robots.txt -router.get('/robots.txt', function(req,res){ +router.get('/robots.txt', (req,res)=>{ res.type('text/plain'); res.send("User-agent: *\n"+ "Disallow: /map/*\n" @@ -31,28 +32,28 @@ router.get('/robots.txt', function(req,res){ }); // favicon.ico -router.get('/favicon.ico', function(req,res){ +router.get('/favicon.ico', (req,res)=>{ res.redirect('/static/img/icon/by/16-32-48.ico'); }); // Endpoint to validate forms -router.get('/validate', function(req,res){ +router.get('/validate', (req,res)=>{ if (req.query.slug) { // validate unique slug - User.findOne({slug:slug(req.query.slug)}, function(err, existingUser){ + User.findOne( {slug:slug(req.query.slug)}, (err,existingUser)=>{ if (err) { console.log('/validate error:',err); } if (existingUser && existingUser.id!==req.user) { res.sendStatus(400); } else { res.sendStatus(200); } - }); + } ); } }); // Link to androidapp in play store -router.get('/android', function(req,res){ +router.get('/android', (req,res)=>{ res.redirect('https://play.google.com/store/apps/details?id=us.keithirwin.tracman'); }); // Link to iphone app in the apple store -router.get('/ios', function(req,res){ +router.get('/ios', (req,res)=>{ res.sendStatus(404); //TODO: Add link to info about why there's no ios app }); diff --git a/config/routes/map.js b/config/routes/map.js index 4e58fac..7d0a068 100644 --- a/config/routes/map.js +++ b/config/routes/map.js @@ -1,4 +1,5 @@ 'use strict'; +//TODO: Use promises const router = require('express').Router(), mw = require('../middleware.js'), @@ -6,12 +7,12 @@ const router = require('express').Router(), User = require('../models.js').user; // Redirect to real slug -router.get('/', mw.ensureAuth, function(req,res){ +router.get('/', mw.ensureAuth, (req,res)=>{ res.redirect(`/map/${req.user.slug}`); }); // Show map -router.get('/:slug?', function(req,res,next){ +router.get('/:slug?', (req,res,next)=>{ var mapuser='', user='', cbc=0; // Confirm sucessful queries diff --git a/config/routes/settings.js b/config/routes/settings.js index d7ac097..653e7da 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -11,20 +11,20 @@ const slug = require('slug'), // Settings form router.route('/') - .all(mw.ensureAuth, function(req,res,next){ + .all( mw.ensureAuth, (req,res,next)=>{ next(); - }) - + } ) + // Get settings form - .get(function(req,res,next){ - User.findById(req.user, function(err,user){ + .get( (req,res,next)=>{ + User.findById( req.user, (err,user)=>{ if (err){ mw.throwErr(err,req); } res.render('settings'); - }); - }) - + } ); + } ) + // Set new settings - .post(function(req,res,next){ + .post( (req,res,next)=>{ User.findByIdAndUpdate(req.user, {$set:{ name: xss(req.body.name), slug: slug(xss(req.body.slug)), @@ -37,65 +37,71 @@ router.route('/') showAlt: (req.body.showAlt)?true:false, showStreetview: (req.body.showStreet)?true:false } - }}, function(err, user){ - if (err) { mw.throwErr(err,req); } - else { req.flash('success', 'Settings updated. '); } - res.redirect('/settings'); - }); - }) + }}, (err,user)=>{ + if (err) { + mw.throwErr(err,req); + res.redirect('/settings'); + } + else { + req.flash('success', 'Settings updated. '); + res.redirect('/settings'); + } + }); + } ) // Delete user account - .delete(function(req,res,next){ - User.findByIdAndRemove( req.user, - function(err) { - if (err) { - mw.throwErr(err,req); - } else { - req.flash('success', 'Your account has been deleted. '); - } + .delete( (req,res,next)=>{ + User.findByIdAndRemove( req.user, (err)=>{ + if (err) { + mw.throwErr(err,req); + res.redirect('/settings'); + } else { + req.flash('success', 'Your account has been deleted. '); res.redirect('/'); } - ); - }); + } ); + } ); // Set password router.route('/password/') - .all(mw.ensureAuth,function(req,res,next){ + .all( mw.ensureAuth, (req,res,next)=>{ next(); - }) - + } ) + // Email user a token, proceed at /password/:token - .get(function(req,res,next){ + .get( (req,res,next)=>{ // Create token for password change - req.user.createToken(function(err,token){ + req.user.createToken( (err,token)=>{ if (err){ next(err); } - - // Confirm password change request by email. + + // 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.

`) - }).catch(function(err){ + }).catch( err=>{ mw.throwErr(err,req); - }).then(function(){ - - // Alert user to check email. + res.redirect('/login#login'); + }).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( (req.isAuthenticated)?'/settings':'/login' ); - + res.redirect('/login#login'); + }); - - }); - - }); + + } ); + + } ); + router.route('/password/:token') - + // Check token - .all(function(req,res,next){ + .all( (req,res,next)=>{ User .findOne({'auth.passToken': req.params.token}) .where('auth.tokenExpires').gt(Date.now()) @@ -110,71 +116,66 @@ router.route('/password/:token') next(); } }); - }) + } ) // Show password change form - .get(function(req,res){ + .get( (req,res)=>{ res.render('password'); - }) - - .post(function(req,res,next){ + } ) + + .post( (req,res,next)=>{ - // Validate matching passwords - if (req.body.password!==req.body.repassword) { - mw.throwErr( new Error('Passwords do not match. '), req ); - } else { + //TODO: Validate password - //TODO: Add logic for new users - - // Delete token - res.locals.passwordUser.auth.passToken = undefined; - res.locals.passwordUser.auth.tokenExpires = undefined; - - // Create hash - res.locals.passwordUser.generateHash(req.body.password, function(err,hash){ - if (err){ mw.throwErr(err); } - else { - - // Save new password to db - res.locals.passwordUser.auth.password = hash; - res.locals.passwordUser.save( function(err){ - if (err){ mw.throwErr(err,req); } - else { - req.flash('success', 'Password set. You can use it to log in now. '); - } - }); - - } - }); - - } + // Delete token + res.locals.passwordUser.auth.passToken = undefined; + res.locals.passwordUser.auth.tokenExpires = undefined; - res.redirect('/login'); + // Create hash + res.locals.passwordUser.generateHash( req.body.password, (err,hash)=>{ + if (err){ mw.throwErr(err,req); } + else { + + // Save new password to db + res.locals.passwordUser.auth.password = hash; + res.locals.passwordUser.save( (err)=>{ + if (err){ + mw.throwErr(err,req); + res.redirect('/login#signup'); + } + else { + req.flash('success', 'Password set. You can use it to log in now. '); + res.redirect('/login#login'); + } + }); + + } + } ); - }); + } ); // Tracman pro router.route('/pro') - .all(mw.ensureAuth, function(req,res,next){ + .all( mw.ensureAuth, (req,res,next)=>{ next(); - }) - + } ) + // Get info about pro - .get(function(req,res,next){ + .get( (req,res,next)=>{ res.render('pro'); - }) - + } ) + // Join Tracman pro - .post(function(req,res){ + .post( (req,res)=>{ User.findByIdAndUpdate(req.user.id, {$set:{ isPro:true }}, - function(err, user){ + (err,user)=>{ if (err){ mw.throwErr(err,req); } else { req.flash('success','You have been signed up for pro. '); } res.redirect('/map'); } ); - }); - + } ); + module.exports = router; \ No newline at end of file diff --git a/config/routes/test.js b/config/routes/test.js index 3d471e4..6b7fbb1 100644 --- a/config/routes/test.js +++ b/config/routes/test.js @@ -6,24 +6,24 @@ const router = require('express').Router(), router - .get('/mail', function(req,res,next){ + .get('/mail', (req,res,next)=>{ mail.send({ to: `"Keith Irwin" `, from: mail.from, subject: 'Test email', text: mail.text("Looks like everything's working! "), html: mail.html("

Looks like everything's working!

") - }).then(function(){ + }).then(()=>{ console.log("Test email should have sent..."); res.sendStatus(200); - }).catch(function(err){ + }).catch((err)=>{ mw.throwErr(err,req); next(); }); }) - .get('/password', function(req,res){ + .get('/password', (req,res)=>{ res.render('password'); - }) + }); module.exports = router; \ No newline at end of file diff --git a/config/sockets.js b/config/sockets.js index a8edae0..3cb72aa 100644 --- a/config/sockets.js +++ b/config/sockets.js @@ -1,7 +1,8 @@ 'use strict'; // Imports -const User = require('./models.js').user; +const mw = require('./middleware.js'), + User = require('./models.js').user; // Check for tracking clients function checkForUsers(io, user) { @@ -9,9 +10,9 @@ 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( function(id){ + if (Object.keys(io.sockets.connected).map( (id)=>{ return io.sockets.connected[id]; - }).some( function(socket){ + }).some( (socket)=>{ return socket.gets==user; })) { //console.log(`Activating updates for ${user}.`); @@ -26,56 +27,56 @@ module.exports = { checkForUsers: checkForUsers, - init: function(io){ - io.on('connection', function(socket) { + init: (io)=>{ + io.on('connection', (socket)=>{ //console.log(`${socket.id} connected.`); - // Log - //socket.on('log', function(text){ + /* Log */ + //socket.on('log', (text)=>{ //console.log(`LOG: ${text}`); //}); // This socket can set location (app) - socket.on('can-set', function(userId){ + socket.on('can-set', (userId)=>{ //console.log(`${socket.id} can set updates for ${userId}.`); - socket.join(userId, function(){ + socket.join(userId, ()=>{ //console.log(`${socket.id} joined ${userId}`); }); checkForUsers( io, userId ); }); // This socket can receive location (map) - socket.on('can-get', function(userId){ + socket.on('can-get', (userId)=>{ socket.gets = userId; //console.log(`${socket.id} can get updates for ${userId}.`); - socket.join(userId, function(){ + socket.join(userId, ()=>{ //console.log(`${socket.id} joined ${userId}`); socket.to(userId).emit('activate', 'true'); }); }); // Set location - socket.on('set', function(loc){ + socket.on('set', (loc)=>{ //console.log(`${socket.id} set location for ${loc.usr}`); loc.time = Date.now(); // Check for sk32 token - if (!loc.tok) { console.log('!loc.tok for loc:',loc) } + if (!loc.tok) { mw.throwErr(new Error(`⛔️ !loc.tok for loc: ${loc}`)) } else { // Get loc.usr - User.findById(loc.usr, function(err, user) { - if (err) { console.log('Error finding user:',err); } - if (!user) { console.log('User not found for loc:',loc); } - else { + User.findById(loc.usr, (err,user)=>{ + if (err) { mw.throwErr(err); } + if (user) { // Confirm sk32 token - if (loc.tok!=user.sk32) { console.log('loc.tok!=user.sk32 || ',loc.tok,'!=',user.sk32); } + if (loc.tok!=user.sk32) { mw.throwErr(new Error(`⛔️ loc.tok!=user.sk32\n\t${loc.tok} != ${user.sk32}`)); } else { // Broadcast location io.to(loc.usr).emit('get', loc); //console.log(`Broadcasting ${loc.lat}, ${loc.lon} to ${loc.usr}`); + // Save in db as last seen user.last = { lat: parseFloat(loc.lat), @@ -84,9 +85,9 @@ module.exports = { spd: parseFloat(loc.spd||0), time: loc.time }; - user.save(function(err) { - if (err) { console.log('Error saving user last location:'+loc.user+'\n'+err); } - }); + user.save( (err)=>{ + if (err) { mw.throwErr(err); } + } ); } } @@ -96,7 +97,7 @@ module.exports = { }); // Shutdown (check for remaining clients) - socket.on('disconnect', function(reason){ + socket.on('disconnect', (reason)=>{ //console.log(`${socket.id} disconnected because of a ${reason}.`); // Check if client was receiving updates @@ -108,8 +109,8 @@ module.exports = { }); // Log errors - socket.on('error', function(err){ - console.log('Socket error! ',err); + socket.on('error', (err)=>{ + mw.throwErr(err); }); }); diff --git a/server.js b/server.js index 68c0e00..025cdb4 100755 --- a/server.js +++ b/server.js @@ -1,7 +1,7 @@ 'use strict'; -/* IMPORTS */ -const +/* IMPORTS */ +const express = require('express'), bodyParser = require('body-parser'), cookieParser = require('cookie-parser'), @@ -11,6 +11,7 @@ const passport = require('passport'), flash = require('connect-flash'), env = require('./config/env.js'), + mw = require('./config/middleware.js'), User = require('./config/models.js').user, app = express(), http = require('http').Server(app), @@ -19,14 +20,18 @@ const /* SETUP */ { - + /* Database */ mongoose.connect(env.mongoSetup, { server:{socketOptions:{ keepAlive:1, connectTimeoutMS:30000 }}, replset:{socketOptions:{ keepAlive:1, connectTimeoutMS:30000 }} + }).catch((err)=>{ + mw.throwErr(err); + }).then(()=>{ + console.log(`💿 Mongoose connected to database`); }); - + /* Templates */ { nunjucks.configure(__dirname+'/views', { autoescape: true, @@ -34,7 +39,7 @@ const }); app.set('view engine','html'); } - + /* Session */ { app.use(cookieParser(env.cookie)); app.use(cookieSession({ @@ -49,7 +54,7 @@ const })); app.use(flash()); } - + /* Auth */ { require('./config/passport.js')(passport); app.use(passport.initialize()); @@ -58,116 +63,116 @@ const // app.use(passport.initialize()); // app.use(passport.session()); // require('./config/auth.js'); - // passport.serializeUser(function(user,done) { + // passport.serializeUser( (user,done)=>{ // done(null, user.id); - // }); - // passport.deserializeUser(function(id,done) { - // User.findById(id, function(err, user) { + // } ); + // passport.deserializeUser( (id,done)=>{ + // User.findById( id, (err,user)=>{ // if(!err) done(null, user); // else done(err, null); - // }); - // }); + // } ); + // } ); } - + /* Routes */ { - + // Static files (keep this before setting default locals) - app.use('/static', express.static(__dirname+'/static')); - + app.use('/static', express.static( __dirname+'/static', {dotfiles:'allow'} )); + // Set default locals available to all views (keep this after static files) - app.get('/*', function(req,res,next){ + app.get( '/*', (req,res,next)=>{ // console.log(`Setting local variables for request to ${req.path}.`); - + // User account res.locals.user = req.user; // console.log(`User set as ${res.locals.user}. `); - + // Flash messages res.locals.successes = req.flash('success'); res.locals.dangers = req.flash('danger'); res.locals.warnings = req.flash('warning'); // console.log(`Flash messages set as:\nSuccesses: ${res.locals.successes}\nWarnings: ${res.locals.warnings}\nDangers: ${res.locals.dangers}`); - + next(); - }); - + } ); + // Main routes app.use( '/', require('./config/routes/index.js') ); - + // Settings app.use( '/settings', require('./config/routes/settings.js') ); - + // Map app.use( ['/map','/trac'], require('./config/routes/map.js') ); - + // Site administration app.use( '/admin', require('./config/routes/admin.js') ); - + // Testing if (env.mode == 'development') { app.use( '/test', require('./config/routes/test.js' ) ); } - + } - + /* Errors */ { // Catch-all for 404s - app.use(function(req,res,next) { + app.use( (req,res,next)=>{ if (!res.headersSent) { - var err = new Error('404: Not found: '+req.url); + var err = new Error('404 Not found: '+req.url); err.status = 404; next(err); } - }); - + } ); + // Handlers if (env.mode=='production') { - app.use(function(err,req,res,next) { + app.use( (err,req,res,next)=>{ if (res.headersSent) { return next(err); } res.status(err.status||500); res.render('error', { code: err.status }); - }); + } ); } else /* Development */{ - app.use(function(err,req,res,next) { + app.use( (err,req,res,next)=>{ console.log(err); if (res.headersSent) { return next(err); } res.status(err.status||500); res.render('error', { code: err.status, message: err.message, - error: err + error: err.stack }); - }); + } ); } } - + /* Sockets */ { sockets.init(io); } - + } /* RUNTIME */ { - - console.log('Starting Tracman server...'); - + + console.log('🖥 Starting Tracman server...'); + // Listen - http.listen(env.port, function(){ - console.log(`Listening in ${env.mode} mode on port ${env.port}. `); - + http.listen( env.port, ()=>{ + console.log(`🌐 Listening in ${env.mode} mode on port ${env.port}... `); + // Check for clients for each user - User.find({}, function(err, users){ + User.find( {}, (err,users)=>{ if (err) { console.log(`DB error finding all users: ${err.message}`); } - users.forEach( function(user){ + users.forEach( (user)=>{ sockets.checkForUsers( io, user.id ); }); }); - + }); - + } module.exports = app; diff --git a/static/css/header.css b/static/css/header.css index 4ca92dd..1a4a7ac 100644 --- a/static/css/header.css +++ b/static/css/header.css @@ -51,7 +51,10 @@ header nav { } header nav ul li a:hover, header nav ul li a:focus, header nav ul li a.active, - +header .logo:hover { + text-decoration: none; + background: rgba(255,255,255,0.1); +} /* Hamburger */ header .hamburger { @@ -72,8 +75,8 @@ header .hamburger { } header .hamburger-inner { top: 50%; margin-top: -2px; -} header .hamburger-inner, -header .hamburger-inner::before, +} header .hamburger-inner, +header .hamburger-inner::before, header .hamburger-inner::after { width: 40px; height: 4px; diff --git a/test.js b/test.js index 7cc90d4..66615a2 100755 --- a/test.js +++ b/test.js @@ -5,15 +5,7 @@ const chai = require('chai'), chai.use(chaiHttp); -describe('Index', function() { - // I think this restarts the server after each try? - // var server; - // beforeEach(function() { - // server = require('./server'); - // }); - // afterEach(function() { - // server.close(); - // }); +describe('Pages', function() { it('Displays homepage', function(done){ request(server).get('/') @@ -21,6 +13,24 @@ describe('Index', function() { .end(function(err,res){ done(); }); }); + it('Displays help page', function(done){ + request(server).get('/help') + .expect(200) + .end(function(err,res){ done(); }); + }); + + it('Displays terms of service', function(done){ + request(server).get('/terms') + .expect(200) + .end(function(err,res){ done(); }); + }); + + it('Displays privacy policy', function(done){ + request(server).get('/privacy') + .expect(200) + .end(function(err,res){ done(); }); + }); + it('Displays robots.txt', function(done){ request(server).get('/robots.txt') .expect(200) @@ -36,54 +46,13 @@ describe('Index', function() { }); -// describe('Auth', function() { +describe('Auth', function() { - // it('Creates an account', function(done){ - // request(server).get('/login') - // .expect(200) - // .end(function(err,res){ - // //TODO: google authentication - // it('Logs out', function(done){ - // request(server).get('/logout') - // .expect(200) - // .end(function(err,res){ - // it('Logs in', function(done){ - // request(server).get('/logout') - // .expect(200) - // .end(function(err,res){ - // cbc=2; - // var deletesAccount = function(done){ - // it('Deletes own account', function(){ - // //TODO: Delete account via GUI - // }); - // } - // it('Shows own map', function(done){ - // request(server).get('/map') - // .expect(200) - // //TODO: Expect no js errors - // .end(function(err,res){ - // if (cbc<2){ deletesAccount(); } - // else { cbc--; } - // done(); - // }); - // }); - - // it('Has the correct account info', function(done){ - // //TODO: Check account info - // if (cbc<2){ deletesAccount(); } - // else { cbc--; } - // done(); - // }); - - // done(); - // }); - // }); - // done(); - // }); - // }); - // done(); - // }); - // }); + it('Creates an account', function(done){ + request(server).post('/signup',{"email":"test@tracman.org"}) + .expect(200) + .end(function(err,res){ done(); }); + }); //TODO: it('Has the correct account info', function(done){ diff --git a/views/admin.html b/views/admin.html index 8c6f461..4b55fe6 100644 --- a/views/admin.html +++ b/views/admin.html @@ -37,7 +37,7 @@ Google {% endif %} -
+
diff --git a/views/error.html b/views/error.html index ed036b6..a759643 100644 --- a/views/error.html +++ b/views/error.html @@ -1,14 +1,12 @@ {% extends 'templates/base.html' %} -{% block title %}{{ super() }} | {{ code }} Error{% endblock %} +{% block title %}{{super()}} | Error{% endblock %} {% block main %} -
-
- {% if code %}

{{code}}

{% endif %} - {% if message %}

{{message}}

{% endif %} - {% if error %}

{{error}}

{% endif %} -

I would really appreciate it if you would report this error.

- {% if code %}{% endif %} -
+
+ {% if code %}

{{code}}

{% endif %} + {% if message %}

{{message}}

{% endif %} + {% if stack %}

{{stack}}

{% else %} +

I would really appreciate it if you would report this error.

{% endif %} + {% if code %}{% endif %}
{% endblock %} \ No newline at end of file diff --git a/views/templates/header.html b/views/templates/header.html index 04880d5..5b0cb54 100644 --- a/views/templates/header.html +++ b/views/templates/header.html @@ -1,15 +1,15 @@
- + - +
- + - +
From d74f0f2087b4153a3327ce20ee636f2f24152ab9 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Thu, 13 Apr 2017 18:59:46 -0400 Subject: [PATCH 055/143] #45 Using ES6 promises now --- config/routes/settings.js | 19 ++++++++++--------- server.js | 28 ++++++++++++++++++---------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/config/routes/settings.js b/config/routes/settings.js index 653e7da..9bd0b2b 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -105,9 +105,10 @@ router.route('/password/:token') User .findOne({'auth.passToken': req.params.token}) .where('auth.tokenExpires').gt(Date.now()) - //TODO: Add own promise libary - .exec((err, user) => { - if (err) { mw.throwErr(err,req); } + .catch((err)=>{ + mw.throwErr(err,req); + }) + .then((user) => { if (!user) { req.flash('danger', 'Password reset token is invalid or has expired. '); res.redirect( (req.isAuthenticated)?'/settings':'/login' ); @@ -124,18 +125,18 @@ router.route('/password/:token') } ) .post( (req,res,next)=>{ - + //TODO: Validate password - + // Delete token res.locals.passwordUser.auth.passToken = undefined; res.locals.passwordUser.auth.tokenExpires = undefined; - + // Create hash res.locals.passwordUser.generateHash( req.body.password, (err,hash)=>{ if (err){ mw.throwErr(err,req); } else { - + // Save new password to db res.locals.passwordUser.auth.password = hash; res.locals.passwordUser.save( (err)=>{ @@ -148,10 +149,10 @@ router.route('/password/:token') res.redirect('/login#login'); } }); - + } } ); - + } ); diff --git a/server.js b/server.js index 025cdb4..989a089 100755 --- a/server.js +++ b/server.js @@ -21,16 +21,24 @@ const /* SETUP */ { - /* Database */ mongoose.connect(env.mongoSetup, { - server:{socketOptions:{ - keepAlive:1, connectTimeoutMS:30000 }}, - replset:{socketOptions:{ - keepAlive:1, connectTimeoutMS:30000 }} - }).catch((err)=>{ - mw.throwErr(err); - }).then(()=>{ - console.log(`💿 Mongoose connected to database`); - }); + /* Database */ { + + // Setup with native ES6 promises + mongoose.Promise = global.Promise; + + // Connect to database + mongoose.connect(env.mongoSetup, { + server:{socketOptions:{ + keepAlive:1, connectTimeoutMS:30000 }}, + replset:{socketOptions:{ + keepAlive:1, connectTimeoutMS:30000 }} + }).catch((err)=>{ + mw.throwErr(err); + }).then(()=>{ + console.log(`💿 Mongoose connected to database`); + }); + + } /* Templates */ { nunjucks.configure(__dirname+'/views', { From 6a7a49bffd5335c8471a32991c22fdf48bf8a52f Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 14 Apr 2017 00:45:25 -0400 Subject: [PATCH 056/143] Various changes --- config/auth.js | 110 +++++++++++++++++++++++------------------------ config/models.js | 24 +++++------ server.js | 44 +++++++++---------- views/map.html | 5 ++- 4 files changed, 92 insertions(+), 91 deletions(-) diff --git a/config/auth.js b/config/auth.js index eee797c..572d635 100644 --- a/config/auth.js +++ b/config/auth.js @@ -15,43 +15,40 @@ module.exports = (app, passport) => { loginOutcome = { failureRedirect: '/login', failureFlash: true - }, + }, connectOutcome = { failureRedirect: '/settings', failureFlash: true }, loginCallback = (req,res)=>{ - res.redirect( req.session.next || '/settings' ); - delete req.session.next; + res.redirect( req.session.next || '/map' ); }; - + // Login/-out app.route('/login') .get( (req,res)=>{ - req.session.next = req.header('Referer'); - if (req.isAuthenticated()){ - res.redirect(req.session.next||'/settings'); } + if (req.isAuthenticated()){ loginCallback(); } else { res.render('login'); } }) .post( passport.authenticate('local',loginOutcome), loginCallback ); app.get('/logout', (req,res)=>{ req.logout(); req.flash('success',`You have been logged out.`); - res.redirect('/'); + res.redirect(req.session.next || '/'); }); - + // Signup app.get('/signup', (req,res)=>{ res.redirect('/login#signup'); }).post('/signup', (req,res,next)=>{ - + // Send token and alert user function sendToken(user){ - + // Create a password token - user.createToken(function(err,token){ + user.createToken((err,token)=>{ if (err){ mw.throwErr(err,req); } - + // Email the instructions to continue mail.send({ from: mail.from, @@ -59,48 +56,52 @@ module.exports = (app, passport) => { subject: 'Complete your Tracman registration', text: mail.text(`Welcome to Tracman! \n\nTo complete your registration, follow this link and set your password:\n${env.url}/settings/password/${token}`), html: mail.html(`

Welcome to Tracman!

To complete your registration, follow this link and set your password:
${env.url}/settings/password/${token}

`) - }).catch(function(err){ + }).catch((err)=>{ mw.throwErr(err,req); res.redirect('/login#signup'); - }).then(function(){ + }).then(()=>{ req.flash('success', `An email has been sent to ${user.email}. Check your inbox to complete your registration. `); - res.redirect('/'); + res.redirect('/login'); }); }); - + } - + // Check if somebody already has that email User.findOne({'email':req.body.email}, (err,user)=>{ if (err){ mw.throwErr(err,req); } - + // User already exists if (user && user.auth.password) { req.flash('warning','A user with that email already exists! If you forgot your password, you can reset it here.'); res.redirect('/login#login'); next(); } - + // User exists but hasn't created a password yet else if (user) { // Send another token (or the same one if it hasn't expired) sendToken(user); } - + // Create user else { - + user = new User(); user.created = Date.now(); user.email = req.body.email; user.slug = slug(user.email.substring(0, user.email.indexOf('@'))); - + // Generate unique slug var generateSlug = new Promise((resolve,reject) => { (function checkSlug(s,cb){ - User.findOne({slug:s}, function(err, existingUser){ - if (err) { mw.throwErr(err,req); } - + + User.findOne({slug:s}) + .catch((err)=>{ + mw.throwErr(err,req); + }) + .then((existingUser)=>{ + // Slug in use: generate a random one and retry if (existingUser){ s = ''; @@ -108,16 +109,19 @@ module.exports = (app, passport) => { s+='abcdefghijkmnpqrtuvwxy346789'.charAt(Math.floor(Math.random()*28)); } checkSlug(s,cb); - + } + // Unique slug: proceed - } else { cb(s); } + else { cb(s); } + }); - })(user.slug, function(newSlug){ + + })(user.slug, (newSlug)=>{ user.slug = newSlug; resolve(); }); }); - + // Generate sk32 var generateSk32 = new Promise((resolve,reject) => { crypto.randomBytes(32, (err,buf)=>{ @@ -126,20 +130,20 @@ module.exports = (app, passport) => { resolve(); }); }); - + // Save user and send the token by email Promise.all([generateSlug, generateSk32]) - .catch(err => { - mw.throwErr(err,req); - }).then(() => { - user.save( (err)=>{ - if (err){ mw.throwErr(err,req); } - sendToken(user); + .catch(err => { + mw.throwErr(err,req); + }).then(() => { + user.save( (err)=>{ + if (err){ mw.throwErr(err,req); } + sendToken(user); + }); }); - }); - + } - + }); }); @@ -191,35 +195,31 @@ module.exports = (app, passport) => { // Social app.get('/login/:service', (req,res,next)=>{ - var service = req.params.service; - if (service==='google'){ - var sendParams = {scope:['profile']}; - } - + let service = req.params.service, + sendParams = (service==='google')? {scope:['profile']} : null; + // Social login if (!req.user) { passport.authenticate(service, sendParams)(req,res,next); } - + // Connect social account else if (!req.user.auth[service]) { passport.authorize(service, sendParams)(req,res,next); } - + // Disconnect social account else { req.user.auth[service] = undefined; - req.user.save( (err)=>{ - if (err) { + req.user.save() + .catch((err)=>{ mw.throwErr(err,req); res.redirect('/settings'); - } - else { + }).then(()=>{ req.flash('success', `${mw.capitalize(service)} account disconnected. `); res.redirect('/settings'); - } - }); - + }); + } }); app.get('/login/:service/cb', (req,res,next)=>{ @@ -236,7 +236,7 @@ module.exports = (app, passport) => { // Android auth //TODO: See if there's a better method app.get('/auth/google/idtoken', passport.authenticate('google-id-token'), (req,res)=>{ - if (!req.user) { res.sendStatus(401); } + if (!req.user){ res.sendStatus(401); } else { res.send(req.user); } } ); diff --git a/config/models.js b/config/models.js index c5a02f4..76d8215 100644 --- a/config/models.js +++ b/config/models.js @@ -42,27 +42,27 @@ const userSchema = new mongoose.Schema({ }).plugin(unique); /* User methods */ { - + // Generate hash for new password - userSchema.methods.generateHash = (password,next)=>{ + userSchema.methods.generateHash = function(password,next){ bcrypt.genSalt(8, (err,salt)=>{ if (err){ return next(err); } bcrypt.hash(password, salt, null, next); }); }; - + // Create password reset token - userSchema.methods.createToken = (next)=>{ + userSchema.methods.createToken = function(next){ var user = this; if ( user.auth.tokenExpires <= Date.now() ){ - + // Reuse old token, resetting clock user.auth.tokenExpires = Date.now() + 3600000; // 1 hour user.save(); - return next(null.user.auth.passToken); - + return next(null,user.auth.passToken); + } else { - + // Create new token crypto.randomBytes(16, (err,buf)=>{ if (err){ next(err,null); } @@ -73,15 +73,15 @@ const userSchema = new mongoose.Schema({ return next(null,user.auth.passToken); } }); - + } }; - + // Check for valid password - userSchema.methods.validPassword = (password,next)=>{ + userSchema.methods.validPassword = function(password,next){ bcrypt.compare(password, this.auth.password, next); }; - + } module.exports = { diff --git a/server.js b/server.js index 989a089..c7c20b8 100755 --- a/server.js +++ b/server.js @@ -35,7 +35,7 @@ const }).catch((err)=>{ mw.throwErr(err); }).then(()=>{ - console.log(`💿 Mongoose connected to database`); + console.log(`💿 Mongoose connected to mongoDB`); }); } @@ -89,40 +89,40 @@ const // Set default locals available to all views (keep this after static files) app.get( '/*', (req,res,next)=>{ - // console.log(`Setting local variables for request to ${req.path}.`); - + + // Path for redirects + req.session.next = ( req.path.substring(0, req.path.indexOf('#')) || req.path )+'#'; + // User account res.locals.user = req.user; - // console.log(`User set as ${res.locals.user}. `); - + // Flash messages res.locals.successes = req.flash('success'); res.locals.dangers = req.flash('danger'); res.locals.warnings = req.flash('warning'); - // console.log(`Flash messages set as:\nSuccesses: ${res.locals.successes}\nWarnings: ${res.locals.warnings}\nDangers: ${res.locals.dangers}`); - + next(); } ); - + // Main routes app.use( '/', require('./config/routes/index.js') ); - + // Settings app.use( '/settings', require('./config/routes/settings.js') ); - + // Map app.use( ['/map','/trac'], require('./config/routes/map.js') ); - + // Site administration app.use( '/admin', require('./config/routes/admin.js') ); - + // Testing if (env.mode == 'development') { app.use( '/test', require('./config/routes/test.js' ) ); } - + } - + /* Errors */ { // Catch-all for 404s app.use( (req,res,next)=>{ @@ -156,21 +156,21 @@ const } ); } } - + /* Sockets */ { sockets.init(io); } - + } /* RUNTIME */ { - - console.log('🖥 Starting Tracman server...'); - + + console.log('🖥 Starting Tracman server...'); + // Listen http.listen( env.port, ()=>{ console.log(`🌐 Listening in ${env.mode} mode on port ${env.port}... `); - + // Check for clients for each user User.find( {}, (err,users)=>{ if (err) { console.log(`DB error finding all users: ${err.message}`); } @@ -178,9 +178,9 @@ const sockets.checkForUsers( io, user.id ); }); }); - + }); - + } module.exports = app; diff --git a/views/map.html b/views/map.html index ae4996c..cd0a95a 100644 --- a/views/map.html +++ b/views/map.html @@ -1,5 +1,5 @@ {% extends 'templates/base.html' %} -{% block title %}{{super()}} | {{mapuser.name}}{% endblock %} +{% block title %}{{super()}}{% if mapuser.name %} | {{mapuser.name}}{% endif %}{% endblock %} {% block head %} {{super()}} @@ -147,6 +147,7 @@ // Google maps API callback window.gmapsCb = function() { + // Create map if (disp!='1'||!settings.showStreetview) { map = new google.maps.Map( mapElem, { @@ -160,7 +161,7 @@ }); marker = new google.maps.Marker({ position: { lat:last.lat, lng:last.lon }, - title: {{ mapuser.name | dump | safe }}, + {% if mapuser.name.length %}title: {{mapuser.name|dump|safe}},{% endif %} map: map, draggable: false }); From 7b2a1e27dd379118f972f6e6539e3b35e8175dd0 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 14 Apr 2017 01:00:18 -0400 Subject: [PATCH 057/143] Added password reset logic --- config/auth.js | 94 ++++++++++++++++++++++++--------------------- views/password.html | 59 ++++++++++++---------------- 2 files changed, 75 insertions(+), 78 deletions(-) diff --git a/config/auth.js b/config/auth.js index 572d635..abfc662 100644 --- a/config/auth.js +++ b/config/auth.js @@ -40,7 +40,8 @@ module.exports = (app, passport) => { // Signup app.get('/signup', (req,res)=>{ res.redirect('/login#signup'); - }).post('/signup', (req,res,next)=>{ + }) + .post('/signup', (req,res,next)=>{ // Send token and alert user function sendToken(user){ @@ -148,50 +149,57 @@ module.exports = (app, passport) => { }); // Forgot password - // app.route('/login/forgot') - // .all( (req,res,next)=>{ - // if (req.isAuthenticated()){ res.redirect('/settings'); } - // else { next(); } - // }) - // .get( (req,res,next)=>{ - // res.render('forgot'); - // }) - // .post( (req,res,next)=>{ + app.route('/login/forgot') + .all( (req,res,next)=>{ + if (req.isAuthenticated()){ loginCallback(); } + else { next(); } + }) + .get( (req,res,next)=>{ + res.render('forgot'); + }) + .post( (req,res,next)=>{ + + //TODO: Validate and sanitize email + // req.assert('email', 'Please enter a valid email address.').isEmail(); + // req.sanitize('email').normalizeEmail({ remove_dots: false }); + + User.findOne( {'email':req.body.email}, (err,user)=>{ + if (err){ mw.throwErr(err); } + + // No user with that email + if (!user) { + // Don't let on that no such user exists, to prevent dictionary attacks + req.flash('success', `If an account exists with the email ${req.body.email}, an email has been sent there with a password reset link. `); + res.redirect('/login'); + } + + // User with that email exists + else { + + // Create reset token + user.createToken( (err,token)=>{ + if (err){ next(err); } + + // Email reset link + mail.send({ + from: mail.from, + to: mail.to(user), + subject: 'Reset your Tracman password', + text: mail.text(`Hi, \n\nDid you request to reset your Tracman password? If so, follow this link to do so:\n${env.url}/settings/password/${token}\n\nIf you didn't initiate this request, just ignore this email. `), + html: mail.html(`

Hi,

Did you request to reset your Tracman password? If so, follow this link to do so:
${env.url}/settings/password/${token}

If you didn't initiate this request, just ignore this email.

`) + }).then(()=>{ + req.flash('success', `If an account exists with the email ${req.body.email}, an email has been sent there with a password reset link. `); + res.redirect('/login'); + }).catch((err)=>{ + mw.throwErr(err); + }); - // //TODO: Validate and sanitize email - // // req.assert('email', 'Please enter a valid email address.').isEmail(); - // // req.sanitize('email').normalizeEmail({ remove_dots: false }); + }); + + } + }); - // User.findOne( {'email':req.body.email}, (err,user)=>{ - // if (err){ next(err); } - // else if (!user) { - // req.flash('danger', `No user has ${req.body.email} set as their email address. `); - // res.redirect('/login/forgot'); - // } else { - - // // Set reset token to user - // user.createToken( (err,token)=>{ - // if (err){ next(err); } - - // // Email reset link - // mail({ - // from: '"Tracman" ', - // to: `"${user.name}"" <${user.email}>`, - // subject: 'Reset your Tracman password', - // text: `Hi, \n\nDid you request to reset your Tracman password? If so, follow this link to do so:\n${env.url}/settings/password/${token}\n\nIf you didn't initiate this request, just ignore this email. `, - // html: `

Hi,

Did you request to reset your Tracman password? If so, follow this link to do so:
${env.url}/settings/password/${token}

If you didn't initiate this request, just ignore this email.

` - // }).then(()=>{ - // req.flash('success', `An email has been sent to ${req.body.email}. Check your email for instructions to reset your password. `); - // res.redirect('/'); - // }).catch((err)=>{ - // next(err); - // }); - - // }); - // } - // }); - - // }); + }); // Social app.get('/login/:service', (req,res,next)=>{ diff --git a/views/password.html b/views/password.html index 110b5cd..de7cf2e 100644 --- a/views/password.html +++ b/views/password.html @@ -7,41 +7,30 @@ {% endblock %} {% block main %} - -
- -

Set Password

- -
- - -

Your password must be at least 8 characters long. You can use any letter, number, symbol, emoji, or spaces. Your password will be stored as a secure hash on the server.

- -
- - -
- -
- - cancel -
- -
- -
+
-{% endblock %} - -{% block javascript %} -{{super()}} - + + +
{% endblock %} \ No newline at end of file From 4ba3ae37d66df4350f0b3688cc27f5e4c24e1b05 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 14 Apr 2017 01:09:05 -0400 Subject: [PATCH 058/143] Added forgot password page, fixed error page --- views/error.html | 4 ++-- views/forgot.html | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 views/forgot.html diff --git a/views/error.html b/views/error.html index a759643..d9133d1 100644 --- a/views/error.html +++ b/views/error.html @@ -5,8 +5,8 @@
{% if code %}

{{code}}

{% endif %} {% if message %}

{{message}}

{% endif %} - {% if stack %}

{{stack}}

{% else %} -

I would really appreciate it if you would report this error.

{% endif %} + {% if stack %}

{{stack}}

{% endif %} + {% if not stack %}

I would really appreciate it if you would report this error.

{% endif %} {% if code %}{% endif %}
{% endblock %} \ No newline at end of file diff --git a/views/forgot.html b/views/forgot.html new file mode 100644 index 0000000..397b790 --- /dev/null +++ b/views/forgot.html @@ -0,0 +1,26 @@ +{% extends 'templates/base.html' %} + +{% block head %} +{{super()}} + +{% endblock %} + +{% block main %} +
+ +

Reset password

+

Enter your email below to recieve a link to reset your password.

+ +
+ +
+ + +
+ + + +
+ +
+{% endblock %} \ No newline at end of file From 9e90c9744880159a7fc129e02867981606e9f2ea Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 14 Apr 2017 01:19:04 -0400 Subject: [PATCH 059/143] Bumped version --- README.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8ea9cae..cfe5605 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Tracman -###### v 0.5.1 +###### v 0.6.0 node.js application to display a map with user's location. diff --git a/package.json b/package.json index a00ec09..41e4825 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tracman", - "version": "0.5.1", + "version": "0.6.0", "description": "Tracks user's GPS location", "main": "server.js", "dependencies": { From dc75a337fdeb1f04ab6173ee5b15e2f6446d175f Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 14 Apr 2017 20:18:20 -0400 Subject: [PATCH 060/143] Moved client-side js for map to seperate files, fixed css --- static/css/base.css | 2 +- static/css/map.css | 9 +- static/js/map-controls.js | 84 ++++++++ static/js/map.js | 258 +++++++++++++++++++++++ views/map.html | 424 +++++--------------------------------- 5 files changed, 400 insertions(+), 377 deletions(-) create mode 100644 static/js/map-controls.js create mode 100644 static/js/map.js diff --git a/static/css/base.css b/static/css/base.css index d378a54..4f4b960 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -107,7 +107,7 @@ pre { .right { float: right; } main { - top: 60px; + top: 59px; position: absolute; left: 0px; right: 0px; diff --git a/static/css/map.css b/static/css/map.css index f1886c1..ad7fcf7 100644 --- a/static/css/map.css +++ b/static/css/map.css @@ -2,11 +2,10 @@ body { color: #fff; width: 100%; height: 100%; - background: #000;} -.wrap { - position: absolute; - bottom: 0px; - width: 100%; + background: #000; +} +main { + overflow: hidden; } /* Alerts */ diff --git a/static/js/map-controls.js b/static/js/map-controls.js new file mode 100644 index 0000000..e976b12 --- /dev/null +++ b/static/js/map-controls.js @@ -0,0 +1,84 @@ +'use strict'; +/* global navigator $ socket userid token mapuser toggleMaps */ + +var wpid, newloc; + +// Set location +function setLocation() { + if (!userid==mapuser._id) {alert('You are not logged in! ');} + else { + if (!navigator.geolocation) {alert('Geolocation not enabled. ');} + else { + navigator.geolocation.getCurrentPosition(function(pos){ + var newloc = { + tok: token, + usr: userid, + lat: pos.coords.latitude, + lon: pos.coords.longitude, + spd: (pos.coords.speed||0) + }; + socket.emit('set', newloc); + toggleMaps(newloc); + console.log('⚜ Set location:',newloc.lat+", "+newloc.lon); + }, function(err) { + alert("Unable to set location."); + console.error('⛔️',err.message); + }, { enableHighAccuracy:true }); + } + } +} + +// Track location +function trackLocation() { + if (!userid==mapuser._id) { alert('You are not logged in! '); } + else { + // Stop tracking + if (wpid) { + $('#controls > .track').html(' Track').tooltip('hide'); + navigator.geolocation.clearWatch(wpid); + wpid = undefined; + // Start tracking + } else { + $('#controls > .track').html(' Stop').tooltip('show'); + if (!navigator.geolocation) { alert('Unable to track location. '); } + else { + wpid = navigator.geolocation.watchPosition(function(pos) { + newloc = { + tok: token, + usr: '{{user.id}}', + lat: pos.coords.latitude, + lon: pos.coords.longitude, + spd: (pos.coords.speed||0) + }; + socket.emit('set',newloc); + toggleMaps(newloc); + console.log('⚜ Set location:',newloc.lat+", "+newloc.lon); + }, function(err){ + alert("Unable to track location."); + console.error(err.message); + }, { enableHighAccuracy:true }); + } + } + } +} + +// Clear location +function clearLocation() { + if (!userid==mapuser._id) { alert('You are not logged in! '); } + else { + // Stop tracking + if (wpid) { + $('#controls > .track').html(' Track').tooltip('hide'); + navigator.geolocation.clearWatch(wpid); + wpid = undefined; + } + newloc = { + tok: token, + usr: userid, + lat:0, lon:0, spd:0 + }; + socket.emit('set',newloc); + toggleMaps(newloc); + console.log('⚜ Cleared location'); + } +} diff --git a/static/js/map.js b/static/js/map.js new file mode 100644 index 0000000..a8096bc --- /dev/null +++ b/static/js/map.js @@ -0,0 +1,258 @@ +'use strict'; +/* global $ io google mapuser userid disp noHeader */ + + +// Variables +var map, pano, marker, elevator, + mapElem = document.getElementById('map'), + panoElem = document.getElementById('pano'); +const socket = io('//'+window.location.hostname); + +function waitForElements(vars,cb){ + if (vars.every(function(v){ return v!==undefined; })){ + cb(); + } else { + setTimeout(waitForElements, 100); + } +} + +function onConnect(socket,userid,mapuserid) { + + // Can get location + socket.emit('can-get', mapuserid ); + console.log("🚹 Receiving updates for",mapuserid); + + // Can set location too + if (mapuserid==userid) { + socket.emit('can-set', userid ); + console.log("🚹 Sending updates for",userid); + } + +} + +// socket.io stuff +socket + .on('connect', function(){ + console.log("⬆️ Connected!"); + waitForElements([mapuser,userid], function() { + onConnect(socket,userid,mapuser._id); + }); + }) + .on('disconnect', function(){ + console.log("⬇️ Disconnected!"); + }) + .on('error', function (err){ + console.error('⛔️',err.message); + }); + +// Parse location +function parseLoc(loc) { + loc.spd = (mapuser.settings.units=='standard')?parseFloat(loc.spd)*2.23694:parseFloat(loc.spd); + loc.dir = parseFloat(loc.dir); + loc.lat = parseFloat(loc.lat); + loc.lon = parseFloat(loc.lon); + loc.time = new Date(loc.time).toLocaleString(); + loc.glatlng = new google.maps.LatLng(loc.lat, loc.lon); + return loc; +} + +// Show/hide map if location is set/unset +function toggleMaps(loc) { + if (loc.lat===0&&loc.lon===0) { + $('#map').hide(); + $('#pano').hide(); + $('#notset').show(); + } else { + $('#map').show(); + $('#pano').show(); + $('#notset').hide(); + } +} +// Toggle maps on page load +$(function() { + toggleMaps(mapuser.last); +}); + +// Google maps API callback +window.gmapsCb = function() { + + // Create map + if (disp!='1'||!mapuser.settings.showStreetview) { + map = new google.maps.Map( mapElem, { + center: new google.maps.LatLng( mapuser.last.lat, mapuser.last.lon ), + panControl: false, + draggable: false, + zoom: mapuser.settings.defaultZoom, + streetViewControl: false, + zoomControlOptions: {position: google.maps.ControlPosition.LEFT_TOP}, + mapTypeId: (mapuser.settings.defaultMap=='road')?google.maps.MapTypeId.ROADMAP:google.maps.MapTypeId.HYBRID + }); + marker = new google.maps.Marker({ + position: { lat:mapuser.last.lat, lng:mapuser.last.lon }, + title: mapuser.name, + map: map, + draggable: false + }); + map.addListener('zoom_changed',function(){ + map.setCenter(marker.getPosition()); + }); + + // Create iFrame logo + if (noHeader.length) { + var logoDiv = document.createElement('div'); + logoDiv.className = 'map-logo'; + logoDiv.innerHTML = ''+ + '[]'+ + 'Tracman'; + map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(logoDiv); + } + + // Create update time block + var timeDiv = document.createElement('div'); + timeDiv.className = 'tim'; + if (mapuser.last.time) { + timeDiv.innerHTML = 'location updated '+new Date(mapuser.last.time).toLocaleString(); + } + map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(timeDiv); + + // Create speed block + if (mapuser.settings.showSpeed) { + const speedSign = document.createElement('div'), + speedLabel = document.createElement('div'), + speedText = document.createElement('div'), + speedUnit = document.createElement('div'); + speedLabel.className = 'spd-label'; + speedLabel.innerHTML = 'SPEED'; + speedText.className = 'spd'; + speedText.innerHTML = (mapuser.settings.units=='standard')?(parseFloat(mapuser.last.spd)*2.23694).toFixed():mapuser.last.spd.toFixed(); + speedUnit.className = 'spd-unit'; + speedUnit.innerHTML = (mapuser.settings.units=='standard')?'m.p.h.':'k.p.h.'; + speedSign.className = 'spd-sign'; + speedSign.appendChild(speedLabel); + speedSign.appendChild(speedText); + speedSign.appendChild(speedUnit); + map.controls[google.maps.ControlPosition.TOP_RIGHT].push(speedSign); + } + + // Create altitude block + if (mapuser.settings.showAlt) { + var elevator = new google.maps.ElevationService; + const altitudeSign = document.createElement('div'), + altitudeLabel = document.createElement('div'), + altitudeText = document.createElement('div'), + altitudeUnit = document.createElement('div'); + altitudeLabel.className = 'alt-label'; + altitudeText.className = 'alt'; + altitudeUnit.className = 'alt-unit'; + altitudeSign.className = 'alt-sign'; + altitudeText.innerHTML = ''; + altitudeLabel.innerHTML = 'ALTITUDE'; + getAltitude(new google.maps.LatLng(mapuser.last.lat,mapuser.last.lon), elevator, function(alt) { + if (alt) { altitudeText.innerHTML = (mapuser.settings.units=='standard')?(alt*3.28084).toFixed():alt.toFixed(); } + }); + altitudeUnit.innerHTML = (mapuser.settings.units=='standard')?'feet':'meters'; + altitudeSign.appendChild(altitudeLabel); + altitudeSign.appendChild(altitudeText); + altitudeSign.appendChild(altitudeUnit); + map.controls[google.maps.ControlPosition.TOP_RIGHT].push(altitudeSign); + } + } + + // Create streetview + updateStreetView(parseLoc(mapuser.last),10); +}; + +// Get location +socket.on('get', function(loc) { + + loc = parseLoc(loc); + + // Update street view + if (disp!='1' || !mapuser.settings.showStreetview) { + $('.tim').text('location updated '+loc.time); + if (mapuser.settings.showSpeed) { $('.spd').text(loc.spd.toFixed()); } + 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()); } + }); + } + toggleMaps(loc); + map.setCenter({lat:loc.lat,lng:loc.lon}); + marker.setPosition({lat:loc.lat,lng:loc.lon}); + } + updateStreetView(loc,10); + + console.log("🌐️ Got location:",loc.lat+", "+loc.lon); +}); + +// Check altitude +function getAltitude(loc,elev,cb){ + elev = elev || new google.maps.ElevationService; + elev.getElevationForLocations({ + 'locations': [loc] + }, function(results, status) { + if (status === google.maps.ElevationStatus.OK && results[0]) { + cb(results[0].elevation); + } + }); +} + +// Get street view imagery +function getStreetViewData(loc,rad,cb) { + if (!sv) { var sv=new google.maps.StreetViewService(); } + sv.getPanorama({location:{lat:loc.lat,lng:loc.lon},radius:rad},function(data,status){ + if (status===google.maps.StreetViewStatus.ZERO_RESULTS){ + getStreetViewData(loc,rad*2,cb); + } else if (status!==google.maps.StreetViewStatus.OK){ console.error(new Error('⛔️ Street view not available:',status).message); } + else { cb(data); } + }); +} + +// Update streetview +function updateStreetView(loc) { + + // Moving + if (loc.spd>1) { + if (mapuser.settings.showStreetview && disp!='0') { + var imgElem = document.getElementById('panoImg'); + getStreetViewData(loc, 50, function(data){ + if (!imgElem) { + // Create image + pano = undefined; + $('#pano').empty(); + $('#pano').append($('',{ + alt: 'Street view image', + src: 'https://maps.googleapis.com/maps/api/streetview?size=800x800&location='+loc.lat+','+loc.lon+'&fov=90&heading='+loc.dir+'&key={{api}}', + id: 'panoImg' + })); + } + // Set image + $('#panoImg').attr('src','https://maps.googleapis.com/maps/api/streetview?size='+$('#pano').width()+'x'+$('#pano').height()+'&location='+data.location.latLng.lat()+','+data.location.latLng.lng()+'&fov=90&heading='+loc.dir+'&key={{api}}'); + }); + } + } + + // Not moving and pano not set + else if (pano==null) { + getStreetViewData(loc, 50, function(data){ + // Create panorama + $('#pano').empty(); + const panoOptions = { + panControl: false, + zoomControl: false, + addressControl: false, + linksControl: false, + motionTracking: false, + motionTrackingControl: false + }; + pano = new google.maps.StreetViewPanorama(panoElem, panoOptions); + // Set panorama + pano.setPano(data.location.pano); + pano.setPov({ + pitch: 0, + heading: Math.atan((loc.lon-data.location.latLng.lng())/(loc.lat-data.location.latLng.lat()))*(180/Math.PI) + }); + }); + } + +} diff --git a/views/map.html b/views/map.html index cd0a95a..5e687fd 100644 --- a/views/map.html +++ b/views/map.html @@ -4,412 +4,94 @@ {% block head %} {{super()}} - {% endblock %} {% block main %} -
+
+
-
-
- -
- {% if user.id == mapuser.id %} - Your location is unset. You can click 'set' below to set it to your current position. - {% else %} - This user has no location set. - {% endif %} -
- +
{% if user.id == mapuser.id %} -
- {% if mapuser.settings.showStreetview and disp!='0' %} - - {% endif %} - - - - - -
+ Your location is unset. You can click 'set' below to set it to your current position. + {% else %} + This user has no location set. {% endif %} -
+ {% if user.id == mapuser.id %} +
+ {% if mapuser.settings.showStreetview and disp!='0' %} + + {% endif %} + + + + + +
+ {% endif %} + +{% endblock %} + +{% block javascript %} +{{super()}} + - + {% if user.id == mapuser.id %}{% endif %} + -{% endblock %} +{% endblock %} \ No newline at end of file From 81a9bba50747eb60ddb8fa95eb4b4cea04704f19 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 14 Apr 2017 20:19:06 -0400 Subject: [PATCH 061/143] #50 Fixed server-side map logic --- config/routes/map.js | 51 ++++++++++---------------------------------- 1 file changed, 11 insertions(+), 40 deletions(-) diff --git a/config/routes/map.js b/config/routes/map.js index 730576c..7f99b70 100644 --- a/config/routes/map.js +++ b/config/routes/map.js @@ -13,53 +13,24 @@ router.get('/', mw.ensureAuth, (req,res)=>{ // Show map router.get('/:slug?', (req,res,next)=>{ - var mapuser='', user='', cbc=0; - - // Confirm sucessful queries - function checkQuery(err,found) { - if (err){ mw.throwErr(err,req); } - if (found){ return found; } - } - - // Call renderMap() on completion - function checkCBC() { - cbc+=1; - if (cbc>1){ renderMap(); } - } - - // QUERIES - // Get logged in user -> user - if (req.isAuthenticated()) { - User.findById(req.user, function(err, found) { - user = checkQuery(err,found); - checkCBC(); - }); - } else { checkCBC(); } - // Get tracked user -> mapuser - if (req.params.slug) { - User.findOne({slug:req.params.slug}, function(err, found) { - mapuser = checkQuery(err,found); - checkCBC(); - }); - } else { checkCBC(); } - - // Show map - function renderMap() { - // GET /map shows logged-in user's map - if (!mapuser && !user) { - res.redirect('/'); + + User.findOne({slug:req.params.slug}) + .then( (mapuser)=>{ + if (mapuser===undefined){ + res.sendStatus(404); } else { - if (user && !mapuser) { mapuser = user; } res.render('map', { mapuser: mapuser, mapApi: env.googleMapsAPI, - user: user, + user: req.user, noFooter: '1', - noHeader: (req.query.noheader)?req.query.noheader.match(/\d/)[0]:'', - disp: (req.query.disp)?req.query.disp.match(/\d/)[0]:'' // 0=map, 1=streetview, 2=both + noHeader: (req.query.noheader)?req.query.noheader.match(/\d/)[0]:0, + disp: (req.query.disp)?req.query.disp.match(/\d/)[0]:2 // 0=map, 1=streetview, 2=both }); } - } + }).catch( (err)=>{ + mw.throwErr(err,req); + }); }); From fe080486d5f2df3746086d83937d5c187e37632e Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 14 Apr 2017 21:29:50 -0400 Subject: [PATCH 062/143] Cleaned up some routing --- config/auth.js | 197 +++++++++++++++++++++++++------------------------ 1 file changed, 99 insertions(+), 98 deletions(-) diff --git a/config/auth.js b/config/auth.js index abfc662..c376fca 100644 --- a/config/auth.js +++ b/config/auth.js @@ -38,125 +38,126 @@ module.exports = (app, passport) => { }); // Signup - app.get('/signup', (req,res)=>{ - res.redirect('/login#signup'); - }) - .post('/signup', (req,res,next)=>{ - - // Send token and alert user - function sendToken(user){ + app.route('/signup') + .get( (req,res)=>{ + res.redirect('/login#signup'); + }) + .post( (req,res,next)=>{ - // Create a password token - user.createToken((err,token)=>{ + // Send token and alert user + function sendToken(user){ + + // Create a password token + user.createToken((err,token)=>{ + if (err){ mw.throwErr(err,req); } + + // Email the instructions to continue + mail.send({ + from: mail.from, + to: `<${user.email}>`, + subject: 'Complete your Tracman registration', + text: mail.text(`Welcome to Tracman! \n\nTo complete your registration, follow this link and set your password:\n${env.url}/settings/password/${token}`), + html: mail.html(`

Welcome to Tracman!

To complete your registration, follow this link and set your password:
${env.url}/settings/password/${token}

`) + }).then(()=>{ + req.flash('success', `An email has been sent to ${user.email}. Check your inbox to complete your registration. `); + res.redirect('/login'); + }).catch((err)=>{ + mw.throwErr(err,req); + res.redirect('/login#signup'); + }); + }); + + } + + // Check if somebody already has that email + User.findOne({'email':req.body.email}, (err,user)=>{ if (err){ mw.throwErr(err,req); } - // Email the instructions to continue - mail.send({ - from: mail.from, - to: `<${user.email}>`, - subject: 'Complete your Tracman registration', - text: mail.text(`Welcome to Tracman! \n\nTo complete your registration, follow this link and set your password:\n${env.url}/settings/password/${token}`), - html: mail.html(`

Welcome to Tracman!

To complete your registration, follow this link and set your password:
${env.url}/settings/password/${token}

`) - }).catch((err)=>{ - mw.throwErr(err,req); - res.redirect('/login#signup'); - }).then(()=>{ - req.flash('success', `An email has been sent to ${user.email}. Check your inbox to complete your registration. `); - res.redirect('/login'); - }); - }); - - } - - // Check if somebody already has that email - User.findOne({'email':req.body.email}, (err,user)=>{ - if (err){ mw.throwErr(err,req); } - - // User already exists - if (user && user.auth.password) { - req.flash('warning','A user with that email already exists! If you forgot your password, you can reset it here.'); - res.redirect('/login#login'); - next(); - } - - // User exists but hasn't created a password yet - else if (user) { - // Send another token (or the same one if it hasn't expired) - sendToken(user); - } - - // Create user - else { + // User already exists + if (user && user.auth.password) { + req.flash('warning','A user with that email already exists! If you forgot your password, you can reset it here.'); + res.redirect('/login#login'); + next(); + } - user = new User(); - user.created = Date.now(); - user.email = req.body.email; - user.slug = slug(user.email.substring(0, user.email.indexOf('@'))); + // User exists but hasn't created a password yet + else if (user) { + // Send another token (or the same one if it hasn't expired) + sendToken(user); + } - // Generate unique slug - var generateSlug = new Promise((resolve,reject) => { - (function checkSlug(s,cb){ - - User.findOne({slug:s}) - .catch((err)=>{ - mw.throwErr(err,req); - }) - .then((existingUser)=>{ + // Create user + else { + + user = new User(); + user.created = Date.now(); + user.email = req.body.email; + user.slug = slug(user.email.substring(0, user.email.indexOf('@'))); + + // Generate unique slug + let slug = new Promise((resolve,reject) => { + (function checkSlug(s,cb){ - // Slug in use: generate a random one and retry - if (existingUser){ - s = ''; - while (s.length<6) { - s+='abcdefghijkmnpqrtuvwxy346789'.charAt(Math.floor(Math.random()*28)); + User.findOne({slug:s}) + .catch((err)=>{ + mw.throwErr(err,req); + }) + .then((existingUser)=>{ + + // Slug in use: generate a random one and retry + if (existingUser){ + crypto.randomBytes(6, (err,buf)=>{ + if (err) { mw.throwErr(err,req); } + s = buf.toString('hex'); + checkSlug(s,cb); + }); } - checkSlug(s,cb); - } - - // Unique slug: proceed - else { cb(s); } + + // Unique slug: proceed + else { cb(s); } + + }); + })(user.slug, (newSlug)=>{ + user.slug = newSlug; + resolve(); }); - - })(user.slug, (newSlug)=>{ - user.slug = newSlug; - resolve(); }); - }); - - // Generate sk32 - var generateSk32 = new Promise((resolve,reject) => { - crypto.randomBytes(32, (err,buf)=>{ - if (err) { mw.throwErr(err,req); } - user.sk32 = buf.toString('hex'); - resolve(); + + // Generate sk32 + let sk32 = new Promise((resolve,reject) => { + crypto.randomBytes(32, (err,buf)=>{ + if (err) { mw.throwErr(err,req); } + user.sk32 = buf.toString('hex'); + resolve(); + }); }); - }); - - // Save user and send the token by email - Promise.all([generateSlug, generateSk32]) - .catch(err => { - mw.throwErr(err,req); - }).then(() => { - user.save( (err)=>{ - if (err){ mw.throwErr(err,req); } + + // Save user and send the token by email + Promise.all([slug, sk32]) + .then( ()=> { + user.save(); + }).then( ()=>{ sendToken(user); + }).catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/login#signup'); }); - }); + + } - } - + }); }); - }); - + // Forgot password app.route('/login/forgot') .all( (req,res,next)=>{ if (req.isAuthenticated()){ loginCallback(); } else { next(); } - }) + } ) .get( (req,res,next)=>{ res.render('forgot'); - }) + } ) .post( (req,res,next)=>{ //TODO: Validate and sanitize email @@ -199,7 +200,7 @@ module.exports = (app, passport) => { } }); - }); + } ); // Social app.get('/login/:service', (req,res,next)=>{ From 3b0fad69bf5ad60b6e56b8d7351fc8fbe4b6abb7 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 14 Apr 2017 21:45:49 -0400 Subject: [PATCH 063/143] Fixed some auth logic --- config/auth.js | 71 ++++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/config/auth.js b/config/auth.js index c376fca..84d96db 100644 --- a/config/auth.js +++ b/config/auth.js @@ -4,7 +4,6 @@ const mw = require('./middleware.js'), mail = require('./mail.js'), User = require('./models.js').user, - slug = require('slug'), crypto = require('crypto'), env = require('./env.js'); @@ -160,46 +159,50 @@ module.exports = (app, passport) => { } ) .post( (req,res,next)=>{ - //TODO: Validate and sanitize email + //TODO: Validate email // req.assert('email', 'Please enter a valid email address.').isEmail(); // req.sanitize('email').normalizeEmail({ remove_dots: false }); - User.findOne( {'email':req.body.email}, (err,user)=>{ - if (err){ mw.throwErr(err); } - - // No user with that email - if (!user) { - // Don't let on that no such user exists, to prevent dictionary attacks - req.flash('success', `If an account exists with the email ${req.body.email}, an email has been sent there with a password reset link. `); - res.redirect('/login'); - } - - // User with that email exists - else { + User.findOne({'email':req.body.email}) + .then((user)=>{ - // Create reset token - user.createToken( (err,token)=>{ - if (err){ next(err); } + // No user with that email + if (!user) { + // Don't let on that no such user exists, to prevent dictionary attacks + req.flash('success', `If an account exists with the email ${req.body.email}, an email has been sent there with a password reset link. `); + res.redirect('/login'); + } + + // User with that email does exist + else { - // Email reset link - mail.send({ - from: mail.from, - to: mail.to(user), - subject: 'Reset your Tracman password', - text: mail.text(`Hi, \n\nDid you request to reset your Tracman password? If so, follow this link to do so:\n${env.url}/settings/password/${token}\n\nIf you didn't initiate this request, just ignore this email. `), - html: mail.html(`

Hi,

Did you request to reset your Tracman password? If so, follow this link to do so:
${env.url}/settings/password/${token}

If you didn't initiate this request, just ignore this email.

`) - }).then(()=>{ - req.flash('success', `If an account exists with the email ${req.body.email}, an email has been sent there with a password reset link. `); - res.redirect('/login'); - }).catch((err)=>{ - mw.throwErr(err); + // Create reset token + user.createToken( (err,token)=>{ + if (err){ next(err); } + + // Email reset link + mail.send({ + from: mail.from, + to: mail.to(user), + subject: 'Reset your Tracman password', + text: mail.text(`Hi, \n\nDid you request to reset your Tracman password? If so, follow this link to do so:\n${env.url}/settings/password/${token}\n\nIf you didn't initiate this request, just ignore this email. `), + html: mail.html(`

Hi,

Did you request to reset your Tracman password? If so, follow this link to do so:
${env.url}/settings/password/${token}

If you didn't initiate this request, just ignore this email.

`) + }).then(()=>{ + req.flash('success', `If an account exists with the email ${req.body.email}, an email has been sent there with a password reset link. `); + res.redirect('/login'); + }).catch((err)=>{ + mw.throwErr(err); + }); + }); - - }); + + } - } - }); - + }).catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/login/forgot'); + }); + } ); // Social From ae93050d159e391e521686a2707932da8558c6f8 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 14 Apr 2017 21:46:21 -0400 Subject: [PATCH 064/143] Added tests to be made --- test.js | 111 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 82 insertions(+), 29 deletions(-) diff --git a/test.js b/test.js index 66615a2..ac04079 100755 --- a/test.js +++ b/test.js @@ -5,7 +5,7 @@ const chai = require('chai'), chai.use(chaiHttp); -describe('Pages', function() { +describe('Public', function() { it('Displays homepage', function(done){ request(server).get('/') @@ -46,7 +46,7 @@ describe('Pages', function() { }); -describe('Auth', function() { +describe('User', function() { it('Creates an account', function(done){ request(server).post('/signup',{"email":"test@tracman.org"}) @@ -54,11 +54,7 @@ describe('Auth', function() { .end(function(err,res){ done(); }); }); - //TODO: it('Has the correct account info', function(done){ - - // }); - - //TODO: it('Logs out', function(done){ + //TODO: it('Creates a password', function(done){ // }); @@ -66,7 +62,85 @@ describe('Auth', function() { // }); + //TODO: it('Logs out', function(done){ + + // }); + + //TODO: it('Forgets password', function(done){ + + // }); + + //TODO: it('Changes forgotten password', function(done){ + + // }); + + //TODO: it('Logs back in', function(done){ + + // }); + + //TODO: it('Changes email address', function(done){ + + // }); + + //TODO: it('Changes password', function(done){ + + // }); + + //TODO: it('Changes settings', function(done){ + + // }); + + //TODO: it('Connects a Google account', function(done){ + + // }); + + //TODO: it('Connects a Facebook account', function(done){ + + // }); + + //TODO: it('Connects a Twitter account', function(done){ + + // }); + + //TODO: it('Logs in with Google', function(done){ + + // }); + + //TODO: it('Logs in with Facebook', function(done){ + + // }); + + //TODO: it('Logs in with Twitter', function(done){ + + // }); + + //TODO: it('Disconnects a Google account', function(done){ + + // }); + + //TODO: it('Disconnects a Facebook account', function(done){ + + // }); + + //TODO: it('Disconnects a Twitter account', function(done){ + + // }); + //TODO: it('Shows own map', function(done){ + // request(server).get('/map') + // .expect(200) + // .end(function(err,res){ done(); }); + // }); + + //TODO: it('Sets own location', function(done){ + + // }); + + //TODO: it('Tracks own location', function(done){ + + // }); + + //TODO: it('Clears own location', function(done){ // }); @@ -74,25 +148,4 @@ describe('Auth', function() { // }); -// }); - -// describe('Map controls', function() { - - //TODO: it('Sets location', function(done){ - - // }); - - //TODO: it('Clears location', function(done){ - - // }); - - //TODO: it('Starts tracking', function(done){ - - // }); - - //TODO: it('Stops tracking', function(done){ - - // }); - -// }); - +}); From ec2bc48fda43d7b619e19f704951c126f9d1aa15 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 14 Apr 2017 21:49:07 -0400 Subject: [PATCH 065/143] Added express-validator --- package.json | 1 + server.js | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 41e4825..80e1d99 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "cookie-parser": "^1.4.1", "cookie-session": "^2.0.0-alpha.1", "express": "^4.15.2", + "express-validator": "^3.1.3", "firebase": "^3.7.2", "kerberos": "0.0.17", "moment": "^2.12.0", diff --git a/server.js b/server.js index 64bd559..185f751 100755 --- a/server.js +++ b/server.js @@ -4,6 +4,7 @@ const express = require('express'), bodyParser = require('body-parser'), + expressValidator = require('express-validator'), cookieParser = require('cookie-parser'), cookieSession = require('cookie-session'), mongoose = require('mongoose'), @@ -22,10 +23,10 @@ const /* SETUP */ { /* Database */ { - + // Setup with native ES6 promises mongoose.Promise = global.Promise; - + // Connect to database mongoose.connect(env.mongoSetup, { server:{socketOptions:{ @@ -37,7 +38,7 @@ const }).then(()=>{ console.log(`💿 Mongoose connected to mongoDB`); }); - + } /* Templates */ { @@ -60,6 +61,7 @@ const app.use(bodyParser.urlencoded({ extended: true })); + app.use(expressValidator()); app.use(flash()); } From f51ec1308dc84125be90d882d7d1f0623e7937b1 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 14 Apr 2017 21:52:30 -0400 Subject: [PATCH 066/143] Added server-side validation for email addresses at /login/forgot --- config/auth.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/auth.js b/config/auth.js index 84d96db..df1fb8d 100644 --- a/config/auth.js +++ b/config/auth.js @@ -159,12 +159,12 @@ module.exports = (app, passport) => { } ) .post( (req,res,next)=>{ - //TODO: Validate email - // req.assert('email', 'Please enter a valid email address.').isEmail(); - // req.sanitize('email').normalizeEmail({ remove_dots: false }); + // Validate email + req.checkBody('email', 'Please enter a valid email address.').isEmail(); + req.sanitizeBody('email').normalizeEmail({remove_dots:false}); User.findOne({'email':req.body.email}) - .then((user)=>{ + .then( (user)=>{ // No user with that email if (!user) { From 3809daa99947557b89407ad7f32e6bee0c1fba4b Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 14 Apr 2017 21:55:51 -0400 Subject: [PATCH 067/143] Validated emails at /signup --- config/auth.js | 145 ++++++++++++++++++++++++++----------------------- 1 file changed, 77 insertions(+), 68 deletions(-) diff --git a/config/auth.js b/config/auth.js index df1fb8d..0ec3a6d 100644 --- a/config/auth.js +++ b/config/auth.js @@ -68,84 +68,93 @@ module.exports = (app, passport) => { } + // Validate email + req.checkBody('email', 'Please enter a valid email address.').isEmail(); + req.sanitizeBody('email').normalizeEmail({remove_dots:false}); + // Check if somebody already has that email - User.findOne({'email':req.body.email}, (err,user)=>{ - if (err){ mw.throwErr(err,req); } - - // User already exists - if (user && user.auth.password) { - req.flash('warning','A user with that email already exists! If you forgot your password, you can reset it here.'); - res.redirect('/login#login'); - next(); - } - - // User exists but hasn't created a password yet - else if (user) { - // Send another token (or the same one if it hasn't expired) - sendToken(user); - } - - // Create user - else { + User.findOne({'email':req.body.email}) + .then( (user)=>{ - user = new User(); - user.created = Date.now(); - user.email = req.body.email; - user.slug = slug(user.email.substring(0, user.email.indexOf('@'))); + // User already exists + if (user && user.auth.password) { + req.flash('warning','A user with that email already exists! If you forgot your password, you can reset it here.'); + res.redirect('/login#login'); + next(); + } - // Generate unique slug - let slug = new Promise((resolve,reject) => { - (function checkSlug(s,cb){ - - User.findOne({slug:s}) - .catch((err)=>{ - mw.throwErr(err,req); - }) - .then((existingUser)=>{ + // User exists but hasn't created a password yet + else if (user) { + // Send another token (or the same one if it hasn't expired) + sendToken(user); + } + + // Create user + else { + + user = new User(); + user.created = Date.now(); + user.email = req.body.email; + user.slug = slug(user.email.substring(0, user.email.indexOf('@'))); + + // Generate unique slug + let slug = new Promise((resolve,reject) => { + (function checkSlug(s,cb){ - // Slug in use: generate a random one and retry - if (existingUser){ - crypto.randomBytes(6, (err,buf)=>{ - if (err) { mw.throwErr(err,req); } - s = buf.toString('hex'); - checkSlug(s,cb); - }); - } - - // Unique slug: proceed - else { cb(s); } + User.findOne({slug:s}) + .catch((err)=>{ + mw.throwErr(err,req); + }) + .then((existingUser)=>{ + + // Slug in use: generate a random one and retry + if (existingUser){ + crypto.randomBytes(6, (err,buf)=>{ + if (err) { mw.throwErr(err,req); } + s = buf.toString('hex'); + checkSlug(s,cb); + }); + } + + // Unique slug: proceed + else { cb(s); } + + }); + })(user.slug, (newSlug)=>{ + user.slug = newSlug; + resolve(); }); - - })(user.slug, (newSlug)=>{ - user.slug = newSlug; - resolve(); }); - }); - - // Generate sk32 - let sk32 = new Promise((resolve,reject) => { - crypto.randomBytes(32, (err,buf)=>{ - if (err) { mw.throwErr(err,req); } - user.sk32 = buf.toString('hex'); - resolve(); + + // Generate sk32 + let sk32 = new Promise((resolve,reject) => { + crypto.randomBytes(32, (err,buf)=>{ + if (err) { mw.throwErr(err,req); } + user.sk32 = buf.toString('hex'); + resolve(); + }); }); - }); + + // Save user and send the token by email + Promise.all([slug, sk32]) + .then( ()=> { + user.save(); + }).then( ()=>{ + sendToken(user); + }).catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/login#signup'); + }); + + } - // Save user and send the token by email - Promise.all([slug, sk32]) - .then( ()=> { - user.save(); - }).then( ()=>{ - sendToken(user); - }).catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/login#signup'); - }); - - } + }) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/signup'); + }); - }); }); // Forgot password From de827a55431853cbf6d849ecbd70a16ffdbfdcda Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 14 Apr 2017 22:10:52 -0400 Subject: [PATCH 068/143] Cleaned up, made promises --- config/models.js | 2 + config/routes/settings.js | 161 ++++++++++++++++++++------------------ 2 files changed, 88 insertions(+), 75 deletions(-) diff --git a/config/models.js b/config/models.js index 76d8215..0dcd5f9 100644 --- a/config/models.js +++ b/config/models.js @@ -43,6 +43,8 @@ const userSchema = new mongoose.Schema({ /* User methods */ { + //TODO: Return promises instead of taking callbacks + // Generate hash for new password userSchema.methods.generateHash = function(password,next){ bcrypt.genSalt(8, (err,salt)=>{ diff --git a/config/routes/settings.js b/config/routes/settings.js index 9bd0b2b..f0b4dd4 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -16,55 +16,59 @@ router.route('/') } ) // Get settings form - .get( (req,res,next)=>{ - User.findById( req.user, (err,user)=>{ - if (err){ mw.throwErr(err,req); } - res.render('settings'); - } ); + .get( (req,res)=>{ + res.render('settings'); } ) // Set new settings .post( (req,res,next)=>{ - User.findByIdAndUpdate(req.user, {$set:{ - name: xss(req.body.name), - slug: slug(xss(req.body.slug)), - email: req.body.email, - settings: { - units: req.body.units, - defaultMap: req.body.map, - defaultZoom: req.body.zoom, - showSpeed: (req.body.showSpeed)?true:false, - showAlt: (req.body.showAlt)?true:false, - showStreetview: (req.body.showStreet)?true:false - } - }}, (err,user)=>{ - if (err) { + + //TODO: Validate everything! + + User.findByIdAndUpdate(req.user.id, {$set:{ + name: xss(req.body.name), + slug: slug(xss(req.body.slug)), + email: req.body.email, + settings: { + units: req.body.units, + defaultMap: req.body.map, + defaultZoom: req.body.zoom, + showSpeed: (req.body.showSpeed)?true:false, + showAlt: (req.body.showAlt)?true:false, + showStreetview: (req.body.showStreet)?true:false + } + }}) + .then( (user)=>{ + req.flash('success', 'Settings updated. '); + res.redirect('/settings'); + }) + .catch( (err)=>{ mw.throwErr(err,req); res.redirect('/settings'); - } - else { - req.flash('success', 'Settings updated. '); - res.redirect('/settings'); - } - }); + }); + } ) // Delete user account .delete( (req,res,next)=>{ - User.findByIdAndRemove( req.user, (err)=>{ - if (err) { - mw.throwErr(err,req); - res.redirect('/settings'); - } else { + + //TODO: Reenter password? + + User.findByIdAndRemove(req.user) + .then(()=>{ req.flash('success', 'Your account has been deleted. '); res.redirect('/'); - } - } ); + }) + .catch((err)=>{ + mw.throwErr(err,req); + res.redirect('/settings'); + }); + } ); // Set password -router.route('/password/') +router.route('/password') .all( mw.ensureAuth, (req,res,next)=>{ next(); } ) @@ -73,29 +77,31 @@ router.route('/password/') .get( (req,res,next)=>{ // Create token for password change - req.user.createToken( (err,token)=>{ - if (err){ next(err); } - - // 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.

`) - }).catch( err=>{ + req.user.createToken() + .then( (token)=>{ + + // 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.

`) + }).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'); + }).catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/login#login'); + }); + + }) + .catch( (err)=>{ mw.throwErr(err,req); - res.redirect('/login#login'); - }).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'); - + res.redirect('/password'); }); - } ); - } ); router.route('/password/:token') @@ -105,9 +111,6 @@ router.route('/password/:token') User .findOne({'auth.passToken': req.params.token}) .where('auth.tokenExpires').gt(Date.now()) - .catch((err)=>{ - mw.throwErr(err,req); - }) .then((user) => { if (!user) { req.flash('danger', 'Password reset token is invalid or has expired. '); @@ -116,6 +119,10 @@ router.route('/password/:token') res.locals.passwordUser = user; next(); } + }) + .catch((err)=>{ + mw.throwErr(err,req); + res.redirect('/password'); }); } ) @@ -125,34 +132,36 @@ router.route('/password/:token') } ) .post( (req,res,next)=>{ - + //TODO: Validate password - + // Delete token res.locals.passwordUser.auth.passToken = undefined; res.locals.passwordUser.auth.tokenExpires = undefined; // Create hash res.locals.passwordUser.generateHash( req.body.password, (err,hash)=>{ - if (err){ mw.throwErr(err,req); } + if (err){ + mw.throwErr(err,req); + res.redirect(`/password/${req.params.token}`); + } else { // Save new password to db res.locals.passwordUser.auth.password = hash; - res.locals.passwordUser.save( (err)=>{ - if (err){ - mw.throwErr(err,req); - res.redirect('/login#signup'); - } - else { + res.locals.passwordUser.save() + .then( ()=>{ req.flash('success', 'Password set. You can use it to log in now. '); res.redirect('/login#login'); - } - }); - + }) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/login#signup'); + }); + } } ); - + } ); @@ -170,13 +179,15 @@ router.route('/pro') // Join Tracman pro .post( (req,res)=>{ User.findByIdAndUpdate(req.user.id, - {$set:{ isPro:true }}, - (err,user)=>{ - if (err){ mw.throwErr(err,req); } - else { req.flash('success','You have been signed up for pro. '); } + {$set:{ isPro:true }}) + .then( (user)=>{ + req.flash('success','You have been signed up for pro. '); res.redirect('/map'); - } - ); + }) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/pro'); + }); } ); module.exports = router; \ No newline at end of file From fc80498f2d11b6cf5f35a11f2cc95a67cef4460d Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 15 Apr 2017 10:22:13 -0400 Subject: [PATCH 069/143] Added client-side password checking --- config/routes/settings.js | 67 ++++++++++++++++++++++----------------- config/routes/test.js | 12 +++++-- package.json | 1 + static/css/base.css | 20 +++++++----- static/css/form.css | 13 +++++--- views/password.html | 36 +++++++++++++++------ 6 files changed, 95 insertions(+), 54 deletions(-) diff --git a/config/routes/settings.js b/config/routes/settings.js index f0b4dd4..799a975 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -2,6 +2,7 @@ const slug = require('slug'), xss = require('xss'), + mellt = require('mellt'), mw = require('../middleware.js'), User = require('../models.js').user, mail = require('../mail.js'), @@ -130,37 +131,45 @@ router.route('/password/:token') .get( (req,res)=>{ res.render('password'); } ) - + + // Set new password .post( (req,res,next)=>{ - //TODO: Validate password - - // Delete token - res.locals.passwordUser.auth.passToken = undefined; - res.locals.passwordUser.auth.tokenExpires = undefined; - - // Create hash - res.locals.passwordUser.generateHash( req.body.password, (err,hash)=>{ - if (err){ - mw.throwErr(err,req); - res.redirect(`/password/${req.params.token}`); - } - else { - - // Save new password to db - res.locals.passwordUser.auth.password = hash; - res.locals.passwordUser.save() - .then( ()=>{ - req.flash('success', 'Password set. You can use it to log in now. '); - res.redirect('/login#login'); - }) - .catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/login#signup'); - }); - - } - } ); + // Validate password + let daysToCrack = mellt.CheckPassword(req.body.password); + if (daysToCrack<10) { + mw.throwErr(new Error(`That password could be cracked in ${daysToCrack} days! Come up with a more complex password that would take at least 10 days to crack. `)); + res.redirect(`/settings/password/${req.params.token}`); + } else { + + // Delete token + res.locals.passwordUser.auth.passToken = undefined; + res.locals.passwordUser.auth.tokenExpires = undefined; + + // Create hash + res.locals.passwordUser.generateHash( req.body.password, (err,hash)=>{ + if (err){ + mw.throwErr(err,req); + res.redirect(`/password/${req.params.token}`); + } + else { + + // Save new password to db + res.locals.passwordUser.auth.password = hash; + res.locals.passwordUser.save() + .then( ()=>{ + req.flash('success', 'Password set. You can use it to log in now. '); + res.redirect('/login#login'); + }) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/login#signup'); + }); + + } + } ); + + } } ); diff --git a/config/routes/test.js b/config/routes/test.js index 6b7fbb1..a25f0cf 100644 --- a/config/routes/test.js +++ b/config/routes/test.js @@ -13,17 +13,23 @@ router subject: 'Test email', text: mail.text("Looks like everything's working! "), html: mail.html("

Looks like everything's working!

") - }).then(()=>{ + }) + .then(()=>{ console.log("Test email should have sent..."); res.sendStatus(200); - }).catch((err)=>{ + }) + .catch((err)=>{ mw.throwErr(err,req); - next(); + res.sendStatus(500); }); }) .get('/password', (req,res)=>{ res.render('password'); + }) + .post('/password', (req,res)=>{ + //TODO: Server-side checks + res.sendStatus(200); }); module.exports = router; \ No newline at end of file diff --git a/package.json b/package.json index 80e1d99..59bcebc 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "express-validator": "^3.1.3", "firebase": "^3.7.2", "kerberos": "0.0.17", + "mellt": "^1.0.0", "moment": "^2.12.0", "mongodb": "^2.1.4", "mongoose": "^4.9.0", diff --git a/static/css/base.css b/static/css/base.css index 4f4b960..120dc41 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -87,6 +87,8 @@ pre { .hide { display: none !important; } .red, .red:hover { color: #fb6e3d !important; } .yellow, .yellow:hover { color: #fbc93d !important; } + .green, .green:hover { color: #8ae137 !important; } + .shadow { -moz-box-shadow: .18vw .18vw .36vw #000; -webkit-box-shadow: .18vw .18vw .36vw #000; @@ -134,13 +136,13 @@ section { font-weight:600; display: inline-block; padding: 15px 30px; - transition: 100ms; - cursor: pointer; background: rgba(255,255,255,0.1); color: #eee; - border: 1px solid #666; border-radius: .5vw; -} .btn:not(.disabled) { +} .btn:not(:disabled) { + border: 1px solid #666; + transition: 100ms; + cursor: pointer; -moz-box-shadow: inset .11vw .18vw .52vw rgba(255,255,255,.2), inset -.11vw -.18vw .52vw rgba(0,0,0,.4), @@ -153,10 +155,12 @@ section { inset .11vw .18vw .52vw rgba(255,255,255,.2), inset -.11vw -.18vw .52vw rgba(0,0,0,.4), .18vw .18vw .36vw #000; -} .btn:hover:not(.disabled) { +} .btn:disabled { + border: 1px solid #999; +} .btn:hover:not(:disabled) { text-decoration: none; background: rgba(255,255,255,0.2); -} .btn:active:not(.disabled) { +} .btn:active:not(:disabled) { -moz-box-shadow: inset .11vw .18vw .52vw rgba(0,0,0,.4), inset -.11vw -.18vw .52vw rgba(255,255,255,.2); @@ -166,10 +170,10 @@ section { box-shadow: inset .11vw .18vw .52vw rgba(0,0,0,.4), inset -.11vw -.18vw .52vw rgba(255,255,255,.2); -} .btn:focus:not(.disabled){ +} .btn:focus:not(:disabled){ border: 1px solid #fbc93d; } -.btn.main { +.btn.main:not(:disabled) { color: #fbc93d; } .btn .fa { diff --git a/static/css/form.css b/static/css/form.css index ab11e56..bfb318a 100644 --- a/static/css/form.css +++ b/static/css/form.css @@ -17,16 +17,21 @@ form label { /* Input formatting */ form input, form textarea, form select { - -moz-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); - -webkit-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); - box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); color: #eee; background-color: #202020; background-color: rgba(255,255,255,0.1); padding: 1% 1.5%; - border: 1px solid #666; border-radius: .3vw; } +form input:not(:disabled), form textarea:not(:disabled), form select:not(:disabled) { + border: 1px solid #666; + -moz-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); + -webkit-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); + box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); +} +form input:disabled, form textarea:disabled form select:disabled { + border: 1px solid #999; +} form input:not(.input-addon):not(.input-with-addon):not([type="radio"]):not([type="checkbox"]), form .input-with-addon-group { min-width: 50%; diff --git a/views/password.html b/views/password.html index de7cf2e..2e1e54a 100644 --- a/views/password.html +++ b/views/password.html @@ -13,24 +13,40 @@
- -

Your password must be at least 8 characters long. You can use any letter, number, symbol, emoji, or spaces. Your password will be stored as a secure hash on the server.

- -
- - + +

Your password must be at least 8 characters long. You can use any letter, number, symbol, emoji, or spaces. Your password will be stored as a salted hash on the server.

+ +
+ + + + + +
+ +

-
- - cancel +
+
+{% endblock %} + +{% block javascript %} +{{super()}} + + + {% endblock %} \ No newline at end of file From 95a64eb7e92256b328880c79d31b1517b60573d7 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 15 Apr 2017 10:22:28 -0400 Subject: [PATCH 070/143] Added client-side password checking --- static/js/common-passwords.js | 10002 ++++++++++++++++++++++++++++++++ static/js/mellt.js | 209 + static/js/password.js | 90 + 3 files changed, 10301 insertions(+) create mode 100644 static/js/common-passwords.js create mode 100644 static/js/mellt.js create mode 100644 static/js/password.js diff --git a/static/js/common-passwords.js b/static/js/common-passwords.js new file mode 100644 index 0000000..18a785d --- /dev/null +++ b/static/js/common-passwords.js @@ -0,0 +1,10002 @@ +Mellt.prototype.CommonPasswords = [ + 'password', + '123456', + '12345678', + '1234', + 'qwerty', + '12345', + 'dragon', + 'pussy', + 'baseball', + 'football', + 'letmein', + 'monkey', + '696969', + 'abc123', + 'mustang', + 'michael', + 'shadow', + 'master', + 'jennifer', + '111111', + '2000', + 'jordan', + 'superman', + 'harley', + '1234567', + 'fuckme', + 'hunter', + 'fuckyou', + 'trustno1', + 'ranger', + 'buster', + 'thomas', + 'tigger', + 'robert', + 'soccer', + 'fuck', + 'batman', + 'test', + 'pass', + 'killer', + 'hockey', + 'george', + 'charlie', + 'andrew', + 'michelle', + 'love', + 'sunshine', + 'jessica', + 'asshole', + '6969', + 'pepper', + 'daniel', + 'access', + '123456789', + '654321', + 'joshua', + 'maggie', + 'starwars', + 'silver', + 'william', + 'dallas', + 'yankees', + '123123', + 'ashley', + '666666', + 'hello', + 'amanda', + 'orange', + 'biteme', + 'freedom', + 'computer', + 'sexy', + 'thunder', + 'nicole', + 'ginger', + 'heather', + 'hammer', + 'summer', + 'corvette', + 'taylor', + 'fucker', + 'austin', + '1111', + 'merlin', + 'matthew', + '121212', + 'golfer', + 'cheese', + 'princess', + 'martin', + 'chelsea', + 'patrick', + 'richard', + 'diamond', + 'yellow', + 'bigdog', + 'secret', + 'asdfgh', + 'sparky', + 'cowboy', + 'camaro', + 'anthony', + 'matrix', + 'falcon', + 'iloveyou', + 'bailey', + 'guitar', + 'jackson', + 'purple', + 'scooter', + 'phoenix', + 'aaaaaa', + 'morgan', + 'tigers', + 'porsche', + 'mickey', + 'maverick', + 'cookie', + 'nascar', + 'peanut', + 'justin', + '131313', + 'money', + 'horny', + 'samantha', + 'panties', + 'steelers', + 'joseph', + 'snoopy', + 'boomer', + 'whatever', + 'iceman', + 'smokey', + 'gateway', + 'dakota', + 'cowboys', + 'eagles', + 'chicken', + 'dick', + 'black', + 'zxcvbn', + 'please', + 'andrea', + 'ferrari', + 'knight', + 'hardcore', + 'melissa', + 'compaq', + 'coffee', + 'booboo', + 'bitch', + 'johnny', + 'bulldog', + 'xxxxxx', + 'welcome', + 'james', + 'player', + 'ncc1701', + 'wizard', + 'scooby', + 'charles', + 'junior', + 'internet', + 'bigdick', + 'mike', + 'brandy', + 'tennis', + 'blowjob', + 'banana', + 'monster', + 'spider', + 'lakers', + 'miller', + 'rabbit', + 'enter', + 'mercedes', + 'brandon', + 'steven', + 'fender', + 'john', + 'yamaha', + 'diablo', + 'chris', + 'boston', + 'tiger', + 'marine', + 'chicago', + 'rangers', + 'gandalf', + 'winter', + 'bigtits', + 'barney', + 'edward', + 'raiders', + 'porn', + 'badboy', + 'blowme', + 'spanky', + 'bigdaddy', + 'johnson', + 'chester', + 'london', + 'midnight', + 'blue', + 'fishing', + '000000', + 'hannah', + 'slayer', + '11111111', + 'rachel', + 'sexsex', + 'redsox', + 'thx1138', + 'asdf', + 'marlboro', + 'panther', + 'zxcvbnm', + 'arsenal', + 'oliver', + 'qazwsx', + 'mother', + 'victoria', + '7777777', + 'jasper', + 'angel', + 'david', + 'winner', + 'crystal', + 'golden', + 'butthead', + 'viking', + 'jack', + 'iwantu', + 'shannon', + 'murphy', + 'angels', + 'prince', + 'cameron', + 'girls', + 'madison', + 'wilson', + 'carlos', + 'hooters', + 'willie', + 'startrek', + 'captain', + 'maddog', + 'jasmine', + 'butter', + 'booger', + 'angela', + 'golf', + 'lauren', + 'rocket', + 'tiffany', + 'theman', + 'dennis', + 'liverpoo', + 'flower', + 'forever', + 'green', + 'jackie', + 'muffin', + 'turtle', + 'sophie', + 'danielle', + 'redskins', + 'toyota', + 'jason', + 'sierra', + 'winston', + 'debbie', + 'giants', + 'packers', + 'newyork', + 'jeremy', + 'casper', + 'bubba', + '112233', + 'sandra', + 'lovers', + 'mountain', + 'united', + 'cooper', + 'driver', + 'tucker', + 'helpme', + 'fucking', + 'pookie', + 'lucky', + 'maxwell', + '8675309', + 'bear', + 'suckit', + 'gators', + '5150', + '222222', + 'shithead', + 'fuckoff', + 'jaguar', + 'monica', + 'fred', + 'happy', + 'hotdog', + 'tits', + 'gemini', + 'lover', + 'xxxxxxxx', + '777777', + 'canada', + 'nathan', + 'victor', + 'florida', + '88888888', + 'nicholas', + 'rosebud', + 'metallic', + 'doctor', + 'trouble', + 'success', + 'stupid', + 'tomcat', + 'warrior', + 'peaches', + 'apples', + 'fish', + 'qwertyui', + 'magic', + 'buddy', + 'dolphins', + 'rainbow', + 'gunner', + '987654', + 'freddy', + 'alexis', + 'braves', + 'cock', + '2112', + '1212', + 'cocacola', + 'xavier', + 'dolphin', + 'testing', + 'bond007', + 'member', + 'calvin', + 'voodoo', + '7777', + 'samson', + 'alex', + 'apollo', + 'fire', + 'tester', + 'walter', + 'beavis', + 'voyager', + 'peter', + 'porno', + 'bonnie', + 'rush2112', + 'beer', + 'apple', + 'scorpio', + 'jonathan', + 'skippy', + 'sydney', + 'scott', + 'red123', + 'power', + 'gordon', + 'travis', + 'beaver', + 'star', + 'jackass', + 'flyers', + 'boobs', + '232323', + 'zzzzzz', + 'steve', + 'rebecca', + 'scorpion', + 'doggie', + 'legend', + 'ou812', + 'yankee', + 'blazer', + 'bill', + 'runner', + 'birdie', + 'bitches', + '555555', + 'parker', + 'topgun', + 'asdfasdf', + 'heaven', + 'viper', + 'animal', + '2222', + 'bigboy', + '4444', + 'arthur', + 'baby', + 'private', + 'godzilla', + 'donald', + 'williams', + 'lifehack', + 'phantom', + 'dave', + 'rock', + 'august', + 'sammy', + 'cool', + 'brian', + 'platinum', + 'jake', + 'bronco', + 'paul', + 'mark', + 'frank', + 'heka6w2', + 'copper', + 'billy', + 'cumshot', + 'garfield', + 'willow', + 'cunt', + 'little', + 'carter', + 'slut', + 'albert', + '69696969', + 'kitten', + 'super', + 'jordan23', + 'eagle1', + 'shelby', + 'america', + '11111', + 'jessie', + 'house', + 'free', + '123321', + 'chevy', + 'bullshit', + 'white', + 'broncos', + 'horney', + 'surfer', + 'nissan', + '999999', + 'saturn', + 'airborne', + 'elephant', + 'marvin', + 'shit', + 'action', + 'adidas', + 'qwert', + 'kevin', + '1313', + 'explorer', + 'walker', + 'police', + 'christin', + 'december', + 'benjamin', + 'wolf', + 'sweet', + 'therock', + 'king', + 'online', + 'dickhead', + 'brooklyn', + 'teresa', + 'cricket', + 'sharon', + 'dexter', + 'racing', + 'penis', + 'gregory', + '0000', + 'teens', + 'redwings', + 'dreams', + 'michigan', + 'hentai', + 'magnum', + '87654321', + 'nothing', + 'donkey', + 'trinity', + 'digital', + '333333', + 'stella', + 'cartman', + 'guinness', + '123abc', + 'speedy', + 'buffalo', + 'kitty', + 'pimpin', + 'eagle', + 'einstein', + 'kelly', + 'nelson', + 'nirvana', + 'vampire', + 'xxxx', + 'playboy', + 'louise', + 'pumpkin', + 'snowball', + 'test123', + 'girl', + 'sucker', + 'mexico', + 'beatles', + 'fantasy', + 'ford', + 'gibson', + 'celtic', + 'marcus', + 'cherry', + 'cassie', + '888888', + 'natasha', + 'sniper', + 'chance', + 'genesis', + 'hotrod', + 'reddog', + 'alexande', + 'college', + 'jester', + 'passw0rd', + 'bigcock', + 'smith', + 'lasvegas', + 'carmen', + 'slipknot', + '3333', + 'death', + 'kimberly', + '1q2w3e', + 'eclipse', + '1q2w3e4r', + 'stanley', + 'samuel', + 'drummer', + 'homer', + 'montana', + 'music', + 'aaaa', + 'spencer', + 'jimmy', + 'carolina', + 'colorado', + 'creative', + 'hello1', + 'rocky', + 'goober', + 'friday', + 'bollocks', + 'scotty', + 'abcdef', + 'bubbles', + 'hawaii', + 'fluffy', + 'mine', + 'stephen', + 'horses', + 'thumper', + '5555', + 'pussies', + 'darkness', + 'asdfghjk', + 'pamela', + 'boobies', + 'buddha', + 'vanessa', + 'sandman', + 'naughty', + 'douglas', + 'honda', + 'matt', + 'azerty', + '6666', + 'shorty', + 'money1', + 'beach', + 'loveme', + '4321', + 'simple', + 'poohbear', + '444444', + 'badass', + 'destiny', + 'sarah', + 'denise', + 'vikings', + 'lizard', + 'melanie', + 'assman', + 'sabrina', + 'nintendo', + 'water', + 'good', + 'howard', + 'time', + '123qwe', + 'november', + 'xxxxx', + 'october', + 'leather', + 'bastard', + 'young', + '101010', + 'extreme', + 'hard', + 'password1', + 'vincent', + 'pussy1', + 'lacrosse', + 'hotmail', + 'spooky', + 'amateur', + 'alaska', + 'badger', + 'paradise', + 'maryjane', + 'poop', + 'crazy', + 'mozart', + 'video', + 'russell', + 'vagina', + 'spitfire', + 'anderson', + 'norman', + 'eric', + 'cherokee', + 'cougar', + 'barbara', + 'long', + '420420', + 'family', + 'horse', + 'enigma', + 'allison', + 'raider', + 'brazil', + 'blonde', + 'jones', + '55555', + 'dude', + 'drowssap', + 'jeff', + 'school', + 'marshall', + 'lovely', + '1qaz2wsx', + 'jeffrey', + 'caroline', + 'franklin', + 'booty', + 'molly', + 'snickers', + 'leslie', + 'nipples', + 'courtney', + 'diesel', + 'rocks', + 'eminem', + 'westside', + 'suzuki', + 'daddy', + 'passion', + 'hummer', + 'ladies', + 'zachary', + 'frankie', + 'elvis', + 'reggie', + 'alpha', + 'suckme', + 'simpson', + 'patricia', + '147147', + 'pirate', + 'tommy', + 'semperfi', + 'jupiter', + 'redrum', + 'freeuser', + 'wanker', + 'stinky', + 'ducati', + 'paris', + 'natalie', + 'babygirl', + 'bishop', + 'windows', + 'spirit', + 'pantera', + 'monday', + 'patches', + 'brutus', + 'houston', + 'smooth', + 'penguin', + 'marley', + 'forest', + 'cream', + '212121', + 'flash', + 'maximus', + 'nipple', + 'bobby', + 'bradley', + 'vision', + 'pokemon', + 'champion', + 'fireman', + 'indian', + 'softball', + 'picard', + 'system', + 'clinton', + 'cobra', + 'enjoy', + 'lucky1', + 'claire', + 'claudia', + 'boogie', + 'timothy', + 'marines', + 'security', + 'dirty', + 'admin', + 'wildcats', + 'pimp', + 'dancer', + 'hardon', + 'veronica', + 'fucked', + 'abcd1234', + 'abcdefg', + 'ironman', + 'wolverin', + 'remember', + 'great', + 'freepass', + 'bigred', + 'squirt', + 'justice', + 'francis', + 'hobbes', + 'kermit', + 'pearljam', + 'mercury', + 'domino', + '9999', + 'denver', + 'brooke', + 'rascal', + 'hitman', + 'mistress', + 'simon', + 'tony', + 'bbbbbb', + 'friend', + 'peekaboo', + 'naked', + 'budlight', + 'electric', + 'sluts', + 'stargate', + 'saints', + 'bondage', + 'brittany', + 'bigman', + 'zombie', + 'swimming', + 'duke', + 'qwerty1', + 'babes', + 'scotland', + 'disney', + 'rooster', + 'brenda', + 'mookie', + 'swordfis', + 'candy', + 'duncan', + 'olivia', + 'hunting', + 'blink182', + 'alicia', + '8888', + 'samsung', + 'bubba1', + 'whore', + 'virginia', + 'general', + 'passport', + 'aaaaaaaa', + 'erotic', + 'liberty', + 'arizona', + 'jesus', + 'abcd', + 'newport', + 'skipper', + 'rolltide', + 'balls', + 'happy1', + 'galore', + 'christ', + 'weasel', + '242424', + 'wombat', + 'digger', + 'classic', + 'bulldogs', + 'poopoo', + 'accord', + 'popcorn', + 'turkey', + 'jenny', + 'amber', + 'bunny', + 'mouse', + '007007', + 'titanic', + 'liverpool', + 'dreamer', + 'everton', + 'friends', + 'chevelle', + 'carrie', + 'gabriel', + 'psycho', + 'nemesis', + 'burton', + 'pontiac', + 'connor', + 'eatme', + 'lickme', + 'roland', + 'cumming', + 'mitchell', + 'ireland', + 'lincoln', + 'arnold', + 'spiderma', + 'patriots', + 'goblue', + 'devils', + 'eugene', + 'empire', + 'asdfg', + 'cardinal', + 'brown', + 'shaggy', + 'froggy', + 'qwer', + 'kawasaki', + 'kodiak', + 'people', + 'phpbb', + 'light', + '54321', + 'kramer', + 'chopper', + 'hooker', + 'honey', + 'whynot', + 'lesbian', + 'lisa', + 'baxter', + 'adam', + 'snake', + 'teen', + 'ncc1701d', + 'qqqqqq', + 'airplane', + 'britney', + 'avalon', + 'sandy', + 'sugar', + 'sublime', + 'stewart', + 'wildcat', + 'raven', + 'scarface', + 'elizabet', + '123654', + 'trucks', + 'wolfpack', + 'pervert', + 'lawrence', + 'raymond', + 'redhead', + 'american', + 'alyssa', + 'bambam', + 'movie', + 'woody', + 'shaved', + 'snowman', + 'tiger1', + 'chicks', + 'raptor', + '1969', + 'stingray', + 'shooter', + 'france', + 'stars', + 'madmax', + 'kristen', + 'sports', + 'jerry', + '789456', + 'garcia', + 'simpsons', + 'lights', + 'ryan', + 'looking', + 'chronic', + 'alison', + 'hahaha', + 'packard', + 'hendrix', + 'perfect', + 'service', + 'spring', + 'srinivas', + 'spike', + 'katie', + '252525', + 'oscar', + 'brother', + 'bigmac', + 'suck', + 'single', + 'cannon', + 'georgia', + 'popeye', + 'tattoo', + 'texas', + 'party', + 'bullet', + 'taurus', + 'sailor', + 'wolves', + 'panthers', + 'japan', + 'strike', + 'flowers', + 'pussycat', + 'chris1', + 'loverboy', + 'berlin', + 'sticky', + 'marina', + 'tarheels', + 'fisher', + 'russia', + 'connie', + 'wolfgang', + 'testtest', + 'mature', + 'bass', + 'catch22', + 'juice', + 'michael1', + 'nigger', + '159753', + 'women', + 'alpha1', + 'trooper', + 'hawkeye', + 'head', + 'freaky', + 'dodgers', + 'pakistan', + 'machine', + 'pyramid', + 'vegeta', + 'katana', + 'moose', + 'tinker', + 'coyote', + 'infinity', + 'inside', + 'pepsi', + 'letmein1', + 'bang', + 'control', + 'hercules', + 'morris', + 'james1', + 'tickle', + 'outlaw', + 'browns', + 'billybob', + 'pickle', + 'test1', + 'michele', + 'antonio', + 'sucks', + 'pavilion', + 'changeme', + 'caesar', + 'prelude', + 'tanner', + 'adrian', + 'darkside', + 'bowling', + 'wutang', + 'sunset', + 'robbie', + 'alabama', + 'danger', + 'zeppelin', + 'juan', + 'rusty', + 'pppppp', + 'nick', + '2001', + 'ping', + 'darkstar', + 'madonna', + 'qwe123', + 'bigone', + 'casino', + 'cheryl', + 'charlie1', + 'mmmmmm', + 'integra', + 'wrangler', + 'apache', + 'tweety', + 'qwerty12', + 'bobafett', + 'simone', + 'none', + 'business', + 'sterling', + 'trevor', + 'transam', + 'dustin', + 'harvey', + 'england', + '2323', + 'seattle', + 'ssssss', + 'rose', + 'harry', + 'openup', + 'pandora', + 'pussys', + 'trucker', + 'wallace', + 'indigo', + 'storm', + 'malibu', + 'weed', + 'review', + 'babydoll', + 'doggy', + 'dilbert', + 'pegasus', + 'joker', + 'catfish', + 'flipper', + 'valerie', + 'herman', + 'fuckit', + 'detroit', + 'kenneth', + 'cheyenne', + 'bruins', + 'stacey', + 'smoke', + 'joey', + 'seven', + 'marino', + 'fetish', + 'xfiles', + 'wonder', + 'stinger', + 'pizza', + 'babe', + 'pretty', + 'stealth', + 'manutd', + 'gracie', + 'gundam', + 'cessna', + 'longhorn', + 'presario', + 'mnbvcxz', + 'wicked', + 'mustang1', + 'victory', + '21122112', + 'shelly', + 'awesome', + 'athena', + 'q1w2e3r4', + 'help', + 'holiday', + 'knicks', + 'street', + 'redneck', + '12341234', + 'casey', + 'gizmo', + 'scully', + 'dragon1', + 'devildog', + 'triumph', + 'eddie', + 'bluebird', + 'shotgun', + 'peewee', + 'ronnie', + 'angel1', + 'daisy', + 'special', + 'metallica', + 'madman', + 'country', + 'impala', + 'lennon', + 'roscoe', + 'omega', + 'access14', + 'enterpri', + 'miranda', + 'search', + 'smitty', + 'blizzard', + 'unicorn', + 'tight', + 'rick', + 'ronald', + 'asdf1234', + 'harrison', + 'trigger', + 'truck', + 'danny', + 'home', + 'winnie', + 'beauty', + 'thailand', + '1234567890', + 'cadillac', + 'castle', + 'tyler', + 'bobcat', + 'buddy1', + 'sunny', + 'stones', + 'asian', + 'freddie', + 'chuck', + 'butt', + 'loveyou', + 'norton', + 'hellfire', + 'hotsex', + 'indiana', + 'short', + 'panzer', + 'lonewolf', + 'trumpet', + 'colors', + 'blaster', + '12121212', + 'fireball', + 'logan', + 'precious', + 'aaron', + 'elaine', + 'jungle', + 'atlanta', + 'gold', + 'corona', + 'curtis', + 'nikki', + 'polaris', + 'timber', + 'theone', + 'baller', + 'chipper', + 'orlando', + 'island', + 'skyline', + 'dragons', + 'dogs', + 'benson', + 'licker', + 'goldie', + 'engineer', + 'kong', + 'pencil', + 'basketba', + 'open', + 'hornet', + 'world', + 'linda', + 'barbie', + 'chan', + 'farmer', + 'valentin', + 'wetpussy', + 'indians', + 'larry', + 'redman', + 'foobar', + 'travel', + 'morpheus', + 'bernie', + 'target', + '141414', + 'hotstuff', + 'photos', + 'laura', + 'savage', + 'holly', + 'rocky1', + 'fuck_inside', + 'dollar', + 'turbo', + 'design', + 'newton', + 'hottie', + 'moon', + '202020', + 'blondes', + '4128', + 'lestat', + 'avatar', + 'future', + 'goforit', + 'random', + 'abgrtyu', + 'jjjjjj', + 'cancer', + 'q1w2e3', + 'smiley', + 'goldberg', + 'express', + 'virgin', + 'zipper', + 'wrinkle1', + 'stone', + 'andy', + 'babylon', + 'dong', + 'powers', + 'consumer', + 'dudley', + 'monkey1', + 'serenity', + 'samurai', + '99999999', + 'bigboobs', + 'skeeter', + 'lindsay', + 'joejoe', + 'master1', + 'aaaaa', + 'chocolat', + 'christia', + 'birthday', + 'stephani', + 'tang', + '1234qwer', + 'alfred', + 'ball', + '98765432', + 'maria', + 'sexual', + 'maxima', + '77777777', + 'sampson', + 'buckeye', + 'highland', + 'kristin', + 'seminole', + 'reaper', + 'bassman', + 'nugget', + 'lucifer', + 'airforce', + 'nasty', + 'watson', + 'warlock', + '2121', + 'philip', + 'always', + 'dodge', + 'chrissy', + 'burger', + 'bird', + 'snatch', + 'missy', + 'pink', + 'gang', + 'maddie', + 'holmes', + 'huskers', + 'piglet', + 'photo', + 'joanne', + 'hamilton', + 'dodger', + 'paladin', + 'christy', + 'chubby', + 'buckeyes', + 'hamlet', + 'abcdefgh', + 'bigfoot', + 'sunday', + 'manson', + 'goldfish', + 'garden', + 'deftones', + 'icecream', + 'blondie', + 'spartan', + 'julie', + 'harold', + 'charger', + 'brandi', + 'stormy', + 'sherry', + 'pleasure', + 'juventus', + 'rodney', + 'galaxy', + 'holland', + 'escort', + 'zxcvb', + 'planet', + 'jerome', + 'wesley', + 'blues', + 'song', + 'peace', + 'david1', + 'ncc1701e', + '1966', + '51505150', + 'cavalier', + 'gambit', + 'karen', + 'sidney', + 'ripper', + 'oicu812', + 'jamie', + 'sister', + 'marie', + 'martha', + 'nylons', + 'aardvark', + 'nadine', + 'minnie', + 'whiskey', + 'bing', + 'plastic', + 'anal', + 'babylon5', + 'chang', + 'savannah', + 'loser', + 'racecar', + 'insane', + 'yankees1', + 'mememe', + 'hansolo', + 'chiefs', + 'fredfred', + 'freak', + 'frog', + 'salmon', + 'concrete', + 'yvonne', + 'zxcv', + 'shamrock', + 'atlantis', + 'warren', + 'wordpass', + 'julian', + 'mariah', + 'rommel', + '1010', + 'harris', + 'predator', + 'sylvia', + 'massive', + 'cats', + 'sammy1', + 'mister', + 'stud', + 'marathon', + 'rubber', + 'ding', + 'trunks', + 'desire', + 'montreal', + 'justme', + 'faster', + 'kathleen', + 'irish', + '1999', + 'bertha', + 'jessica1', + 'alpine', + 'sammie', + 'diamonds', + 'tristan', + '00000', + 'swinger', + 'shan', + 'stallion', + 'pitbull', + 'letmein2', + 'roberto', + 'ready', + 'april', + 'palmer', + 'ming', + 'shadow1', + 'audrey', + 'chong', + 'clitoris', + 'wang', + 'shirley', + 'fuckers', + 'jackoff', + 'bluesky', + 'sundance', + 'renegade', + 'hollywoo', + '151515', + 'bernard', + 'wolfman', + 'soldier', + 'picture', + 'pierre', + 'ling', + 'goddess', + 'manager', + 'nikita', + 'sweety', + 'titans', + 'hang', + 'fang', + 'ficken', + 'niners', + 'bottom', + 'bubble', + 'hello123', + 'ibanez', + 'webster', + 'sweetpea', + 'stocking', + '323232', + 'tornado', + 'lindsey', + 'content', + 'bruce', + 'buck', + 'aragorn', + 'griffin', + 'chen', + 'campbell', + 'trojan', + 'christop', + 'newman', + 'wayne', + 'tina', + 'rockstar', + 'father', + 'geronimo', + 'pascal', + 'crimson', + 'brooks', + 'hector', + 'penny', + 'anna', + 'google', + 'camera', + 'chandler', + 'fatcat', + 'lovelove', + 'cody', + 'cunts', + 'waters', + 'stimpy', + 'finger', + 'cindy', + 'wheels', + 'viper1', + 'latin', + 'robin', + 'greenday', + '987654321', + 'creampie', + 'brendan', + 'hiphop', + 'willy', + 'snapper', + 'funtime', + 'duck', + 'trombone', + 'adult', + 'cotton', + 'cookies', + 'kaiser', + 'mulder', + 'westham', + 'latino', + 'jeep', + 'ravens', + 'aurora', + 'drizzt', + 'madness', + 'energy', + 'kinky', + '314159', + 'sophia', + 'stefan', + 'slick', + 'rocker', + '55555555', + 'freeman', + 'french', + 'mongoose', + 'speed', + 'dddddd', + 'hong', + 'henry', + 'hungry', + 'yang', + 'catdog', + 'cheng', + 'ghost', + 'gogogo', + 'randy', + 'tottenha', + 'curious', + 'butterfl', + 'mission', + 'january', + 'singer', + 'sherman', + 'shark', + 'techno', + 'lancer', + 'lalala', + 'autumn', + 'chichi', + 'orion', + 'trixie', + 'clifford', + 'delta', + 'bobbob', + 'bomber', + 'holden', + 'kang', + 'kiss', + '1968', + 'spunky', + 'liquid', + 'mary', + 'beagle', + 'granny', + 'network', + 'bond', + 'kkkkkk', + 'millie', + '1973', + 'biggie', + 'beetle', + 'teacher', + 'susan', + 'toronto', + 'anakin', + 'genius', + 'dream', + 'cocks', + 'dang', + 'bush', + 'karate', + 'snakes', + 'bangkok', + 'callie', + 'fuckyou2', + 'pacific', + 'daytona', + 'kelsey', + 'infantry', + 'skywalke', + 'foster', + 'felix', + 'sailing', + 'raistlin', + 'vanhalen', + 'huang', + 'herbert', + 'jacob', + 'blackie', + 'tarzan', + 'strider', + 'sherlock', + 'lang', + 'gong', + 'sang', + 'dietcoke', + 'ultimate', + 'tree', + 'shai', + 'sprite', + 'ting', + 'artist', + 'chai', + 'chao', + 'devil', + 'python', + 'ninja', + 'misty', + 'ytrewq', + 'sweetie', + 'superfly', + '456789', + 'tian', + 'jing', + 'jesus1', + 'freedom1', + 'dian', + 'drpepper', + 'potter', + 'chou', + 'darren', + 'hobbit', + 'violet', + 'yong', + 'shen', + 'phillip', + 'maurice', + 'gloria', + 'nolimit', + 'mylove', + 'biscuit', + 'yahoo', + 'shasta', + 'sex4me', + 'smoker', + 'smile', + 'pebbles', + 'pics', + 'philly', + 'tong', + 'tintin', + 'lesbians', + 'marlin', + 'cactus', + 'frank1', + 'tttttt', + 'chun', + 'danni', + 'emerald', + 'showme', + 'pirates', + 'lian', + 'dogg', + 'colleen', + 'xiao', + 'xian', + 'tazman', + 'tanker', + 'patton', + 'toshiba', + 'richie', + 'alberto', + 'gotcha', + 'graham', + 'dillon', + 'rang', + 'emily', + 'keng', + 'jazz', + 'bigguy', + 'yuan', + 'woman', + 'tomtom', + 'marion', + 'greg', + 'chaos', + 'fossil', + 'flight', + 'racerx', + 'tuan', + 'creamy', + 'boss', + 'bobo', + 'musicman', + 'warcraft', + 'window', + 'blade', + 'shuang', + 'sheila', + 'shun', + 'lick', + 'jian', + 'microsoft', + 'rong', + 'allen', + 'feng', + 'getsome', + 'sally', + 'quality', + 'kennedy', + 'morrison', + '1977', + 'beng', + 'wwwwww', + 'yoyoyo', + 'zhang', + 'seng', + 'teddy', + 'joanna', + 'andreas', + 'harder', + 'luke', + 'qazxsw', + 'qian', + 'cong', + 'chuan', + 'deng', + 'nang', + 'boeing', + 'keeper', + 'western', + 'isabelle', + '1963', + 'subaru', + 'sheng', + 'thuglife', + 'teng', + 'jiong', + 'miao', + 'martina', + 'mang', + 'maniac', + 'pussie', + 'tracey', + 'a1b2c3', + 'clayton', + 'zhou', + 'zhuang', + 'xing', + 'stonecol', + 'snow', + 'spyder', + 'liang', + 'jiang', + 'memphis', + 'regina', + 'ceng', + 'magic1', + 'logitech', + 'chuang', + 'dark', + 'million', + 'blow', + 'sesame', + 'shao', + 'poison', + 'titty', + 'terry', + 'kuan', + 'kuai', + 'kyle', + 'mian', + 'guan', + 'hamster', + 'guai', + 'ferret', + 'florence', + 'geng', + 'duan', + 'pang', + 'maiden', + 'quan', + 'velvet', + 'nong', + 'neng', + 'nookie', + 'buttons', + 'bian', + 'bingo', + 'biao', + 'zhong', + 'zeng', + 'xiong', + 'zhun', + 'ying', + 'zong', + 'xuan', + 'zang', + '0.0.000', + 'suan', + 'shei', + 'shui', + 'sharks', + 'shang', + 'shua', + 'small', + 'peng', + 'pian', + 'piao', + 'liao', + 'meng', + 'miami', + 'reng', + 'guang', + 'cang', + 'change', + 'ruan', + 'diao', + 'luan', + 'lucas', + 'qing', + 'chui', + 'chuo', + 'cuan', + 'nuan', + 'ning', + 'heng', + 'huan', + 'kansas', + 'muscle', + 'monroe', + 'weng', + 'whitney', + '1passwor', + 'bluemoon', + 'zhui', + 'zhua', + 'xiang', + 'zheng', + 'zhen', + 'zhei', + 'zhao', + 'zhan', + 'yomama', + 'zhai', + 'zhuo', + 'zuan', + 'tarheel', + 'shou', + 'shuo', + 'tiao', + 'lady', + 'leonard', + 'leng', + 'kuang', + 'jiao', + '13579', + 'basket', + 'qiao', + 'qiong', + 'qiang', + 'chuai', + 'nian', + 'niao', + 'niang', + 'huai', + '22222222', + 'bianca', + 'zhuan', + 'zhuai', + 'shuan', + 'shuai', + 'stardust', + 'jumper', + 'margaret', + 'archie', + '66666666', + 'charlott', + 'forget', + 'qwertz', + 'bones', + 'history', + 'milton', + 'waterloo', + '2002', + 'stuff', + '11223344', + 'office', + 'oldman', + 'preston', + 'trains', + 'murray', + 'vertigo', + '246810', + 'black1', + 'swallow', + 'smiles', + 'standard', + 'alexandr', + 'parrot', + 'luther', + 'user', + 'nicolas', + '1976', + 'surfing', + 'pioneer', + 'pete', + 'masters', + 'apple1', + 'asdasd', + 'auburn', + 'hannibal', + 'frontier', + 'panama', + 'lucy', + 'buffy', + 'brianna', + 'welcome1', + 'vette', + 'blue22', + 'shemale', + '111222', + 'baggins', + 'groovy', + 'global', + 'turner', + '181818', + '1979', + 'blades', + 'spanking', + 'life', + 'byteme', + 'lobster', + 'collins', + 'dawg', + 'hilton', + 'japanese', + '1970', + '1964', + '2424', + 'polo', + 'markus', + 'coco', + 'deedee', + 'mikey', + '1972', + '171717', + '1701', + 'strip', + 'jersey', + 'green1', + 'capital', + 'sasha', + 'sadie', + 'putter', + 'vader', + 'seven7', + 'lester', + 'marcel', + 'banshee', + 'grendel', + 'gilbert', + 'dicks', + 'dead', + 'hidden', + 'iloveu', + '1980', + 'sound', + 'ledzep', + 'michel', + '147258', + 'female', + 'bugger', + 'buffett', + 'bryan', + 'hell', + 'kristina', + 'molson', + '2020', + 'wookie', + 'sprint', + 'thanks', + 'jericho', + '102030', + 'grace', + 'fuckin', + 'mandy', + 'ranger1', + 'trebor', + 'deepthroat', + 'bonehead', + 'molly1', + 'mirage', + 'models', + '1984', + '2468', + 'stuart', + 'showtime', + 'squirrel', + 'pentium', + 'mario', + 'anime', + 'gator', + 'powder', + 'twister', + 'connect', + 'neptune', + 'bruno', + 'butts', + 'engine', + 'eatshit', + 'mustangs', + 'woody1', + 'shogun', + 'septembe', + 'pooh', + 'jimbo', + 'roger', + 'annie', + 'bacon', + 'center', + 'russian', + 'sabine', + 'damien', + 'mollie', + 'voyeur', + '2525', + '363636', + 'leonardo', + 'camel', + 'chair', + 'germany', + 'giant', + 'qqqq', + 'nudist', + 'bone', + 'sleepy', + 'tequila', + 'megan', + 'fighter', + 'garrett', + 'dominic', + 'obiwan', + 'makaveli', + 'vacation', + 'walnut', + '1974', + 'ladybug', + 'cantona', + 'ccbill', + 'satan', + 'rusty1', + 'passwor1', + 'columbia', + 'napoleon', + 'dusty', + 'kissme', + 'motorola', + 'william1', + '1967', + 'zzzz', + 'skater', + 'smut', + 'play', + 'matthew1', + 'robinson', + 'valley', + 'coolio', + 'dagger', + 'boner', + 'bull', + 'horndog', + 'jason1', + 'blake', + 'penguins', + 'rescue', + 'griffey', + '8j4ye3uz', + 'californ', + 'champs', + 'qwertyuiop', + 'portland', + 'queen', + 'colt45', + 'boat', + 'xxxxxxx', + 'xanadu', + 'tacoma', + 'mason', + 'carpet', + 'gggggg', + 'safety', + 'palace', + 'italia', + 'stevie', + 'picturs', + 'picasso', + 'thongs', + 'tempest', + 'ricardo', + 'roberts', + 'asd123', + 'hairy', + 'foxtrot', + 'gary', + 'nimrod', + 'hotboy', + '343434', + '1111111', + 'asdfghjkl', + 'goose', + 'overlord', + 'blood', + 'wood', + 'stranger', + '454545', + 'shaolin', + 'sooners', + 'socrates', + 'spiderman', + 'peanuts', + 'maxine', + 'rogers', + '13131313', + 'andrew1', + 'filthy', + 'donnie', + 'ohyeah', + 'africa', + 'national', + 'kenny', + 'keith', + 'monique', + 'intrepid', + 'jasmin', + 'pickles', + 'assass', + 'fright', + 'potato', + 'darwin', + 'hhhhhh', + 'kingdom', + 'weezer', + '424242', + 'pepsi1', + 'throat', + 'romeo', + 'gerard', + 'looker', + 'puppy', + 'butch', + 'monika', + 'suzanne', + 'sweets', + 'temple', + 'laurie', + 'josh', + 'megadeth', + 'analsex', + 'nymets', + 'ddddddd', + 'bigballs', + 'support', + 'stick', + 'today', + 'down', + 'oakland', + 'oooooo', + 'qweasd', + 'chucky', + 'bridge', + 'carrot', + 'chargers', + 'discover', + 'dookie', + 'condor', + 'night', + 'butler', + 'hoover', + 'horny1', + 'isabella', + 'sunrise', + 'sinner', + 'jojo', + 'megapass', + 'martini', + 'assfuck', + 'grateful', + 'ffffff', + 'abigail', + 'esther', + 'mushroom', + 'janice', + 'jamaica', + 'wright', + 'sims', + 'space', + 'there', + 'timmy', + '7654321', + '77777', + 'cccccc', + 'gizmodo', + 'roxanne', + 'ralph', + 'tractor', + 'cristina', + 'dance', + 'mypass', + 'hongkong', + 'helena', + '1975', + 'blue123', + 'pissing', + 'thomas1', + 'redred', + 'rich', + 'basketball', + 'attack', + 'cash', + 'satan666', + 'drunk', + 'dixie', + 'dublin', + 'bollox', + 'kingkong', + 'katrina', + 'miles', + '1971', + '22222', + '272727', + 'sexx', + 'penelope', + 'thompson', + 'anything', + 'bbbb', + 'battle', + 'grizzly', + 'passat', + 'porter', + 'tracy', + 'defiant', + 'bowler', + 'knickers', + 'monitor', + 'wisdom', + 'wild', + 'slappy', + 'thor', + 'letsgo', + 'robert1', + 'feet', + 'rush', + 'brownie', + 'hudson', + '098765', + 'playing', + 'playtime', + 'lightnin', + 'melvin', + 'atomic', + 'bart', + 'hawk', + 'goku', + 'glory', + 'llllll', + 'qwaszx', + 'cosmos', + 'bosco', + 'knights', + 'bentley', + 'beast', + 'slapshot', + 'lewis', + 'assword', + 'frosty', + 'gillian', + 'sara', + 'dumbass', + 'mallard', + 'dddd', + 'deanna', + 'elwood', + 'wally', + '159357', + 'titleist', + 'angelo', + 'aussie', + 'guest', + 'golfing', + 'doobie', + 'loveit', + 'chloe', + 'elliott', + 'werewolf', + 'vipers', + 'janine', + '1965', + 'blabla', + 'surf', + 'sucking', + 'tardis', + 'serena', + 'shelley', + 'thegame', + 'legion', + 'rebels', + 'fernando', + 'fast', + 'gerald', + 'sarah1', + 'double', + 'onelove', + 'loulou', + 'toto', + 'crash', + 'blackcat', + '0007', + 'tacobell', + 'soccer1', + 'jedi', + 'manuel', + 'method', + 'river', + 'chase', + 'ludwig', + 'poopie', + 'derrick', + 'boob', + 'breast', + 'kittycat', + 'isabel', + 'belly', + 'pikachu', + 'thunder1', + 'thankyou', + 'jose', + 'celeste', + 'celtics', + 'frances', + 'frogger', + 'scoobydo', + 'sabbath', + 'coltrane', + 'budman', + 'willis', + 'jackal', + 'bigger', + 'zzzzz', + 'silvia', + 'sooner', + 'licking', + 'gopher', + 'geheim', + 'lonestar', + 'primus', + 'pooper', + 'newpass', + 'brasil', + 'heather1', + 'husker', + 'element', + 'moomoo', + 'beefcake', + 'zzzzzzzz', + 'tammy', + 'shitty', + 'smokin', + 'personal', + 'jjjj', + 'anthony1', + 'anubis', + 'backup', + 'gorilla', + 'fuckface', + 'painter', + 'lowrider', + 'punkrock', + 'traffic', + 'claude', + 'daniela', + 'dale', + 'delta1', + 'nancy', + 'boys', + 'easy', + 'kissing', + 'kelley', + 'wendy', + 'theresa', + 'amazon', + 'alan', + 'fatass', + 'dodgeram', + 'dingdong', + 'malcolm', + 'qqqqqqqq', + 'breasts', + 'boots', + 'honda1', + 'spidey', + 'poker', + 'temp', + 'johnjohn', + 'miguel', + '147852', + 'archer', + 'asshole1', + 'dogdog', + 'tricky', + 'crusader', + 'weather', + 'syracuse', + 'spankme', + 'speaker', + 'meridian', + 'amadeus', + 'back', + 'harley1', + 'falcons', + 'dorothy', + 'turkey50', + 'kenwood', + 'keyboard', + 'ilovesex', + '1978', + 'blackman', + 'shazam', + 'shalom', + 'lickit', + 'jimbob', + 'richmond', + 'roller', + 'carson', + 'check', + 'fatman', + 'funny', + 'garbage', + 'sandiego', + 'loving', + 'magnus', + 'cooldude', + 'clover', + 'mobile', + 'bell', + 'payton', + 'plumber', + 'texas1', + 'tool', + 'topper', + 'jenna', + 'mariners', + 'rebel', + 'harmony', + 'caliente', + 'celica', + 'fletcher', + 'german', + 'diana', + 'oxford', + 'osiris', + 'orgasm', + 'punkin', + 'porsche9', + 'tuesday', + 'close', + 'breeze', + 'bossman', + 'kangaroo', + 'billie', + 'latinas', + 'judith', + 'astros', + 'scruffy', + 'donna', + 'qwertyu', + 'davis', + 'hearts', + 'kathy', + 'jammer', + 'java', + 'springer', + 'rhonda', + 'ricky', + '1122', + 'goodtime', + 'chelsea1', + 'freckles', + 'flyboy', + 'doodle', + 'city', + 'nebraska', + 'bootie', + 'kicker', + 'webmaster', + 'vulcan', + 'iverson', + '191919', + 'blueeyes', + 'stoner', + '321321', + 'farside', + 'rugby', + 'director', + 'pussy69', + 'power1', + 'bobbie', + 'hershey', + 'hermes', + 'monopoly', + 'west', + 'birdman', + 'blessed', + 'blackjac', + 'southern', + 'peterpan', + 'thumbs', + 'lawyer', + 'melinda', + 'fingers', + 'fuckyou1', + 'rrrrrr', + 'a1b2c3d4', + 'coke', + 'nicola', + 'bohica', + 'heart', + 'elvis1', + 'kids', + 'blacky', + 'stories', + 'sentinel', + 'snake1', + 'phoebe', + 'jesse', + 'richard1', + '1234abcd', + 'guardian', + 'candyman', + 'fisting', + 'scarlet', + 'dildo', + 'pancho', + 'mandingo', + 'lucky7', + 'condom', + 'munchkin', + 'billyboy', + 'summer1', + 'student', + 'sword', + 'skiing', + 'sergio', + 'site', + 'sony', + 'thong', + 'rootbeer', + 'assassin', + 'cassidy', + 'frederic', + 'fffff', + 'fitness', + 'giovanni', + 'scarlett', + 'durango', + 'postal', + 'achilles', + 'dawn', + 'dylan', + 'kisses', + 'warriors', + 'imagine', + 'plymouth', + 'topdog', + 'asterix', + 'hallo', + 'cameltoe', + 'fuckfuck', + 'bridget', + 'eeeeee', + 'mouth', + 'weird', + 'will', + 'sithlord', + 'sommer', + 'toby', + 'theking', + 'juliet', + 'avenger', + 'backdoor', + 'goodbye', + 'chevrole', + 'faith', + 'lorraine', + 'trance', + 'cosworth', + 'brad', + 'houses', + 'homers', + 'eternity', + 'kingpin', + 'verbatim', + 'incubus', + '1961', + 'blond', + 'zaphod', + 'shiloh', + 'spurs', + 'station', + 'jennie', + 'maynard', + 'mighty', + 'aliens', + 'hank', + 'charly', + 'running', + 'dogman', + 'omega1', + 'printer', + 'aggies', + 'chocolate', + 'deadhead', + 'hope', + 'javier', + 'bitch1', + 'stone55', + 'pineappl', + 'thekid', + 'lizzie', + 'rockets', + 'ashton', + 'camels', + 'formula', + 'forrest', + 'rosemary', + 'oracle', + 'rain', + 'pussey', + 'porkchop', + 'abcde', + 'clancy', + 'nellie', + 'mystic', + 'inferno', + 'blackdog', + 'steve1', + 'pauline', + 'alexander', + 'alice', + 'alfa', + 'grumpy', + 'flames', + 'scream', + 'lonely', + 'puffy', + 'proxy', + 'valhalla', + 'unreal', + 'cynthia', + 'herbie', + 'engage', + 'yyyyyy', + '010101', + 'solomon', + 'pistol', + 'melody', + 'celeb', + 'flying', + 'gggg', + 'santiago', + 'scottie', + 'oakley', + 'portugal', + 'a12345', + 'newbie', + 'mmmm', + 'venus', + '1qazxsw2', + 'beverly', + 'zorro', + 'work', + 'writer', + 'stripper', + 'sebastia', + 'spread', + 'phil', + 'tobias', + 'links', + 'members', + 'metal', + '1221', + 'andre', + '565656', + 'funfun', + 'trojans', + 'again', + 'cyber', + 'hurrican', + 'moneys', + '1x2zkg8w', + 'zeus', + 'thing', + 'tomato', + 'lion', + 'atlantic', + 'celine', + 'usa123', + 'trans', + 'account', + 'aaaaaaa', + 'homerun', + 'hyperion', + 'kevin1', + 'blacks', + '44444444', + 'skittles', + 'sean', + 'hastings', + 'fart', + 'gangbang', + 'fubar', + 'sailboat', + 'older', + 'oilers', + 'craig', + 'conrad', + 'church', + 'damian', + 'dean', + 'broken', + 'buster1', + 'hithere', + 'immortal', + 'sticks', + 'pilot', + 'peters', + 'lexmark', + 'jerkoff', + 'maryland', + 'anders', + 'cheers', + 'possum', + 'columbus', + 'cutter', + 'muppet', + 'beautiful', + 'stolen', + 'swordfish', + 'sport', + 'sonic', + 'peter1', + 'jethro', + 'rockon', + 'asdfghj', + 'pass123', + 'paper', + 'pornos', + 'ncc1701a', + 'bootys', + 'buttman', + 'bonjour', + 'escape', + '1960', + 'becky', + 'bears', + '362436', + 'spartans', + 'tinman', + 'threesom', + 'lemons', + 'maxmax', + '1414', + 'bbbbb', + 'camelot', + 'chad', + 'chewie', + 'gogo', + 'fusion', + 'saint', + 'dilligaf', + 'nopass', + 'myself', + 'hustler', + 'hunter1', + 'whitey', + 'beast1', + 'yesyes', + 'spank', + 'smudge', + 'pinkfloy', + 'patriot', + 'lespaul', + 'annette', + 'hammers', + 'catalina', + 'finish', + 'formula1', + 'sausage', + 'scooter1', + 'orioles', + 'oscar1', + 'over', + 'colombia', + 'cramps', + 'natural', + 'eating', + 'exotic', + 'iguana', + 'bella', + 'suckers', + 'strong', + 'sheena', + 'start', + 'slave', + 'pearl', + 'topcat', + 'lancelot', + 'angelica', + 'magelan', + 'racer', + 'ramona', + 'crunch', + 'british', + 'button', + 'eileen', + 'steph', + '456123', + 'skinny', + 'seeking', + 'rockhard', + 'chief', + 'filter', + 'first', + 'freaks', + 'sakura', + 'pacman', + 'poontang', + 'dalton', + 'newlife', + 'homer1', + 'klingon', + 'watcher', + 'walleye', + 'tasha', + 'tasty', + 'sinatra', + 'starship', + 'steel', + 'starbuck', + 'poncho', + 'amber1', + 'gonzo', + 'grover', + 'catherin', + 'carol', + 'candle', + 'firefly', + 'goblin', + 'scotch', + 'diver', + 'usmc', + 'huskies', + 'eleven', + 'kentucky', + 'kitkat', + 'israel', + 'beckham', + 'bicycle', + 'yourmom', + 'studio', + 'tara', + '33333333', + 'shane', + 'splash', + 'jimmy1', + 'reality', + '12344321', + 'caitlin', + 'focus', + 'sapphire', + 'mailman', + 'raiders1', + 'clark', + 'ddddd', + 'hopper', + 'excalibu', + 'more', + 'wilbur', + 'illini', + 'imperial', + 'phillips', + 'lansing', + 'maxx', + 'gothic', + 'golfball', + 'carlton', + 'camille', + 'facial', + 'front242', + 'macdaddy', + 'qwer1234', + 'vectra', + 'cowboys1', + 'crazy1', + 'dannyboy', + 'jane', + 'betty', + 'benny', + 'bennett', + 'leader', + 'martinez', + 'aquarius', + 'barkley', + 'hayden', + 'caught', + 'franky', + 'ffff', + 'floyd', + 'sassy', + 'pppp', + 'pppppppp', + 'prodigy', + 'clarence', + 'noodle', + 'eatpussy', + 'vortex', + 'wanking', + 'beatrice', + 'billy1', + 'siemens', + 'pedro', + 'phillies', + 'research', + 'groups', + 'carolyn', + 'chevy1', + 'cccc', + 'fritz', + 'gggggggg', + 'doughboy', + 'dracula', + 'nurses', + 'loco', + 'madrid', + 'lollipop', + 'trout', + 'utopia', + 'chrono', + 'cooler', + 'conner', + 'nevada', + 'wibble', + 'werner', + 'summit', + 'marco', + 'marilyn', + '1225', + 'babies', + 'capone', + 'fugazi', + 'panda', + 'mama', + 'qazwsxed', + 'puppies', + 'triton', + '9876', + 'command', + 'nnnnnn', + 'ernest', + 'momoney', + 'iforgot', + 'wolfie', + 'studly', + 'shawn', + 'renee', + 'alien', + 'hamburg', + '81fukkc', + '741852', + 'catman', + 'china', + 'forgot', + 'gagging', + 'scott1', + 'drew', + 'oregon', + 'qweqwe', + 'train', + 'crazybab', + 'daniel1', + 'cutlass', + 'brothers', + 'holes', + 'heidi', + 'mothers', + 'music1', + 'what', + 'walrus', + '1957', + 'bigtime', + 'bike', + 'xtreme', + 'simba', + 'ssss', + 'rookie', + 'angie', + 'bathing', + 'fresh', + 'sanchez', + 'rotten', + 'maestro', + 'luis', + 'look', + 'turbo1', + '99999', + 'butthole', + 'hhhh', + 'elijah', + 'monty', + 'bender', + 'yoda', + 'shania', + 'shock', + 'phish', + 'thecat', + 'rightnow', + 'reagan', + 'baddog', + 'asia', + 'greatone', + 'gateway1', + 'randall', + 'abstr', + 'napster', + 'brian1', + 'bogart', + 'high', + 'hitler', + 'emma', + 'kill', + 'weaver', + 'wildfire', + 'jackson1', + 'isaiah', + '1981', + 'belinda', + 'beaner', + 'yoyo', + '0.0.0.000', + 'super1', + 'select', + 'snuggles', + 'slutty', + 'some', + 'phoenix1', + 'technics', + 'toon', + 'raven1', + 'rayray', + '123789', + '1066', + 'albion', + 'greens', + 'fashion', + 'gesperrt', + 'santana', + 'paint', + 'powell', + 'credit', + 'darling', + 'mystery', + 'bowser', + 'bottle', + 'brucelee', + 'hehehe', + 'kelly1', + 'mojo', + '1998', + 'bikini', + 'woofwoof', + 'yyyy', + 'strap', + 'sites', + 'spears', + 'theodore', + 'julius', + 'richards', + 'amelia', + 'central', + 'f**k', + 'nyjets', + 'punisher', + 'username', + 'vanilla', + 'twisted', + 'bryant', + 'brent', + 'bunghole', + 'here', + 'elizabeth', + 'erica', + 'kimber', + 'viagra', + 'veritas', + 'pony', + 'pool', + 'titts', + 'labtec', + 'lifetime', + 'jenny1', + 'masterbate', + 'mayhem', + 'redbull', + 'govols', + 'gremlin', + '505050', + 'gmoney', + 'rupert', + 'rovers', + 'diamond1', + 'lorenzo', + 'trident', + 'abnormal', + 'davidson', + 'deskjet', + 'cuddles', + 'nice', + 'bristol', + 'karina', + 'milano', + 'vh5150', + 'jarhead', + '1982', + 'bigbird', + 'bizkit', + 'sixers', + 'slider', + 'star69', + 'starfish', + 'penetration', + 'tommy1', + 'john316', + 'meghan', + 'michaela', + 'market', + 'grant', + 'caligula', + 'carl', + 'flicks', + 'films', + 'madden', + 'railroad', + 'cosmo', + 'cthulhu', + 'bradford', + 'br0d3r', + 'military', + 'bearbear', + 'swedish', + 'spawn', + 'patrick1', + 'polly', + 'these', + 'todd', + 'reds', + 'anarchy', + 'groove', + 'franco', + 'fuckher', + 'oooo', + 'tyrone', + 'vegas', + 'airbus', + 'cobra1', + 'christine', + 'clips', + 'delete', + 'duster', + 'kitty1', + 'mouse1', + 'monkeys', + 'jazzman', + '1919', + '262626', + 'swinging', + 'stroke', + 'stocks', + 'sting', + 'pippen', + 'labrador', + 'jordan1', + 'justdoit', + 'meatball', + 'females', + 'saturday', + 'park', + 'vector', + 'cooter', + 'defender', + 'desert', + 'demon', + 'nike', + 'bubbas', + 'bonkers', + 'english', + 'kahuna', + 'wildman', + '4121', + 'sirius', + 'static', + 'piercing', + 'terror', + 'teenage', + 'leelee', + 'marissa', + 'microsof', + 'mechanic', + 'robotech', + 'rated', + 'hailey', + 'chaser', + 'sanders', + 'salsero', + 'nuts', + 'macross', + 'quantum', + 'rachael', + 'tsunami', + 'universe', + 'daddy1', + 'cruise', + 'nguyen', + 'newpass6', + 'nudes', + 'hellyeah', + 'vernon', + '1959', + 'zaq12wsx', + 'striker', + 'sixty', + 'steele', + 'spice', + 'spectrum', + 'smegma', + 'thumb', + 'jjjjjjjj', + 'mellow', + 'astrid', + 'cancun', + 'cartoon', + 'sabres', + 'samiam', + 'pants', + 'oranges', + 'oklahoma', + 'lust', + 'coleman', + 'denali', + 'nude', + 'noodles', + 'buzz', + 'brest', + 'hooter', + 'mmmmmmmm', + 'warthog', + 'bloody', + 'blueblue', + 'zappa', + 'wolverine', + 'sniffing', + 'lance', + 'jean', + 'jjjjj', + 'harper', + 'calico', + 'freee', + 'rover', + 'door', + 'pooter', + 'closeup', + 'bonsai', + 'evelyn', + 'emily1', + 'kathryn', + 'keystone', + 'iiii', + '1955', + 'yzerman', + 'theboss', + 'tolkien', + 'jill', + 'megaman', + 'rasta', + 'bbbbbbbb', + 'bean', + 'handsome', + 'hal9000', + 'goofy', + 'gringo', + 'gofish', + 'gizmo1', + 'samsam', + 'scuba', + 'onlyme', + 'tttttttt', + 'corrado', + 'clown', + 'clapton', + 'deborah', + 'boris', + 'bulls', + 'vivian', + 'jayhawk', + 'bethany', + 'wwww', + 'sharky', + 'seeker', + 'ssssssss', + 'somethin', + 'pillow', + 'thesims', + 'lighter', + 'lkjhgf', + 'melissa1', + 'marcius2', + 'barry', + 'guiness', + 'gymnast', + 'casey1', + 'goalie', + 'godsmack', + 'doug', + 'lolo', + 'rangers1', + 'poppy', + 'abby', + 'clemson', + 'clipper', + 'deeznuts', + 'nobody', + 'holly1', + 'elliot', + 'eeee', + 'kingston', + 'miriam', + 'belle', + 'yosemite', + 'sucked', + 'sex123', + 'sexy69', + 'pic\'s', + 'tommyboy', + 'lamont', + 'meat', + 'masterbating', + 'marianne', + 'marc', + 'gretzky', + 'happyday', + 'frisco', + 'scratch', + 'orchid', + 'orange1', + 'manchest', + 'quincy', + 'unbelievable', + 'aberdeen', + 'dawson', + 'nathalie', + 'ne1469', + 'boxing', + 'hill', + 'korn', + 'intercourse', + '161616', + '1985', + 'ziggy', + 'supersta', + 'stoney', + 'senior', + 'amature', + 'barber', + 'babyboy', + 'bcfields', + 'goliath', + 'hack', + 'hardrock', + 'children', + 'frodo', + 'scout', + 'scrappy', + 'rosie', + 'qazqaz', + 'tracker', + 'active', + 'craving', + 'commando', + 'cohiba', + 'deep', + 'cyclone', + 'dana', + 'bubba69', + 'katie1', + 'mpegs', + 'vsegda', + 'jade', + 'irish1', + 'better', + 'sexy1', + 'sinclair', + 'smelly', + 'squerting', + 'lions', + 'jokers', + 'jeanette', + 'julia', + 'jojojo', + 'meathead', + 'ashley1', + 'groucho', + 'cheetah', + 'champ', + 'firefox', + 'gandalf1', + 'packer', + 'magnolia', + 'love69', + 'tyler1', + 'typhoon', + 'tundra', + 'bobby1', + 'kenworth', + 'village', + 'volley', + 'beth', + 'wolf359', + '0420', + '000007', + 'swimmer', + 'skydive', + 'smokes', + 'patty', + 'peugeot', + 'pompey', + 'legolas', + 'kristy', + 'redhot', + 'rodman', + 'redalert', + 'having', + 'grapes', + '4runner', + 'carrera', + 'floppy', + 'dollars', + 'ou8122', + 'quattro', + 'adams', + 'cloud9', + 'davids', + 'nofear', + 'busty', + 'homemade', + 'mmmmm', + 'whisper', + 'vermont', + 'webmaste', + 'wives', + 'insertion', + 'jayjay', + 'philips', + 'phone', + 'topher', + 'tongue', + 'temptress', + 'midget', + 'ripken', + 'havefun', + 'gretchen', + 'canon', + 'celebrity', + 'five', + 'getting', + 'ghetto', + 'direct', + 'otto', + 'ragnarok', + 'trinidad', + 'usnavy', + 'conover', + 'cruiser', + 'dalshe', + 'nicole1', + 'buzzard', + 'hottest', + 'kingfish', + 'misfit', + 'moore', + 'milfnew', + 'warlord', + 'wassup', + 'bigsexy', + 'blackhaw', + 'zippy', + 'shearer', + 'tights', + 'thursday', + 'kungfu', + 'labia', + 'journey', + 'meatloaf', + 'marlene', + 'rider', + 'area51', + 'batman1', + 'bananas', + '636363', + 'cancel', + 'ggggg', + 'paradox', + 'mack', + 'lynn', + 'queens', + 'adults', + 'aikido', + 'cigars', + 'nova', + 'hoosier', + 'eeyore', + 'moose1', + 'warez', + 'interacial', + 'streaming', + '313131', + 'pertinant', + 'pool6123', + 'mayday', + 'rivers', + 'revenge', + 'animated', + 'banker', + 'baddest', + 'gordon24', + 'ccccc', + 'fortune', + 'fantasies', + 'touching', + 'aisan', + 'deadman', + 'homepage', + 'ejaculation', + 'whocares', + 'iscool', + 'jamesbon', + '1956', + '1pussy', + 'womam', + 'sweden', + 'skidoo', + 'spock', + 'sssss', + 'petra', + 'pepper1', + 'pinhead', + 'micron', + 'allsop', + 'amsterda', + 'army', + 'aside', + 'gunnar', + '666999', + 'chip', + 'foot', + 'fowler', + 'february', + 'face', + 'fletch', + 'george1', + 'sapper', + 'science', + 'sasha1', + 'luckydog', + 'lover1', + 'magick', + 'popopo', + 'public', + 'ultima', + 'derek', + 'cypress', + 'booker', + 'businessbabe', + 'brandon1', + 'edwards', + 'experience', + 'vulva', + 'vvvv', + 'jabroni', + 'bigbear', + 'yummy', + '010203', + 'searay', + 'secret1', + 'showing', + 'sinbad', + 'sexxxx', + 'soleil', + 'software', + 'piccolo', + 'thirteen', + 'leopard', + 'legacy', + 'jensen', + 'justine', + 'memorex', + 'marisa', + 'mathew', + 'redwing', + 'rasputin', + '134679', + 'anfield', + 'greenbay', + 'gore', + 'catcat', + 'feather', + 'scanner', + 'pa55word', + 'contortionist', + 'danzig', + 'daisy1', + 'hores', + 'erik', + 'exodus', + 'vinnie', + 'iiiiii', + 'zero', + '1001', + 'subway', + 'tank', + 'second', + 'snapple', + 'sneakers', + 'sonyfuck', + 'picks', + 'poodle', + 'test1234', + 'their', + 'llll', + 'junebug', + 'june', + 'marker', + 'mellon', + 'ronaldo', + 'roadkill', + 'amanda1', + 'asdfjkl', + 'beaches', + 'greene', + 'great1', + 'cheerleaers', + 'force', + 'doitnow', + 'ozzy', + 'madeline', + 'radio', + 'tyson', + 'christian', + 'daphne', + 'boxster', + 'brighton', + 'housewifes', + 'emmanuel', + 'emerson', + 'kkkk', + 'mnbvcx', + 'moocow', + 'vides', + 'wagner', + 'janet', + '1717', + 'bigmoney', + 'blonds', + '1000', + 'storys', + 'stereo', + '4545', + '420247', + 'seductive', + 'sexygirl', + 'lesbean', + 'live', + 'justin1', + '124578', + 'animals', + 'balance', + 'hansen', + 'cabbage', + 'canadian', + 'gangbanged', + 'dodge1', + 'dimas', + 'lori', + 'loud', + 'malaka', + 'puss', + 'probes', + 'adriana', + 'coolman', + 'crawford', + 'dante', + 'nacked', + 'hotpussy', + 'erotica', + 'kool', + 'mirror', + 'wearing', + 'implants', + 'intruder', + 'bigass', + 'zenith', + 'woohoo', + 'womans', + 'tanya', + 'tango', + 'stacy', + 'pisces', + 'laguna', + 'krystal', + 'maxell', + 'andyod22', + 'barcelon', + 'chainsaw', + 'chickens', + 'flash1', + 'downtown', + 'orgasms', + 'magicman', + 'profit', + 'pusyy', + 'pothead', + 'coconut', + 'chuckie', + 'contact', + 'clevelan', + 'designer', + 'builder', + 'budweise', + 'hotshot', + 'horizon', + 'hole', + 'experienced', + 'mondeo', + 'wifes', + '1962', + 'strange', + 'stumpy', + 'smiths', + 'sparks', + 'slacker', + 'piper', + 'pitchers', + 'passwords', + 'laptop', + 'jeremiah', + 'allmine', + 'alliance', + 'bbbbbbb', + 'asscock', + 'halflife', + 'grandma', + 'hayley', + '88888', + 'cecilia', + 'chacha', + 'saratoga', + 'sandy1', + 'santos', + 'doogie', + 'number', + 'positive', + 'qwert40', + 'transexual', + 'crow', + 'close-up', + 'darrell', + 'bonita', + 'ib6ub9', + 'volvo', + 'jacob1', + 'iiiii', + 'beastie', + 'sunnyday', + 'stoned', + 'sonics', + 'starfire', + 'snapon', + 'pictuers', + 'pepe', + 'testing1', + 'tiberius', + 'lisalisa', + 'lesbain', + 'litle', + 'retard', + 'ripple', + 'austin1', + 'badgirl', + 'golfgolf', + 'flounder', + 'garage', + 'royals', + 'dragoon', + 'dickie', + 'passwor', + 'ocean', + 'majestic', + 'poppop', + 'trailers', + 'dammit', + 'nokia', + 'bobobo', + 'br549', + 'emmitt', + 'knock', + 'minime', + 'mikemike', + 'whitesox', + '1954', + '3232', + '353535', + 'seamus', + 'solo', + 'sparkle', + 'sluttey', + 'pictere', + 'titten', + 'lback', + '1024', + 'angelina', + 'goodluck', + 'charlton', + 'fingerig', + 'gallaries', + 'goat', + 'ruby', + 'passme', + 'oasis', + 'lockerroom', + 'logan1', + 'rainman', + 'twins', + 'treasure', + 'absolutely', + 'club', + 'custom', + 'cyclops', + 'nipper', + 'bucket', + 'homepage-', + 'hhhhh', + 'momsuck', + 'indain', + '2345', + 'beerbeer', + 'bimmer', + 'susanne', + 'stunner', + 'stevens', + '456456', + 'shell', + 'sheba', + 'tootsie', + 'tiny', + 'testerer', + 'reefer', + 'really', + '1012', + 'harcore', + 'gollum', + '545454', + 'chico', + 'caveman', + 'carole', + 'fordf150', + 'fishes', + 'gaymen', + 'saleen', + 'doodoo', + 'pa55w0rd', + 'looney', + 'presto', + 'qqqqq', + 'cigar', + 'bogey', + 'brewer', + 'helloo', + 'dutch', + 'kamikaze', + 'monte', + 'wasser', + 'vietnam', + 'visa', + 'japanees', + '0123', + 'swords', + 'slapper', + 'peach', + 'jump', + 'marvel', + 'masterbaiting', + 'march', + 'redwood', + 'rolling', + '1005', + 'ametuer', + 'chiks', + 'cathy', + 'callaway', + 'fucing', + 'sadie1', + 'panasoni', + 'mamas', + 'race', + 'rambo', + 'unknown', + 'absolut', + 'deacon', + 'dallas1', + 'housewife', + 'kristi', + 'keywest', + 'kirsten', + 'kipper', + 'morning', + 'wings', + 'idiot', + '18436572', + '1515', + 'beating', + 'zxczxc', + 'sullivan', + '303030', + 'shaman', + 'sparrow', + 'terrapin', + 'jeffery', + 'masturbation', + 'mick', + 'redfish', + '1492', + 'angus', + 'barrett', + 'goirish', + 'hardcock', + 'felicia', + 'forfun', + 'galary', + 'freeporn', + 'duchess', + 'olivier', + 'lotus', + 'pornographic', + 'ramses', + 'purdue', + 'traveler', + 'crave', + 'brando', + 'enter1', + 'killme', + 'moneyman', + 'welder', + 'windsor', + 'wifey', + 'indon', + 'yyyyy', + 'stretch', + 'taylor1', + '4417', + 'shopping', + 'picher', + 'pickup', + 'thumbnils', + 'johnboy', + 'jets', + 'jess', + 'maureen', + 'anne', + 'ameteur', + 'amateurs', + 'apollo13', + 'hambone', + 'goldwing', + '5050', + 'charley', + 'sally1', + 'doghouse', + 'padres', + 'pounding', + 'quest', + 'truelove', + 'underdog', + 'trader', + 'crack', + 'climber', + 'bolitas', + 'bravo', + 'hohoho', + 'model', + 'italian', + 'beanie', + 'beretta', + 'wrestlin', + 'stroker', + 'tabitha', + 'sherwood', + 'sexyman', + 'jewels', + 'johannes', + 'mets', + 'marcos', + 'rhino', + 'bdsm', + 'balloons', + 'goodman', + 'grils', + 'happy123', + 'flamingo', + 'games', + 'route66', + 'devo', + 'dino', + 'outkast', + 'paintbal', + 'magpie', + 'llllllll', + 'twilight', + 'critter', + 'christie', + 'cupcake', + 'nickel', + 'bullseye', + 'krista', + 'knickerless', + 'mimi', + 'murder', + 'videoes', + 'binladen', + 'xerxes', + 'slim', + 'slinky', + 'pinky', + 'peterson', + 'thanatos', + 'meister', + 'menace', + 'ripley', + 'retired', + 'albatros', + 'balloon', + 'bank', + 'goten', + '5551212', + 'getsdown', + 'donuts', + 'divorce', + 'nwo4life', + 'lord', + 'lost', + 'underwear', + 'tttt', + 'comet', + 'deer', + 'damnit', + 'dddddddd', + 'deeznutz', + 'nasty1', + 'nonono', + 'nina', + 'enterprise', + 'eeeee', + 'misfit99', + 'milkman', + 'vvvvvv', + 'isaac', + '1818', + 'blueboy', + 'beans', + 'bigbutt', + 'wyatt', + 'tech', + 'solution', + 'poetry', + 'toolman', + 'laurel', + 'juggalo', + 'jetski', + 'meredith', + 'barefoot', + '50spanks', + 'gobears', + 'scandinavian', + 'original', + 'truman', + 'cubbies', + 'nitram', + 'briana', + 'ebony', + 'kings', + 'warner', + 'bilbo', + 'yumyum', + 'zzzzzzz', + 'stylus', + '321654', + 'shannon1', + 'server', + 'secure', + 'silly', + 'squash', + 'starman', + 'steeler', + 'staples', + 'phrases', + 'techniques', + 'laser', + '135790', + 'allan', + 'barker', + 'athens', + 'cbr600', + 'chemical', + 'fester', + 'gangsta', + 'fucku2', + 'freeze', + 'game', + 'salvador', + 'droopy', + 'objects', + 'passwd', + 'lllll', + 'loaded', + 'louis', + 'manchester', + 'losers', + 'vedder', + 'clit', + 'chunky', + 'darkman', + 'damage', + 'buckshot', + 'buddah', + 'boobed', + 'henti', + 'hillary', + 'webber', + 'winter1', + 'ingrid', + 'bigmike', + 'beta', + 'zidane', + 'talon', + 'slave1', + 'pissoff', + 'person', + 'thegreat', + 'living', + 'lexus', + 'matador', + 'readers', + 'riley', + 'roberta', + 'armani', + 'ashlee', + 'goldstar', + '5656', + 'cards', + 'fmale', + 'ferris', + 'fuking', + 'gaston', + 'fucku', + 'ggggggg', + 'sauron', + 'diggler', + 'pacers', + 'looser', + 'pounded', + 'premier', + 'pulled', + 'town', + 'trisha', + 'triangle', + 'cornell', + 'collin', + 'cosmic', + 'deeper', + 'depeche', + 'norway', + 'bright', + 'helmet', + 'kristine', + 'kendall', + 'mustard', + 'misty1', + 'watch', + 'jagger', + 'bertie', + 'berger', + 'word', + '3x7pxr', + 'silver1', + 'smoking', + 'snowboar', + 'sonny', + 'paula', + 'penetrating', + 'photoes', + 'lesbens', + 'lambert', + 'lindros', + 'lillian', + 'roadking', + 'rockford', + '1357', + '143143', + 'asasas', + 'goodboy', + '898989', + 'chicago1', + 'card', + 'ferrari1', + 'galeries', + 'godfathe', + 'gawker', + 'gargoyle', + 'gangster', + 'rubble', + 'rrrr', + 'onetime', + 'pussyman', + 'pooppoop', + 'trapper', + 'twenty', + 'abraham', + 'cinder', + 'company', + 'newcastl', + 'boricua', + 'bunny1', + 'boxer', + 'hotred', + 'hockey1', + 'hooper', + 'edward1', + 'evan', + 'kris', + 'misery', + 'moscow', + 'milk', + 'mortgage', + 'bigtit', + 'show', + 'snoopdog', + 'three', + 'lionel', + 'leanne', + 'joshua1', + 'july', + '1230', + 'assholes', + 'cedric', + 'fallen', + 'farley', + 'gene', + 'frisky', + 'sanity', + 'script', + 'divine', + 'dharma', + 'lucky13', + 'property', + 'tricia', + 'akira', + 'desiree', + 'broadway', + 'butterfly', + 'hunt', + 'hotbox', + 'hootie', + 'heat', + 'howdy', + 'earthlink', + 'karma', + 'kiteboy', + 'motley', + 'westwood', + '1988', + 'bert', + 'blackbir', + 'biggles', + 'wrench', + 'working', + 'wrestle', + 'slippery', + 'pheonix', + 'penny1', + 'pianoman', + 'tomorrow', + 'thedude', + 'jenn', + 'jonjon', + 'jones1', + 'mattie', + 'memory', + 'micheal', + 'roadrunn', + 'arrow', + 'attitude', + 'azzer', + 'seahawks', + 'diehard', + 'dotcom', + 'lola', + 'tunafish', + 'chivas', + 'cinnamon', + 'clouds', + 'deluxe', + 'northern', + 'nuclear', + 'north', + 'boom', + 'boobie', + 'hurley', + 'krishna', + 'momomo', + 'modles', + 'volume', + '23232323', + 'bluedog', + 'wwwwwww', + 'zerocool', + 'yousuck', + 'pluto', + 'limewire', + 'link', + 'joung', + 'marcia', + 'awnyce', + 'gonavy', + 'haha', + 'films+pic+galeries', + 'fabian', + 'francois', + 'girsl', + 'fuckthis', + 'girfriend', + 'rufus', + 'drive', + 'uncencored', + 'a123456', + 'airport', + 'clay', + 'chrisbln', + 'combat', + 'cygnus', + 'cupoi', + 'never', + 'netscape', + 'brett', + 'hhhhhhhh', + 'eagles1', + 'elite', + 'knockers', + 'kendra', + 'mommy', + '1958', + 'tazmania', + 'shonuf', + 'piano', + 'pharmacy', + 'thedog', + 'lips', + 'jillian', + 'jenkins', + 'midway', + 'arsenal1', + 'anaconda', + 'australi', + 'gromit', + 'gotohell', + '787878', + '66666', + 'carmex2', + 'camber', + 'gator1', + 'ginger1', + 'fuzzy', + 'seadoo', + 'dorian', + 'lovesex', + 'rancid', + 'uuuuuu', + '911911', + 'nature', + 'bulldog1', + 'helen', + 'health', + 'heater', + 'higgins', + 'kirk', + 'monalisa', + 'mmmmmmm', + 'whiteout', + 'virtual', + 'ventura', + 'jamie1', + 'japanes', + 'james007', + '2727', + '2469', + 'blam', + 'bitchass', + 'believe', + 'zephyr', + 'stiffy', + 'sweet1', + 'silent', + 'southpar', + 'spectre', + 'tigger1', + 'tekken', + 'lenny', + 'lakota', + 'lionking', + 'jjjjjjj', + 'medical', + 'megatron', + '1369', + 'hawaiian', + 'gymnastic', + 'golfer1', + 'gunners', + '7779311', + '515151', + 'famous', + 'glass', + 'screen', + 'rudy', + 'royal', + 'sanfran', + 'drake', + 'optimus', + 'panther1', + 'love1', + 'mail', + 'maggie1', + 'pudding', + 'venice', + 'aaron1', + 'delphi', + 'niceass', + 'bounce', + 'busted', + 'house1', + 'killer1', + 'miracle', + 'momo', + 'musashi', + 'jammin', + '2003', + '234567', + 'wp2003wp', + 'submit', + 'silence', + 'sssssss', + 'state', + 'spikes', + 'sleeper', + 'passwort', + 'toledo', + 'kume', + 'media', + 'meme', + 'medusa', + 'mantis', + 'remote', + 'reading', + 'reebok', + '1017', + 'artemis', + 'hampton', + 'harry1', + 'cafc91', + 'fettish', + 'friendly', + 'oceans', + 'oooooooo', + 'mango', + 'ppppp', + 'trainer', + 'troy', + 'uuuu', + '909090', + 'cross', + 'death1', + 'news', + 'bullfrog', + 'hokies', + 'holyshit', + 'eeeeeee', + 'mitch', + 'jasmine1', + '&', + '&', + 'sergeant', + 'spinner', + 'leon', + 'jockey', + 'records', + 'right', + 'babyblue', + 'hans', + 'gooner', + '474747', + 'cheeks', + 'cars', + 'candice', + 'fight', + 'glow', + 'pass1234', + 'parola', + 'okokok', + 'pablo', + 'magical', + 'major', + 'ramsey', + 'poseidon', + '989898', + 'confused', + 'circle', + 'crusher', + 'cubswin', + 'nnnn', + 'hollywood', + 'erin', + 'kotaku', + 'milo', + 'mittens', + 'whatsup', + 'vvvvv', + 'iomega', + 'insertions', + 'bengals', + 'bermuda', + 'biit', + 'yellow1', + '012345', + 'spike1', + 'south', + 'sowhat', + 'pitures', + 'peacock', + 'pecker', + 'theend', + 'juliette', + 'jimmie', + 'romance', + 'augusta', + 'hayabusa', + 'hawkeyes', + 'castro', + 'florian', + 'geoffrey', + 'dolly', + 'lulu', + 'qaz123', + 'usarmy', + 'twinkle', + 'cloud', + 'chuckles', + 'cold', + 'hounddog', + 'hover', + 'hothot', + 'europa', + 'ernie', + 'kenshin', + 'kojak', + 'mikey1', + 'water1', + '196969', + 'because', + 'wraith', + 'zebra', + 'wwwww', + '33333', + 'simon1', + 'spider1', + 'snuffy', + 'philippe', + 'thunderb', + 'teddy1', + 'lesley', + 'marino13', + 'maria1', + 'redline', + 'renault', + 'aloha', + 'antoine', + 'handyman', + 'cerberus', + 'gamecock', + 'gobucks', + 'freesex', + 'duffman', + 'ooooo', + 'papa', + 'nuggets', + 'magician', + 'longbow', + 'preacher', + 'porno1', + 'county', + 'chrysler', + 'contains', + 'dalejr', + 'darius', + 'darlene', + 'dell', + 'navy', + 'buffy1', + 'hedgehog', + 'hoosiers', + 'honey1', + 'hott', + 'heyhey', + 'europe', + 'dutchess', + 'everest', + 'wareagle', + 'ihateyou', + 'sunflowe', + '3434', + 'senators', + 'shag', + 'spoon', + 'sonoma', + 'stalker', + 'poochie', + 'terminal', + 'terefon', + 'laurence', + 'maradona', + 'maryann', + 'marty', + 'roman', + '1007', + '142536', + 'alibaba', + 'america1', + 'bartman', + 'astro', + 'goth', + 'century', + 'chicken1', + 'cheater', + 'four', + 'ghost1', + 'passpass', + 'oral', + 'r2d2c3po', + 'civic', + 'cicero', + 'myxworld', + 'kkkkk', + 'missouri', + 'wishbone', + 'infiniti', + 'jameson', + '1a2b3c', + '1qwerty', + 'wonderboy', + 'skip', + 'shojou', + 'stanford', + 'sparky1', + 'smeghead', + 'poiuy', + 'titanium', + 'torres', + 'lantern', + 'jelly', + 'jeanne', + 'meier', + '1213', + 'bayern', + 'basset', + 'gsxr750', + 'cattle', + 'charlene', + 'fishing1', + 'fullmoon', + 'gilles', + 'dima', + 'obelix', + 'popo', + 'prissy', + 'ramrod', + 'unique', + 'absolute', + 'bummer', + 'hotone', + 'dynasty', + 'entry', + 'konyor', + 'missy1', + 'moses', + '282828', + 'yeah', + 'xyz123', + 'stop', + '426hemi', + '404040', + 'seinfeld', + 'simmons', + 'pingpong', + 'lazarus', + 'matthews', + 'marine1', + 'manning', + 'recovery', + '12345a', + 'beamer', + 'babyface', + 'greece', + 'gustav', + '7007', + 'charity', + 'camilla', + 'ccccccc', + 'faggot', + 'foxy', + 'frozen', + 'gladiato', + 'duckie', + 'dogfood', + 'paranoid', + 'packers1', + 'longjohn', + 'radical', + 'tuna', + 'clarinet', + 'claudio', + 'circus', + 'danny1', + 'novell', + 'nights', + 'bonbon', + 'kashmir', + 'kiki', + 'mortimer', + 'modelsne', + 'moondog', + 'monaco', + 'vladimir', + 'insert', + '1953', + 'zxc123', + 'supreme', + '3131', + 'sexxx', + 'selena', + 'softail', + 'poipoi', + 'pong', + 'together', + 'mars', + 'martin1', + 'rogue', + 'alone', + 'avalanch', + 'audia4', + '55bgates', + 'cccccccc', + 'chick', + 'came11', + 'figaro', + 'geneva', + 'dogboy', + 'dnsadm', + 'dipshit', + 'paradigm', + 'othello', + 'operator', + 'officer', + 'malone', + 'post', + 'rafael', + 'valencia', + 'tripod', + 'choice', + 'chopin', + 'coucou', + 'coach', + 'cocksuck', + 'common', + 'creature', + 'borussia', + 'book', + 'browning', + 'heritage', + 'hiziad', + 'homerj', + 'eight', + 'earth', + 'millions', + 'mullet', + 'whisky', + 'jacques', + 'store', + '4242', + 'speedo', + 'starcraf', + 'skylar', + 'spaceman', + 'piggy', + 'pierce', + 'tiger2', + 'legos', + 'lala', + 'jezebel', + 'judy', + 'joker1', + 'mazda', + 'barton', + 'baker', + '727272', + 'chester1', + 'fishman', + 'food', + 'rrrrrrrr', + 'sandwich', + 'dundee', + 'lumber', + 'magazine', + 'radar', + 'ppppppp', + 'tranny', + 'aaliyah', + 'admiral', + 'comics', + 'cleo', + 'delight', + 'buttfuck', + 'homeboy', + 'eternal', + 'kilroy', + 'kellie', + 'khan', + 'violin', + 'wingman', + 'walmart', + 'bigblue', + 'blaze', + 'beemer', + 'beowulf', + 'bigfish', + 'yyyyyyy', + 'woodie', + 'yeahbaby', + '0123456', + 'tbone', + 'style', + 'syzygy', + 'starter', + 'lemon', + 'linda1', + 'merlot', + 'mexican', + '11235813', + 'anita', + 'banner', + 'bangbang', + 'badman', + 'barfly', + 'grease', + 'carla', + 'charles1', + 'ffffffff', + 'screw', + 'doberman', + 'diane', + 'dogshit', + 'overkill', + 'counter', + 'coolguy', + 'claymore', + 'demons', + 'demo', + 'nomore', + 'normal', + 'brewster', + 'hhhhhhh', + 'hondas', + 'iamgod', + 'enterme', + 'everett', + 'electron', + 'eastside', + 'kayla', + 'minimoni', + 'mybaby', + 'wildbill', + 'wildcard', + 'ipswich', + '200000', + 'bearcat', + 'zigzag', + 'yyyyyyyy', + 'xander', + 'sweetnes', + '369369', + 'skyler', + 'skywalker', + 'pigeon', + 'peyton', + 'tipper', + 'lilly', + 'asdf123', + 'alphabet', + 'asdzxc', + 'babybaby', + 'banane', + 'barnes', + 'guyver', + 'graphics', + 'grand', + 'chinook', + 'florida1', + 'flexible', + 'fuckinside', + 'otis', + 'ursitesux', + 'tototo', + 'trust', + 'tower', + 'adam12', + 'christma', + 'corey', + 'chrome', + 'buddie', + 'bombers', + 'bunker', + 'hippie', + 'keegan', + 'misfits', + 'vickie', + '292929', + 'woofer', + 'wwwwwwww', + 'stubby', + 'sheep', + 'secrets', + 'sparta', + 'stang', + 'spud', + 'sporty', + 'pinball', + 'jorge', + 'just4fun', + 'johanna', + 'maxxxx', + 'rebecca1', + 'gunther', + 'fatima', + 'fffffff', + 'freeway', + 'garion', + 'score', + 'rrrrr', + 'sancho', + 'outback', + 'maggot', + 'puddin', + 'trial', + 'adrienne', + '987456', + 'colton', + 'clyde', + 'brain', + 'brains', + 'hoops', + 'eleanor', + 'dwayne', + 'kirby', + 'mydick', + 'villa', + '19691969', + 'bigcat', + 'becker', + 'shiner', + 'silverad', + 'spanish', + 'templar', + 'lamer', + 'juicy', + 'marsha', + 'mike1', + 'maximum', + 'rhiannon', + 'real', + '1223', + '10101010', + 'arrows', + 'andres', + 'alucard', + 'baldwin', + 'baron', + 'avenue', + 'ashleigh', + 'haggis', + 'channel', + 'cheech', + 'safari', + 'ross', + 'dog123', + 'orion1', + 'paloma', + 'qwerasdf', + 'presiden', + 'vegitto', + 'trees', + '969696', + 'adonis', + 'colonel', + 'cookie1', + 'newyork1', + 'brigitte', + 'buddyboy', + 'hellos', + 'heineken', + 'dwight', + 'eraser', + 'kerstin', + 'motion', + 'moritz', + 'millwall', + 'visual', + 'jaybird', + '1983', + 'beautifu', + 'bitter', + 'yvette', + 'zodiac', + 'steven1', + 'sinister', + 'slammer', + 'smashing', + 'slick1', + 'sponge', + 'teddybea', + 'theater', + 'this', + 'ticklish', + 'lipstick', + 'jonny', + 'massage', + 'mann', + 'reynolds', + 'ring', + '1211', + 'amazing', + 'aptiva', + 'applepie', + 'bailey1', + 'guitar1', + 'chanel', + 'canyon', + 'gagged', + 'fuckme1', + 'rough', + 'digital1', + 'dinosaur', + 'punk', + '98765', + '90210', + 'clowns', + 'cubs', + 'daniels', + 'deejay', + 'nigga', + 'naruto', + 'boxcar', + 'icehouse', + 'hotties', + 'electra', + 'kent', + 'widget', + 'india', + 'insanity', + '1986', + '2004', + 'best', + 'bluefish', + 'bingo1', + '*****', + 'stratus', + 'strength', + 'sultan', + 'storm1', + '44444', + '4200', + 'sentnece', + 'season', + 'sexyboy', + 'sigma', + 'smokie', + 'spam', + 'point', + 'pippo', + 'ticket', + 'temppass', + 'joel', + 'manman', + 'medicine', + '1022', + 'anton', + 'almond', + 'bacchus', + 'aztnm', + 'axio', + 'awful', + 'bamboo', + 'hakr', + 'gregor', + 'hahahaha', + '5678', + 'casanova', + 'caprice', + 'camero1', + 'fellow', + 'fountain', + 'dupont', + 'dolphin1', + 'dianne', + 'paddle', + 'magnet', + 'qwert1', + 'pyon', + 'porsche1', + 'tripper', + 'vampires', + 'coming', + 'noway', + 'burrito', + 'bozo', + 'highheel', + 'hughes', + 'hookem', + 'eddie1', + 'ellie', + 'entropy', + 'kkkkkkkk', + 'kkkkkkk', + 'illinois', + 'jacobs', + '1945', + '1951', + '24680', + '21212121', + '100000', + 'stonecold', + 'taco', + 'subzero', + 'sharp', + 'sexxxy', + 'skolko', + 'shanna', + 'skyhawk', + 'spurs1', + 'sputnik', + 'piazza', + 'testpass', + 'letter', + 'lane', + 'kurt', + 'jiggaman', + 'matilda', + '1224', + 'harvard', + 'hannah1', + '525252', + '4ever', + 'carbon', + 'chef', + 'federico', + 'ghosts', + 'gina', + 'scorpio1', + 'rt6ytere', + 'madison1', + 'loki', + 'raquel', + 'promise', + 'coolness', + 'christina', + 'coldbeer', + 'citadel', + 'brittney', + 'highway', + 'evil', + 'monarch', + 'morgan1', + 'washingt', + '1997', + 'bella1', + 'berry', + 'yaya', + 'yolanda', + 'superb', + 'taxman', + 'studman', + 'stephanie', + '3636', + 'sherri', + 'sheriff', + 'shepherd', + 'poland', + 'pizzas', + 'tiffany1', + 'toilet', + 'latina', + 'lassie', + 'larry1', + 'joseph1', + 'mephisto', + 'meagan', + 'marian', + 'reptile', + 'rico', + 'razor', + '1013', + 'barron', + 'hammer1', + 'gypsy', + 'grande', + 'carroll', + 'camper', + 'chippy', + 'cat123', + 'call', + 'chimera', + 'fiesta', + 'glock', + 'glenn', + 'domain', + 'dieter', + 'dragonba', + 'onetwo', + 'nygiants', + 'odessa', + 'password2', + 'louie', + 'quartz', + 'prowler', + 'prophet', + 'towers', + 'ultra', + 'cocker', + 'corleone', + 'dakota1', + 'cumm', + 'nnnnnnn', + 'natalia', + 'boxers', + 'hugo', + 'heynow', + 'hollow', + 'iceberg', + 'elvira', + 'kittykat', + 'kate', + 'kitchen', + 'wasabi', + 'vikings1', + 'impact', + 'beerman', + 'string', + 'sleep', + 'splinter', + 'snoopy1', + 'pipeline', + 'pocket', + 'legs', + 'maple', + 'mickey1', + 'manuela', + 'mermaid', + 'micro', + 'meowmeow', + 'redbird', + 'alisha', + 'baura', + 'battery', + 'grass', + 'chevys', + 'chestnut', + 'caravan', + 'carina', + 'charmed', + 'fraser', + 'frogman', + 'diving', + 'dogger', + 'draven', + 'drifter', + 'oatmeal', + 'paris1', + 'longdong', + 'quant4307s', + 'rachel1', + 'vegitta', + 'cole', + 'cobras', + 'corsair', + 'dadada', + 'noelle', + 'mylife', + 'nine', + 'bowwow', + 'body', + 'hotrats', + 'eastwood', + 'moonligh', + 'modena', + 'wave', + 'illusion', + 'iiiiiii', + 'jayhawks', + 'birgit', + 'zone', + 'sutton', + 'susana', + 'swingers', + 'shocker', + 'shrimp', + 'sexgod', + 'squall', + 'stefanie', + 'squeeze', + 'soul', + 'patrice', + 'poiu', + 'players', + 'tigers1', + 'toejam', + 'tickler', + 'line', + 'julie1', + 'jimbo1', + 'jefferso', + 'juanita', + 'michael2', + 'rodeo', + 'robot', + '1023', + 'annie1', + 'bball', + 'guess', + 'happy2', + 'charter', + 'farm', + 'flasher', + 'falcon1', + 'fiction', + 'fastball', + 'gadget', + 'scrabble', + 'diaper', + 'dirtbike', + 'dinner', + 'oliver1', + 'partner', + 'paco', + 'lucille', + 'macman', + 'poopy', + 'popper', + 'postman', + 'ttttttt', + 'ursula', + 'acura', + 'cowboy1', + 'conan', + 'daewoo', + 'cyrus', + 'customer', + 'nation', + 'nemrac58', + 'nnnnn', + 'nextel', + 'bolton', + 'bobdylan', + 'hopeless', + 'eureka', + 'extra', + 'kimmie', + 'kcj9wx5n', + 'killbill', + 'musica', + 'volkswag', + 'wage', + 'windmill', + 'wert', + 'vintage', + 'iloveyou1', + 'itsme', + 'bessie', + 'zippo', + '311311', + 'starligh', + 'smokey1', + 'spot', + 'snappy', + 'soulmate', + 'plasma', + 'thelma', + 'tonight', + 'krusty', + 'just4me', + 'mcdonald', + 'marius', + 'rochelle', + 'rebel1', + '1123', + 'alfredo', + 'aubrey', + 'audi', + 'chantal', + 'fick', + 'goaway', + 'roses', + 'sales', + 'rusty2', + 'dirt', + 'dogbone', + 'doofus', + 'ooooooo', + 'oblivion', + 'mankind', + 'luck', + 'mahler', + 'lllllll', + 'pumper', + 'puck', + 'pulsar', + 'valkyrie', + 'tupac', + 'compass', + 'concorde', + 'costello', + 'cougars', + 'delaware', + 'niceguy', + 'nocturne', + 'bob123', + 'boating', + 'bronze', + 'hopkins', + 'herewego', + 'hewlett', + 'houhou', + 'hubert', + 'earnhard', + 'eeeeeeee', + 'keller', + 'mingus', + 'mobydick', + 'venture', + 'verizon', + 'imation', + '1950', + '1948', + '1949', + '223344', + 'bigbig', + 'blossom', + 'zack', + 'wowwow', + 'sissy', + 'skinner', + 'spiker', + 'square', + 'snooker', + 'sluggo', + 'player1', + 'junk', + 'jeannie', + 'jsbach', + 'jumbo', + 'jewel', + 'medic', + 'robins', + 'reddevil', + 'reckless', + '123456a', + '1125', + '1031', + 'beacon', + 'astra', + 'gumby', + 'hammond', + 'hassan', + '757575', + '585858', + 'chillin', + 'fuck1', + 'sander', + 'lowell', + 'radiohea', + 'upyours', + 'trek', + 'courage', + 'coolcool', + 'classics', + 'choochoo', + 'darryl', + 'nikki1', + 'nitro', + 'bugs', + 'boytoy', + 'ellen', + 'excite', + 'kirsty', + 'kane', + 'wingnut', + 'wireless', + 'icu812', + '1master', + 'beatle', + 'bigblock', + 'blanca', + 'wolfen', + 'summer99', + 'sugar1', + 'tartar', + 'sexysexy', + 'senna', + 'sexman', + 'sick', + 'someone', + 'soprano', + 'pippin', + 'platypus', + 'pixies', + 'telephon', + 'land', + 'laura1', + 'laurent', + 'rimmer', + 'road', + 'report', + '1020', + '12qwaszx', + 'arturo', + 'around', + 'hamish', + 'halifax', + 'fishhead', + 'forum', + 'dododo', + 'doit', + 'outside', + 'paramedi', + 'lonesome', + 'mandy1', + 'twist', + 'uuuuu', + 'uranus', + 'ttttt', + 'butcher', + 'bruce1', + 'helper', + 'hopeful', + 'eduard', + 'dusty1', + 'kathy1', + 'katherin', + 'moonbeam', + 'muscles', + 'monster1', + 'monkeybo', + 'morton', + 'windsurf', + 'vvvvvvv', + 'vivid', + 'install', + '1947', + '187187', + '1941', + '1952', + 'tatiana', + 'susan1', + '31415926', + 'sinned', + 'sexxy', + 'senator', + 'sebastian', + 'shadows', + 'smoothie', + 'snowflak', + 'playstat', + 'playa', + 'playboy1', + 'toaster', + 'jerry1', + 'marie1', + 'mason1', + 'merlin1', + 'roger1', + 'roadster', + '112358', + '1121', + 'andrea1', + 'bacardi', + 'auto', + 'hardware', + 'hardy', + '789789', + '5555555', + 'captain1', + 'flores', + 'fergus', + 'sascha', + 'rrrrrrr', + 'dome', + 'onion', + 'nutter', + 'lololo', + 'qqqqqqq', + 'quick', + 'undertak', + 'uuuuuuuu', + 'uuuuuuu', + 'criminal', + 'cobain', + 'cindy1', + 'coors', + 'dani', + 'descent', + 'nimbus', + 'nomad', + 'nanook', + 'norwich', + 'bomb', + 'bombay', + 'broker', + 'hookup', + 'kiwi', + 'winners', + 'jackpot', + '1a2b3c4d', + '1776', + 'beardog', + 'bighead', + 'blast', + 'bird33', + '0987', + 'stress', + 'shot', + 'spooge', + 'pelican', + 'peepee', + 'perry', + 'pointer', + 'titan', + 'thedoors', + 'jeremy1', + 'annabell', + 'altima', + 'baba', + 'hallie', + 'hate', + 'hardone', + '5454', + 'candace', + 'catwoman', + 'flip', + 'faithful', + 'finance', + 'farmboy', + 'farscape', + 'genesis1', + 'salomon', + 'destroy', + 'papers', + 'option', + 'page', + 'loser1', + 'lopez', + 'r2d2', + 'pumpkins', + 'training', + 'chriss', + 'cumcum', + 'ninjas', + 'ninja1', + 'hung', + 'erika', + 'eduardo', + 'killers', + 'miller1', + 'islander', + 'jamesbond', + 'intel', + 'jarvis', + '19841984', + '2626', + 'bizzare', + 'blue12', + 'biker', + 'yoyoma', + 'sushi', + 'styles', + 'shitface', + 'series', + 'shanti', + 'spanker', + 'steffi', + 'smart', + 'sphinx', + 'please1', + 'paulie', + 'pistons', + 'tiburon', + 'limited', + 'maxwell1', + 'mdogg', + 'rockies', + 'armstron', + 'alexia', + 'arlene', + 'alejandr', + 'arctic', + 'banger', + 'audio', + 'asimov', + 'augustus', + 'grandpa', + '753951', + '4you', + 'chilly', + 'care1839', + 'chapman', + 'flyfish', + 'fantasia', + 'freefall', + 'santa', + 'sandrine', + 'oreo', + 'ohshit', + 'macbeth', + 'madcat', + 'loveya', + 'mallory', + 'rage', + 'quentin', + 'qwerqwer', + 'project', + 'ramirez', + 'colnago', + 'citizen', + 'chocha', + 'cobalt', + 'crystal1', + 'dabears', + 'nevets', + 'nineinch', + 'broncos1', + 'helene', + 'huge', + 'edgar', + 'epsilon', + 'easter', + 'kestrel', + 'moron', + 'virgil', + 'winston1', + 'warrior1', + 'iiiiiiii', + 'iloveyou2', + '1616', + 'beat', + 'bettina', + 'woowoo', + 'zander', + 'straight', + 'shower', + 'sloppy', + 'specialk', + 'tinkerbe', + 'jellybea', + 'reader', + 'romero', + 'redsox1', + 'ride', + '1215', + '1112', + 'annika', + 'arcadia', + 'answer', + 'baggio', + 'base', + 'guido', + '555666', + 'carmel', + 'cayman', + 'cbr900rr', + 'chips', + 'gabriell', + 'gertrude', + 'glennwei', + 'roxy', + 'sausages', + 'disco', + 'pass1', + 'luna', + 'lovebug', + 'macmac', + 'queenie', + 'puffin', + 'vanguard', + 'trip', + 'trinitro', + 'airwolf', + 'abbott', + 'aaa111', + 'cocaine', + 'cisco', + 'cottage', + 'dayton', + 'deadly', + 'datsun', + 'bricks', + 'bumper', + 'eldorado', + 'kidrock', + 'wizard1', + 'whiskers', + 'wind', + 'wildwood', + 'istheman', + 'interest', + 'italy', + '25802580', + 'benoit', + 'bigones', + 'woodland', + 'wolfpac', + 'strawber', + 'suicide', + '3030', + 'sheba1', + 'sixpack', + 'peace1', + 'physics', + 'pearson', + 'tigger2', + 'toad', + 'megan1', + 'meow', + 'ringo', + 'roll', + 'amsterdam', + '717171', + '686868', + '5424', + 'catherine', + 'canuck', + 'football1', + 'footjob', + 'fulham', + 'seagull', + 'orgy', + 'lobo', + 'mancity', + 'truth', + 'trace', + 'vancouve', + 'vauxhall', + 'acidburn', + 'derf', + 'myspace1', + 'boozer', + 'buttercu', + 'howell', + 'hola', + 'easton', + 'minemine', + 'munch', + 'jared', + '1dragon', + 'biology', + 'bestbuy', + 'bigpoppa', + 'blackout', + 'blowfish', + 'bmw325', + 'bigbob', + 'stream', + 'talisman', + 'tazz', + 'sundevil', + '3333333', + 'skate', + 'shutup', + 'shanghai', + 'shop', + 'spencer1', + 'slowhand', + 'polish', + 'pinky1', + 'tootie', + 'thecrow', + 'leroy', + 'jonathon', + 'jubilee', + 'jingle', + 'martine', + 'matrix1', + 'manowar', + 'michaels', + 'messiah', + 'mclaren', + 'resident', + 'reilly', + 'redbaron', + 'rollins', + 'romans', + 'return', + 'rivera', + 'andromed', + 'athlon', + 'beach1', + 'badgers', + 'guitars', + 'harald', + 'harddick', + 'gotribe', + '6996', + '7grout', + '5wr2i7h8', + '635241', + 'chase1', + 'carver', + 'charlotte', + 'fallout', + 'fiddle', + 'fredrick', + 'fenris', + 'francesc', + 'fortuna', + 'ferguson', + 'fairlane', + 'felipe', + 'felix1', + 'forward', + 'gasman', + 'frost', + 'fucks', + 'sahara', + 'sassy1', + 'dogpound', + 'dogbert', + 'divx1', + 'manila', + 'loretta', + 'priest', + 'pornporn', + 'quasar', + 'venom', + '987987', + 'access1', + 'clippers', + 'daylight', + 'decker', + 'daman', + 'data', + 'dentist', + 'crusty', + 'nathan1', + 'nnnnnnnn', + 'bruno1', + 'bucks', + 'brodie', + 'budapest', + 'kittens', + 'kerouac', + 'mother1', + 'waldo1', + 'wedding', + 'whistler', + 'whatwhat', + 'wanderer', + 'idontkno', + '1942', + '1946', + 'bigdawg', + 'bigpimp', + 'zaqwsx', + '414141', + '3000gt', + '434343', + 'shoes', + 'serpent', + 'starr', + 'smurf', + 'pasword', + 'tommie', + 'thisisit', + 'lake', + 'john1', + 'robotics', + 'redeye', + 'rebelz', + '1011', + 'alatam', + 'asses', + 'asians', + 'bama', + 'banzai', + 'harvest', + 'gonzalez', + 'hair', + 'hanson', + '575757', + '5329', + 'cascade', + 'chinese', + 'fatty', + 'fender1', + 'flower2', + 'funky', + 'sambo', + 'drummer1', + 'dogcat', + 'dottie', + 'oedipus', + 'osama', + 'macleod', + 'prozac', + 'private1', + 'rampage', + 'punch', + 'presley', + 'concord', + 'cook', + 'cinema', + 'cornwall', + 'cleaner', + 'christopher', + 'ciccio', + 'corinne', + 'clutch', + 'corvet07', + 'daemon', + 'bruiser', + 'boiler', + 'hjkl', + 'eyes', + 'egghead', + 'expert', + 'ethan', + 'kasper', + 'mordor', + 'wasted', + 'jamess', + 'iverson3', + 'bluesman', + 'zouzou', + '090909', + '1002', + 'switch', + 'stone1', + '4040', + 'sisters', + 'sexo', + 'shawna', + 'smith1', + 'sperma', + 'sneaky', + 'polska', + 'thewho', + 'terminat', + 'krypton', + 'lawson', + 'library', + 'lekker', + 'jules', + 'johnson1', + 'johann', + 'justus', + 'rockie', + 'romano', + 'aspire', + 'bastards', + 'goodie', + 'cheese1', + 'fenway', + 'fishon', + 'fishin', + 'fuckoff1', + 'girls1', + 'sawyer', + 'dolores', + 'desmond', + 'duane', + 'doomsday', + 'pornking', + 'ramones', + 'rabbits', + 'transit', + 'aaaaa1', + 'clock', + 'delilah', + 'noel', + 'boyz', + 'bookworm', + 'bongo', + 'bunnies', + 'brady', + 'buceta', + 'highbury', + 'henry1', + 'heels', + 'eastern', + 'krissy', + 'mischief', + 'mopar', + 'ministry', + 'vienna', + 'weston', + 'wildone', + 'vodka', + 'jayson', + 'bigbooty', + 'beavis1', + 'betsy', + 'xxxxxx1', + 'yogibear', + '000001', + '0815', + 'zulu', + '420000', + 'september', + 'sigmar', + 'sprout', + 'stalin', + 'peggy', + 'patch', + 'lkjhgfds', + 'lagnaf', + 'rolex', + 'redfox', + 'referee', + '123123123', + '1231', + 'angus1', + 'ariana', + 'ballin', + 'attila', + 'hall', + 'greedy', + 'grunt', + '747474', + 'carpedie', + 'cecile', + 'caramel', + 'foxylady', + 'field', + 'gatorade', + 'gidget', + 'futbol', + 'frosch', + 'saiyan', + 'schmidt', + 'drums', + 'donner', + 'doggy1', + 'drum', + 'doudou', + 'pack', + 'pain', + 'nutmeg', + 'quebec', + 'valdepen', + 'trash', + 'triple', + 'tosser', + 'tuscl', + 'track', + 'comfort', + 'choke', + 'comein', + 'cola', + 'deputy', + 'deadpool', + 'bremen', + 'borders', + 'bronson', + 'break', + 'hotass', + 'hotmail1', + 'eskimo', + 'eggman', + 'koko', + 'kieran', + 'katrin', + 'kordell1', + 'komodo', + 'mone', + 'munich', + 'vvvvvvvv', + 'winger', + 'jaeger', + 'ivan', + 'jackson5', + '2222222', + 'bergkamp', + 'bennie', + 'bigben', + 'zanzibar', + 'worm', + 'xxx123', + 'sunny1', + '373737', + 'services', + 'sheridan', + 'slater', + 'slayer1', + 'snoop', + 'stacie', + 'peachy', + 'thecure', + 'times', + 'little1', + 'jennaj', + 'marquis', + 'middle', + 'rasta69', + '1114', + 'aries', + 'havana', + 'gratis', + 'calgary', + 'checkers', + 'flanker', + 'salope', + 'dirty1', + 'draco', + 'dogface', + 'luv2epus', + 'rainbow6', + 'qwerty123', + 'umpire', + 'turnip', + 'vbnm', + 'tucson', + 'troll', + 'aileen', + 'codered', + 'commande', + 'damon', + 'nana', + 'neon', + 'nico', + 'nightwin', + 'neil', + 'boomer1', + 'bushido', + 'hotmail0', + 'horace', + 'enternow', + 'kaitlyn', + 'keepout', + 'karen1', + 'mindy', + 'mnbv', + 'viewsoni', + 'volcom', + 'wizards', + 'wine', + '1995', + 'berkeley', + 'bite', + 'zach', + 'woodstoc', + 'tarpon', + 'shinobi', + 'starstar', + 'phat', + 'patience', + 'patrol', + 'toolbox', + 'julien', + 'johnny1', + 'joebob', + 'marble', + 'riders', + 'reflex', + '120676', + '1235', + 'angelus', + 'anthrax', + 'atlas', + 'hawks', + 'grandam', + 'harlem', + 'hawaii50', + 'gorgeous', + '655321', + 'cabron', + 'challeng', + 'callisto', + 'firewall', + 'firefire', + 'fischer', + 'flyer', + 'flower1', + 'factory', + 'federal', + 'gambler', + 'frodo1', + 'funk', + 'sand', + 'sam123', + 'scania', + 'dingo', + 'papito', + 'passmast', + 'olive', + 'palermo', + 'ou8123', + 'lock', + 'ranch', + 'pride', + 'randy1', + 'twiggy', + 'travis1', + 'transfer', + 'treetop', + 'addict', + 'admin1', + '963852', + 'aceace', + 'clarissa', + 'cliff', + 'cirrus', + 'clifton', + 'colin', + 'bobdole', + 'bonner', + 'bogus', + 'bonjovi', + 'bootsy', + 'boater', + 'elway7', + 'edison', + 'kelvin', + 'kenny1', + 'moonshin', + 'montag', + 'moreno', + 'wayne1', + 'white1', + 'jazzy', + 'jakejake', + '1994', + '1991', + '2828', + 'blunt', + 'bluejays', + 'beau', + 'belmont', + 'worthy', + 'systems', + 'sensei', + 'southpark', + 'stan', + 'peeper', + 'pharao', + 'pigpen', + 'tomahawk', + 'teensex', + 'leedsutd', + 'larkin', + 'jermaine', + 'jeepster', + 'jimjim', + 'josephin', + 'melons', + 'marlon', + 'matthias', + 'marriage', + 'robocop', + '1003', + '1027', + 'antelope', + 'azsxdc', + 'gordo', + 'hazard', + 'granada', + '8989', + '7894', + 'ceasar', + 'cabernet', + 'cheshire', + 'california', + 'chelle', + 'candy1', + 'fergie', + 'fanny', + 'fidelio', + 'giorgio', + 'fuckhead', + 'ruth', + 'sanford', + 'diego', + 'dominion', + 'devon', + 'panic', + 'longer', + 'mackie', + 'qawsed', + 'trucking', + 'twelve', + 'chloe1', + 'coral', + 'daddyo', + 'nostromo', + 'boyboy', + 'booster', + 'bucky', + 'honolulu', + 'esquire', + 'dynamite', + 'motor', + 'mollydog', + 'wilder', + 'windows1', + 'waffle', + 'wallet', + 'warning', + 'virus', + 'washburn', + 'wealth', + 'vincent1', + 'jabber', + 'jaguars', + 'javelin', + 'irishman', + 'idefix', + 'bigdog1', + 'blue42', + 'blanked', + 'blue32', + 'biteme1', + 'bearcats', + 'blaine', + 'yessir', + 'sylveste', + 'team', + 'stephan', + 'sunfire', + 'tbird', + 'stryker', + '3ip76k2', + 'sevens', + 'sheldon', + 'pilgrim', + 'tenchi', + 'titman', + 'leeds', + 'lithium', + 'lander', + 'linkin', + 'landon', + 'marijuan', + 'mariner', + 'markie', + 'midnite', + 'reddwarf', + '1129', + '123asd', + '12312312', + 'allstar', + 'albany', + 'asdf12', + 'antonia', + 'aspen', + 'hardball', + 'goldfing', + '7734', + '49ers', + 'carlo', + 'chambers', + 'cable', + 'carnage', + 'callum', + 'carlos1', + 'fitter', + 'fandango', + 'festival', + 'flame', + 'gofast', + 'gamma', + 'fucmy69', + 'scrapper', + 'dogwood', + 'django', + 'magneto', + 'loose', + 'premium', + 'addison', + '9999999', + 'abc1234', + 'cromwell', + 'newyear', + 'nichole', + 'bookie', + 'burns', + 'bounty', + 'brown1', + 'bologna', + 'earl', + 'entrance', + 'elway', + 'killjoy', + 'kerry', + 'keenan', + 'kick', + 'klondike', + 'mini', + 'mouser', + 'mohammed', + 'wayer', + 'impreza', + 'irene', + 'insomnia', + '24682468', + '2580', + '24242424', + 'billbill', + 'bellaco', + 'blessing', + 'blues1', + 'bedford', + 'blanco', + 'blunts', + 'stinks', + 'teaser', + 'streets', + 'sf49ers', + 'shovel', + 'solitude', + 'spikey', + 'sonia', + 'pimpdadd', + 'timeout', + 'toffee', + 'lefty', + 'johndoe', + 'johndeer', + 'mega', + 'manolo', + 'mentor', + 'margie', + 'ratman', + 'ridge', + 'record', + 'rhodes', + 'robin1', + '1124', + '1210', + '1028', + '1226', + 'another', + 'babylove', + 'barbados', + 'harbor', + 'gramma', + '646464', + 'carpente', + 'chaos1', + 'fishbone', + 'fireblad', + 'glasgow', + 'frogs', + 'scissors', + 'screamer', + 'salem', + 'scuba1', + 'ducks', + 'driven', + 'doggies', + 'dicky', + 'donovan', + 'obsidian', + 'rams', + 'progress', + 'tottenham', + 'aikman', + 'comanche', + 'corolla', + 'clarke', + 'conway', + 'cumslut', + 'cyborg', + 'dancing', + 'boston1', + 'bong', + 'houdini', + 'helmut', + 'elvisp', + 'edge', + 'keksa12', + 'misha', + 'monty1', + 'monsters', + 'wetter', + 'watford', + 'wiseguy', + 'veronika', + 'visitor', + 'janelle', + '1989', + '1987', + '20202020', + 'biatch', + 'beezer', + 'bigguns', + 'blueball', + 'bitchy', + 'wyoming', + 'yankees2', + 'wrestler', + 'stupid1', + 'sealteam', + 'sidekick', + 'simple1', + 'smackdow', + 'sporting', + 'spiral', + 'smeller', + 'sperm', + 'plato', + 'tophat', + 'test2', + 'theatre', + 'thick', + 'toomuch', + 'leigh', + 'jello', + 'jewish', + 'junkie', + 'maxim', + 'maxime', + 'meadow', + 'remingto', + 'roofer', + '124038', + '1018', + '1269', + '1227', + '123457', + 'arkansas', + 'alberta', + 'aramis', + 'andersen', + 'beaker', + 'barcelona', + 'baltimor', + 'googoo', + 'goochi', + '852456', + '4711', + 'catcher', + 'carman', + 'champ1', + 'chess', + 'fortress', + 'fishfish', + 'firefigh', + 'geezer', + 'rsalinas', + 'samuel1', + 'saigon', + 'scooby1', + 'doors', + 'dick1', + 'devin', + 'doom', + 'dirk', + 'doris', + 'dontknow', + 'load', + 'magpies', + 'manfred', + 'raleigh', + 'vader1', + 'universa', + 'tulips', + 'defense', + 'mygirl', + 'burn', + 'bowtie', + 'bowman', + 'holycow', + 'heinrich', + 'honeys', + 'enforcer', + 'katherine', + 'minerva', + 'wheeler', + 'witch', + 'waterboy', + 'jaime', + 'irving', + '1992', + '23skidoo', + 'bimbo', + 'blue11', + 'birddog', + 'woodman', + 'womble', + 'zildjian', + '030303', + 'stinker', + 'stoppedby', + 'sexybabe', + 'speakers', + 'slugger', + 'spotty', + 'smoke1', + 'polopolo', + 'perfect1', + 'things', + 'torpedo', + 'tender', + 'thrasher', + 'lakeside', + 'lilith', + 'jimmys', + 'jerk', + 'junior1', + 'marsh', + 'masamune', + 'rice', + 'root', + '1214', + 'april1', + 'allgood', + 'bambi', + 'grinch', + '767676', + '5252', + 'cherries', + 'chipmunk', + 'cezer121', + 'carnival', + 'capecod', + 'finder', + 'flint', + 'fearless', + 'goats', + 'funstuff', + 'gideon', + 'savior', + 'seabee', + 'sandro', + 'schalke', + 'salasana', + 'disney1', + 'duckman', + 'options', + 'pancake', + 'pantera1', + 'malice', + 'lookin', + 'love123', + 'lloyd', + 'qwert123', + 'puppet', + 'prayers', + 'union', + 'tracer', + 'crap', + 'creation', + 'cwoui', + 'nascar24', + 'hookers', + 'hollie', + 'hewitt', + 'estrella', + 'erection', + 'ernesto', + 'ericsson', + 'edthom', + 'kaylee', + 'kokoko', + 'kokomo', + 'kimball', + 'morales', + 'mooses', + 'monk', + 'walton', + 'weekend', + 'inter', + 'internal', + '1michael', + '1993', + '19781978', + '25252525', + 'worker', + 'summers', + 'surgery', + 'shibby', + 'shamus', + 'skibum', + 'sheepdog', + 'sex69', + 'spliff', + 'slipper', + 'spoons', + 'spanner', + 'snowbird', + 'slow', + 'toriamos', + 'temp123', + 'tennesse', + 'lakers1', + 'jomama', + 'julio', + 'mazdarx7', + 'rosario', + 'recon', + 'riddle', + 'room', + 'revolver', + '1025', + '1101', + 'barney1', + 'babycake', + 'baylor', + 'gotham', + 'gravity', + 'hallowee', + 'hancock', + '616161', + '515000', + 'caca', + 'cannabis', + 'castor', + 'chilli', + 'fdsa', + 'getout', + 'fuck69', + 'gators1', + 'sail', + 'sable', + 'rumble', + 'dolemite', + 'dork', + 'dickens', + 'duffer', + 'dodgers1', + 'painting', + 'onions', + 'logger', + 'lorena', + 'lookout', + 'magic32', + 'port', + 'poon', + 'prime', + 'twat', + 'coventry', + 'citroen', + 'christmas', + 'civicsi', + 'cocksucker', + 'coochie', + 'compaq1', + 'nancy1', + 'buzzer', + 'boulder', + 'butkus', + 'bungle', + 'hogtied', + 'honor', + 'hero', + 'hotgirls', + 'hilary', + 'heidi1', + 'eggplant', + 'mustang6', + 'mortal', + 'monkey12', + 'wapapapa', + 'wendy1', + 'volleyba', + 'vibrate', + 'vicky', + 'bledsoe', + 'blink', + 'birthday4', + 'woof', + 'xxxxx1', + 'talk', + 'stephen1', + 'suburban', + 'stock', + 'tabatha', + 'sheeba', + 'start1', + 'soccer10', + 'something', + 'starcraft', + 'soccer12', + 'peanut1', + 'plastics', + 'penthous', + 'peterbil', + 'tools', + 'tetsuo', + 'torino', + 'tennis1', + 'termite', + 'ladder', + 'last', + 'lemmein', + 'lakewood', + 'jughead', + 'melrose', + 'megane', + 'reginald', + 'redone', + 'request', + 'angela1', + 'alive', + 'alissa', + 'goodgirl', + 'gonzo1', + 'golden1', + 'gotyoass', + '656565', + '626262', + 'capricor', + 'chains', + 'calvin1', + 'foolish', + 'fallon', + 'getmoney', + 'godfather', + 'gabber', + 'gilligan', + 'runaway', + 'salami', + 'dummy', + 'dungeon', + 'dudedude', + 'dumb', + 'dope', + 'opus', + 'paragon', + 'oxygen', + 'panhead', + 'pasadena', + 'opendoor', + 'odyssey', + 'magellan', + 'lottie', + 'printing', + 'pressure', + 'prince1', + 'trustme', + 'christa', + 'court', + 'davies', + 'neville', + 'nono', + 'bread', + 'buffet', + 'hound', + 'kajak', + 'killkill', + 'mona', + 'moto', + 'mildred', + 'winner1', + 'vixen', + 'whiteboy', + 'versace', + 'winona', + 'voyager1', + 'instant', + 'indy', + 'jackjack', + 'bigal', + 'beech', + 'biggun', + 'blake1', + 'blue99', + 'big1', + 'woods', + 'synergy', + 'success1', + '336699', + 'sixty9', + 'shark1', + 'skin', + 'simba1', + 'sharpe', + 'sebring', + 'spongebo', + 'spunk', + 'springs', + 'sliver', + 'phialpha', + 'password9', + 'pizza1', + 'plane', + 'perkins', + 'pookey', + 'tickling', + 'lexingky', + 'lawman', + 'joe123', + 'jolly', + 'mike123', + 'romeo1', + 'redheads', + 'reserve', + 'apple123', + 'alanis', + 'ariane', + 'antony', + 'backbone', + 'aviation', + 'band', + 'hand', + 'green123', + 'haley', + 'carlitos', + 'byebye', + 'cartman1', + 'camden', + 'chewy', + 'camaross', + 'favorite6', + 'forumwp', + 'franks', + 'ginscoot', + 'fruity', + 'sabrina1', + 'devil666', + 'doughnut', + 'pantie', + 'oldone', + 'paintball', + 'lumina', + 'rainbow1', + 'prosper', + 'total', + 'true', + 'umbrella', + 'ajax', + '951753', + 'achtung', + 'abc12345', + 'compact', + 'color', + 'corn', + 'complete', + 'christi', + 'closer', + 'corndog', + 'deerhunt', + 'darklord', + 'dank', + 'nimitz', + 'brandy1', + 'bowl', + 'breanna', + 'holidays', + 'hetfield', + 'holein1', + 'hillbill', + 'hugetits', + 'east', + 'evolutio', + 'kenobi', + 'whiplash', + 'waldo', + 'wg8e3wjf', + 'wing', + 'istanbul', + 'invis', + '1996', + 'benton', + 'bigjohn', + 'bluebell', + 'beef', + 'beater', + 'benji', + 'bluejay', + 'xyzzy', + 'wrestling', + 'storage', + 'superior', + 'suckdick', + 'taichi', + 'stellar', + 'stephane', + 'shaker', + 'skirt', + 'seymour', + 'semper', + 'splurge', + 'squeak', + 'pearls', + 'playball', + 'pitch', + 'phyllis', + 'pooky', + 'piss', + 'tomas', + 'titfuck', + 'joemama', + 'johnny5', + 'marcello', + 'marjorie', + 'married', + 'maxi', + 'rhubarb', + 'rockwell', + 'ratboy', + 'reload', + 'rooney', + 'redd', + '1029', + '1030', + '1220', + 'anchor', + 'bbking', + 'baritone', + 'gryphon', + 'gone', + '57chevy', + '494949', + 'celeron', + 'fishy', + 'gladiator', + 'fucker1', + 'roswell', + 'dougie', + 'downer', + 'dicker', + 'diva', + 'domingo', + 'donjuan', + 'nympho', + 'omar', + 'praise', + 'racers', + 'trick', + 'trauma', + 'truck1', + 'trample', + 'acer', + 'corwin', + 'cricket1', + 'clemente', + 'climax', + 'denmark', + 'cuervo', + 'notnow', + 'nittany', + 'neutron', + 'native', + 'bosco1', + 'buffa', + 'breaker', + 'hello2', + 'hydro', + 'estelle', + 'exchange', + 'explore', + 'kisskiss', + 'kittys', + 'kristian', + 'montecar', + 'modem', + 'mississi', + 'mooney', + 'weiner', + 'washington', + '20012001', + 'bigdick1', + 'bibi', + 'benfica', + 'yahoo1', + 'striper', + 'tabasco', + 'supra', + '383838', + '456654', + 'seneca', + 'serious', + 'shuttle', + 'socks', + 'stanton', + 'penguin1', + 'pathfind', + 'testibil', + 'thethe', + 'listen', + 'lightning', + 'lighting', + 'jeter2', + 'marma', + 'mark1', + 'metoo', + 'republic', + 'rollin', + 'redleg', + 'redbone', + 'redskin', + 'rocco', + '1245', + 'armand', + 'anthony7', + 'altoids', + 'andrews', + 'barley', + 'away', + 'asswipe', + 'bauhaus', + 'bbbbbb1', + 'gohome', + 'harrier', + 'golfpro', + 'goldeney', + '818181', + '6666666', + '5000', + '5rxypn', + 'cameron1', + 'calling', + 'checker', + 'calibra', + 'fields', + 'freefree', + 'faith1', + 'fist', + 'fdm7ed', + 'finally', + 'giraffe', + 'glasses', + 'giggles', + 'fringe', + 'gate', + 'georgie', + 'scamper', + 'rrpass1', + 'screwyou', + 'duffy', + 'deville', + 'dimples', + 'pacino', + 'ontario', + 'passthie', + 'oberon', + 'quest1', + 'postov1000', + 'puppydog', + 'puffer', + 'raining', + 'protect', + 'qwerty7', + 'trey', + 'tribe', + 'ulysses', + 'tribal', + 'adam25', + 'a1234567', + 'compton', + 'collie', + 'cleopatr', + 'contract', + 'davide', + 'norris', + 'namaste', + 'myrtle', + 'buffalo1', + 'bonovox', + 'buckley', + 'bukkake', + 'burning', + 'burner', + 'bordeaux', + 'burly', + 'hun999', + 'emilie', + 'elmo', + 'enters', + 'enrique', + 'keisha', + 'mohawk', + 'willard', + 'vgirl', + 'whale', + 'vince', + 'jayden', + 'jarrett', + '1812', + '1943', + '222333', + 'bigjim', + 'bigd', + 'zoom', + 'wordup', + 'ziggy1', + 'yahooo', + 'workout', + 'young1', + 'written', + 'xmas', + 'zzzzzz1', + 'surfer1', + 'strife', + 'sunlight', + 'tasha1', + 'skunk', + 'shauna', + 'seth', + 'soft', + 'sprinter', + 'peaches1', + 'planes', + 'pinetree', + 'plum', + 'pimping', + 'theforce', + 'thedon', + 'toocool', + 'leeann', + 'laddie', + 'list', + 'lkjh', + 'lara', + 'joke', + 'jupiter1', + 'mckenzie', + 'matty', + 'rene', + 'redrose', + '1200', + '102938', + 'annmarie', + 'alexa', + 'antares', + 'austin31', + 'ground', + 'goose1', + '737373', + '78945612', + '789987', + '6464', + 'calimero', + 'caster', + 'casper1', + 'cement', + 'chevrolet', + 'chessie', + 'caddy', + 'chill', + 'child', + 'canucks', + 'feeling', + 'favorite', + 'fellatio', + 'f00tball', + 'francine', + 'gateway2', + 'gigi', + 'gamecube', + 'giovanna', + 'rugby1', + 'scheisse', + 'dshade', + 'dudes', + 'dixie1', + 'owen', + 'offshore', + 'olympia', + 'lucas1', + 'macaroni', + 'manga', + 'pringles', + 'puff', + 'tribble', + 'trouble1', + 'ussy', + 'core', + 'clint', + 'coolhand', + 'colonial', + 'colt', + 'debra', + 'darthvad', + 'dealer', + 'cygnusx1', + 'natalie1', + 'newark', + 'husband', + 'hiking', + 'errors', + 'eighteen', + 'elcamino', + 'emmett', + 'emilia', + 'koolaid', + 'knight1', + 'murphy1', + 'volcano', + 'idunno', + '2005', + '2233', + 'block', + 'benito', + 'blueberr', + 'biguns', + 'yamahar1', + 'zapper', + 'zorro1', + '0911', + '3006', + 'sixsix', + 'shopper', + 'siobhan', + 'sextoy', + 'stafford', + 'snowboard', + 'speedway', + 'sounds', + 'pokey', + 'peabody', + 'playboy2', + 'titi', + 'think', + 'toast', + 'toonarmy', + 'lister', + 'lambda', + 'joecool', + 'jonas', + 'joyce', + 'juniper', + 'mercer', + 'max123', + 'manny', + 'massimo', + 'mariposa', + 'met2002', + 'reggae', + 'ricky1', + '1236', + '1228', + '1016', + 'all4one', + 'arianna', + 'baberuth', + 'asgard', + 'gonzales', + '484848', + '5683', + '6669', + 'catnip', + 'chiquita', + 'charisma', + 'capslock', + 'cashmone', + 'chat', + 'figure', + 'galant', + 'frenchy', + 'gizmodo1', + 'girlies', + 'gabby', + 'garner', + 'screwy', + 'doubled', + 'divers', + 'dte4uw', + 'done', + 'dragonfl', + 'maker', + 'locks', + 'rachelle', + 'treble', + 'twinkie', + 'trailer', + 'tropical', + 'acid', + 'crescent', + 'cooking', + 'cococo', + 'cory', + 'dabomb', + 'daffy', + 'dandfa', + 'cyrano', + 'nathanie', + 'briggs', + 'boners', + 'helium', + 'horton', + 'hoffman', + 'hellas', + 'espresso', + 'emperor', + 'killa', + 'kikimora', + 'wanda', + 'w4g8at', + 'verona', + 'ilikeit', + 'iforget', + '1944', + '20002000', + 'birthday1', + 'beatles1', + 'blue1', + 'bigdicks', + 'beethove', + 'blacklab', + 'blazers', + 'benny1', + 'woodwork', + '0069', + '0101', + 'taffy', + 'susie', + 'survivor', + 'swim', + 'stokes', + '4567', + 'shodan', + 'spoiled', + 'steffen', + 'pissed', + 'pavlov', + 'pinnacle', + 'place', + 'petunia', + 'terrell', + 'thirty', + 'toni', + 'tito', + 'teenie', + 'lemonade', + 'lily', + 'lillie', + 'lalakers', + 'lebowski', + 'lalalala', + 'ladyboy', + 'jeeper', + 'joyjoy', + 'mercury1', + 'mantle', + 'mannn', + 'rocknrol', + 'riversid', + 'reeves', + '123aaa', + '11112222', + '121314', + '1021', + '1004', + '1120', + 'allen1', + 'ambers', + 'amstel', + 'ambrose', + 'alice1', + 'alleycat', + 'allegro', + 'ambrosia', + 'alley', + 'australia', + 'hatred', + 'gspot', + 'graves', + 'goodsex', + 'hattrick', + 'harpoon', + '878787', + '8inches', + '4wwvte', + 'cassandr', + 'charlie123', + 'case', + 'chavez', + 'fighting', + 'gabriela', + 'gatsby', + 'fudge', + 'gerry', + 'generic', + 'gareth', + 'fuckme2', + 'samm', + 'sage', + 'seadog', + 'satchmo', + 'scxakv', + 'santafe', + 'dipper', + 'dingle', + 'dizzy', + 'outoutout', + 'madmad', + 'london1', + 'qbg26i', + 'pussy123', + 'randolph', + 'vaughn', + 'tzpvaw', + 'vamp', + 'comedy', + 'comp', + 'cowgirl', + 'coldplay', + 'dawgs', + 'delaney', + 'nt5d27', + 'novifarm', + 'needles', + 'notredam', + 'newness', + 'mykids', + 'bryan1', + 'bouncer', + 'hihihi', + 'honeybee', + 'iceman1', + 'herring', + 'horn', + 'hook', + 'hotlips', + 'dynamo', + 'klaus', + 'kittie', + 'kappa', + 'kahlua', + 'muffy', + 'mizzou', + 'mohamed', + 'musical', + 'wannabe', + 'wednesda', + 'whatup', + 'weller', + 'waterfal', + 'willy1', + 'invest', + 'blanche', + 'bear1', + 'billabon', + 'youknow', + 'zelda', + 'yyyyyy1', + 'zachary1', + '01234567', + '070462', + 'zurich', + 'superstar', + 'storms', + 'tail', + 'stiletto', + 'strat', + '427900', + 'sigmachi', + 'shelter', + 'shells', + 'sexy123', + 'smile1', + 'sophie1', + 'stefano', + 'stayout', + 'somerset', + 'smithers', + 'playmate', + 'pinkfloyd', + 'phish1', + 'payday', + 'thebear', + 'telefon', + 'laetitia', + 'kswbdu', + 'larson', + 'jetta', + 'jerky', + 'melina', + 'metro', + 'revoluti', + 'retire', + 'respect', + '1216', + '1201', + '1204', + '1222', + '1115', + 'archange', + 'barry1', + 'handball', + '676767', + 'chandra', + 'chewbacc', + 'flesh', + 'furball', + 'gocubs', + 'fruit', + 'fullback', + 'gman', + 'gentle', + 'dunbar', + 'dewalt', + 'dominiqu', + 'diver1', + 'dhip6a', + 'olemiss', + 'ollie', + 'mandrake', + 'mangos', + 'pretzel', + 'pusssy', + 'tripleh', + 'valdez', + 'vagabond', + 'clean', + 'comment', + 'crew', + 'clovis', + 'deaths', + 'dandan', + 'csfbr5yy', + 'deadspin', + 'darrel', + 'ninguna', + 'noah', + 'ncc74656', + 'bootsie', + 'bp2002', + 'bourbon', + 'brennan', + 'bumble', + 'books', + 'hose', + 'heyyou', + 'houston1', + 'hemlock', + 'hippo', + 'hornets', + 'hurricane', + 'horseman', + 'hogan', + 'excess', + 'extensa', + 'muffin1', + 'virginie', + 'werdna', + 'idontknow', + 'info', + 'iron', + 'jack1', + '1bitch', + '151nxjmt', + 'bendover', + 'bmwbmw', + 'bills', + 'zaq123', + 'wxcvbn', + 'surprise', + 'supernov', + 'tahoe', + 'talbot', + 'simona', + 'shakur', + 'sexyone', + 'seviyi', + 'sonja', + 'smart1', + 'speed1', + 'pepito', + 'phantom1', + 'playoffs', + 'terry1', + 'terrier', + 'laser1', + 'lite', + 'lancia', + 'johngalt', + 'jenjen', + 'jolene', + 'midori', + 'message', + 'maserati', + 'matteo', + 'mental', + 'miami1', + 'riffraff', + 'ronald1', + 'reason', + 'rhythm', + '1218', + '1026', + '123987', + '1015', + '1103', + 'armada', + 'architec', + 'austria', + 'gotmilk', + 'hawkins', + 'gray', + 'camila', + 'camp', + 'cambridg', + 'charge', + 'camero', + 'flex', + 'foreplay', + 'getoff', + 'glacier', + 'glotest', + 'froggie', + 'gerbil', + 'rugger', + 'sanity72', + 'salesman', + 'donna1', + 'dreaming', + 'deutsch', + 'orchard', + 'oyster', + 'palmtree', + 'ophelia', + 'pajero', + 'm5wkqf', + 'magenta', + 'luckyone', + 'treefrog', + 'vantage', + 'usmarine', + 'tyvugq', + 'uptown', + 'abacab', + 'aaaaaa1', + 'advance', + 'chuck1', + 'delmar', + 'darkange', + 'cyclones', + 'nate', + 'navajo', + 'nope', + 'border', + 'bubba123', + 'building', + 'iawgk2', + 'hrfzlz', + 'dylan1', + 'enrico', + 'encore', + 'emilio', + 'eclipse1', + 'killian', + 'kayleigh', + 'mutant', + 'mizuno', + 'mustang2', + 'video1', + 'viewer', + 'weed420', + 'whales', + 'jaguar1', + 'insight', + '1990', + '159159', + '1love', + 'bliss', + 'bears1', + 'bigtruck', + 'binder', + 'bigboss', + 'blitz', + 'xqgann', + 'yeahyeah', + 'zeke', + 'zardoz', + 'stickman', + 'table', + '3825', + 'signal', + 'sentra', + 'side', + 'shiva', + 'skipper1', + 'singapor', + 'southpaw', + 'sonora', + 'squid', + 'slamdunk', + 'slimjim', + 'placid', + 'photon', + 'placebo', + 'pearl1', + 'test12', + 'therock1', + 'tiger123', + 'leinad', + 'legman', + 'jeepers', + 'joeblow', + 'mccarthy', + 'mike23', + 'redcar', + 'rhinos', + 'rjw7x4', + '1102', + '13576479', + '112211', + 'alcohol', + 'gwju3g', + 'greywolf', + '7bgiqk', + '7878', + '535353', + '4snz9g', + 'candyass', + 'cccccc1', + 'carola', + 'catfight', + 'cali', + 'fister', + 'fosters', + 'finland', + 'frankie1', + 'gizzmo', + 'fuller', + 'royalty', + 'rugrat', + 'sandie', + 'rudolf', + 'dooley', + 'dive', + 'doreen', + 'dodo', + 'drop', + 'oemdlg', + 'out3xf', + 'paddy', + 'opennow', + 'puppy1', + 'qazwsxedc', + 'pregnant', + 'quinn', + 'ramjet', + 'under', + 'uncle', + 'abraxas', + 'corner', + 'creed', + 'cocoa', + 'crown', + 'cows', + 'cn42qj', + 'dancer1', + 'death666', + 'damned', + 'nudity', + 'negative', + 'nimda2k', + 'buick', + 'bobb', + 'braves1', + 'brook', + 'henrik', + 'higher', + 'hooligan', + 'dust', + 'everlast', + 'karachi', + 'mortis', + 'mulligan', + 'monies', + 'motocros', + 'wally1', + 'weapon', + 'waterman', + 'view', + 'willie1', + 'vicki', + 'inspiron', + '1test', + '2929', + 'bigblack', + 'xytfu7', + 'yackwin', + 'zaq1xsw2', + 'yy5rbfsc', + '100100', + '0660', + 'tahiti', + 'takehana', + 'talks', + '332211', + '3535', + 'sedona', + 'seawolf', + 'skydiver', + 'shine', + 'spleen', + 'slash', + 'spjfet', + 'special1', + 'spooner', + 'slimshad', + 'sopranos', + 'spock1', + 'penis1', + 'patches1', + 'terri', + 'thierry', + 'thething', + 'toohot', + 'large', + 'limpone', + 'johnnie', + 'mash4077', + 'matchbox', + 'masterp', + 'maxdog', + 'ribbit', + 'reed', + 'rita', + 'rockin', + 'redhat', + 'rising', + '1113', + '14789632', + '1331', + 'allday', + 'aladin', + 'andrey', + 'amethyst', + 'ariel', + 'anytime', + 'baseball1', + 'athome', + 'basil', + 'goofy1', + 'greenman', + 'gustavo', + 'goofball', + 'ha8fyp', + 'goodday', + '778899', + 'charon', + 'chappy', + 'castillo', + 'caracas', + 'cardiff', + 'capitals', + 'canada1', + 'cajun', + 'catter', + 'freddy1', + 'favorite2', + 'frazier', + 'forme', + 'follow', + 'forsaken', + 'feelgood', + 'gavin', + 'gfxqx686', + 'garlic', + 'sarge', + 'saskia', + 'sanjose', + 'russ', + 'salsa', + 'dilbert1', + 'dukeduke', + 'downhill', + 'longhair', + 'loop', + 'locutus', + 'lockdown', + 'malachi', + 'mamacita', + 'lolipop', + 'rainyday', + 'pumpkin1', + 'punker', + 'prospect', + 'rambo1', + 'rainbows', + 'quake', + 'twin', + 'trinity1', + 'trooper1', + 'aimee', + 'citation', + 'coolcat', + 'crappy', + 'default', + 'dental', + 'deniro', + 'd9ungl', + 'daddys', + 'napoli', + 'nautica', + 'nermal', + 'bukowski', + 'brick', + 'bubbles1', + 'bogota', + 'board', + 'branch', + 'breath', + 'buds', + 'hulk', + 'humphrey', + 'hitachi', + 'evans', + 'ender', + 'export', + 'kikiki', + 'kcchiefs', + 'kram', + 'morticia', + 'montrose', + 'mongo', + 'waqw3p', + 'wizzard', + 'visited', + 'whdbtp', + 'whkzyc', + 'image', + '154ugeiu', + '1fuck', + 'binky', + 'blind', + 'bigred1', + 'blubber', + 'benz', + 'becky1', + 'year2005', + 'wonderfu', + 'wooden', + 'xrated', + '0001', + 'tampabay', + 'survey', + 'tammy1', + 'stuffer', + '3mpz4r', + '3000', + '3some', + 'selina', + 'sierra1', + 'shampoo', + 'silk', + 'shyshy', + 'slapnuts', + 'standby', + 'spartan1', + 'sprocket', + 'sometime', + 'stanley1', + 'poker1', + 'plus', + 'thought', + 'theshit', + 'torture', + 'thinking', + 'lavalamp', + 'light1', + 'laserjet', + 'jediknig', + 'jjjjj1', + 'jocelyn', + 'mazda626', + 'menthol', + 'maximo', + 'margaux', + 'medic1', + 'release', + 'richter', + 'rhino1', + 'roach', + 'renate', + 'repair', + 'reveal', + '1209', + '1234321', + 'amigos', + 'apricot', + 'alexandra', + 'asdfgh1', + 'hairball', + 'hatter', + 'graduate', + 'grimace', + '7xm5rq', + '6789', + 'cartoons', + 'capcom', + 'cheesy', + 'cashflow', + 'carrots', + 'camping', + 'fanatic', + 'fool', + 'format', + 'fleming', + 'girlie', + 'glover', + 'gilmore', + 'gardner', + 'safeway', + 'ruthie', + 'dogfart', + 'dondon', + 'diapers', + 'outsider', + 'odin', + 'opiate', + 'lollol', + 'love12', + 'loomis', + 'mallrats', + 'prague', + 'primetime21', + 'pugsley', + 'program', + 'r29hqq', + 'touch', + 'valleywa', + 'airman', + 'abcdefg1', + 'darkone', + 'cummer', + 'dempsey', + 'damn', + 'nadia', + 'natedogg', + 'nineball', + 'ndeyl5', + 'natchez', + 'newone', + 'normandy', + 'nicetits', + 'buddy123', + 'buddys', + 'homely', + 'husky', + 'iceland', + 'hr3ytm', + 'highlife', + 'holla', + 'earthlin', + 'exeter', + 'eatmenow', + 'kimkim', + 'karine', + 'k2trix', + 'kernel', + 'kirkland', + 'money123', + 'moonman', + 'miles1', + 'mufasa', + 'mousey', + 'wilma', + 'wilhelm', + 'whites', + 'warhamme', + 'instinct', + 'jackass1', + '2277', + '20spanks', + 'blobby', + 'blair', + 'blinky', + 'bikers', + 'blackjack', + 'becca', + 'blue23', + 'xman', + 'wyvern', + '085tzzqi', + 'zxzxzx', + 'zsmj2v', + 'suede', + 't26gn4', + 'sugars', + 'sylvie', + 'tantra', + 'swoosh', + 'swiss', + '4226', + '4271', + '321123', + '383pdjvl', + 'shoe', + 'shane1', + 'shelby1', + 'spades', + 'spain', + 'smother', + 'soup', + 'sparhawk', + 'pisser', + 'photo1', + 'pebble', + 'phones', + 'peavey', + 'picnic', + 'pavement', + 'terra', + 'thistle', + 'tokyo', + 'therapy', + 'lives', + 'linden', + 'kronos', + 'lilbit', + 'linux', + 'johnston', + 'material', + 'melanie1', + 'marbles', + 'redlight', + 'reno', + 'recall', + '1208', + '1138', + '1008', + 'alchemy', + 'aolsucks', + 'alexalex', + 'atticus', + 'auditt', + 'ballet', + 'b929ezzh', + 'goodyear', + 'hanna', + 'griffith', + 'gubber', + '863abgsg', + '7474', + '797979', + '464646', + '543210', + '4zqauf', + '4949', + 'ch5nmk', + 'carlito', + 'chewey', + 'carebear', + 'caleb', + 'checkmat', + 'cheddar', + 'chachi', + 'fever', + 'forgetit', + 'fine', + 'forlife', + 'giants1', + 'gates', + 'getit', + 'gamble', + 'gerhard', + 'galileo', + 'g3ujwg', + 'ganja', + 'rufus1', + 'rushmore', + 'scouts', + 'discus', + 'dudeman', + 'olympus', + 'oscars', + 'osprey', + 'madcow', + 'locust', + 'loyola', + 'mammoth', + 'proton', + 'rabbit1', + 'question', + 'ptfe3xxp', + 'pwxd5x', + 'purple1', + 'punkass', + 'prophecy', + 'uyxnyd', + 'tyson1', + 'aircraft', + 'access99', + 'abcabc', + 'cocktail', + 'colts', + 'civilwar', + 'cleveland', + 'claudia1', + 'contour', + 'clement', + 'dddddd1', + 'cypher', + 'denied', + 'dapzu455', + 'dagmar', + 'daisydog', + 'name', + 'noles', + 'butters', + 'buford', + 'hoochie', + 'hotel', + 'hoser', + 'eddy', + 'ellis', + 'eldiablo', + 'kingrich', + 'mudvayne', + 'motown', + 'mp8o6d', + 'wife', + 'vipergts', + 'italiano', + 'innocent', + '2055', + '2211', + 'beavers', + 'bloke', + 'blade1', + 'yamato', + 'zooropa', + 'yqlgr667', + '050505', + 'zxcvbnm1', + 'zw6syj', + 'suckcock', + 'tango1', + 'swing', + 'stern', + 'stephens', + 'swampy', + 'susanna', + 'tammie', + '445566', + '333666', + '380zliki', + 'sexpot', + 'sexylady', + 'sixtynin', + 'sickboy', + 'spiffy', + 'sleeping', + 'skylark', + 'sparkles', + 'slam', + 'pintail', + 'phreak', + 'places', + 'teller', + 'timtim', + 'tires', + 'thighs', + 'left', + 'latex', + 'llamas', + 'letsdoit', + 'lkjhg', + 'landmark', + 'letters', + 'lizzard', + 'marlins', + 'marauder', + 'metal1', + 'manu', + 'register', + 'righton', + '1127', + 'alain', + 'alcat', + 'amigo', + 'basebal1', + 'azertyui', + 'attract', + 'azrael', + 'hamper', + 'gotenks', + 'golfgti', + 'gutter', + 'hawkwind', + 'h2slca', + 'harman', + 'grace1', + '6chid8', + '789654', + 'canine', + 'casio', + 'cazzo', + 'chamber', + 'cbr900', + 'cabrio', + 'calypso', + 'capetown', + 'feline', + 'flathead', + 'fisherma', + 'flipmode', + 'fungus', + 'goal', + 'g9zns4', + 'full', + 'giggle', + 'gabriel1', + 'fuck123', + 'saffron', + 'dogmeat', + 'dreamcas', + 'dirtydog', + 'dunlop', + 'douche', + 'dresden', + 'dickdick', + 'destiny1', + 'pappy', + 'oaktree', + 'lydia', + 'luft4', + 'puta', + 'prayer', + 'ramada', + 'trumpet1', + 'vcradq', + 'tulip', + 'tracy71', + 'tycoon', + 'aaaaaaa1', + 'conquest', + 'click', + 'chitown', + 'corps', + 'creepers', + 'constant', + 'couples', + 'code', + 'cornhole', + 'danman', + 'dada', + 'density', + 'd9ebk7', + 'cummins', + 'darth', + 'cute', + 'nash', + 'nirvana1', + 'nixon', + 'norbert', + 'nestle', + 'brenda1', + 'bonanza', + 'bundy', + 'buddies', + 'hotspur', + 'heavy', + 'horror', + 'hufmqw', + 'electro', + 'erasure', + 'enough', + 'elisabet', + 'etvww4', + 'ewyuza', + 'eric1', + 'kinder', + 'kenken', + 'kismet', + 'klaatu', + 'musician', + 'milamber', + 'willi', + 'waiting', + 'isacs155', + 'igor', + '1million', + '1letmein', + 'x35v8l', + 'yogi', + 'ywvxpz', + 'xngwoj', + 'zippy1', + '020202', + '****', + 'stonewal', + 'sweeney', + 'story', + 'sentry', + 'sexsexsex', + 'spence', + 'sonysony', + 'smirnoff', + 'star12', + 'solace', + 'sledge', + 'states', + 'snyder', + 'star1', + 'paxton', + 'pentagon', + 'pkxe62', + 'pilot1', + 'pommes', + 'paulpaul', + 'plants', + 'tical', + 'tictac', + 'toes', + 'lighthou', + 'lemans', + 'kubrick', + 'letmein22', + 'letmesee', + 'jys6wz', + 'jonesy', + 'jjjjjj1', + 'jigga', + 'joelle', + 'mate', + 'merchant', + 'redstorm', + 'riley1', + 'rosa', + 'relief', + '14141414', + '1126', + 'allison1', + 'badboy1', + 'asthma', + 'auggie', + 'basement', + 'hartley', + 'hartford', + 'hardwood', + 'gumbo', + '616913', + '57np39', + '56qhxs', + '4mnveh', + 'cake', + 'forbes', + 'fatluvr69', + 'fqkw5m', + 'fidelity', + 'feathers', + 'fresno', + 'godiva', + 'gecko', + 'gladys', + 'gibson1', + 'gogators', + 'fridge', + 'general1', + 'saxman', + 'rowing', + 'sammys', + 'scotts', + 'scout1', + 'sasasa', + 'samoht', + 'dragon69', + 'ducky', + 'dragonball', + 'driller', + 'p3wqaw', + 'nurse', + 'papillon', + 'oneone', + 'openit', + 'optimist', + 'longshot', + 'portia', + 'rapier', + 'pussy2', + 'ralphie', + 'tuxedo', + 'ulrike', + 'undertow', + 'trenton', + 'copenhag', + 'come', + 'delldell', + 'culinary', + 'deltas', + 'mytime', + 'nicky', + 'nickie', + 'noname', + 'noles1', + 'bucker', + 'bopper', + 'bullock', + 'burnout', + 'bryce', + 'hedges', + 'ibilltes', + 'hihje863', + 'hitter', + 'ekim', + 'espana', + 'eatme69', + 'elpaso', + 'envelope', + 'express1', + 'eeeeee1', + 'eatme1', + 'karaoke', + 'kara', + 'mustang5', + 'misses', + 'wellingt', + 'willem', + 'waterski', + 'webcam', + 'jasons', + 'infinite', + 'iloveyou!', + 'jakarta', + 'belair', + 'bigdad', + 'beerme', + 'yoshi', + 'yinyang', + 'zimmer', + 'x24ik3', + '063dyjuy', + '0000007', + 'ztmfcq', + 'stopit', + 'stooges', + 'survival', + 'stockton', + 'symow8', + 'strato', + '2hot4u', + 'ship', + 'simons', + 'skins', + 'shakes', + 'sex1', + 'shield', + 'snacks', + 'softtail', + 'slimed123', + 'pizzaman', + 'pipe', + 'pitt', + 'pathetic', + 'pinto', + 'tigercat', + 'tonton', + 'lager', + 'lizzy', + 'juju', + 'john123', + 'jennings', + 'josiah', + 'jesse1', + 'jordon', + 'jingles', + 'martian', + 'mario1', + 'rootedit', + 'rochard', + 'redwine', + 'requiem', + 'riverrat', + 'rats', + '1117', + '1014', + '1205', + 'althea', + 'allie', + 'amor', + 'amiga', + 'alpina', + 'alert', + 'atreides', + 'banana1', + 'bahamut', + 'hart', + 'golfman', + 'happines', + '7uftyx', + '5432', + '5353', + '5151', + '4747', + 'byron', + 'chatham', + 'chadwick', + 'cherie', + 'foxfire', + 'ffvdj474', + 'freaked', + 'foreskin', + 'gayboy', + 'gggggg1', + 'glenda', + 'gameover', + 'glitter', + 'funny1', + 'scoobydoo', + 'scroll', + 'rudolph', + 'saddle', + 'saxophon', + 'dingbat', + 'digimon', + 'omicron', + 'parsons', + 'ohio', + 'panda1', + 'loloxx', + 'macintos', + 'lululu', + 'lollypop', + 'racer1', + 'queen1', + 'qwertzui', + 'prick', + 'upnfmc', + 'tyrant', + 'trout1', + '9skw5g', + 'aceman', + 'adelaide', + 'acls2h', + 'aaabbb', + 'acapulco', + 'aggie', + 'comcast', + 'craft', + 'crissy', + 'cloudy', + 'cq2kph', + 'custer', + 'd6o8pm', + 'cybersex', + 'davecole', + 'darian', + 'crumbs', + 'daisey', + 'davedave', + 'dasani', + 'needle', + 'mzepab', + 'myporn', + 'narnia', + 'nineteen', + 'booger1', + 'bravo1', + 'budgie', + 'btnjey', + 'highlander', + 'hotel6', + 'humbug', + 'edwin', + 'ewtosi', + 'kristin1', + 'kobe', + 'knuckles', + 'keith1', + 'katarina', + 'muff', + 'muschi', + 'montana1', + 'wingchun', + 'wiggle', + 'whatthe', + 'walking', + 'watching', + 'vette1', + 'vols', + 'virago', + 'intj3a', + 'ishmael', + 'intern', + 'jachin', + 'illmatic', + '199999', + '2010', + 'beck', + 'blender', + 'bigpenis', + 'bengal', + 'blue1234', + 'your', + 'zaqxsw', + 'xray', + 'xxxxxxx1', + 'zebras', + 'yanks', + 'worlds', + 'tadpole', + 'stripes', + 'svetlana', + '3737', + '4343', + '3728', + '4444444', + '368ejhih', + 'solar', + 'sonne', + 'smalls', + 'sniffer', + 'sonata', + 'squirts', + 'pitcher', + 'playstation', + 'pktmxr', + 'pescator', + 'points', + 'texaco', + 'lesbos', + 'lilian', + 'l8v53x', + 'jo9k2jw2', + 'jimbeam', + 'josie', + 'jimi', + 'jupiter2', + 'jurassic', + 'marines1', + 'maya', + 'rocket1', + 'ringer', + '14725836', + '12345679', + '1219', + '123098', + '1233', + 'alessand', + 'althor', + 'angelika', + 'arch', + 'armando', + 'alpha123', + 'basher', + 'barefeet', + 'balboa', + 'bbbbb1', + 'banks', + 'badabing', + 'harriet', + 'gopack', + 'golfnut', + 'gsxr1000', + 'gregory1', + '766rglqy', + '8520', + '753159', + '8dihc6', + '69camaro', + '666777', + 'cheeba', + 'chino', + 'calendar', + 'cheeky', + 'camel1', + 'fishcake', + 'falling', + 'flubber', + 'giuseppe', + 'gianni', + 'gloves', + 'gnasher23', + 'frisbee', + 'fuzzy1', + 'fuzzball', + 'sauce', + 'save13tx', + 'schatz', + 'russell1', + 'sandra1', + 'scrotum', + 'scumbag', + 'sabre', + 'samdog', + 'dripping', + 'dragon12', + 'dragster', + 'paige', + 'orwell', + 'mainland', + 'lunatic', + 'lonnie', + 'lotion', + 'maine', + 'maddux', + 'qn632o', + 'poophead', + 'rapper', + 'porn4life', + 'producer', + 'rapunzel', + 'tracks', + 'velocity', + 'vanessa1', + 'ulrich', + 'trueblue', + 'vampire1', + 'abacus', + '902100', + 'crispy', + 'corky', + 'crane', + 'chooch', + 'd6wnro', + 'cutie', + 'deal', + 'dabulls', + 'dehpye', + 'navyseal', + 'njqcw4', + 'nownow', + 'nigger1', + 'nightowl', + 'nonenone', + 'nightmar', + 'bustle', + 'buddy2', + 'boingo', + 'bugman', + 'bulletin', + 'bosshog', + 'bowie', + 'hybrid', + 'hillside', + 'hilltop', + 'hotlegs', + 'honesty', + 'hzze929b', + 'hhhhh1', + 'hellohel', + 'eloise', + 'evilone', + 'edgewise', + 'e5pftu', + 'eded', + 'embalmer', + 'excalibur', + 'elefant', + 'kenzie', + 'karl', + 'karin', + 'killah', + 'kleenex', + 'mouses', + 'mounta1n', + 'motors', + 'mutley', + 'muffdive', + 'vivitron', + 'winfield', + 'wednesday', + 'w00t88', + 'iloveit', + 'jarjar', + 'incest', + 'indycar', + '17171717', + '1664', + '17011701', + '222777', + '2663', + 'beelch', + 'benben', + 'yitbos', + 'yyyyy1', + 'yasmin', + 'zapata', + 'zzzzz1', + 'stooge', + 'tangerin', + 'taztaz', + 'stewart1', + 'summer69', + 'sweetness', + 'system1', + 'surveyor', + 'stirling', + '3qvqod', + '3way', + '456321', + 'sizzle', + 'simhrq', + 'shrink', + 'shawnee', + 'someday', + 'sparty', + 'ssptx452', + 'sphere', + 'spark', + 'slammed', + 'sober', + 'persian', + 'peppers', + 'ploppy', + 'pn5jvw', + 'poobear', + 'pianos', + 'plaster', + 'testme', + 'tiff', + 'thriller', + 'larissa', + 'lennox', + 'jewell', + 'master12', + 'messier', + 'rockey', + '1229', + '1217', + '1478', + '1009', + 'anastasi', + 'almighty', + 'amonra', + 'aragon', + 'argentin', + 'albino', + 'azazel', + 'grinder', + '6uldv8', + '83y6pv', + '8888888', + '4tlved', + '515051', + 'carsten', + 'changes', + 'flanders', + 'flyers88', + 'ffffff1', + 'firehawk', + 'foreman', + 'firedog', + 'flashman', + 'ggggg1', + 'gerber', + 'godspeed', + 'galway', + 'giveitup', + 'funtimes', + 'gohan', + 'giveme', + 'geryfe', + 'frenchie', + 'sayang', + 'rudeboy', + 'savanna', + 'sandals', + 'devine', + 'dougal', + 'drag0n', + 'dga9la', + 'disaster', + 'desktop', + 'only', + 'onlyone', + 'otter', + 'pandas', + 'mafia', + 'lombard', + 'luckys', + 'lovejoy', + 'lovelife', + 'manders', + 'product', + 'qqh92r', + 'qcmfd454', + 'pork', + 'radar1', + 'punani', + 'ptbdhw', + 'turtles', + 'undertaker', + 'trs8f7', + 'tramp', + 'ugejvp', + 'abba', + '911turbo', + 'acdc', + 'abcd123', + 'clever', + 'corina', + 'cristian', + 'create', + 'crash1', + 'colony', + 'crosby', + 'delboy', + 'daniele', + 'davinci', + 'daughter', + 'notebook', + 'niki', + 'nitrox', + 'borabora', + 'bonzai', + 'budd', + 'brisbane', + 'hotter', + 'heeled', + 'heroes', + 'hooyah', + 'hotgirl', + 'i62gbq', + 'horse1', + 'hills', + 'hpk2qc', + 'epvjb6', + 'echo', + 'korean', + 'kristie', + 'mnbvc', + 'mohammad', + 'mind', + 'mommy1', + 'munster', + 'wade', + 'wiccan', + 'wanted', + 'jacket', + '2369', + 'bettyboo', + 'blondy', + 'bismark', + 'beanbag', + 'bjhgfi', + 'blackice', + 'yvtte545', + 'ynot', + 'yess', + 'zlzfrh', + 'wolvie', + '007bond', + '******', + 'tailgate', + 'tanya1', + 'sxhq65', + 'stinky1', + '3234412', + '3ki42x', + 'seville', + 'shimmer', + 'sheryl', + 'sienna', + 'shitshit', + 'skillet', + 'seaman', + 'sooners1', + 'solaris', + 'smartass', + 'pastor', + 'pasta', + 'pedros', + 'pennywis', + 'pfloyd', + 'tobydog', + 'thetruth', + 'lethal', + 'letme1n', + 'leland', + 'jenifer', + 'mario66', + 'micky', + 'rocky2', + 'rewq', + 'ripped', + 'reindeer', + '1128', + '1207', + '1104', + '1432', + 'aprilia', + 'allstate', + 'alyson', + 'bagels', + 'basic', + 'baggies', + 'barb', + 'barrage', + 'greatest', + 'gomez', + 'guru', + 'guard', + '72d5tn', + '606060', + '4wcqjn', + 'caldwell', + 'chance1', + 'catalog', + 'faust', + 'film', + 'flange', + 'fran', + 'fartman', + 'geil', + 'gbhcf2', + 'fussball', + 'glen', + 'fuaqz4', + 'gameboy', + 'garnet', + 'geneviev', + 'rotary', + 'seahawk', + 'russel', + 'saab', + 'seal', + 'samadams', + 'devlt4', + 'ditto', + 'drevil', + 'drinker', + 'deuce', + 'dipstick', + 'donut', + 'octopus', + 'ottawa', + 'losangel', + 'loverman', + 'porky', + 'q9umoz', + 'rapture', + 'pump', + 'pussy4me', + 'university', + 'triplex', + 'ue8fpw', + 'trent', + 'trophy', + 'turbos', + 'troubles', + 'agent', + 'aaa340', + 'churchil', + 'crazyman', + 'consult', + 'creepy', + 'craven', + 'class', + 'cutiepie', + 'ddddd1', + 'dejavu', + 'cuxldv', + 'nettie', + 'nbvibt', + 'nikon', + 'niko', + 'norwood', + 'nascar1', + 'nolan', + 'bubba2', + 'boobear', + 'boogers', + 'buff', + 'bullwink', + 'bully', + 'bulldawg', + 'horsemen', + 'escalade', + 'editor', + 'eagle2', + 'dynamic', + 'ella', + 'efyreg', + 'edition', + 'kidney', + 'minnesot', + 'mogwai', + 'morrow', + 'msnxbi', + 'moonlight', + 'mwq6qlzo', + 'wars', + 'werder', + 'verygood', + 'voodoo1', + 'wheel', + 'iiiiii1', + '159951', + '1624', + '1911a1', + '2244', + 'bellagio', + 'bedlam', + 'belkin', + 'bill1', + 'woodrow', + 'xirt2k', + 'worship', + '??????', + 'tanaka', + 'swift', + 'susieq', + 'sundown', + 'sukebe', + 'tales', + 'swifty', + '2fast4u', + 'senate', + 'sexe', + 'sickness', + 'shroom', + 'shaun', + 'seaweed', + 'skeeter1', + 'status', + 'snicker', + 'sorrow', + 'spanky1', + 'spook', + 'patti', + 'phaedrus', + 'pilots', + 'pinch', + 'peddler', + 'theo', + 'thumper1', + 'tessie', + 'tiger7', + 'tmjxn151', + 'thematri', + 'l2g7k3', + 'letmeinn', + 'lazy', + 'jeffjeff', + 'joan', + 'johnmish', + 'mantra', + 'mariana', + 'mike69', + 'marshal', + 'mart', + 'mazda6', + 'riptide', + 'robots', + 'rental', + '1107', + '1130', + '142857', + '11001001', + '1134', + 'armored', + 'alvin', + 'alec', + 'allnight', + 'alright', + 'amatuers', + 'bartok', + 'attorney', + 'astral', + 'baboon', + 'bahamas', + 'balls1', + 'bassoon', + 'hcleeb', + 'happyman', + 'granite', + 'graywolf', + 'golf1', + 'gomets', + '8vjzus', + '7890', + '789123', + '8uiazp', + '5757', + '474jdvff', + '551scasi', + '50cent', + 'camaro1', + 'cherry1', + 'chemist', + 'final', + 'firenze', + 'fishtank', + 'farrell', + 'freewill', + 'glendale', + 'frogfrog', + 'gerhardt', + 'ganesh', + 'same', + 'scirocco', + 'devilman', + 'doodles', + 'dinger', + 'okinawa', + 'olympic', + 'nursing', + 'orpheus', + 'ohmygod', + 'paisley', + 'pallmall', + 'null', + 'lounge', + 'lunchbox', + 'manhatta', + 'mahalo', + 'mandarin', + 'qwqwqw', + 'qguvyt', + 'pxx3eftp', + 'president', + 'rambler', + 'puzzle', + 'poppy1', + 'turk182', + 'trotter', + 'vdlxuc', + 'trish', + 'tugboat', + 'valiant', + 'tracie', + 'uwrl7c', + 'chris123', + 'coaster', + 'cmfnpu', + 'decimal', + 'debbie1', + 'dandy', + 'daedalus', + 'dede', + 'natasha1', + 'nissan1', + 'nancy123', + 'nevermin', + 'napalm', + 'newcastle', + 'boats', + 'branden', + 'britt', + 'bonghit', + 'hester', + 'ibxnsm', + 'hhhhhh1', + 'holger', + 'durham', + 'edmonton', + 'erwin', + 'equinox', + 'dvader', + 'kimmy', + 'knulla', + 'mustafa', + 'monsoon', + 'mistral', + 'morgana', + 'monica1', + 'mojave', + 'month', + 'monterey', + 'mrbill', + 'vkaxcs', + 'victor1', + 'wacker', + 'wendell', + 'violator', + 'vfdhif', + 'wilson1', + 'wavpzt', + 'verena', + 'wildstar', + 'winter99', + 'iqzzt580', + 'jarrod', + 'imback', + '1914', + '19741974', + '1monkey', + '1q2w3e4r5t', + '2500', + '2255', + 'blank', + 'bigshow', + 'bigbucks', + 'blackcoc', + 'zoomer', + 'wtcacq', + 'wobble', + 'xmen', + 'xjznq5', + 'yesterda', + 'yhwnqc', + 'zzzxxx', + 'streak', + '393939', + '2fchbg', + 'skinhead', + 'skilled', + 'shakira', + 'shaft', + 'shadow12', + 'seaside', + 'sigrid', + 'sinful', + 'silicon', + 'smk7366', + 'snapshot', + 'sniper1', + 'soccer11', + 'staff', + 'slap', + 'smutty', + 'peepers', + 'pleasant', + 'plokij', + 'pdiddy', + 'pimpdaddy', + 'thrust', + 'terran', + 'topaz', + 'today1', + 'lionhear', + 'littlema', + 'lauren1', + 'lincoln1', + 'lgnu9d', + 'laughing', + 'juneau', + 'methos', + 'medina', + 'merlyn', + 'rogue1', + 'romulus', + 'redshift', + '1202', + '1469', + '12locked', + 'arizona1', + 'alfarome', + 'al9agd', + 'aol123', + 'altec', + 'apollo1', + 'arse', + 'baker1', + 'bbb747', + 'bach', + 'axeman', + 'astro1', + 'hawthorn', + 'goodfell', + 'hawks1', + 'gstring', + 'hannes', + '8543852', + '868686', + '4ng62t', + '554uzpad', + '5401', + '567890', + '5232', + 'catfood', + 'frame', + 'flow', + 'fire1', + 'flipflop', + 'fffff1', + 'fozzie', + 'fluff', + 'garrison', + 'fzappa', + 'furious', + 'round', + 'rustydog', + 'sandberg', + 'scarab', + 'satin', + 'ruger', + 'samsung1', + 'destin', + 'diablo2', + 'dreamer1', + 'detectiv', + 'dominick', + 'doqvq3', + 'drywall', + 'paladin1', + 'papabear', + 'offroad', + 'panasonic', + 'nyyankee', + 'luetdi', + 'qcfmtz', + 'pyf8ah', + 'puddles', + 'privacy', + 'rainer', + 'pussyeat', + 'ralph1', + 'princeto', + 'trivia', + 'trewq', + 'tri5a3', + 'advent', + '9898', + 'agyvorc', + 'clarkie', + 'coach1', + 'courier', + 'contest', + 'christo', + 'corinna', + 'chowder', + 'concept', + 'climbing', + 'cyzkhw', + 'davidb', + 'dad2ownu', + 'days', + 'daredevi', + 'de7mdf', + 'nose', + 'necklace', + 'nazgul', + 'booboo1', + 'broad', + 'bonzo', + 'brenna', + 'boot', + 'butch1', + 'huskers1', + 'hgfdsa', + 'hornyman', + 'elmer', + 'elektra', + 'england1', + 'elodie', + 'kermit1', + 'knife', + 'kaboom', + 'minute', + 'modern', + 'motherfucker', + 'morten', + 'mocha', + 'monday1', + 'morgoth', + 'ward', + 'weewee', + 'weenie', + 'walters', + 'vorlon', + 'website', + 'wahoo', + 'ilovegod', + 'insider', + 'jayman', + '1911', + '1dallas', + '1900', + '1ranger', + '201jedlz', + '2501', + '1qaz', + 'bertram', + 'bignuts', + 'bigbad', + 'beebee', + 'billows', + 'belize', + 'bebe', + 'wvj5np', + 'wu4etd', + 'yamaha1', + 'wrinkle5', + 'zebra1', + 'yankee1', + 'zoomzoom', + '09876543', + '0311', + '?????', + 'stjabn', + 'tainted', + '3tmnej', + 'shoot', + 'skooter', + 'skelter', + 'sixteen', + 'starlite', + 'smack', + 'spice1', + 'stacey1', + 'smithy', + 'perrin', + 'pollux', + 'peternorth', + 'pixie', + 'paulina', + 'piston', + 'pick', + 'poets', + 'pine', + 'toons', + 'tooth', + 'topspin', + 'kugm7b', + 'legends', + 'jeepjeep', + 'juliana', + 'joystick', + 'junkmail', + 'jojojojo', + 'jonboy', + 'judge', + 'midland', + 'meteor', + 'mccabe', + 'matter', + 'mayfair', + 'meeting', + 'merrill', + 'raul', + 'riches', + 'reznor', + 'rockrock', + 'reboot', + 'reject', + 'robyn', + 'renee1', + 'roadway', + 'rasta220', + '1411', + '1478963', + '1019', + 'archery', + 'allman', + 'andyandy', + 'barks', + 'bagpuss', + 'auckland', + 'gooseman', + 'hazmat', + 'gucci', + 'guns', + 'grammy', + 'happydog', + 'greek', + '7kbe9d', + '7676', + '6bjvpe', + '5lyedn', + '5858', + '5291', + 'charlie2', + 'chas', + 'c7lrwu', + 'candys', + 'chateau', + 'ccccc1', + 'cardinals', + 'fear', + 'fihdfv', + 'fortune12', + 'gocats', + 'gaelic', + 'fwsadn', + 'godboy', + 'gldmeo', + 'fx3tuo', + 'fubar1', + 'garland', + 'generals', + 'gforce', + 'rxmtkp', + 'rulz', + 'sairam', + 'dunhill', + 'division', + 'dogggg', + 'detect', + 'details', + 'doll', + 'drinks', + 'ozlq6qwm', + 'ov3ajy', + 'lockout', + 'makayla', + 'macgyver', + 'mallorca', + 'loves', + 'prima', + 'pvjegu', + 'qhxbij', + 'raphael', + 'prelude1', + 'totoro', + 'tusymo', + 'trousers', + 'tunnel', + 'valeria', + 'tulane', + 'turtle1', + 'tracy1', + 'aerosmit', + 'abbey1', + 'address', + 'clticic', + 'clueless', + 'cooper1', + 'comets', + 'collect', + 'corbin', + 'delpiero', + 'derick', + 'cyprus', + 'dante1', + 'dave1', + 'nounours', + 'neal', + 'nexus6', + 'nero', + 'nogard', + 'norfolk', + 'brent1', + 'booyah', + 'bootleg', + 'buckaroo', + 'bulls23', + 'bulls1', + 'booper', + 'heretic', + 'icecube', + 'hellno', + 'hounds', + 'honeydew', + 'hooters1', + 'hoes', + 'howie', + 'hevnm4', + 'hugohugo', + 'eighty', + 'epson', + 'evangeli', + 'eeeee1', + 'eyphed' +]; \ No newline at end of file diff --git a/static/js/mellt.js b/static/js/mellt.js new file mode 100644 index 0000000..eb9da99 --- /dev/null +++ b/static/js/mellt.js @@ -0,0 +1,209 @@ +'use strict'; +/** + * Mellt + * + * Tests the strength of a password by calculating how long it would take to + * brute force it. + * + * @version 0.1.0 + * @link http://mel.lt/ The homepage for this script. + * @link http://www.hammerofgod.com/passwordcheck.aspx Much of this is based + * on the description of Thor's Godly Privacy password strength checker, + * however the actual code below is all my own. + * @link http://xato.net/passwords/more-top-worst-passwords/ The included + * common passwords list is from Mark Burnett's password collection (which + * is excellent). You can of course use your own password file instead. + */ +var Mellt = function() { + + /** + * @var integer HashesPerSecond The number of attempts per second you expect + * an attacker to be able to attempt. Set to 1 billion by default. + */ + this.HashesPerSecond = 1000000000; + + /** + * @var string CommonPasswords A variable containing an array of common + * passwords to check against. If you include common-passwords.js in your + * HTML after including Mellt.js, the contents of that file will be used + * if this isn't set. + * Set this to null (and don't include common-passwords.js) to skip + * checking common passwords. + */ + this.CommonPasswords = null; + + /** + * @var array $CharacterSets An array of strings, each string containing a + * character set. These should proceed in the order of simplest (0-9) to most + * complex (all characters). More complex = more characters. + */ + this.CharacterSets = [ + // We're making some guesses here about human nature (again much of this is + // based on the TGP password strength checker, and Timothy "Thor" Mullen + // deserves the credit for the thinking behind this). Basically we're combining + // what we know about users (SHIFT+numbers are more common than other + // punctuation for example) combined with how an attacker will attack a + // password (most common letters first, expanding outwards). + // + // If you want to support passwords that use non-english characters, and + // your attacker knows this (for example, a Russian site would be expected + // to contain passwords in Russian characters) add your characters to one of + // the sets below, or create new sets and insert them in the right places. + "0123456789", + "abcdefghijklmnopqrstuvwxyz", + "abcdefghijklmnopqrstuvwxyz0123456789", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-=_+", + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-=_+[]\"{}|;':,./<>?`~" + ]; +}; +Mellt.prototype = { + + CommonPasswords: null, + + /** + * Tests password strength by simulating how long it would take a cracker to + * brute force your password. + * + * Also optionally tests against a list of common passwords (contained in an + * external file) to weed out things like "password", which from a pure brute + * force perspective would be harder to break if it wasn't so common. + * + * The character sets being used in this checker assume English (ASCII) + * characters (no umlauts for example). If you run a non-english site, and you + * suspect the crackers will realize this, you may want to modify the + * character set to include the characters in your language. + * + * @param string $password The password to test the strength of + * @return integer Returns an integer specifying how many days it would take + * to brute force the password (at 1 billion checks a second) or -1 to + * indicate the password was found in the common passwords file. Obviously if + * they don't have direct access to the hashed passwords this time would be + * longer, and even then most computers (at the time of this writing) won't be + * able to test 1 billion hashes a second, but this function measures worst + * case scenario, so... I would recommend you require at least 30 days to brute + * force a password, obviously more if you're a bank or other secure system. + * @throws Exception If an error is encountered. + */ + CheckPassword: function(password) { + + // First check passwords in the common password file if available. + // We do this because "password" takes 129 seconds, but is the first + // thing an attacker will try. + if (!this.CommonPasswords && Mellt.prototype.CommonPasswords) { + this.CommonPasswords = Mellt.prototype.CommonPasswords; + } + if (this.CommonPasswords) { + + var text = password.toLowerCase(); + for (var t=0; t-1) { + baseKey = characterSetKey; + base = characterSet; + foundChar = true; + break; + } + } + // If the character we were looking for wasn't anywhere in any of the + // character sets, assign the largest (last) character set as default. + if (!foundChar) { + base = this.CharacterSets[this.CharacterSets.length-1]; + break; + } + } + + // Starting at the first character, figure out it's position in the character set + // and how many attempts will take to get there. For example, say your password + // was an integer (a bank card PIN number for example): + // 0 (or 0000 if you prefer) would be the very first password they attempted by the attacker. + // 9999 would be the last password they attempted (assuming 4 characters). + // Thus a password/PIN of 6529 would take 6529 attempts until the attacker found + // the proper combination. The same logic words for alphanumeric passwords, just + // with a larger number of possibilities for each position in the password. The + // key thing to note is the attacker doesn't need to test the entire range (every + // possible combination of all characters) they just need to get to the point in + // the list of possibilities that is your password. They can (in this example) + // ignore anything between 6530 and 9999. Using this logic, 'aaa' would be a worse + // password than 'zzz', because the attacker would encounter 'aaa' first. + var attempts = 0; + var charactersInBase = base.length; + var charactersInPassword = password.length; + for (var position=0; position1000000000) { + return 1000000000; + } + + return Math.round(days); + + } + +}; diff --git a/static/js/password.js b/static/js/password.js new file mode 100644 index 0000000..f80e551 --- /dev/null +++ b/static/js/password.js @@ -0,0 +1,90 @@ +'use strict'; +/* global $ Mellt */ + +const mellt = new Mellt(); + +function checkMatch(){ + $('#submit').prop('title',"You need to type your password again before you can save it. "); + + // They match + if ( $('#p1').val() === $('#p2').val() ) { + $('#submit').prop('disabled',false).prop('title',"Click here to save your password. "); + } + + // User has retyped, but they don't match yet + else if ($('#p2').val()!=='') { + $('#password-help').text("Those passwords don't match! ").css({'color':'#fb6e3d'}); + $('#submit').prop('disabled',true).prop('title',"You need to type the same password twice before you can save it. "); + } + +} + +// On page load +$(function(){ + + // On typing password + $('.password').keyup(function(){ + + // Nothing entered + if ( $('#p1').val()==='' && $('#p2').val()==='' ){ + $('#password-help').hide(); + $('#submit').prop('disabled',true).prop('title',"You need to enter a password first. "); + } + + // Only second password entered + else if ($('#p1').val()==='') { + $('#password-help').show().text("Those passwords don't match! "); + $('#submit').prop('disabled',true).prop('title',"You need to type the same password twice correctly before you can save it. "); + } + + // At least first password entered + else { + $('#password-help').show(); + + // Check first password + var daysToCrack = mellt.CheckPassword($('#p1').val()); + + // Not good enough + if (daysToCrack<0) { + $('#password-help').text("That's is one of the world's most commonly used passwords. You may not use it for Tracman and should not use it anywhere. ").css({'color':'#fb6e3d'}); + $('#submit').prop('disabled',true).prop('title',"You need to come up with a better password. "); + } + else if (daysToCrack<10) { + $('#password-help').text("That password isn't good enough. It could be cracked in "+daysToCrack+" day"+(daysToCrack!=1?'s':'')+". Try something a little longer or more complex. ").css({'color':'#fb6e3d'}); + $('#submit').prop('disabled',true).prop('title',"You need to come up with a better password. "); + } + + // Good enough + else if (daysToCrack<=30) { + $('#password-help').text("That password is good enough, but it could still be cracked in "+daysToCrack+" days. ").css({'color':'#eee'}); + checkMatch(); + } + else if (daysToCrack<=365) { + $('#password-help').text("That password is pretty good. It would take "+daysToCrack+" days to crack. ").css({'color':'#8ae137'}); + checkMatch(); + } + else { + var years = Math.round(daysToCrack / 365 * 10) / 10; + if (years>1000000) { + years = (Math.round(years/1000000*10)/10)+' million'; + } + if (years>1000) { + years = (Math.round(years/1000))+' thousand'; + } + $('#password-help').text("That password is great! It could take up to "+years+" years to crack!").css({'color':'#8ae137'}); + checkMatch(); + } + } + + }); + + // On checking 'show' + $('#show').click(function(){ + if ($(this).is(':checked')) { + $('.password').attr('type','text'); + } else { + $('.password').attr('type','password'); + } + }); + +}); From 12932694295b60c606a1a3efbbb66ff22e8e1820 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 15 Apr 2017 10:48:37 -0400 Subject: [PATCH 071/143] Added server-side logic to test --- config/routes/test.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/config/routes/test.js b/config/routes/test.js index a25f0cf..13a1fce 100644 --- a/config/routes/test.js +++ b/config/routes/test.js @@ -1,6 +1,7 @@ 'use strict'; const router = require('express').Router(), + mellt = require('mellt'), mw = require('../middleware.js'), mail = require('../mail.js'); @@ -27,9 +28,16 @@ router .get('/password', (req,res)=>{ res.render('password'); }) - .post('/password', (req,res)=>{ - //TODO: Server-side checks - res.sendStatus(200); + .post('/password', (req,res,next)=>{ + let daysToCrack = mellt.CheckPassword(req.body.password); + if (daysToCrack<10) { + let err = new Error(`That password could be cracked in ${daysToCrack} days! Come up with a more complex password that would take at least 10 days to crack. `) + mw.throwErr(err); + next(err); + } + else { + res.sendStatus(200); + } }); module.exports = router; \ No newline at end of file From 0793c44a6a8705b3e653263689f4fad57123db98 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 15 Apr 2017 10:48:56 -0400 Subject: [PATCH 072/143] Fixed password page formatting --- static/js/password.js | 6 +++++- views/password.html | 19 +++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/static/js/password.js b/static/js/password.js index f80e551..7f63c77 100644 --- a/static/js/password.js +++ b/static/js/password.js @@ -49,8 +49,12 @@ $(function(){ $('#password-help').text("That's is one of the world's most commonly used passwords. You may not use it for Tracman and should not use it anywhere. ").css({'color':'#fb6e3d'}); $('#submit').prop('disabled',true).prop('title',"You need to come up with a better password. "); } + else if (daysToCrack<1) { + $('#password-help').text("That password is pretty bad. It could be cracked in less than a day. Try adding more words, numbers, or symbols. ").css({'color':'#fb6e3d'}); + $('#submit').prop('disabled',true).prop('title',"You need to come up with a better password. "); + } else if (daysToCrack<10) { - $('#password-help').text("That password isn't good enough. It could be cracked in "+daysToCrack+" day"+(daysToCrack!=1?'s':'')+". Try something a little longer or more complex. ").css({'color':'#fb6e3d'}); + $('#password-help').text("That password isn't good enough. It could be cracked in "+daysToCrack+" day"+(daysToCrack!=1?'s':'')+". Try adding another word, number, or symbol. ").css({'color':'#fb6e3d'}); $('#submit').prop('disabled',true).prop('title',"You need to come up with a better password. "); } diff --git a/views/password.html b/views/password.html index 2e1e54a..1218794 100644 --- a/views/password.html +++ b/views/password.html @@ -14,22 +14,29 @@

Your password must be at least 8 characters long. You can use any letter, number, symbol, emoji, or spaces. Your password will be stored as a salted hash on the server.

- - + + + + - - + +
From e1838dfaee41b4d7e0350996c063aff12a5fb504 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 15 Apr 2017 10:53:12 -0400 Subject: [PATCH 073/143] Added limit for very good passwords --- static/js/password.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/static/js/password.js b/static/js/password.js index 7f63c77..bdefddd 100644 --- a/static/js/password.js +++ b/static/js/password.js @@ -64,10 +64,10 @@ $(function(){ checkMatch(); } else if (daysToCrack<=365) { - $('#password-help').text("That password is pretty good. It would take "+daysToCrack+" days to crack. ").css({'color':'#8ae137'}); + $('#password-help').text("That password is good. It would take "+daysToCrack+" days to crack. ").css({'color':'#8ae137'}); checkMatch(); } - else { + else if (daysToCrack<1000000000) { var years = Math.round(daysToCrack / 365 * 10) / 10; if (years>1000000) { years = (Math.round(years/1000000*10)/10)+' million'; @@ -78,6 +78,10 @@ $(function(){ $('#password-help').text("That password is great! It could take up to "+years+" years to crack!").css({'color':'#8ae137'}); checkMatch(); } + else { + $('#password-help').text("That password is amazing! It is virtually impossible to crack!").css({'color':'#8ae137'}); + checkMatch(); + } } }); From 26749cb8bd81682aa5b30de610db9a7fa4d39071 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 15 Apr 2017 11:00:04 -0400 Subject: [PATCH 074/143] No need for exclamation here --- config/routes/test.js | 2 +- static/js/password.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/routes/test.js b/config/routes/test.js index 13a1fce..6e75736 100644 --- a/config/routes/test.js +++ b/config/routes/test.js @@ -31,7 +31,7 @@ router .post('/password', (req,res,next)=>{ let daysToCrack = mellt.CheckPassword(req.body.password); if (daysToCrack<10) { - let err = new Error(`That password could be cracked in ${daysToCrack} days! Come up with a more complex password that would take at least 10 days to crack. `) + let err = new Error(`That password could be cracked in ${daysToCrack} days! Come up with a more complex password that would take at least 10 days to crack. `); mw.throwErr(err); next(err); } diff --git a/static/js/password.js b/static/js/password.js index bdefddd..7702963 100644 --- a/static/js/password.js +++ b/static/js/password.js @@ -13,7 +13,7 @@ function checkMatch(){ // User has retyped, but they don't match yet else if ($('#p2').val()!=='') { - $('#password-help').text("Those passwords don't match! ").css({'color':'#fb6e3d'}); + $('#password-help').text("Those passwords don't match... ").css({'color':'#fb6e3d'}); $('#submit').prop('disabled',true).prop('title',"You need to type the same password twice before you can save it. "); } @@ -33,7 +33,7 @@ $(function(){ // Only second password entered else if ($('#p1').val()==='') { - $('#password-help').show().text("Those passwords don't match! "); + $('#password-help').show().text("Those passwords don't match... "); $('#submit').prop('disabled',true).prop('title',"You need to type the same password twice correctly before you can save it. "); } From 5a5d71fb66aa6f27e75dcbccf5d6a2251d87197a Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sun, 16 Apr 2017 16:47:36 -0400 Subject: [PATCH 075/143] Removed whitespace --- static/css/base.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/css/base.css b/static/css/base.css index 120dc41..6a46dba 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -87,7 +87,7 @@ pre { .hide { display: none !important; } .red, .red:hover { color: #fb6e3d !important; } .yellow, .yellow:hover { color: #fbc93d !important; } - .green, .green:hover { color: #8ae137 !important; } +.green, .green:hover { color: #8ae137 !important; } .shadow { -moz-box-shadow: .18vw .18vw .36vw #000; From 1988047e74027f92cac7fe2b9b6f002936138072 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sun, 16 Apr 2017 16:50:02 -0400 Subject: [PATCH 076/143] Added script type --- views/admin.html | 2 +- views/map.html | 8 ++++---- views/password.html | 6 +++--- views/templates/base.html | 2 +- views/templates/footer.html | 2 +- views/templates/header.html | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/views/admin.html b/views/admin.html index 4b55fe6..d9df9a4 100644 --- a/views/admin.html +++ b/views/admin.html @@ -49,7 +49,7 @@
- + - - - {% if user.id == mapuser.id %}{% endif %} + + + + {% if user.id == mapuser.id %}{% endif %} - - + + + {% endblock %} \ No newline at end of file diff --git a/views/templates/base.html b/views/templates/base.html index 28ad474..c6942ac 100644 --- a/views/templates/base.html +++ b/views/templates/base.html @@ -45,7 +45,7 @@ - + {% endblock %} {% if not noHeader %}{% endif %} diff --git a/views/templates/footer.html b/views/templates/footer.html index 037ee84..c731aa9 100644 --- a/views/templates/footer.html +++ b/views/templates/footer.html @@ -15,4 +15,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/views/templates/header.html b/views/templates/header.html index 5b0cb54..acdb59d 100644 --- a/views/templates/header.html +++ b/views/templates/header.html @@ -54,4 +54,4 @@ {% endfor %} - \ No newline at end of file + \ No newline at end of file From f3b832d2ed0dc94e72f9dbe2ed9358908860d23a Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sun, 16 Apr 2017 16:53:58 -0400 Subject: [PATCH 077/143] Fixed "Cannot read property 'redirect' of undefined" --- config/auth.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/auth.js b/config/auth.js index 0ec3a6d..cc3b82f 100644 --- a/config/auth.js +++ b/config/auth.js @@ -26,7 +26,7 @@ module.exports = (app, passport) => { // Login/-out app.route('/login') .get( (req,res)=>{ - if (req.isAuthenticated()){ loginCallback(); } + if (req.isAuthenticated()){ loginCallback(req,res); } else { res.render('login'); } }) .post( passport.authenticate('local',loginOutcome), loginCallback ); @@ -160,7 +160,7 @@ module.exports = (app, passport) => { // Forgot password app.route('/login/forgot') .all( (req,res,next)=>{ - if (req.isAuthenticated()){ loginCallback(); } + if (req.isAuthenticated()){ loginCallback(req,res); } else { next(); } } ) .get( (req,res,next)=>{ From 21df06aa627785849f64d6002e4f57f5c5450934 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sun, 16 Apr 2017 16:57:45 -0400 Subject: [PATCH 078/143] Moved auth.js to routes/ --- config/auth.js | 264 ------------------------------------------------- server.js | 2 +- 2 files changed, 1 insertion(+), 265 deletions(-) delete mode 100644 config/auth.js diff --git a/config/auth.js b/config/auth.js deleted file mode 100644 index cc3b82f..0000000 --- a/config/auth.js +++ /dev/null @@ -1,264 +0,0 @@ -'use strict'; - -const - mw = require('./middleware.js'), - mail = require('./mail.js'), - User = require('./models.js').user, - crypto = require('crypto'), - env = require('./env.js'); - -module.exports = (app, passport) => { - - // Methods for success and failure - const - loginOutcome = { - failureRedirect: '/login', - failureFlash: true - }, - connectOutcome = { - failureRedirect: '/settings', - failureFlash: true - }, - loginCallback = (req,res)=>{ - res.redirect( req.session.next || '/map' ); - }; - - // Login/-out - app.route('/login') - .get( (req,res)=>{ - if (req.isAuthenticated()){ loginCallback(req,res); } - else { res.render('login'); } - }) - .post( passport.authenticate('local',loginOutcome), loginCallback ); - app.get('/logout', (req,res)=>{ - req.logout(); - req.flash('success',`You have been logged out.`); - res.redirect(req.session.next || '/'); - }); - - // Signup - app.route('/signup') - .get( (req,res)=>{ - res.redirect('/login#signup'); - }) - .post( (req,res,next)=>{ - - // Send token and alert user - function sendToken(user){ - - // Create a password token - user.createToken((err,token)=>{ - if (err){ mw.throwErr(err,req); } - - // Email the instructions to continue - mail.send({ - from: mail.from, - to: `<${user.email}>`, - subject: 'Complete your Tracman registration', - text: mail.text(`Welcome to Tracman! \n\nTo complete your registration, follow this link and set your password:\n${env.url}/settings/password/${token}`), - html: mail.html(`

Welcome to Tracman!

To complete your registration, follow this link and set your password:
${env.url}/settings/password/${token}

`) - }).then(()=>{ - req.flash('success', `An email has been sent to ${user.email}. Check your inbox to complete your registration. `); - res.redirect('/login'); - }).catch((err)=>{ - mw.throwErr(err,req); - res.redirect('/login#signup'); - }); - }); - - } - - // Validate email - req.checkBody('email', 'Please enter a valid email address.').isEmail(); - req.sanitizeBody('email').normalizeEmail({remove_dots:false}); - - // Check if somebody already has that email - User.findOne({'email':req.body.email}) - .then( (user)=>{ - - // User already exists - if (user && user.auth.password) { - req.flash('warning','A user with that email already exists! If you forgot your password, you can reset it here.'); - res.redirect('/login#login'); - next(); - } - - // User exists but hasn't created a password yet - else if (user) { - // Send another token (or the same one if it hasn't expired) - sendToken(user); - } - - // Create user - else { - - user = new User(); - user.created = Date.now(); - user.email = req.body.email; - user.slug = slug(user.email.substring(0, user.email.indexOf('@'))); - - // Generate unique slug - let slug = new Promise((resolve,reject) => { - (function checkSlug(s,cb){ - - User.findOne({slug:s}) - .catch((err)=>{ - mw.throwErr(err,req); - }) - .then((existingUser)=>{ - - // Slug in use: generate a random one and retry - if (existingUser){ - crypto.randomBytes(6, (err,buf)=>{ - if (err) { mw.throwErr(err,req); } - s = buf.toString('hex'); - checkSlug(s,cb); - }); - } - - // Unique slug: proceed - else { cb(s); } - - }); - - })(user.slug, (newSlug)=>{ - user.slug = newSlug; - resolve(); - }); - }); - - // Generate sk32 - let sk32 = new Promise((resolve,reject) => { - crypto.randomBytes(32, (err,buf)=>{ - if (err) { mw.throwErr(err,req); } - user.sk32 = buf.toString('hex'); - resolve(); - }); - }); - - // Save user and send the token by email - Promise.all([slug, sk32]) - .then( ()=> { - user.save(); - }).then( ()=>{ - sendToken(user); - }).catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/login#signup'); - }); - - } - - }) - .catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/signup'); - }); - - }); - - // Forgot password - app.route('/login/forgot') - .all( (req,res,next)=>{ - if (req.isAuthenticated()){ loginCallback(req,res); } - else { next(); } - } ) - .get( (req,res,next)=>{ - res.render('forgot'); - } ) - .post( (req,res,next)=>{ - - // Validate email - req.checkBody('email', 'Please enter a valid email address.').isEmail(); - req.sanitizeBody('email').normalizeEmail({remove_dots:false}); - - User.findOne({'email':req.body.email}) - .then( (user)=>{ - - // No user with that email - if (!user) { - // Don't let on that no such user exists, to prevent dictionary attacks - req.flash('success', `If an account exists with the email ${req.body.email}, an email has been sent there with a password reset link. `); - res.redirect('/login'); - } - - // User with that email does exist - else { - - // Create reset token - user.createToken( (err,token)=>{ - if (err){ next(err); } - - // Email reset link - mail.send({ - from: mail.from, - to: mail.to(user), - subject: 'Reset your Tracman password', - text: mail.text(`Hi, \n\nDid you request to reset your Tracman password? If so, follow this link to do so:\n${env.url}/settings/password/${token}\n\nIf you didn't initiate this request, just ignore this email. `), - html: mail.html(`

Hi,

Did you request to reset your Tracman password? If so, follow this link to do so:
${env.url}/settings/password/${token}

If you didn't initiate this request, just ignore this email.

`) - }).then(()=>{ - req.flash('success', `If an account exists with the email ${req.body.email}, an email has been sent there with a password reset link. `); - res.redirect('/login'); - }).catch((err)=>{ - mw.throwErr(err); - }); - - }); - - } - - }).catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/login/forgot'); - }); - - } ); - - // Social - app.get('/login/:service', (req,res,next)=>{ - let service = req.params.service, - sendParams = (service==='google')? {scope:['profile']} : null; - - // Social login - if (!req.user) { - passport.authenticate(service, sendParams)(req,res,next); - } - - // Connect social account - else if (!req.user.auth[service]) { - passport.authorize(service, sendParams)(req,res,next); - } - - // Disconnect social account - else { - req.user.auth[service] = undefined; - req.user.save() - .catch((err)=>{ - mw.throwErr(err,req); - res.redirect('/settings'); - }).then(()=>{ - req.flash('success', `${mw.capitalize(service)} account disconnected. `); - res.redirect('/settings'); - }); - - } - }); - app.get('/login/:service/cb', (req,res,next)=>{ - var service = req.params.service; - if (!req.user) { - passport.authenticate(service, loginOutcome)(req,res,next); - } else { - req.flash('success', `${mw.capitalize(service)} account connected. `); - req.session.next = '/settings'; - passport.authenticate(service, connectOutcome)(req,res,next); - } - }, loginCallback); - - // Android auth - //TODO: See if there's a better method - app.get('/auth/google/idtoken', passport.authenticate('google-id-token'), (req,res)=>{ - if (!req.user){ res.sendStatus(401); } - else { res.send(req.user); } - } ); - -}; diff --git a/server.js b/server.js index 185f751..c4cdc6f 100755 --- a/server.js +++ b/server.js @@ -69,7 +69,7 @@ const require('./config/passport.js')(passport); app.use(passport.initialize()); app.use(passport.session()); - require('./config/auth.js')(app, passport); + require('./config/routes/auth.js')(app, passport); } /* Routes */ { From e222b62f821bbfaffbb8c1ff912d7534ddbd97a6 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sun, 16 Apr 2017 17:23:15 -0400 Subject: [PATCH 079/143] Made bigger social logi buttons for desktops/tablets --- config/routes/settings.js | 43 +++++++++++++++++---------------- static/css/login.css | 50 +++++++++++++++++++++++++++------------ views/login.html | 21 ++++++++++++---- 3 files changed, 74 insertions(+), 40 deletions(-) diff --git a/config/routes/settings.js b/config/routes/settings.js index 799a975..3c1b659 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -27,26 +27,26 @@ router.route('/') //TODO: Validate everything! User.findByIdAndUpdate(req.user.id, {$set:{ - name: xss(req.body.name), - slug: slug(xss(req.body.slug)), - email: req.body.email, - settings: { - units: req.body.units, - defaultMap: req.body.map, - defaultZoom: req.body.zoom, - showSpeed: (req.body.showSpeed)?true:false, - showAlt: (req.body.showAlt)?true:false, - showStreetview: (req.body.showStreet)?true:false - } - }}) - .then( (user)=>{ - req.flash('success', 'Settings updated. '); - res.redirect('/settings'); - }) - .catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/settings'); - }); + name: xss(req.body.name), + slug: slug(xss(req.body.slug)), + email: req.body.email, + settings: { + units: req.body.units, + defaultMap: req.body.map, + defaultZoom: req.body.zoom, + showSpeed: (req.body.showSpeed)?true:false, + showAlt: (req.body.showAlt)?true:false, + showStreetview: (req.body.showStreet)?true:false + } + }}) + .then( (user)=>{ + req.flash('success', 'Settings updated. '); + res.redirect('/settings'); + }) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/settings'); + }); } ) @@ -140,7 +140,8 @@ router.route('/password/:token') if (daysToCrack<10) { mw.throwErr(new Error(`That password could be cracked in ${daysToCrack} days! Come up with a more complex password that would take at least 10 days to crack. `)); res.redirect(`/settings/password/${req.params.token}`); - } else { + } + else { // Delete token res.locals.passwordUser.auth.passToken = undefined; diff --git a/static/css/login.css b/static/css/login.css index 4d50610..b8cc889 100644 --- a/static/css/login.css +++ b/static/css/login.css @@ -13,37 +13,57 @@ form #social-login { } #social-login .btn { - padding: 0; - font-size: 1.3em; - height: 60px; + padding: 2%; text-align: center; - width: 60px; margin: 0 3%; color: #FFF; -} #social-login .btn .fa { - margin: 0; +} +#social-login .btn .fa { position: relative; - padding-top: 20px; -} #social-login .btn.gp { +} +#social-login .btn .text { + font-size: .8em; +} +#social-login .btn.gp { background: rgb(206,77,57); -} #social-login .btn.gp:hover { +} +#social-login .btn.gp:hover { background: rgb(251,122,102); -} #social-login .btn.fb { +} +#social-login .btn.fb { background: rgb(48,88,145); -} #social-login .btn.fb:hover { +} +#social-login .btn.fb:hover { background: rgb(93,133,190); -} #social-login .btn.tw { +} +#social-login .btn.tw { background: rgb(44,168,210); -} #social-login .btn.tw:hover { +} +#social-login .btn.tw:hover { background: rgb(89,213,255); } +@media (max-width: 600px) { + #social-login .btn { + padding: 0; + width: 60px; + height: 60px; + } + #social-login .btn .text { + display: none; + } + #social-login .btn .fa { + margin: 18px auto; + } +} @media (max-width: 800px) { + section > .flex { flex-direction: column; } section > .flex > div { width: 100%; } - hr.hide { display:block; } -} \ No newline at end of file + hr { display: block; } + +} diff --git a/views/login.html b/views/login.html index 077baa1..df6202e 100644 --- a/views/login.html +++ b/views/login.html @@ -25,9 +25,22 @@
@@ -41,7 +54,7 @@
-
+

Create account

From 4de5964e7084f6291ec36a4cc0dd86fa1986aeb2 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sun, 16 Apr 2017 19:26:48 -0400 Subject: [PATCH 080/143] Added "SHOW" button to login password input --- config/routes/test.js | 7 ++++++ static/css/base.css | 50 +++++++++++++++++++++------------------ static/css/form.css | 48 +++++++++++++++++++++++++++---------- static/css/login.css | 55 +++++++++++++++++++++++++++++++++++-------- views/login.html | 41 +++++++++++++++++++++++--------- views/settings.html | 4 ++-- 6 files changed, 147 insertions(+), 58 deletions(-) diff --git a/config/routes/test.js b/config/routes/test.js index 6e75736..7e7bec1 100644 --- a/config/routes/test.js +++ b/config/routes/test.js @@ -38,6 +38,13 @@ router else { res.sendStatus(200); } + }) + + .get('/settings', (req,res)=>{ + res.render('settings'); + }) + .post('/settings', (req,res)=>{ + //TODO: Test validation here. }); module.exports = router; \ No newline at end of file diff --git a/static/css/base.css b/static/css/base.css index 6a46dba..23a2fe9 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -84,6 +84,31 @@ pre { word-wrap: break-word; } + +main { + top: 59px; + position: absolute; + left: 0px; + right: 0px; + bottom: 0px; + overflow-y: auto; +} +.container { + padding-right: 5%; + padding-left: 5%; + width: 100%; + margin: 0 auto; +} +.container:after { + content: ""; + display: block; + clear: both; +} +section { + padding: 10vh 0 5vh; +} + +/* Modifiers */ .hide { display: none !important; } .red, .red:hover { color: #fb6e3d !important; } .yellow, .yellow:hover { color: #fbc93d !important; } @@ -108,28 +133,6 @@ pre { .left { float: left; } .right { float: right; } -main { - top: 59px; - position: absolute; - left: 0px; - right: 0px; - bottom: 0px; - overflow-y: auto; -} -.container { - padding-right: 5%; - padding-left: 5%; - width: 100%; - margin: 0 auto; -} -.container:after { - content: ""; - display: block; - clear: both; -} -section { - padding: 10vh 0 5vh; -} /* Buttons */ .btn { @@ -156,7 +159,8 @@ section { inset -.11vw -.18vw .52vw rgba(0,0,0,.4), .18vw .18vw .36vw #000; } .btn:disabled { - border: 1px solid #999; + color: #aaa; + border: 1px solid #444; } .btn:hover:not(:disabled) { text-decoration: none; background: rgba(255,255,255,0.2); diff --git a/static/css/form.css b/static/css/form.css index bfb318a..c442211 100644 --- a/static/css/form.css +++ b/static/css/form.css @@ -23,21 +23,25 @@ form input, form textarea, form select { padding: 1% 1.5%; border-radius: .3vw; } -form input:not(:disabled), form textarea:not(:disabled), form select:not(:disabled) { +form input:not(:disabled), form textarea:not(:disabled), +form select:not(:disabled), form .input-addon { border: 1px solid #666; -moz-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); -webkit-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); } -form input:disabled, form textarea:disabled form select:disabled { - border: 1px solid #999; +form input:disabled:not(.input-addon), form textarea:disabled, +form select:disabled { + border: 1px solid #444; } form input:not(.input-addon):not(.input-with-addon):not([type="radio"]):not([type="checkbox"]), form .input-with-addon-group { min-width: 50%; } -form input:active:not(.input-addon), form textarea:active, form select:active, -form input:focus:not(.input-addon), form textarea:focus, form select:focus { +form input:active:not(.input-addon), form textarea:active, +form select:active, +form input:focus:not(.input-addon), form textarea:focus, +form select:focus { outline: none; border: 1px solid #fbc93d; } @@ -45,25 +49,45 @@ form input:focus:not(.input-addon), form textarea:focus, form select:focus { form .input-with-addon-group { display: flex; } +form .input-addon, form .input-with-addon { + -moz-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); + -webkit-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); + box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); +} form .input-addon { + text-align: center; + width: auto; +} +form .input-with-addon { + flex-grow: 1; +} +form .input-addon.left { padding: 1% 0 1% 1.5%; border-right-color: #202020; border-right-color: rgba(102,102,102,0); border-top-right-radius: 0; border-bottom-right-radius: 0; - -moz-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); - -webkit-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); - box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); } -form .input-with-addon { +form .input-with-addon.left { padding: 1% 1.5% 1% 0; border-left-color: #202020; border-left-color: rgba(102,102,102,0); border-top-left-radius: 0; border-bottom-left-radius: 0; - -moz-box-shadow: inset 0 .18vw .25vw rgba(0,0,0,.5); - -webkit-box-shadow: inset 0 .18vw .25vw rgba(0,0,0,.5); - box-shadow: inset 0 .18vw .25vw rgba(0,0,0,.5); +} +form .input-addon.right { + padding: 1% 1.5% 1% 0; + border-left-color: #202020; + border-left-color: rgba(102,102,102,0); + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +form .input-with-addon.right { + padding: 1% 0 1% 1.5%; + border-right-color: #202020; + border-right-color: rgba(102,102,102,0); + border-top-right-radius: 0; + border-bottom-right-radius: 0; } ::-webkit-input-placeholder { diff --git a/static/css/login.css b/static/css/login.css index b8cc889..167dda1 100644 --- a/static/css/login.css +++ b/static/css/login.css @@ -1,17 +1,45 @@ -section > .flex > div { +/* More padding on the bottom */ +.container { + padding-bottom: 10vh; +} + +/* Sections */ +#login, #signup { width: 50%; - padding: 0 2%; + margin: 0 2%; } -form input { +/* Form overrides */ +form .form-group { + margin: 8% 0 0; +} +form input:not(.input-addon):not(.input-with-addon), +form .input-with-addon-group { width: 96%; + margin: 0 auto 5vh; } - -form input.btn, -form #social-login { +form .input-with-addon, form .input-addon { + margin: 0; +} +form .input-with-addon-group { width: 100%; } +p, input, #social-login { + margin-bottom: 5vh; +} +form input.btn[type="submit"] { + margin: 0 2% 5vh; +} +form #social-login { + justify-content: space-around; + width: 100%; +} +#show { + padding: 1%; + cursor: pointer; +} +/* Social buttons */ #social-login .btn { padding: 2%; text-align: center; @@ -22,7 +50,7 @@ form #social-login { position: relative; } #social-login .btn .text { - font-size: .8em; + font-size: .6em; } #social-login .btn.gp { background: rgb(206,77,57); @@ -42,7 +70,8 @@ form #social-login { #social-login .btn.tw:hover { background: rgb(89,213,255); } -@media (max-width: 600px) { +/* Small buttons */ +@media (max-width:600px), (min-width:800px) and (max-width:1200px) { #social-login .btn { padding: 0; width: 60px; @@ -56,14 +85,20 @@ form #social-login { } } -@media (max-width: 800px) { +/* Single column */ +@media (max-width:800px) { + #login, #signup { + width: 100%; + } section > .flex { flex-direction: column; } section > .flex > div { width: 100%; } - hr { display: block; } + hr { + display: block !important; + } } diff --git a/views/login.html b/views/login.html index df6202e..46f2ce8 100644 --- a/views/login.html +++ b/views/login.html @@ -4,11 +4,6 @@ {{super()}} - {% endblock %} {% block main %} @@ -24,7 +19,7 @@

Login

-
@@ -59,8 +57,8 @@

Create account

Welcome aboard!

-
- + +

You will be sent an email confrimation with a link to create a password.

By signing up, you agree to our terms of service and privacy policy.

@@ -71,4 +69,25 @@ +{% endblock %} + +{% block javascript %} + {% endblock %} \ No newline at end of file diff --git a/views/settings.html b/views/settings.html index d1875ff..b46539d 100644 --- a/views/settings.html +++ b/views/settings.html @@ -106,8 +106,8 @@
- - + +
From cf667dc35acd1fa9862d68ab9a61a10d51d52663 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sun, 16 Apr 2017 21:00:39 -0400 Subject: [PATCH 081/143] Fixed login flash and redirects --- config/passport.js | 2 + config/routes/auth.js | 281 ++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + server.js | 16 +-- 4 files changed, 293 insertions(+), 7 deletions(-) create mode 100644 config/routes/auth.js diff --git a/config/passport.js b/config/passport.js index 2d7829c..644247b 100644 --- a/config/passport.js +++ b/config/passport.js @@ -33,6 +33,7 @@ module.exports = (passport)=>{ // No user with that email if (!user) { + req.session.next = undefined; return done( null, false, req.flash('danger','Incorrect email or password.') ); } @@ -45,6 +46,7 @@ module.exports = (passport)=>{ // Password incorrect if (!res) { + req.session.next = undefined; return done( null, false, req.flash('danger','Incorrect email or password.') ); } diff --git a/config/routes/auth.js b/config/routes/auth.js new file mode 100644 index 0000000..018a3ce --- /dev/null +++ b/config/routes/auth.js @@ -0,0 +1,281 @@ +'use strict'; + +const + mw = require('../middleware.js'), + mail = require('../mail.js'), + User = require('../models.js').user, + crypto = require('crypto'), + env = require('../env.js'); + +module.exports = (app, passport) => { + + // Methods for success and failure + const + loginOutcome = { + failureRedirect: '/login', + failureFlash: true + }, + connectOutcome = { + failureRedirect: '/settings', + failureFlash: true + }, + loginCallback = (req,res)=>{ + // Prevent redirect loop + if (req.session.next.substring(0,7)==='/login#'){ + req.session.next = '#'; + res.redirect('/map'); + } + else { + res.redirect( req.session.next || '/map' ); + } + }; + + // Login/-out + app.route('/login') + .get( (req,res)=>{ + + // Already logged in + if (req.isAuthenticated()) { loginCallback(req,res); } + + // Show login page + else { res.render('login'); } + + }) + .post( passport.authenticate('local',loginOutcome), loginCallback ); + app.get('/logout', (req,res)=>{ + req.logout(); + req.flash('success',`You have been logged out.`); + // Prevent redirect loop + if (req.session.next.substring(0,8)==='/logout#') { + req.session.next = '#'; + res.redirect('/'); + } else { + res.redirect(req.session.next || '/'); + } + }); + + // Signup + app.route('/signup') + .get( (req,res)=>{ + res.redirect('/login#signup'); + }) + .post( (req,res,next)=>{ + + // Send token and alert user + function sendToken(user){ + + // Create a password token + user.createToken((err,token)=>{ + if (err){ mw.throwErr(err,req); } + + // Email the instructions to continue + mail.send({ + from: mail.from, + to: `<${user.email}>`, + subject: 'Complete your Tracman registration', + text: mail.text(`Welcome to Tracman! \n\nTo complete your registration, follow this link and set your password:\n${env.url}/settings/password/${token}`), + html: mail.html(`

Welcome to Tracman!

To complete your registration, follow this link and set your password:
${env.url}/settings/password/${token}

`) + }).then(()=>{ + req.flash('success', `An email has been sent to ${user.email}. Check your inbox to complete your registration. `); + res.redirect('/login'); + }).catch((err)=>{ + mw.throwErr(err,req); + res.redirect('/login#signup'); + }); + }); + + } + + // Validate email + req.checkBody('email', 'Please enter a valid email address.').isEmail(); + req.sanitizeBody('email').normalizeEmail({remove_dots:false}); + + // Check if somebody already has that email + User.findOne({'email':req.body.email}) + .then( (user)=>{ + + // User already exists + if (user && user.auth.password) { + req.flash('warning','A user with that email already exists! If you forgot your password, you can reset it here.'); + res.redirect('/login#login'); + next(); + } + + // User exists but hasn't created a password yet + else if (user) { + // Send another token (or the same one if it hasn't expired) + sendToken(user); + } + + // Create user + else { + + user = new User(); + user.created = Date.now(); + user.email = req.body.email; + user.slug = slug(user.email.substring(0, user.email.indexOf('@'))); + + // Generate unique slug + let slug = new Promise((resolve,reject) => { + (function checkSlug(s,cb){ + + User.findOne({slug:s}) + .catch((err)=>{ + mw.throwErr(err,req); + }) + .then((existingUser)=>{ + + // Slug in use: generate a random one and retry + if (existingUser){ + crypto.randomBytes(6, (err,buf)=>{ + if (err) { mw.throwErr(err,req); } + s = buf.toString('hex'); + checkSlug(s,cb); + }); + } + + // Unique slug: proceed + else { cb(s); } + + }); + + })(user.slug, (newSlug)=>{ + user.slug = newSlug; + resolve(); + }); + }); + + // Generate sk32 + let sk32 = new Promise((resolve,reject) => { + crypto.randomBytes(32, (err,buf)=>{ + if (err) { mw.throwErr(err,req); } + user.sk32 = buf.toString('hex'); + resolve(); + }); + }); + + // Save user and send the token by email + Promise.all([slug, sk32]) + .then( ()=> { + user.save(); + }).then( ()=>{ + sendToken(user); + }).catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/login#signup'); + }); + + } + + }) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/signup'); + }); + + }); + + // Forgot password + app.route('/login/forgot') + .all( (req,res,next)=>{ + if (req.isAuthenticated()){ loginCallback(req,res); } + else { next(); } + } ) + .get( (req,res,next)=>{ + res.render('forgot'); + } ) + .post( (req,res,next)=>{ + + // Validate email + req.checkBody('email', 'Please enter a valid email address.').isEmail(); + req.sanitizeBody('email').normalizeEmail({remove_dots:false}); + + User.findOne({'email':req.body.email}) + .then( (user)=>{ + + // No user with that email + if (!user) { + // Don't let on that no such user exists, to prevent dictionary attacks + req.flash('success', `If an account exists with the email ${req.body.email}, an email has been sent there with a password reset link. `); + res.redirect('/login'); + } + + // User with that email does exist + else { + + // Create reset token + user.createToken( (err,token)=>{ + if (err){ next(err); } + + // Email reset link + mail.send({ + from: mail.from, + to: mail.to(user), + subject: 'Reset your Tracman password', + text: mail.text(`Hi, \n\nDid you request to reset your Tracman password? If so, follow this link to do so:\n${env.url}/settings/password/${token}\n\nIf you didn't initiate this request, just ignore this email. `), + html: mail.html(`

Hi,

Did you request to reset your Tracman password? If so, follow this link to do so:
${env.url}/settings/password/${token}

If you didn't initiate this request, just ignore this email.

`) + }).then(()=>{ + req.flash('success', `If an account exists with the email ${req.body.email}, an email has been sent there with a password reset link. `); + res.redirect('/login'); + }).catch((err)=>{ + mw.throwErr(err); + }); + + }); + + } + + }).catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/login/forgot'); + }); + + } ); + + // Social + app.get('/login/:service', (req,res,next)=>{ + let service = req.params.service, + sendParams = (service==='google')? {scope:['profile']} : null; + + // Social login + if (!req.user) { + passport.authenticate(service, sendParams)(req,res,next); + } + + // Connect social account + else if (!req.user.auth[service]) { + passport.authorize(service, sendParams)(req,res,next); + } + + // Disconnect social account + else { + req.user.auth[service] = undefined; + req.user.save() + .catch((err)=>{ + mw.throwErr(err,req); + res.redirect('/settings'); + }).then(()=>{ + req.flash('success', `${mw.capitalize(service)} account disconnected. `); + res.redirect('/settings'); + }); + + } + }); + app.get('/login/:service/cb', (req,res,next)=>{ + var service = req.params.service; + if (!req.user) { + passport.authenticate(service, loginOutcome)(req,res,next); + } else { + req.flash('success', `${mw.capitalize(service)} account connected. `); + passport.authenticate(service, connectOutcome)(req,res,next); + } + }, loginCallback); + + // Android auth + //TODO: See if there's a better method + app.get('/auth/google/idtoken', passport.authenticate('google-id-token'), (req,res)=>{ + if (!req.user){ res.sendStatus(401); } + else { res.send(req.user); } + } ); + +}; diff --git a/package.json b/package.json index 59bcebc..2914901 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "bcrypt-nodejs": "0.0.3", "body-parser": "^1.17.1", "connect-flash": "^0.1.1", + "connect-flash-plus": "^0.2.1", "cookie-parser": "^1.4.1", "cookie-session": "^2.0.0-alpha.1", "express": "^4.15.2", diff --git a/server.js b/server.js index c4cdc6f..5c2e82b 100755 --- a/server.js +++ b/server.js @@ -10,7 +10,7 @@ const mongoose = require('mongoose'), nunjucks = require('nunjucks'), passport = require('passport'), - flash = require('connect-flash'), + flash = require('connect-flash-plus'), env = require('./config/env.js'), mw = require('./config/middleware.js'), User = require('./config/models.js').user, @@ -64,21 +64,20 @@ const app.use(expressValidator()); app.use(flash()); } - + /* Auth */ { require('./config/passport.js')(passport); app.use(passport.initialize()); app.use(passport.session()); - require('./config/routes/auth.js')(app, passport); } - + /* Routes */ { - + // Static files (keep this before setting default locals) app.use('/static', express.static( __dirname+'/static', {dotfiles:'allow'} )); - + // Set default locals available to all views (keep this after static files) - app.get( '/*', (req,res,next)=>{ + app.get( '*', (req,res,next)=>{ // Path for redirects req.session.next = ( req.path.substring(0, req.path.indexOf('#')) || req.path )+'#'; @@ -94,6 +93,9 @@ const next(); } ); + // Auth routes + require('./config/routes/auth.js')(app, passport); + // Main routes app.use( '/', require('./config/routes/index.js') ); From 603260273a6c5c80c5fdccf1f02615d463f1960c Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sun, 16 Apr 2017 21:17:20 -0400 Subject: [PATCH 082/143] Added info about iOS app, fixed validation, started client-side settings validation --- config/routes/index.js | 31 ++++++++++++++++++++++++------- config/routes/test.js | 3 +++ views/help.html | 11 +++++++++++ views/settings.html | 20 +------------------- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/config/routes/index.js b/config/routes/index.js index 132c687..46e767d 100644 --- a/config/routes/index.js +++ b/config/routes/index.js @@ -38,13 +38,31 @@ router.get('/favicon.ico', (req,res)=>{ // Endpoint to validate forms router.get('/validate', (req,res)=>{ - if (req.query.slug) { // validate unique slug - User.findOne( {slug:slug(req.query.slug)}, (err,existingUser)=>{ - if (err) { console.log('/validate error:',err); } - if (existingUser && existingUser.id!==req.user) { res.sendStatus(400); } + + // Validate unused slug + if (req.query.slug) { + User.findOne({ slug: slug(req.query.slug) }) + .then( (existingUser)=>{ + if (existingUser && existingUser.id!==req.user) { + res.sendStatus(400); + } else { res.sendStatus(200); } - } ); + }) + .catch( (err)=>{ mw.throwErr(err); }); } + + // Validate unused email + else if (req.query.email) { + User.findOne({ email: slug(req.query.email) }) + .then( (existingUser)=>{ + if (existingUser && existingUser.id!==req.user) { + res.sendStatus(400); + } + else { res.sendStatus(200); } + }) + .catch( (err)=>{ mw.throwErr(err); }); + } + }); // Link to androidapp in play store @@ -54,8 +72,7 @@ router.get('/android', (req,res)=>{ // Link to iphone app in the apple store router.get('/ios', (req,res)=>{ - res.sendStatus(404); - //TODO: Add link to info about why there's no ios app + res.redirect('/help#why-is-there-no-ios-app'); }); module.exports = router; \ No newline at end of file diff --git a/config/routes/test.js b/config/routes/test.js index 7e7bec1..99d1106 100644 --- a/config/routes/test.js +++ b/config/routes/test.js @@ -44,7 +44,10 @@ router res.render('settings'); }) .post('/settings', (req,res)=>{ + //TODO: Test validation here. + + }); module.exports = router; \ No newline at end of file diff --git a/views/help.html b/views/help.html index 1d86f5a..051f87e 100644 --- a/views/help.html +++ b/views/help.html @@ -55,6 +55,7 @@
  • How is the altitude determined?
  • What is the street view image?
  • Can I contribute to Tracman?
  • +
  • Why is there no iOS app?
  • How do I share my location?

    @@ -84,6 +85,16 @@

    Sure! Tracman has some github repositories you can clone.

    I also accept donations to help with development and server fees. You can pay with cash or bitcoin.

    + +

    Why is there no iOS app?

    + +

    There are a few reasons I haven't made a version of the android app for iPhone/iPad/iPod:

    + +
      +
    • I would need to learn a new programming language, and spend tons of time developing the app. I can't just copy the android code over. Everything would need to be built from scratch.
    • +
    • Apple charges $100/year in developer fees.
    • +
    • iOS apps can only be built using a mac.
    • +
    Go to map diff --git a/views/settings.html b/views/settings.html index b46539d..e66dc08 100644 --- a/views/settings.html +++ b/views/settings.html @@ -196,23 +196,5 @@ {% block javascript %} {{super()}} - + {% endblock %} \ No newline at end of file From 168ba7c068d70ea33b9eace986bd7b2126af24ef Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sun, 16 Apr 2017 21:18:24 -0400 Subject: [PATCH 083/143] Added whitespace --- views/settings.html | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/views/settings.html b/views/settings.html index e66dc08..2aeb440 100644 --- a/views/settings.html +++ b/views/settings.html @@ -7,25 +7,25 @@ {% endblock %} {% block main %} - +
    - +

    Settings

    - + - +

    Account settings

    - +
    - +
    - +
    {% if user.auth.google %}Disconnect{% else %}Connect{% endif %} Google @@ -180,8 +126,8 @@
    -
    - + From 955ef3027f352c952745f731803791ce2c8139ff Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Mon, 17 Apr 2017 12:45:48 -0400 Subject: [PATCH 085/143] Added client-side validation for settings --- config/routes/index.js | 19 ++++++++++++++----- static/css/form.css | 1 + static/css/settings.css | 12 ++++++++++-- views/settings.html | 21 ++++++++++++--------- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/config/routes/index.js b/config/routes/index.js index 46e767d..4b07d0a 100644 --- a/config/routes/index.js +++ b/config/routes/index.js @@ -38,12 +38,14 @@ router.get('/favicon.ico', (req,res)=>{ // Endpoint to validate forms router.get('/validate', (req,res)=>{ + console.log(req.query); - // Validate unused slug + // Validate unique slug if (req.query.slug) { + console.log(`Checking slug: ${req.query.slug} for user ${req.user.id}`); User.findOne({ slug: slug(req.query.slug) }) .then( (existingUser)=>{ - if (existingUser && existingUser.id!==req.user) { + if (existingUser && existingUser.id!==req.user.id) { res.sendStatus(400); } else { res.sendStatus(200); } @@ -51,11 +53,12 @@ router.get('/validate', (req,res)=>{ .catch( (err)=>{ mw.throwErr(err); }); } - // Validate unused email + // Validate unique email else if (req.query.email) { - User.findOne({ email: slug(req.query.email) }) + console.log(`Checking email: ${req.query.email} for user ${req.user.id}`); + User.findOne({ email: req.query.email }) .then( (existingUser)=>{ - if (existingUser && existingUser.id!==req.user) { + if (existingUser && existingUser.id!==req.user.id) { res.sendStatus(400); } else { res.sendStatus(200); } @@ -63,6 +66,11 @@ router.get('/validate', (req,res)=>{ .catch( (err)=>{ mw.throwErr(err); }); } + // Create slug + else if (req.query.slugify) { + res.send(slug(req.query.slugify)); + } + }); // Link to androidapp in play store @@ -71,6 +79,7 @@ router.get('/android', (req,res)=>{ }); // Link to iphone app in the apple store +// ... maybe someday router.get('/ios', (req,res)=>{ res.redirect('/help#why-is-there-no-ios-app'); }); diff --git a/static/css/form.css b/static/css/form.css index c442211..6399ec3 100644 --- a/static/css/form.css +++ b/static/css/form.css @@ -5,6 +5,7 @@ form { .form-group { display: flex; + flex-wrap: wrap; justify-content: space-between; margin: 8% 0; } diff --git a/static/css/settings.css b/static/css/settings.css index 7fe8166..6206095 100644 --- a/static/css/settings.css +++ b/static/css/settings.css @@ -57,7 +57,15 @@ padding: 0 0 60px; justify-content: space-around; } -#submit-group .btn { +#submit-group .main { width: 50%; - background: #333; +} + +/* Help */ +.help { + display: none; + width: 100%; + margin-top: 2%; + margin-bottom: 0; + text-align: right; } \ No newline at end of file diff --git a/views/settings.html b/views/settings.html index 5e6b57a..7da99c5 100644 --- a/views/settings.html +++ b/views/settings.html @@ -19,13 +19,15 @@
    - + +

    - - -
    + + +

    +

    Map settings

    @@ -53,8 +55,9 @@
    - +
    +

    @@ -127,8 +130,8 @@
    - - cancel + + cancel
    From ef3c8c203a3eb76ef0ec7a8ccf12a52e94b8acde Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Mon, 17 Apr 2017 13:25:27 -0400 Subject: [PATCH 086/143] Fixed sample environment file --- config/env-sample.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/env-sample.js b/config/env-sample.js index eb3c0b7..c4a6c90 100644 --- a/config/env-sample.js +++ b/config/env-sample.js @@ -25,7 +25,6 @@ module.exports = { googleClientSecret: 'XXXXXXXXX_XXXXXXXXXXXXXX', // Google maps API key - googleMapsAPI: 'XXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXX', - + googleMapsAPI: 'XXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXX' }; From cd88855ecb81d4da08bf4a5bfe5fe01fd7d81431 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Mon, 17 Apr 2017 22:11:20 -0400 Subject: [PATCH 087/143] Added xss package to package.json --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 2914901..174eb33 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "passport-local": "^1.0.0", "passport-twitter": "^1.0.4", "slug": "^0.9.1", - "socket.io": "^1.4.4" + "socket.io": "^1.4.4", + "xss": "^0.3.3" }, "devDependencies": { "chai": "^3.5.0", From 2d5716adad831188198693a8bc31ebf3d18ee587 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Mon, 17 Apr 2017 22:19:21 -0400 Subject: [PATCH 088/143] Fixed select box formatting --- static/css/form.css | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/static/css/form.css b/static/css/form.css index 6399ec3..7e69431 100644 --- a/static/css/form.css +++ b/static/css/form.css @@ -24,8 +24,7 @@ form input, form textarea, form select { padding: 1% 1.5%; border-radius: .3vw; } -form input:not(:disabled), form textarea:not(:disabled), -form select:not(:disabled), form .input-addon { +form input:not(:disabled), form textarea:not(:disabled), form .input-addon { border: 1px solid #666; -moz-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); -webkit-box-shadow: inset .11vw .18vw .25vw rgba(0,0,0,.5); @@ -103,13 +102,16 @@ form .input-with-addon.right { color: #666; } -form select { - -moz-box-shadow: inset 0 1px 6px rgba(255,255,255,.2), - inset -.11vw -.18vw .52vw rgba(0,0,0,.4); - -webkit-box-shadow: inset -.11vw -.18vw .52vw rgba(255,255,255,.2), - inset -.11vw -.18vw .52vw rgba(0,0,0,.4); - box-shadow: inset 0 .11vw .52vw rgba(255,255,255,.2), - inset 0 .11vw .52vw rgba(0,0,0,.4); +form select:not(:disabled) { + -moz-box-shadow: inset 0.11vw 0.18vw 0.52vw rgba(255,255,255,.2), + inset -0.11vw -0.18vw 0.52vw rgba(0,0,0,.4), + 0.18vw 0.18vw 0.36vw #000; + -webkit-box-shadow: inset 0.11vw 0.18vw 0.52vw rgba(255,255,255,.2), + inset -0.11vw -0.18vw 0.52vw rgba(0,0,0,.4), + 0.18vw 0.18vw 0.36vw #000; + box-shadow: inset 0.11vw 0.18vw 0.52vw rgba(255,255,255,.2), + inset -0.11vw -0.18vw 0.52vw rgba(0,0,0,.4), + 0.18vw 0.18vw 0.36vw #000; } form select > option { background: #222; From 71f51d9f6b708679d2ea92c3593ac09dbb4b5764 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Mon, 17 Apr 2017 23:57:09 -0400 Subject: [PATCH 089/143] Added client-side javascript for /settings --- static/js/settings.js | 122 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 static/js/settings.js diff --git a/static/js/settings.js b/static/js/settings.js new file mode 100644 index 0000000..4183cc9 --- /dev/null +++ b/static/js/settings.js @@ -0,0 +1,122 @@ +'use strict'; +/* global location $ */ + +// Validate email addresses +function validateEmail(email) { + var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(email); +} + +// Turn inputed value into slug +function slugify(cb) { + $.get('/validate?slugify='+$('#slug-input').val()) + .done(function(data){ + console.log('Got '+data); + $('#slug-input').val(data); + 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){ + + 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. "); + $('#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(){ + slugify( function(){ + validateForm('slug'); + }); + }); + + // Validate email + $('#email-input').change(function(){ + validateForm('email'); + }); + + // Validate form after name change + $('#name-input').change(validateForm); + + // 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", + success: function(){ + location.reload(); + }, + fail: function(){ + alert("Failed to delete account!"); + } + }); + } + }); + +}); From 91962dc2579f8a05c63c910779832cd07f051eee Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 18 Apr 2017 00:12:39 -0400 Subject: [PATCH 090/143] #51 Added XSS validation on the client-side --- config/routes/index.js | 10 ++++++---- static/js/settings.js | 19 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/config/routes/index.js b/config/routes/index.js index 4b07d0a..87750c6 100644 --- a/config/routes/index.js +++ b/config/routes/index.js @@ -3,6 +3,7 @@ const mw = require('../middleware.js'), router = require('express').Router(), slug = require('slug'), + xss = require('xss'), User = require('../models.js').user; // Index @@ -38,11 +39,9 @@ router.get('/favicon.ico', (req,res)=>{ // Endpoint to validate forms router.get('/validate', (req,res)=>{ - console.log(req.query); // Validate unique slug if (req.query.slug) { - console.log(`Checking slug: ${req.query.slug} for user ${req.user.id}`); User.findOne({ slug: slug(req.query.slug) }) .then( (existingUser)=>{ if (existingUser && existingUser.id!==req.user.id) { @@ -55,7 +54,6 @@ router.get('/validate', (req,res)=>{ // Validate unique email else if (req.query.email) { - console.log(`Checking email: ${req.query.email} for user ${req.user.id}`); User.findOne({ email: req.query.email }) .then( (existingUser)=>{ if (existingUser && existingUser.id!==req.user.id) { @@ -71,6 +69,10 @@ router.get('/validate', (req,res)=>{ res.send(slug(req.query.slugify)); } + else if (req.query.xss) { + res.send(xss(req.query.xss)); + } + }); // Link to androidapp in play store @@ -84,4 +86,4 @@ router.get('/ios', (req,res)=>{ res.redirect('/help#why-is-there-no-ios-app'); }); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/static/js/settings.js b/static/js/settings.js index 4183cc9..ce3c0a5 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -7,12 +7,11 @@ function validateEmail(email) { return re.test(email); } -// Turn inputed value into slug -function slugify(cb) { - $.get('/validate?slugify='+$('#slug-input').val()) +// Replace inputed value with response +function validateFromEndpoint(type, selector, cb) { + $.get('/validate?'+type+'='+$(selector).val()) .done(function(data){ - console.log('Got '+data); - $('#slug-input').val(data); + $(selector).val(data); cb(); }); } @@ -90,8 +89,8 @@ $(function(){ // Validate slug $('#slug-input').change(function(){ - slugify( function(){ - validateForm('slug'); + validateFromEndpoint('slugify','#slug-input',function(){ + validateForm(slug); }); }); @@ -100,8 +99,10 @@ $(function(){ validateForm('email'); }); - // Validate form after name change - $('#name-input').change(validateForm); + // Validate name + $('#name-input').change(function(){ + validateFromEndpoint('xss','#name-input',validateForm); + }); // Delete account $('#delete').click(function(){ From 86802b66d577b67df3db25cc892c8861304e0d42 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 18 Apr 2017 00:21:21 -0400 Subject: [PATCH 091/143] #54 Only make street view requests once --- static/js/map.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/static/js/map.js b/static/js/map.js index a8096bc..515d3a1 100644 --- a/static/js/map.js +++ b/static/js/map.js @@ -201,9 +201,7 @@ function getAltitude(loc,elev,cb){ function getStreetViewData(loc,rad,cb) { if (!sv) { var sv=new google.maps.StreetViewService(); } sv.getPanorama({location:{lat:loc.lat,lng:loc.lon},radius:rad},function(data,status){ - if (status===google.maps.StreetViewStatus.ZERO_RESULTS){ - getStreetViewData(loc,rad*2,cb); - } else if (status!==google.maps.StreetViewStatus.OK){ console.error(new Error('⛔️ Street view not available:',status).message); } + if (status!==google.maps.StreetViewStatus.OK){ console.error(new Error('⛔️ Street view not available:',status).message); } else { cb(data); } }); } From 95908be6433d3bfa1ec83b6482614a34d7bdf6e8 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 18 Apr 2017 00:24:30 -0400 Subject: [PATCH 092/143] Slug should be sanatized for xss too --- config/routes/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes/index.js b/config/routes/index.js index 87750c6..621690a 100644 --- a/config/routes/index.js +++ b/config/routes/index.js @@ -66,7 +66,7 @@ router.get('/validate', (req,res)=>{ // Create slug else if (req.query.slugify) { - res.send(slug(req.query.slugify)); + res.send(slug(xss(req.query.slugify))); } else if (req.query.xss) { From 1dab4dc2662bb8ccbca04b3ed3350f425f64042f Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 18 Apr 2017 00:34:53 -0400 Subject: [PATCH 093/143] #52 Added server-side validation for settings --- config/models.js | 2 +- config/routes/settings.js | 61 +++++++++++++++++++++++++-------------- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/config/models.js b/config/models.js index 0dcd5f9..84bd9a8 100644 --- a/config/models.js +++ b/config/models.js @@ -7,7 +7,7 @@ const mongoose = require('mongoose'), const userSchema = new mongoose.Schema({ name: {type:String}, - email: {type:String, required:true}, + email: {type:String, required:true, unique:true}, slug: {type:String, required:true, unique:true}, auth: { password: String, diff --git a/config/routes/settings.js b/config/routes/settings.js index 3c1b659..8e051fe 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -9,6 +9,11 @@ const slug = require('slug'), env = require('../env.js'), router = require('express').Router(); +// Validate email addresses +function validateEmail(email) { + var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(email); +} // Settings form router.route('/') @@ -24,30 +29,42 @@ router.route('/') // Set new settings .post( (req,res,next)=>{ - //TODO: Validate everything! - - User.findByIdAndUpdate(req.user.id, {$set:{ - name: xss(req.body.name), - slug: slug(xss(req.body.slug)), - email: req.body.email, - settings: { - units: req.body.units, - defaultMap: req.body.map, - defaultZoom: req.body.zoom, - showSpeed: (req.body.showSpeed)?true:false, - showAlt: (req.body.showAlt)?true:false, - showStreetview: (req.body.showStreet)?true:false - } - }}) - .then( (user)=>{ - req.flash('success', 'Settings updated. '); + // Validations + if (req.body.slug==='') { + req.flash('warning', `You must supply a slug. `); res.redirect('/settings'); - }) - .catch( (err)=>{ - mw.throwErr(err,req); + } + else if (!validateEmail(req.body.email)) { + req.flash('warning', `${req.body.email} is not a valid email address. `); res.redirect('/settings'); - }); + } + else { + // Update user document + User.findByIdAndUpdate(req.user.id, {$set:{ + name: xss(req.body.name), + slug: slug(xss(req.body.slug)), + email: req.body.email, + settings: { + units: req.body.units, + defaultMap: req.body.map, + defaultZoom: req.body.zoom, + showSpeed: (req.body.showSpeed)?true:false, + showAlt: (req.body.showAlt)?true:false, + showStreetview: (req.body.showStreet)?true:false + } + }}) + .then( (user)=>{ + req.flash('success', 'Settings updated. '); + res.redirect('/settings'); + }) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/settings'); + }); + + } + } ) // Delete user account @@ -200,4 +217,4 @@ router.route('/pro') }); } ); -module.exports = router; \ No newline at end of file +module.exports = router; From 934e559956551d4214a4d9305fc8c13585604ad3 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 18 Apr 2017 00:41:43 -0400 Subject: [PATCH 094/143] #55 Added scale to map --- config/models.js | 1 + config/routes/settings.js | 1 + static/js/map.js | 1 + views/settings.html | 7 ++++++- 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/config/models.js b/config/models.js index 84bd9a8..96af3cb 100644 --- a/config/models.js +++ b/config/models.js @@ -25,6 +25,7 @@ const userSchema = new mongoose.Schema({ units: {type:String, required:true, default:'standard'}, defaultMap: {type:String, required:true, default:'road'}, defaultZoom: {type:Number, required:true, default:11}, + showScale: {type:Boolean, required:true, default:false}, showSpeed: {type:Boolean, required:true, default:false}, showTemp: {type:Boolean, required:true, default:false}, showAlt: {type:Boolean, required:true, default:false}, diff --git a/config/routes/settings.js b/config/routes/settings.js index 8e051fe..07b519b 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -49,6 +49,7 @@ router.route('/') units: req.body.units, defaultMap: req.body.map, defaultZoom: req.body.zoom, + showScale: (req.body.showScale)?true:false, showSpeed: (req.body.showSpeed)?true:false, showAlt: (req.body.showAlt)?true:false, showStreetview: (req.body.showStreet)?true:false diff --git a/static/js/map.js b/static/js/map.js index 515d3a1..2032a3f 100644 --- a/static/js/map.js +++ b/static/js/map.js @@ -81,6 +81,7 @@ window.gmapsCb = function() { map = new google.maps.Map( mapElem, { center: new google.maps.LatLng( mapuser.last.lat, mapuser.last.lon ), panControl: false, + scaleControl: mapuser.settings.showScale, draggable: false, zoom: mapuser.settings.defaultZoom, streetViewControl: false, diff --git a/views/settings.html b/views/settings.html index 7da99c5..a0d073e 100644 --- a/views/settings.html +++ b/views/settings.html @@ -114,6 +114,11 @@
    +
    + + +
    +
    @@ -146,4 +151,4 @@ {% block javascript %} {{super()}} -{% endblock %} \ No newline at end of file +{% endblock %} From e8b86caf0bd4c47c52200b4800a00ccb8e31826f Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 18 Apr 2017 01:05:54 -0400 Subject: [PATCH 095/143] Revised map controls --- static/js/map-controls.js | 167 +++++++++++++++++++++++--------------- views/map.html | 6 +- 2 files changed, 103 insertions(+), 70 deletions(-) diff --git a/static/js/map-controls.js b/static/js/map-controls.js index e976b12..00cc0ef 100644 --- a/static/js/map-controls.js +++ b/static/js/map-controls.js @@ -1,15 +1,19 @@ 'use strict'; /* global navigator $ socket userid token mapuser toggleMaps */ -var wpid, newloc; - -// Set location -function setLocation() { - if (!userid==mapuser._id) {alert('You are not logged in! ');} - else { - if (!navigator.geolocation) {alert('Geolocation not enabled. ');} - else { - navigator.geolocation.getCurrentPosition(function(pos){ +$(function(){ + + var wpid, newloc; + + // Set location + $('#set-loc').click(function(){ + if (!userid==mapuser._id){ alert('You are not logged in! '); } + else { if (!navigator.geolocation){ alert('Geolocation not enabled. '); } + + else { navigator.geolocation.getCurrentPosition( + + // Success callback + function(pos){ var newloc = { tok: token, usr: userid, @@ -20,65 +24,94 @@ function setLocation() { socket.emit('set', newloc); toggleMaps(newloc); console.log('⚜ Set location:',newloc.lat+", "+newloc.lon); - }, function(err) { + }, + + // Error callback + function(err) { alert("Unable to set location."); console.error('⛔️',err.message); - }, { enableHighAccuracy:true }); - } - } -} - -// Track location -function trackLocation() { - if (!userid==mapuser._id) { alert('You are not logged in! '); } - else { - // Stop tracking - if (wpid) { - $('#controls > .track').html(' Track').tooltip('hide'); - navigator.geolocation.clearWatch(wpid); - wpid = undefined; - // Start tracking - } else { - $('#controls > .track').html(' Stop').tooltip('show'); - if (!navigator.geolocation) { alert('Unable to track location. '); } - else { - wpid = navigator.geolocation.watchPosition(function(pos) { - newloc = { - tok: token, - usr: '{{user.id}}', - lat: pos.coords.latitude, - lon: pos.coords.longitude, - spd: (pos.coords.speed||0) - }; - socket.emit('set',newloc); - toggleMaps(newloc); - console.log('⚜ Set location:',newloc.lat+", "+newloc.lon); - }, function(err){ - alert("Unable to track location."); - console.error(err.message); - }, { enableHighAccuracy:true }); + }, + + // Options + { enableHighAccuracy:true } + + ); } } + + }); + + // Track location + $('#track-loc').click(function(){ + if (!userid==mapuser._id) { alert('You are not logged in! '); } + else { + + // Start tracking + if (!wpid) { + if (!navigator.geolocation) { alert('Unable to track location. '); } + else { + $('#track-loc').html(' Stop').prop('title',"Click here to stop tracking your location. "); + wpid = navigator.geolocation.watchPosition( + + // Success callback + function(pos) { + newloc = { + tok: token, + usr: '{{user.id}}', + lat: pos.coords.latitude, + lon: pos.coords.longitude, + spd: (pos.coords.speed||0) + }; socket.emit('set',newloc); + toggleMaps(newloc); + console.log('⚜ Set location:',newloc.lat+", "+newloc.lon); + }, + + // Error callback + function(err){ + alert("Unable to track location."); + console.error(err.message); + }, + + // Options + { enableHighAccuracy:true } + + ); + } + } - } - } -} + + // Stop tracking + else { + $('#track-loc').html(' Track').prop('title',"Click here to track your location. "); + navigator.geolocation.clearWatch(wpid); + wpid = undefined; + } + -// Clear location -function clearLocation() { - if (!userid==mapuser._id) { alert('You are not logged in! '); } - else { - // Stop tracking - if (wpid) { - $('#controls > .track').html(' Track').tooltip('hide'); - navigator.geolocation.clearWatch(wpid); - wpid = undefined; - } - newloc = { - tok: token, - usr: userid, - lat:0, lon:0, spd:0 - }; - socket.emit('set',newloc); - toggleMaps(newloc); - console.log('⚜ Cleared location'); - } -} + + } + }); + + // Clear location + $('#clear-loc').click(function(){ + if (!userid==mapuser._id) { alert('You are not logged in! '); } + else { + // Stop tracking + if (wpid) { + $('#track-loc').html(' Track'); + navigator.geolocation.clearWatch(wpid); + wpid = undefined; + } + + // Clear location + newloc = { + tok: token, + usr: userid, + lat:0, lon:0, spd:0 + }; socket.emit('set',newloc); + + // Turn off map + toggleMaps(newloc); + console.log('⚜ Cleared location'); + } + }); + +}); \ No newline at end of file diff --git a/views/map.html b/views/map.html index 6dd97dd..959129b 100644 --- a/views/map.html +++ b/views/map.html @@ -65,9 +65,9 @@ {% endif %} - - - + + +
    {% endif %} From d40734a2d90435b1746a474758621d31ec86a215 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 18 Apr 2017 01:18:08 -0400 Subject: [PATCH 096/143] Removed model-level requirements --- config/models.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/config/models.js b/config/models.js index 96af3cb..c3175b4 100644 --- a/config/models.js +++ b/config/models.js @@ -7,7 +7,7 @@ const mongoose = require('mongoose'), const userSchema = new mongoose.Schema({ name: {type:String}, - email: {type:String, required:true, unique:true}, + email: {type:String, unique:true}, slug: {type:String, required:true, unique:true}, auth: { password: String, @@ -22,22 +22,22 @@ const userSchema = new mongoose.Schema({ created: {type:Date, required:true}, lastLogin: Date, settings: { - units: {type:String, required:true, default:'standard'}, - defaultMap: {type:String, required:true, default:'road'}, - defaultZoom: {type:Number, required:true, default:11}, - showScale: {type:Boolean, required:true, default:false}, - showSpeed: {type:Boolean, required:true, default:false}, - showTemp: {type:Boolean, required:true, default:false}, - showAlt: {type:Boolean, required:true, default:false}, - showStreetview: {type:Boolean, required:true, default:false} + units: {type:String, default:'standard'}, + defaultMap: {type:String, default:'road'}, + defaultZoom: {type:Number, default:11}, + showScale: {type:Boolean, default:false}, + showSpeed: {type:Boolean, default:false}, + showTemp: {type:Boolean, default:false}, + showAlt: {type:Boolean, default:false}, + showStreetview: {type:Boolean, default:false} }, last: { time: Date, - lat: {type:Number, required:true, default:0}, - lon: {type:Number, required:true, default:0}, - dir: {type:Number, required:true, default:0}, - alt: {type:Number, required:true, default:0}, - spd: {type:Number, required:true, default:0} + lat: {type:Number, default:0}, + lon: {type:Number, default:0}, + dir: {type:Number, default:0}, + alt: {type:Number, default:0}, + spd: {type:Number, default:0} }, sk32: {type:String, required:true, unique:true} }).plugin(unique); From 5ae0704ef9e9aee6dc18fe3d57ab7052ede40e4b Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 18 Apr 2017 01:23:21 -0400 Subject: [PATCH 097/143] Fixed minor bug --- static/js/map-controls.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/js/map-controls.js b/static/js/map-controls.js index 00cc0ef..e25a5e1 100644 --- a/static/js/map-controls.js +++ b/static/js/map-controls.js @@ -55,7 +55,7 @@ $(function(){ function(pos) { newloc = { tok: token, - usr: '{{user.id}}', + usr: userid, lat: pos.coords.latitude, lon: pos.coords.longitude, spd: (pos.coords.speed||0) From 1c03b997e11ea7b76eed812fc44716d48b4b68ec Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 18 Apr 2017 03:08:57 -0400 Subject: [PATCH 098/143] #58 added email confirmation --- config/models.js | 41 ++++++++--- config/routes/auth.js | 21 +++--- config/routes/settings.js | 142 ++++++++++++++++++++++++++---------- static/css/login.css | 1 + views/templates/header.html | 2 +- 5 files changed, 145 insertions(+), 62 deletions(-) diff --git a/config/models.js b/config/models.js index c3175b4..fab9d0d 100644 --- a/config/models.js +++ b/config/models.js @@ -8,11 +8,13 @@ const mongoose = require('mongoose'), const userSchema = new mongoose.Schema({ name: {type:String}, email: {type:String, unique:true}, + newEmail: String, + emailToken: String, slug: {type:String, required:true, unique:true}, auth: { password: String, passToken: String, - tokenExpires: Date, + passTokenExpires: Date, google: {type:String, unique:true}, facebook: {type:String, unique:true}, twitter: {type:String, unique:true}, @@ -46,6 +48,21 @@ const userSchema = new mongoose.Schema({ //TODO: Return promises instead of taking callbacks + // Create email confirmation token + userSchema.methods.createEmailToken = function(next){ + var user = this; + + crypto.randomBytes(16, (err,buf)=>{ + if (err){ next(err,null); } + else { + user.emailToken = buf.toString('hex'); + user.save(); + return next(null,user.emailToken); + } + }); + + }; + // Generate hash for new password userSchema.methods.generateHash = function(password,next){ bcrypt.genSalt(8, (err,salt)=>{ @@ -55,29 +72,29 @@ const userSchema = new mongoose.Schema({ }; // Create password reset token - userSchema.methods.createToken = function(next){ + userSchema.methods.createPassToken = function(next){ var user = this; - if ( user.auth.tokenExpires <= Date.now() ){ - - // Reuse old token, resetting clock - user.auth.tokenExpires = Date.now() + 3600000; // 1 hour + + // Reuse old token, resetting clock + if ( user.auth.passTokenExpires <= Date.now() ){ + user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour user.save(); return next(null,user.auth.passToken); - - } else { - - // Create new token + } + + // Create new token + else { crypto.randomBytes(16, (err,buf)=>{ if (err){ next(err,null); } else { user.auth.passToken = buf.toString('hex'); - user.auth.tokenExpires = Date.now() + 3600000; // 1 hour + user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour user.save(); return next(null,user.auth.passToken); } }); - } + }; // Check for valid password diff --git a/config/routes/auth.js b/config/routes/auth.js index 018a3ce..55aa188 100644 --- a/config/routes/auth.js +++ b/config/routes/auth.js @@ -65,23 +65,26 @@ module.exports = (app, passport) => { function sendToken(user){ // Create a password token - user.createToken((err,token)=>{ + user.createPassToken((err,token)=>{ if (err){ mw.throwErr(err,req); } // Email the instructions to continue mail.send({ - from: mail.from, - to: `<${user.email}>`, - subject: 'Complete your Tracman registration', - text: mail.text(`Welcome to Tracman! \n\nTo complete your registration, follow this link and set your password:\n${env.url}/settings/password/${token}`), - html: mail.html(`

    Welcome to Tracman!

    To complete your registration, follow this link and set your password:
    ${env.url}/settings/password/${token}

    `) - }).then(()=>{ + from: mail.from, + to: `<${user.email}>`, + subject: 'Complete your Tracman registration', + text: mail.text(`Welcome to Tracman! \n\nTo complete your registration, follow this link and set your password:\n${env.url}/settings/password/${token}`), + html: mail.html(`

    Welcome to Tracman!

    To complete your registration, follow this link and set your password:
    ${env.url}/settings/password/${token}

    `) + }) + .then(()=>{ req.flash('success', `An email has been sent to ${user.email}. Check your inbox to complete your registration. `); res.redirect('/login'); - }).catch((err)=>{ + }) + .catch((err)=>{ mw.throwErr(err,req); res.redirect('/login#signup'); }); + }); } @@ -204,7 +207,7 @@ module.exports = (app, passport) => { else { // Create reset token - user.createToken( (err,token)=>{ + user.createPassToken( (err,token)=>{ if (err){ next(err); } // Email reset link diff --git a/config/routes/settings.js b/config/routes/settings.js index 07b519b..82da4ba 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -40,22 +40,49 @@ router.route('/') } else { - // Update user document - User.findByIdAndUpdate(req.user.id, {$set:{ - name: xss(req.body.name), - slug: slug(xss(req.body.slug)), - email: req.body.email, - settings: { - units: req.body.units, - defaultMap: req.body.map, - defaultZoom: req.body.zoom, - showScale: (req.body.showScale)?true:false, - showSpeed: (req.body.showSpeed)?true:false, - showAlt: (req.body.showAlt)?true:false, - showStreetview: (req.body.showStreet)?true:false - } - }}) - .then( (user)=>{ + // Confirm email change + if (req.user.email!==req.body.email) { + req.user.newEmail = req.body.email; + + req.user.createEmailToken((err,token)=>{ + if (err){ + mw.throwErr(err,req); + res.redirect(req.session.next||'/settings'); + } + + // Send token to user by 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( ()=>{ + // Alert user to check email. + req.flash('warning',`An email has been sent to ${req.body.email}. Check your inbox to confirm your new email. `); + }) + .catch( (err)=>{ + mw.throwErr(err,req); + }); + + }); + } + + // Set new user settings + 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, + defaultZoom: req.body.zoom, + showScale: (req.body.showScale)?true:false, + showSpeed: (req.body.showSpeed)?true:false, + showAlt: (req.body.showAlt)?true:false, + showStreetview: (req.body.showStreet)?true:false + }; + req.user.save() + .then( ()=>{ req.flash('success', 'Settings updated. '); res.redirect('/settings'); }) @@ -74,17 +101,51 @@ router.route('/') //TODO: Reenter password? User.findByIdAndRemove(req.user) - .then(()=>{ + .then( ()=>{ req.flash('success', 'Your account has been deleted. '); res.redirect('/'); }) - .catch((err)=>{ + .catch( (err)=>{ mw.throwErr(err,req); res.redirect('/settings'); }); } ); +// Confirm email address +router.get('/email/:token', mw.ensureAuth, (req,res,next)=>{ + + // Check token + if ( req.user.emailToken===req.params.token) { + + // Set new email + req.user.email = req.user.newEmail; + req.user.save().then( ()=>{ + + // Delete token and newEmail + req.user.emailToken = undefined; + req.user.newEmail = undefined; + req.user.save(); + + // Report success + req.flash('success',`Your email has been set to ${req.user.email}. `); + res.redirect('/settings'); + + }) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect(req.session.next||'/settings'); + }); + + } + + // Invalid token + else { + req.flash('danger', 'Email confirmation token is invalid. '); + res.redirect('/settings'); + } + + } ); // Set password router.route('/password') @@ -96,30 +157,31 @@ router.route('/password') .get( (req,res,next)=>{ // Create token for password change - req.user.createToken() - .then( (token)=>{ - - // 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.

    `) - }).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'); - }).catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/login#login'); - }); - + req.user.createPassToken( (err,token)=>{ + if (err){ + mw.throwErr(err,req); + res.redirect(req.session.next||'/settings'); + } + + // 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.

    `) + }) + .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'); }) .catch( (err)=>{ mw.throwErr(err,req); - res.redirect('/password'); + res.redirect('/login#login'); }); + + }); } ); @@ -129,7 +191,7 @@ router.route('/password/:token') .all( (req,res,next)=>{ User .findOne({'auth.passToken': req.params.token}) - .where('auth.tokenExpires').gt(Date.now()) + .where('auth.passTokenExpires').gt(Date.now()) .then((user) => { if (!user) { req.flash('danger', 'Password reset token is invalid or has expired. '); @@ -163,7 +225,7 @@ router.route('/password/:token') // Delete token res.locals.passwordUser.auth.passToken = undefined; - res.locals.passwordUser.auth.tokenExpires = undefined; + res.locals.passwordUser.auth.passTokenExpires = undefined; // Create hash res.locals.passwordUser.generateHash( req.body.password, (err,hash)=>{ diff --git a/static/css/login.css b/static/css/login.css index 167dda1..f49cb0f 100644 --- a/static/css/login.css +++ b/static/css/login.css @@ -32,6 +32,7 @@ form input.btn[type="submit"] { } form #social-login { justify-content: space-around; + flex-wrap: nowrap; width: 100%; } #show { diff --git a/views/templates/header.html b/views/templates/header.html index acdb59d..cf46b34 100644 --- a/views/templates/header.html +++ b/views/templates/header.html @@ -43,7 +43,7 @@ {% endfor %} {% for warning in warnings %}
    - Whoops! {{ warning | safe }} + Hey! {{ warning | safe }}
    {% endfor %} From e7ba83e7e5cfcf2b05418f8d4300808fecee0f08 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 18 Apr 2017 13:03:24 -0400 Subject: [PATCH 099/143] #58 Fixed missing flash alerting user of confirmation email --- config/routes/settings.js | 57 ++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/config/routes/settings.js b/config/routes/settings.js index 82da4ba..5206247 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -40,7 +40,35 @@ router.route('/') } else { - // Confirm email change + function setSettings(){ + + // 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, + defaultZoom: req.body.zoom, + showScale: (req.body.showScale)?true:false, + showSpeed: (req.body.showSpeed)?true:false, + showAlt: (req.body.showAlt)?true:false, + showStreetview: (req.body.showStreet)?true:false + }; + + // Save user and respond + req.user.save() + .then( ()=>{ + req.flash('success', 'Settings updated. '); + res.redirect('/settings'); + }) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/settings'); + }); + + }; + + // Email changed if (req.user.email!==req.body.email) { req.user.newEmail = req.body.email; @@ -61,6 +89,8 @@ router.route('/') .then( ()=>{ // Alert user to check email. req.flash('warning',`An email has been sent to ${req.body.email}. Check your inbox to confirm your new email. `); + // Set other settings + setSettings(); }) .catch( (err)=>{ mw.throwErr(err,req); @@ -69,27 +99,10 @@ router.route('/') }); } - // Set new user settings - 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, - defaultZoom: req.body.zoom, - showScale: (req.body.showScale)?true:false, - showSpeed: (req.body.showSpeed)?true:false, - showAlt: (req.body.showAlt)?true:false, - showStreetview: (req.body.showStreet)?true:false - }; - req.user.save() - .then( ()=>{ - req.flash('success', 'Settings updated. '); - res.redirect('/settings'); - }) - .catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/settings'); - }); + // Email not changed + else { + setSettings(); + } } From 218435d8c70a496a10d2a35c9383c0a3b130e38f Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 18 Apr 2017 13:10:43 -0400 Subject: [PATCH 100/143] #58 Cleaned up --- config/routes/settings.js | 40 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/config/routes/settings.js b/config/routes/settings.js index 5206247..f0b912f 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -29,18 +29,7 @@ router.route('/') // Set new settings .post( (req,res,next)=>{ - // Validations - if (req.body.slug==='') { - req.flash('warning', `You must supply a slug. `); - 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 { - - function setSettings(){ + function setSettings(){ // Set values req.user.name = xss(req.body.name); @@ -55,7 +44,7 @@ router.route('/') showStreetview: (req.body.showStreet)?true:false }; - // Save user and respond + // Save user and send response req.user.save() .then( ()=>{ req.flash('success', 'Settings updated. '); @@ -66,12 +55,24 @@ router.route('/') res.redirect('/settings'); }); - }; + } + + // Validations + if (req.body.slug==='') { + req.flash('warning', `You must supply a slug. `); + 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 { // Email changed if (req.user.email!==req.body.email) { req.user.newEmail = req.body.email; + // Create token req.user.createEmailToken((err,token)=>{ if (err){ mw.throwErr(err,req); @@ -87,9 +88,7 @@ router.route('/') 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( ()=>{ - // Alert user to check email. - req.flash('warning',`An email has been sent to ${req.body.email}. Check your inbox to confirm your new email. `); - // Set other settings + req.flash('warning',`An email has been sent to ${req.body.email}. Check your inbox to confirm your new email address. `); setSettings(); }) .catch( (err)=>{ @@ -100,9 +99,7 @@ router.route('/') } // Email not changed - else { - setSettings(); - } + else { setSettings(); } } @@ -133,7 +130,8 @@ router.get('/email/:token', mw.ensureAuth, (req,res,next)=>{ // Set new email req.user.email = req.user.newEmail; - req.user.save().then( ()=>{ + req.user.save() + .then( ()=>{ // Delete token and newEmail req.user.emailToken = undefined; From 192d66ca6f481c58dfe94c853e66b9eac463492c Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 18 Apr 2017 23:52:13 -0400 Subject: [PATCH 101/143] Switched mongoose methods to promises --- config/models.js | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/config/models.js b/config/models.js index fab9d0d..b324250 100644 --- a/config/models.js +++ b/config/models.js @@ -52,23 +52,23 @@ const userSchema = new mongoose.Schema({ userSchema.methods.createEmailToken = function(next){ var user = this; - crypto.randomBytes(16, (err,buf)=>{ - if (err){ next(err,null); } - else { - user.emailToken = buf.toString('hex'); - user.save(); - return next(null,user.emailToken); - } - }); + crypto.randomBytes(16) + .then( (buf)=>{ + user.emailToken = buf.toString('hex'); + user.save(); + return next(null,user.emailToken); + }) + .catch( (err)=>{ next(err,null); }); }; // Generate hash for new password userSchema.methods.generateHash = function(password,next){ - bcrypt.genSalt(8, (err,salt)=>{ - if (err){ return next(err); } + bcrypt.genSalt(8) + .then( (salt)=>{ bcrypt.hash(password, salt, null, next); - }); + }) + .catch( (err)=>{ return next(err); }); }; // Create password reset token @@ -84,15 +84,14 @@ const userSchema = new mongoose.Schema({ // Create new token else { - crypto.randomBytes(16, (err,buf)=>{ - if (err){ next(err,null); } - else { - user.auth.passToken = buf.toString('hex'); - user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour - user.save(); - return next(null,user.auth.passToken); - } - }); + crypto.randomBytes(16) + .then( (buf)=>{ + user.auth.passToken = buf.toString('hex'); + user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour + user.save(); + return next(null,user.auth.passToken); + }) + .catch( (err)=>{ return next(err,null); }); } }; From 40808f8a05aabff654eacf8700b50c734ad33f46 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 19 Apr 2017 00:13:57 -0400 Subject: [PATCH 102/143] Fixed redirection after auth --- config/routes/auth.js | 17 ++--------------- server.js | 5 ++++- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/config/routes/auth.js b/config/routes/auth.js index 55aa188..d5b50f1 100644 --- a/config/routes/auth.js +++ b/config/routes/auth.js @@ -20,14 +20,7 @@ module.exports = (app, passport) => { failureFlash: true }, loginCallback = (req,res)=>{ - // Prevent redirect loop - if (req.session.next.substring(0,7)==='/login#'){ - req.session.next = '#'; - res.redirect('/map'); - } - else { - res.redirect( req.session.next || '/map' ); - } + res.redirect( req.session.next || '/map' ); }; // Login/-out @@ -45,13 +38,7 @@ module.exports = (app, passport) => { app.get('/logout', (req,res)=>{ req.logout(); req.flash('success',`You have been logged out.`); - // Prevent redirect loop - if (req.session.next.substring(0,8)==='/logout#') { - req.session.next = '#'; - res.redirect('/'); - } else { - res.redirect(req.session.next || '/'); - } + res.redirect( req.session.next || '/' ); }); // Signup diff --git a/server.js b/server.js index 5c2e82b..3732218 100755 --- a/server.js +++ b/server.js @@ -80,7 +80,10 @@ const app.get( '*', (req,res,next)=>{ // Path for redirects - req.session.next = ( req.path.substring(0, req.path.indexOf('#')) || req.path )+'#'; + let nextPath = ( req.path.substring(0, req.path.indexOf('#')) || req.path ); + if ( nextPath!=='/login' && nextPath!=='/logout' ){ + req.session.next = nextPath+'#'; + } // User account res.locals.user = req.user; From 8069dcd1d14f4d24cb6d0bec2670aa5b3e504974 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 19 Apr 2017 00:40:16 -0400 Subject: [PATCH 103/143] #57 Added email/password auth for android --- config/routes/auth.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/config/routes/auth.js b/config/routes/auth.js index d5b50f1..c0dbed0 100644 --- a/config/routes/auth.js +++ b/config/routes/auth.js @@ -21,6 +21,10 @@ module.exports = (app, passport) => { }, loginCallback = (req,res)=>{ res.redirect( req.session.next || '/map' ); + }, + androidLoginCallback = (req,res)=>{ + if (req.user){ res.send(req.user); } + else { res.sendStatus(401); } }; // Login/-out @@ -261,11 +265,10 @@ module.exports = (app, passport) => { } }, loginCallback); - // Android auth - //TODO: See if there's a better method - app.get('/auth/google/idtoken', passport.authenticate('google-id-token'), (req,res)=>{ - if (!req.user){ res.sendStatus(401); } - else { res.send(req.user); } - } ); + // Android + app.get('/login/android/', passport.authenticate('local'), androidLoginCallback); + app.get('/login/android/google', passport.authenticate('google-id-token'), androidLoginCallback); + //TODO: Add android facebook login + //TODO: Add android twitter login }; From c71c1fd1c1358a2add9e95891d4dd72caf35b357 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 19 Apr 2017 21:37:00 -0400 Subject: [PATCH 104/143] #32 Fixed social logins --- config/passport.js | 125 +++++++++++++++++++++++++++++--------- config/routes/auth.js | 74 ++++++++++++---------- config/routes/settings.js | 8 +-- package.json | 3 +- server.js | 3 +- views/index.html | 3 +- 6 files changed, 148 insertions(+), 68 deletions(-) diff --git a/config/passport.js b/config/passport.js index 644247b..897f63b 100644 --- a/config/passport.js +++ b/config/passport.js @@ -5,6 +5,9 @@ const GoogleStrategy = require('passport-google-oauth20').Strategy, FacebookStrategy = require('passport-facebook').Strategy, TwitterStrategy = require('passport-twitter').Strategy, + GoogleTokenStrategy = require('passport-google-id-token'), + FacebookTokenStrategy = require('passport-facebook-token'), + TwitterTokenStrategy = require('passport-twitter-token'), env = require('./env.js'), mw = require('./middleware.js'), User = require('./models.js').user; @@ -34,7 +37,7 @@ module.exports = (passport)=>{ // No user with that email if (!user) { req.session.next = undefined; - return done( null, false, req.flash('danger','Incorrect email or password.') ); + return done( null, false, req.flash('warning','Incorrect email or password.') ); } // User exists @@ -47,7 +50,7 @@ module.exports = (passport)=>{ // Password incorrect if (!res) { req.session.next = undefined; - return done( null, false, req.flash('danger','Incorrect email or password.') ); + return done( null, false, req.flash('warning','Incorrect email or password.') ); } // Successful login @@ -65,41 +68,55 @@ module.exports = (passport)=>{ // Social login function socialLogin(req, service, profileId, done) { + // console.log(`socialLogin() called`); + let query = {}; + query['auth.'+service] = profileId; - // Log in + // Intent to log in if (!req.user) { - // console.log(`Logging in with ${service}.`); - - var query = {}; - query['auth.'+service] = profileId; - User.findOne(query, (err,user)=>{ - if (err){ return done(err); } + // console.log(`Logging in with ${service}...`); + User.findOne(query) + .then( (user)=>{ // Can't find user - else if (!user){ + if (!user){ // Lazy update from old googleId field if (service==='google') { - User.findOne( {'googleID':parseInt(profileId)}, (err,user)=>{ - if (err){ return done(err); } + User.findOne({ 'googleID': parseInt(profileId) }) + .then( (user)=>{ + + // User exists with old schema if (user) { user.auth.google = profileId; - user.googleId = null; - user.save( (err)=>{ - if (err){ mw.throwErr(err,req); } - else { console.info(`🗂️ Lazily updated schema for ${user.name}.`); } + user.googleId = undefined; + user.save() + .then( ()=>{ + console.info(`🗂️ Lazily updated schema for ${user.name}.`); return done(null, user); - } ); - } else { - req.flash('danger',`There's no user for that ${service} account. `); + }) + .catch( (err)=>{ + mw.throwErr(err,req); + return done(err); + }); + } + + // No such user + else { + req.flash('warning',`There's no user for that ${service} account. `); return done(); } - } ); + + }) + .catch ( (err)=>{ + mw.throwErr(err,req); + return done(err); + }); } // No googleId either else { - req.flash('danger',`There's no user for that ${service} account. `); + req.flash('warning',`There's no user for that ${service} account. `); return done(); } } @@ -109,17 +126,49 @@ module.exports = (passport)=>{ // console.log(`Found user: ${user}`); return done(null, user); } + + }) + .catch( (err)=>{ + mw.throwErr(err,req); + return done(err); }); } - // Connect account + // Intent to connect account else { - // console.log(`Connecting ${service} account.`); - req.user.auth[service] = profileId; - req.user.save( (err)=>{ - if (err){ return done(err); } - else { return done(null, req.user); } - } ); + // console.log(`Connecting ${service} account...`); + + // Check for unique profileId + User.findOne(query) + .then( (existingUser)=>{ + + // Social account already in use + if (existingUser) { + req.flash('warning',`Another user is already connected to that ${service} account. `); + return done(); + } + + // Connect to account + else { + // console.log(`Connecting ${service} account.`); + req.user.auth[service] = profileId; + req.user.save() + .then( ()=>{ + req.flash('success', `${mw.capitalize(service)} account connected. `); + return done(null,req.user); + } ) + .catch( (err)=>{ + mw.throwErr(err); + return done(err); + } ); + } + + }) + .catch( (err)=>{ + mw.throwErr(err,req); + return done(err); + }) + } } @@ -133,6 +182,12 @@ module.exports = (passport)=>{ }, (req, accessToken, refreshToken, profile, done)=>{ socialLogin(req, 'google', profile.id, done); } + )).use('google-token', new GoogleTokenStrategy({ + clientID: env.googleClientId, + passReqToCallback: true + }, (req, parsedToken, googleId, done)=>{ + socialLogin(req,'google', googleId, done); + } )); // Facebook @@ -144,6 +199,13 @@ module.exports = (passport)=>{ }, (req, accessToken, refreshToken, profile, done)=>{ socialLogin(req, 'facebook', profile.id, done); } + )).use('facebook-token', new FacebookTokenStrategy({ + clientID: env.facebookAppId, + clientSecret: env.facebookAppSecret, + passReqToCallback: true + }, (req, accessToken, refreshToken, profile, done)=>{ + socialLogin(req,'facebook', profile.id, done); + } )); // Twitter @@ -155,6 +217,13 @@ module.exports = (passport)=>{ }, (req, token, tokenSecret, profile, done)=>{ socialLogin(req, 'twitter', profile.id, done); } + )).use('twitter-token', new TwitterTokenStrategy({ + consumerKey: env.twitterConsumerKey, + consumerSecret: env.twitterConsumerSecret, + passReqToCallback: true + }, (req, token, tokenSecret, profile, done)=>{ + socialLogin(req,'twitter', profile.id, done); + } )); return passport; diff --git a/config/routes/auth.js b/config/routes/auth.js index c0dbed0..41a1a3b 100644 --- a/config/routes/auth.js +++ b/config/routes/auth.js @@ -14,15 +14,13 @@ module.exports = (app, passport) => { loginOutcome = { failureRedirect: '/login', failureFlash: true - }, - connectOutcome = { - failureRedirect: '/settings', - failureFlash: true - }, + }, loginCallback = (req,res)=>{ + // console.log(`Login callback called... redirecting to ${req.session.next}`); + req.flash('success',"You have been logged in."); res.redirect( req.session.next || '/map' ); }, - androidLoginCallback = (req,res)=>{ + appLoginCallback = (req,res)=>{ if (req.user){ res.send(req.user); } else { res.sendStatus(401); } }; @@ -212,7 +210,8 @@ module.exports = (app, passport) => { req.flash('success', `If an account exists with the email ${req.body.email}, an email has been sent there with a password reset link. `); res.redirect('/login'); }).catch((err)=>{ - mw.throwErr(err); + mw.throwErr(err,req); + res.redirect('/login'); }); }); @@ -225,50 +224,59 @@ module.exports = (app, passport) => { }); } ); - + + // Android + app.get('/login/app/', passport.authenticate('local'), appLoginCallback); + + // Token-based + app.get(['/login/app/google','/auth/google/idtoken'], passport.authenticate('google-token'), appLoginCallback); + app.get('/login/app/facebook', passport.authenticate('facebook-token'), appLoginCallback); + app.get('/login/app/twitter', passport.authenticate('twitter-token'), appLoginCallback); + // Social app.get('/login/:service', (req,res,next)=>{ let service = req.params.service, - sendParams = (service==='google')? {scope:['profile']} : null; + sendParams = (service==='google')?{scope:['https://www.googleapis.com/auth/userinfo.profile']}:null; // Social login if (!req.user) { + // console.log(`Attempting to login with ${service} with params: ${JSON.stringify(sendParams)}...`); passport.authenticate(service, sendParams)(req,res,next); } // Connect social account else if (!req.user.auth[service]) { + // console.log(`Attempting to connect ${service} account...`); passport.authorize(service, sendParams)(req,res,next); } // Disconnect social account else { + // console.log(`Attempting to disconnect ${service} account...`); req.user.auth[service] = undefined; req.user.save() - .catch((err)=>{ - mw.throwErr(err,req); - res.redirect('/settings'); - }).then(()=>{ - req.flash('success', `${mw.capitalize(service)} account disconnected. `); - res.redirect('/settings'); - }); - + .then(()=>{ + req.flash('success', `${mw.capitalize(service)} account disconnected. `); + res.redirect('/settings'); + }) + .catch((err)=>{ + mw.throwErr(err,req); + res.redirect('/settings'); + }); } + }); - app.get('/login/:service/cb', (req,res,next)=>{ - var service = req.params.service; - if (!req.user) { - passport.authenticate(service, loginOutcome)(req,res,next); - } else { - req.flash('success', `${mw.capitalize(service)} account connected. `); - passport.authenticate(service, connectOutcome)(req,res,next); - } - }, loginCallback); - - // Android - app.get('/login/android/', passport.authenticate('local'), androidLoginCallback); - app.get('/login/android/google', passport.authenticate('google-id-token'), androidLoginCallback); - //TODO: Add android facebook login - //TODO: Add android twitter login - + app.get('/login/google/cb', + passport.authenticate('google',loginOutcome), + loginCallback + ); + app.get('/login/facebook/cb', + passport.authenticate('facebook',loginOutcome), + loginCallback + ); + app.get('/login/twitter/cb', + passport.authenticate('twitter',loginOutcome), + loginCallback + ); }; + diff --git a/config/routes/settings.js b/config/routes/settings.js index f0b912f..3b5a506 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -31,10 +31,10 @@ router.route('/') function setSettings(){ - // Set values - req.user.name = xss(req.body.name); - req.user.slug = slug(xss(req.body.slug)); - req.user.settings = { + // 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, defaultZoom: req.body.zoom, diff --git a/package.json b/package.json index 174eb33..bf76c38 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,12 @@ "nunjucks": "^2.3.0", "passport": "^0.3.2", "passport-facebook": "^2.1.1", + "passport-facebook-token": "^3.3.0", "passport-google-id-token": "^0.4.0", - "passport-google-oauth2": "^0.1.6", "passport-google-oauth20": "^1.0.0", "passport-local": "^1.0.0", "passport-twitter": "^1.0.4", + "passport-twitter-token": "^1.3.0", "slug": "^0.9.1", "socket.io": "^1.4.4", "xss": "^0.3.3" diff --git a/server.js b/server.js index 3732218..afe0e3e 100755 --- a/server.js +++ b/server.js @@ -81,7 +81,8 @@ const // Path for redirects let nextPath = ( req.path.substring(0, req.path.indexOf('#')) || req.path ); - if ( nextPath!=='/login' && nextPath!=='/logout' ){ + if ( nextPath.substring(0,6)!=='/login' && nextPath.substring(0,7)!=='/logout' ){ + console.log(`Setting redirect path to "${nextPath}#"`); req.session.next = nextPath+'#'; } diff --git a/views/index.html b/views/index.html index 96cf3f8..d6a8175 100644 --- a/views/index.html +++ b/views/index.html @@ -12,12 +12,13 @@

    Display your realtime GPS location on a map

    {% if user %} Map + Settings {% if user.isAdmin %} Admin {% endif %} {% else %} View example - Join + Join Login {% endif %} From b9fe85c07fbc9f4e4d10d29f5583e1874e70d64f Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Wed, 19 Apr 2017 22:03:45 -0400 Subject: [PATCH 105/143] #52 Fixed login flash messages --- config/passport.js | 21 +++-- config/routes/auth.js | 173 ++++++++++++++++++++---------------------- server.js | 2 +- 3 files changed, 98 insertions(+), 98 deletions(-) diff --git a/config/passport.js b/config/passport.js index 897f63b..c40a98e 100644 --- a/config/passport.js +++ b/config/passport.js @@ -93,6 +93,8 @@ module.exports = (passport)=>{ user.save() .then( ()=>{ console.info(`🗂️ Lazily updated schema for ${user.name}.`); + req.session.flashType = 'success'; + req.session.flashMessage = "You have been logged in. "; return done(null, user); }) .catch( (err)=>{ @@ -103,7 +105,8 @@ module.exports = (passport)=>{ // No such user else { - req.flash('warning',`There's no user for that ${service} account. `); + req.session.flashType = 'warning'; + req.session.flashMessage = `There's no user for that ${service} account. `; return done(); } @@ -116,7 +119,8 @@ module.exports = (passport)=>{ // No googleId either else { - req.flash('warning',`There's no user for that ${service} account. `); + req.session.flashType = 'warning'; + req.session.flashMessage = `There's no user for that ${service} account. `; return done(); } } @@ -124,6 +128,8 @@ module.exports = (passport)=>{ // Successfull social login else { // console.log(`Found user: ${user}`); + req.session.flashType = 'success'; + req.session.flashMessage = "You have been logged in."; return done(null, user); } @@ -136,7 +142,7 @@ module.exports = (passport)=>{ // Intent to connect account else { - // console.log(`Connecting ${service} account...`); + // console.log(`Attempting to connect ${service} account...`); // Check for unique profileId User.findOne(query) @@ -144,7 +150,9 @@ module.exports = (passport)=>{ // Social account already in use if (existingUser) { - req.flash('warning',`Another user is already connected to that ${service} account. `); + // console.log(`${service} account already in use.`); + req.session.flashType = 'warning'; + req.session.flashMessage = `Another user is already connected to that ${service} account. `; return done(); } @@ -154,7 +162,8 @@ module.exports = (passport)=>{ req.user.auth[service] = profileId; req.user.save() .then( ()=>{ - req.flash('success', `${mw.capitalize(service)} account connected. `); + req.session.flashType = 'success'; + req.session.flashMessage = `${mw.capitalize(service)} account connected. `; return done(null,req.user); } ) .catch( (err)=>{ @@ -167,7 +176,7 @@ module.exports = (passport)=>{ .catch( (err)=>{ mw.throwErr(err,req); return done(err); - }) + }); } diff --git a/config/routes/auth.js b/config/routes/auth.js index 41a1a3b..33846fb 100644 --- a/config/routes/auth.js +++ b/config/routes/auth.js @@ -17,7 +17,9 @@ module.exports = (app, passport) => { }, loginCallback = (req,res)=>{ // console.log(`Login callback called... redirecting to ${req.session.next}`); - req.flash('success',"You have been logged in."); + req.flash(req.session.flashType,req.session.flashMessage); + req.session.flashType = undefined; + req.session.flashMessage = undefined; res.redirect( req.session.next || '/map' ); }, appLoginCallback = (req,res)=>{ @@ -84,86 +86,84 @@ module.exports = (app, passport) => { // Check if somebody already has that email User.findOne({'email':req.body.email}) - .then( (user)=>{ + .then( (user)=>{ + + // User already exists + if (user && user.auth.password) { + req.flash('warning','A user with that email already exists! If you forgot your password, you can reset it here.'); + res.redirect('/login#login'); + next(); + } + + // User exists but hasn't created a password yet + else if (user) { + // Send another token (or the same one if it hasn't expired) + sendToken(user); + } + + // Create user + else { - // User already exists - if (user && user.auth.password) { - req.flash('warning','A user with that email already exists! If you forgot your password, you can reset it here.'); - res.redirect('/login#login'); - next(); - } + user = new User(); + user.created = Date.now(); + user.email = req.body.email; + user.slug = slug(user.email.substring(0, user.email.indexOf('@'))); - // User exists but hasn't created a password yet - else if (user) { - // Send another token (or the same one if it hasn't expired) - sendToken(user); - } - - // Create user - else { - - user = new User(); - user.created = Date.now(); - user.email = req.body.email; - user.slug = slug(user.email.substring(0, user.email.indexOf('@'))); - - // Generate unique slug - let slug = new Promise((resolve,reject) => { - (function checkSlug(s,cb){ + // Generate unique slug + let slug = new Promise((resolve,reject) => { + (function checkSlug(s,cb){ + + User.findOne({slug:s}) + .then((existingUser)=>{ - User.findOne({slug:s}) - .catch((err)=>{ - mw.throwErr(err,req); - }) - .then((existingUser)=>{ - - // Slug in use: generate a random one and retry - if (existingUser){ - crypto.randomBytes(6, (err,buf)=>{ - if (err) { mw.throwErr(err,req); } - s = buf.toString('hex'); - checkSlug(s,cb); - }); - } - - // Unique slug: proceed - else { cb(s); } - - }); + // Slug in use: generate a random one and retry + if (existingUser){ + crypto.randomBytes(6, (err,buf)=>{ + if (err) { mw.throwErr(err,req); } + s = buf.toString('hex'); + checkSlug(s,cb); + }); + } - })(user.slug, (newSlug)=>{ - user.slug = newSlug; - resolve(); - }); - }); - - // Generate sk32 - let sk32 = new Promise((resolve,reject) => { - crypto.randomBytes(32, (err,buf)=>{ - if (err) { mw.throwErr(err,req); } - user.sk32 = buf.toString('hex'); - resolve(); - }); - }); - - // Save user and send the token by email - Promise.all([slug, sk32]) - .then( ()=> { - user.save(); - }).then( ()=>{ - sendToken(user); - }).catch( (err)=>{ + // Unique slug: proceed + else { cb(s); } + + }) + .catch((err)=>{ mw.throwErr(err,req); - res.redirect('/login#signup'); }); - - } + + })(user.slug, (newSlug)=>{ + user.slug = newSlug; + resolve(); + }); + }); - }) - .catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/signup'); - }); + // Generate sk32 + let sk32 = new Promise((resolve,reject) => { + crypto.randomBytes(32, (err,buf)=>{ + if (err) { mw.throwErr(err,req); } + user.sk32 = buf.toString('hex'); + resolve(); + }); + }); + + // Save user and send the token by email + Promise.all([slug, sk32]) + .then( ()=>{ user.save(); }) + .then( ()=>{ sendToken(user); }) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/login#signup'); + }); + + } + + }) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/signup'); + }); }); @@ -228,10 +228,10 @@ module.exports = (app, passport) => { // Android app.get('/login/app/', passport.authenticate('local'), appLoginCallback); - // Token-based - app.get(['/login/app/google','/auth/google/idtoken'], passport.authenticate('google-token'), appLoginCallback); - app.get('/login/app/facebook', passport.authenticate('facebook-token'), appLoginCallback); - app.get('/login/app/twitter', passport.authenticate('twitter-token'), appLoginCallback); + // Token-based (android social) + app.get(['/login/app/google','/auth/google/idtoken'], passport.authenticate('google-token'), appLoginCallback); + app.get('/login/app/facebook', passport.authenticate('facebook-token'), appLoginCallback); + app.get('/login/app/twitter', passport.authenticate('twitter-token'), appLoginCallback); // Social app.get('/login/:service', (req,res,next)=>{ @@ -266,17 +266,8 @@ module.exports = (app, passport) => { } }); - app.get('/login/google/cb', - passport.authenticate('google',loginOutcome), - loginCallback - ); - app.get('/login/facebook/cb', - passport.authenticate('facebook',loginOutcome), - loginCallback - ); - app.get('/login/twitter/cb', - passport.authenticate('twitter',loginOutcome), - loginCallback - ); + app.get('/login/google/cb', passport.authenticate('google',loginOutcome), loginCallback ); + app.get('/login/facebook/cb', passport.authenticate('facebook',loginOutcome), loginCallback ); + app.get('/login/twitter/cb', passport.authenticate('twitter',loginOutcome), loginCallback ); + }; - diff --git a/server.js b/server.js index afe0e3e..914f5bb 100755 --- a/server.js +++ b/server.js @@ -82,7 +82,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+'#'; } From 60dc25ff4802d3c916d6649b84baea46026a68c1 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Thu, 20 Apr 2017 21:54:21 -0400 Subject: [PATCH 106/143] #62 Fixed development 404s --- server.js | 8 +++++--- views/error.html | 9 ++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/server.js b/server.js index 914f5bb..7dbad01 100755 --- a/server.js +++ b/server.js @@ -120,10 +120,11 @@ const } /* Errors */ { + // Catch-all for 404s app.use( (req,res,next)=>{ if (!res.headersSent) { - var err = new Error('404 Not found: '+req.url); + var err = new Error(`Not found: ${req.url}`); err.status = 404; next(err); } @@ -132,6 +133,7 @@ const // Handlers if (env.mode=='production') { app.use( (err,req,res,next)=>{ + if (err.status!=='404'){ console.error(err.stack); } if (res.headersSent) { return next(err); } res.status(err.status||500); res.render('error', { @@ -141,13 +143,13 @@ const } else /* Development */{ app.use( (err,req,res,next)=>{ - console.log(err); + console.error(err.stack); if (res.headersSent) { return next(err); } res.status(err.status||500); res.render('error', { code: err.status, message: err.message, - error: err.stack + stack: err.stack }); } ); } diff --git a/views/error.html b/views/error.html index d9133d1..e4e4769 100644 --- a/views/error.html +++ b/views/error.html @@ -1,12 +1,11 @@ {% extends 'templates/base.html' %} -{% block title %}{{super()}} | Error{% endblock %} +{% block title %}{{super()}} | {% if code %}{{code}} {% endif %}Error{% endblock %} {% block main %}
    - {% if code %}

    {{code}}

    {% endif %} - {% if message %}

    {{message}}

    {% endif %} - {% if stack %}

    {{stack}}

    {% endif %} - {% if not stack %}

    I would really appreciate it if you would report this error.

    {% endif %} +

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

    + {% if stack %}

    {{stack}}

    {% else %} {% if code %}{% endif %} + {% if code != '404' %}

    Would you please report this error?

    {% endif %}{% endif %}
    {% endblock %} \ No newline at end of file From c5f9d32d59f3daeac6393ea1d856b6bdd8551927 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Thu, 20 Apr 2017 22:02:15 -0400 Subject: [PATCH 107/143] #62 Fixed production 404s --- server.js | 6 ++++-- views/error.html | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/server.js b/server.js index 7dbad01..c3aa9ee 100755 --- a/server.js +++ b/server.js @@ -133,11 +133,13 @@ const // Handlers if (env.mode=='production') { app.use( (err,req,res,next)=>{ - if (err.status!=='404'){ console.error(err.stack); } + if (err.status!==404){ console.error(err.stack); } if (res.headersSent) { return next(err); } res.status(err.status||500); + console.log(err.status===500)?"Server error":err.message; res.render('error', { - code: err.status + code: err.status, + message: (err.status===500)?"Server error":err.message }); } ); } diff --git a/views/error.html b/views/error.html index e4e4769..5a54b46 100644 --- a/views/error.html +++ b/views/error.html @@ -5,7 +5,8 @@

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

    {% if stack %}

    {{stack}}

    {% else %} - {% if code %}{% endif %} - {% if code != '404' %}

    Would you please report this error?

    {% endif %}{% endif %} + {% if code == '404' %}

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

    + {% else %}

    Would you please report this error?

    {% endif %} + {% if code %}{% endif %}{% endif %}
    {% endblock %} \ No newline at end of file From 6dfcf09ab28d7a744b17ee6d3f9024b4c27573f3 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Thu, 20 Apr 2017 22:15:00 -0400 Subject: [PATCH 108/143] #62 Fixed development 500s --- config/routes/test.js | 3 +-- server.js | 7 +++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/config/routes/test.js b/config/routes/test.js index 99d1106..3e5696e 100644 --- a/config/routes/test.js +++ b/config/routes/test.js @@ -45,8 +45,7 @@ router }) .post('/settings', (req,res)=>{ - //TODO: Test validation here. - + //TODO: Test validation here? }); diff --git a/server.js b/server.js index c3aa9ee..d6dd5e6 100755 --- a/server.js +++ b/server.js @@ -117,6 +117,10 @@ const app.use( '/test', require('./config/routes/test.js' ) ); } + app.get('/500', (req,res)=>{ + Balls + }); + } /* Errors */ { @@ -136,7 +140,6 @@ const if (err.status!==404){ console.error(err.stack); } if (res.headersSent) { return next(err); } res.status(err.status||500); - console.log(err.status===500)?"Server error":err.message; res.render('error', { code: err.status, message: (err.status===500)?"Server error":err.message @@ -149,7 +152,7 @@ const if (res.headersSent) { return next(err); } res.status(err.status||500); res.render('error', { - code: err.status, + code: err.status||500, message: err.message, stack: err.stack }); From 3f2da8abfc0611f3aa7ee1677451328153f58e28 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Thu, 20 Apr 2017 22:31:10 -0400 Subject: [PATCH 109/143] #62 Fixed 401s --- config/middleware.js | 6 +++++- config/passport.js | 1 + config/routes/auth.js | 9 +++++++-- config/routes/map.js | 5 ++--- server.js | 6 +++--- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/config/middleware.js b/config/middleware.js index 82ab95b..51ac38d 100644 --- a/config/middleware.js +++ b/config/middleware.js @@ -30,7 +30,11 @@ module.exports = { // Ensure administrator ensureAdmin: (req,res,next)=>{ if (req.user.isAdmin){ return next(); } - else { res.sendStatus(401); } + else { + let err = new Error("Unauthorized"); + err.status = 401; + next(err); + } //TODO: test this by logging in as !isAdmin and go to /admin } diff --git a/config/passport.js b/config/passport.js index c40a98e..46e5ca6 100644 --- a/config/passport.js +++ b/config/passport.js @@ -119,6 +119,7 @@ module.exports = (passport)=>{ // No googleId either else { + // console.log(`Couldn't find ${service} user.`); req.session.flashType = 'warning'; req.session.flashMessage = `There's no user for that ${service} account. `; return done(); diff --git a/config/routes/auth.js b/config/routes/auth.js index 33846fb..a972c71 100644 --- a/config/routes/auth.js +++ b/config/routes/auth.js @@ -22,9 +22,14 @@ module.exports = (app, passport) => { req.session.flashMessage = undefined; res.redirect( req.session.next || '/map' ); }, - appLoginCallback = (req,res)=>{ + appLoginCallback = (req,res,next)=>{ + console.log('appLoginCallback called.'); if (req.user){ res.send(req.user); } - else { res.sendStatus(401); } + else { + let err = new Error("Unauthorized"); + err.status = 401; + next(err); + } }; // Login/-out diff --git a/config/routes/map.js b/config/routes/map.js index 7f99b70..33bcc5d 100644 --- a/config/routes/map.js +++ b/config/routes/map.js @@ -16,9 +16,8 @@ router.get('/:slug?', (req,res,next)=>{ User.findOne({slug:req.params.slug}) .then( (mapuser)=>{ - if (mapuser===undefined){ - res.sendStatus(404); - } else { + if (!mapuser){ next(); } //404 + else { res.render('map', { mapuser: mapuser, mapApi: env.googleMapsAPI, diff --git a/server.js b/server.js index d6dd5e6..079ae80 100755 --- a/server.js +++ b/server.js @@ -117,9 +117,9 @@ const app.use( '/test', require('./config/routes/test.js' ) ); } - app.get('/500', (req,res)=>{ - Balls - }); + // app.get('/500', (req,res)=>{ + // Balls + // }); } From 14f3d18d5c1c779331301bbc5b313df51cd5cdca Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Thu, 20 Apr 2017 22:39:33 -0400 Subject: [PATCH 110/143] #62 Fixed production 500s --- server.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/server.js b/server.js index 079ae80..a1cb474 100755 --- a/server.js +++ b/server.js @@ -117,10 +117,6 @@ const app.use( '/test', require('./config/routes/test.js' ) ); } - // app.get('/500', (req,res)=>{ - // Balls - // }); - } /* Errors */ { @@ -134,19 +130,21 @@ const } } ); - // Handlers - if (env.mode=='production') { + // Production handlers + if (env.mode!=='development') { app.use( (err,req,res,next)=>{ if (err.status!==404){ console.error(err.stack); } if (res.headersSent) { return next(err); } res.status(err.status||500); res.render('error', { - code: err.status, - message: (err.status===500)?"Server error":err.message + code: err.status||500, + message: (err.status<=499)?err.message:"Server error" }); } ); } - else /* Development */{ + + // Development handlers + else { app.use( (err,req,res,next)=>{ console.error(err.stack); if (res.headersSent) { return next(err); } @@ -158,6 +156,7 @@ const }); } ); } + } /* Sockets */ { From f7d2fd3482a1b4463504f93a2ad988d2239c766c Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Thu, 20 Apr 2017 23:07:35 -0400 Subject: [PATCH 111/143] #50 Added many promises --- config/models.js | 11 +++++++---- config/passport.js | 11 +++++++---- config/routes/admin.js | 26 +++++++++++++++++--------- config/routes/auth.js | 23 ++++++++++++++++------- config/routes/index.js | 4 ++-- config/routes/settings.js | 21 ++++++++++----------- config/routes/test.js | 2 +- config/sockets.js | 18 ++++++++---------- server.js | 19 ++++++++++--------- 9 files changed, 78 insertions(+), 57 deletions(-) diff --git a/config/models.js b/config/models.js index b324250..3da2b55 100644 --- a/config/models.js +++ b/config/models.js @@ -56,7 +56,6 @@ const userSchema = new mongoose.Schema({ .then( (buf)=>{ user.emailToken = buf.toString('hex'); user.save(); - return next(null,user.emailToken); }) .catch( (err)=>{ next(err,null); }); @@ -78,8 +77,13 @@ const userSchema = new mongoose.Schema({ // Reuse old token, resetting clock if ( user.auth.passTokenExpires <= Date.now() ){ user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour - user.save(); - return next(null,user.auth.passToken); + user.save() + .then( ()=>{ + return next(null,user.auth.passToken); + }) + .catch( (err)=>{ + return next(err,user.auth.passToken); + }); } // Create new token @@ -89,7 +93,6 @@ const userSchema = new mongoose.Schema({ user.auth.passToken = buf.toString('hex'); user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour user.save(); - return next(null,user.auth.passToken); }) .catch( (err)=>{ return next(err,null); }); } diff --git a/config/passport.js b/config/passport.js index 46e5ca6..c10bc02 100644 --- a/config/passport.js +++ b/config/passport.js @@ -31,8 +31,8 @@ module.exports = (passport)=>{ passwordField: 'password', passReqToCallback: true }, (req,email,password,done)=>{ - User.findOne( {'email':email}, (err,user)=>{ - if (err){ return done(err); } + User.findOne({'email':email}) + .then( (user)=>{ // No user with that email if (!user) { @@ -62,7 +62,11 @@ module.exports = (passport)=>{ } ); } - } ); + + }) + .catch( (err)=>{ + return done(err); + }); } )); @@ -168,7 +172,6 @@ module.exports = (passport)=>{ return done(null,req.user); } ) .catch( (err)=>{ - mw.throwErr(err); return done(err); } ); } diff --git a/config/routes/admin.js b/config/routes/admin.js index 9374140..a32ab64 100644 --- a/config/routes/admin.js +++ b/config/routes/admin.js @@ -12,25 +12,33 @@ router.route('/') .get( (req,res)=>{ User.find({}).sort({lastLogin:-1}) - .catch( (err)=>{ - mw.throwErr(err); - }).then( (found)=>{ + .then( (found)=>{ res.render('admin', { noFooter: '1', users: found }); - }); + }) + .catch( (err)=>{ mw.throwErr(err,req); }); } ) .post( (req,res,next)=>{ if (req.body.delete) { - User.findOneAndRemove( {'_id':req.body.delete}, (err,user)=>{ - if (err){ req.flash('error', err.message); } - else { req.flash('success', ''+user.name+' deleted.'); } + User.findOneAndRemove({'_id':req.body.delete}) + .then( (user)=>{ + req.flash('success', ''+user.name+' deleted.'); res.redirect('/admin#users'); - } ); - } else { console.error(new Error('POST without action sent. ')); next(); } + }) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/admin#users'); + }); + } + else { + let err = new Error('POST without action sent. '); + err.status = 500; + next(); + } } ); module.exports = router; \ No newline at end of file diff --git a/config/routes/auth.js b/config/routes/auth.js index a972c71..c363b34 100644 --- a/config/routes/auth.js +++ b/config/routes/auth.js @@ -123,10 +123,14 @@ module.exports = (app, passport) => { // Slug in use: generate a random one and retry if (existingUser){ - crypto.randomBytes(6, (err,buf)=>{ - if (err) { mw.throwErr(err,req); } + crypto.randomBytes(6) + .then( (buf)=>{ s = buf.toString('hex'); checkSlug(s,cb); + }) + .catch( (err)=>{ + mw.throwErr(err,req); + reject(); }); } @@ -136,6 +140,7 @@ module.exports = (app, passport) => { }) .catch((err)=>{ mw.throwErr(err,req); + reject(); }); })(user.slug, (newSlug)=>{ @@ -146,10 +151,14 @@ module.exports = (app, passport) => { // Generate sk32 let sk32 = new Promise((resolve,reject) => { - crypto.randomBytes(32, (err,buf)=>{ - if (err) { mw.throwErr(err,req); } + crypto.randomBytes(32) + .then( (buf)=>{ user.sk32 = buf.toString('hex'); resolve(); + }) + .catch( (err)=>{ + mw.throwErr(err,req); + reject(); }); }); @@ -158,9 +167,9 @@ module.exports = (app, passport) => { .then( ()=>{ user.save(); }) .then( ()=>{ sendToken(user); }) .catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/login#signup'); - }); + mw.throwErr(err,req); + res.redirect('/login#signup'); + }); } diff --git a/config/routes/index.js b/config/routes/index.js index 621690a..086a7fc 100644 --- a/config/routes/index.js +++ b/config/routes/index.js @@ -49,7 +49,7 @@ router.get('/validate', (req,res)=>{ } else { res.sendStatus(200); } }) - .catch( (err)=>{ mw.throwErr(err); }); + .catch( (err)=>{ mw.throwErr(err,req); }); } // Validate unique email @@ -61,7 +61,7 @@ router.get('/validate', (req,res)=>{ } else { res.sendStatus(200); } }) - .catch( (err)=>{ mw.throwErr(err); }); + .catch( (err)=>{ mw.throwErr(err,req); }); } // Create slug diff --git a/config/routes/settings.js b/config/routes/settings.js index 3b5a506..1494d54 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -132,16 +132,15 @@ router.get('/email/:token', mw.ensureAuth, (req,res,next)=>{ req.user.email = req.user.newEmail; req.user.save() .then( ()=>{ - // Delete token and newEmail req.user.emailToken = undefined; req.user.newEmail = undefined; req.user.save(); - + }) + .then( ()=>{ // Report success req.flash('success',`Your email has been set to ${req.user.email}. `); res.redirect('/settings'); - }) .catch( (err)=>{ mw.throwErr(err,req); @@ -249,14 +248,14 @@ router.route('/password/:token') // Save new password to db res.locals.passwordUser.auth.password = hash; res.locals.passwordUser.save() - .then( ()=>{ - req.flash('success', 'Password set. You can use it to log in now. '); - res.redirect('/login#login'); - }) - .catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/login#signup'); - }); + .then( ()=>{ + req.flash('success', 'Password set. You can use it to log in now. '); + res.redirect('/login#login'); + }) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/login#signup'); + }); } } ); diff --git a/config/routes/test.js b/config/routes/test.js index 3e5696e..bbb4d18 100644 --- a/config/routes/test.js +++ b/config/routes/test.js @@ -32,7 +32,7 @@ router let daysToCrack = mellt.CheckPassword(req.body.password); if (daysToCrack<10) { let err = new Error(`That password could be cracked in ${daysToCrack} days! Come up with a more complex password that would take at least 10 days to crack. `); - mw.throwErr(err); + mw.throwErr(err,req); next(err); } else { diff --git a/config/sockets.js b/config/sockets.js index 3cb72aa..eb0793a 100644 --- a/config/sockets.js +++ b/config/sockets.js @@ -65,12 +65,12 @@ module.exports = { else { // Get loc.usr - User.findById(loc.usr, (err,user)=>{ - if (err) { mw.throwErr(err); } + User.findById(loc.usr) + .then( (user)=>{ if (user) { // Confirm sk32 token - if (loc.tok!=user.sk32) { mw.throwErr(new Error(`⛔️ loc.tok!=user.sk32\n\t${loc.tok} != ${user.sk32}`)); } + if (loc.tok!=user.sk32) { mw.throwErr(new Error(`loc.tok!=user.sk32\n\t${loc.tok} != ${user.sk32}`)); } else { // Broadcast location @@ -85,13 +85,13 @@ module.exports = { spd: parseFloat(loc.spd||0), time: loc.time }; - user.save( (err)=>{ - if (err) { mw.throwErr(err); } - } ); + user.save() + .catch( (err)=>{ console.error(`⛔ ${err.stack}`); } ); } } - }); + }) + .catch( (err)=>{ console.error(`⛔ ${err.stack}`); }); } }); @@ -109,9 +109,7 @@ module.exports = { }); // Log errors - socket.on('error', (err)=>{ - mw.throwErr(err); - }); + socket.on('error', (err)=>{ console.error(`⛔ ${err.stack}`); }); }); } diff --git a/server.js b/server.js index a1cb474..037b821 100755 --- a/server.js +++ b/server.js @@ -33,11 +33,9 @@ const keepAlive:1, connectTimeoutMS:30000 }}, replset:{socketOptions:{ keepAlive:1, connectTimeoutMS:30000 }} - }).catch((err)=>{ - mw.throwErr(err); - }).then(()=>{ - console.log(`💿 Mongoose connected to mongoDB`); - }); + }) + .then( ()=>{ console.log(`💿 Mongoose connected to mongoDB`); }) + .catch( (err)=>{ console.error(`⛔ ${err.stack}`); }); } @@ -133,7 +131,7 @@ const // Production handlers if (env.mode!=='development') { app.use( (err,req,res,next)=>{ - if (err.status!==404){ console.error(err.stack); } + if (err.status!==404){ console.error(`⛔ ${err.stack}`); } if (res.headersSent) { return next(err); } res.status(err.status||500); res.render('error', { @@ -146,7 +144,7 @@ const // Development handlers else { app.use( (err,req,res,next)=>{ - console.error(err.stack); + console.error(`⛔ ${err.stack}`); if (res.headersSent) { return next(err); } res.status(err.status||500); res.render('error', { @@ -174,11 +172,14 @@ const console.log(`🌐 Listening in ${env.mode} mode on port ${env.port}... `); // Check for clients for each user - User.find( {}, (err,users)=>{ - if (err) { console.log(`DB error finding all users: ${err.message}`); } + User.find({}) + .then( (users)=>{ users.forEach( (user)=>{ sockets.checkForUsers( io, user.id ); }); + }) + .catch( (err)=>{ + console.error(`⛔ ${err.stack}`); }); }); From 059c3bd91d9fab43c28915223e784a0659d85aad Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Fri, 21 Apr 2017 21:00:38 -0400 Subject: [PATCH 112/143] Minor changes --- config/routes/auth.js | 2 +- server.js | 4 +++- static/css/login.css | 12 ++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/config/routes/auth.js b/config/routes/auth.js index c363b34..a02baf8 100644 --- a/config/routes/auth.js +++ b/config/routes/auth.js @@ -23,7 +23,7 @@ module.exports = (app, passport) => { res.redirect( req.session.next || '/map' ); }, appLoginCallback = (req,res,next)=>{ - console.log('appLoginCallback called.'); + // console.log('appLoginCallback called.'); if (req.user){ res.send(req.user); } else { let err = new Error("Unauthorized"); diff --git a/server.js b/server.js index 037b821..c484297 100755 --- a/server.js +++ b/server.js @@ -144,7 +144,9 @@ const // Development handlers else { app.use( (err,req,res,next)=>{ - console.error(`⛔ ${err.stack}`); + if (err.status!==404) { + console.error(`⛔ ${err.stack}`); + } if (res.headersSent) { return next(err); } res.status(err.status||500); res.render('error', { diff --git a/static/css/login.css b/static/css/login.css index f49cb0f..8e9a71f 100644 --- a/static/css/login.css +++ b/static/css/login.css @@ -54,22 +54,22 @@ form #social-login { font-size: .6em; } #social-login .btn.gp { - background: rgb(206,77,57); + background: #ce4d39; } #social-login .btn.gp:hover { - background: rgb(251,122,102); + background: #fb7a66; } #social-login .btn.fb { - background: rgb(48,88,145); + background: #305891; } #social-login .btn.fb:hover { - background: rgb(93,133,190); + background: #5d85be; } #social-login .btn.tw { - background: rgb(44,168,210); + background: #2ca8d2; } #social-login .btn.tw:hover { - background: rgb(89,213,255); + background: #59d5ff; } /* Small buttons */ @media (max-width:600px), (min-width:800px) and (max-width:1200px) { From 9e4ac8909bd71f67299329a86be2fb699bc94ad5 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sat, 22 Apr 2017 08:06:17 -0400 Subject: [PATCH 113/143] #65 Added url to development DB --- config/env-sample.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/env-sample.js b/config/env-sample.js index c4a6c90..8a3d2f4 100644 --- a/config/env-sample.js +++ b/config/env-sample.js @@ -11,6 +11,8 @@ module.exports = { // Location of your mongoDB mongoSetup: 'mongodb://localhost:27017/tracman', + // Or use the test database from mLab + //mongoSetup: 'mongodb://tracman:MUPSLXQ34f9cQTc5@ds113841.mlab.com:13841/tracman-dev', // URL and port where this will run url: 'https://localhost:8080', From ede80170ec6837b5375f7e4aaa544104b6ae537f Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Sun, 23 Apr 2017 09:05:35 -0400 Subject: [PATCH 114/143] Fixed socket.io error messages --- config/sockets.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/config/sockets.js b/config/sockets.js index eb0793a..6f9882d 100644 --- a/config/sockets.js +++ b/config/sockets.js @@ -57,20 +57,30 @@ 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 sk32 token - if (!loc.tok) { mw.throwErr(new Error(`⛔️ !loc.tok for loc: ${loc}`)) } + // Check for user and sk32 token + if (!loc.usr){ + console.error("⛔", new Error(`Recieved an update from ${socket.id} without a usr!`).message); + } + else if (!loc.tok){ + console.error("⛔", new Error(`Recieved an update from ${socket.id} for usr ${loc.usr} without an sk32!`).message); + } else { // Get loc.usr User.findById(loc.usr) .then( (user)=>{ - if (user) { + if (!user){ + console.error("⛔", new Error(`Recieved an update from ${socket.id} for ${loc.usr}, but no such user was found in the db!`).message); + } + else { // Confirm sk32 token - if (loc.tok!=user.sk32) { mw.throwErr(new Error(`loc.tok!=user.sk32\n\t${loc.tok} != ${user.sk32}`)); } + if (loc.tok!=user.sk32) { + console.error("⛔", new Error(`Recieved an update from ${socket.id} for usr ${loc.usr} with tok of ${loc.tok}, but that user's sk32 is ${user.sk32}!`).message); + } else { // Broadcast location @@ -86,12 +96,12 @@ module.exports = { time: loc.time }; user.save() - .catch( (err)=>{ console.error(`⛔ ${err.stack}`); } ); + .catch( (err)=>{ console.error("⛔", err.stack); }); } } }) - .catch( (err)=>{ console.error(`⛔ ${err.stack}`); }); + .catch( (err)=>{ console.error("⛔", err.stack); }); } }); @@ -109,7 +119,7 @@ module.exports = { }); // Log errors - socket.on('error', (err)=>{ console.error(`⛔ ${err.stack}`); }); + socket.on('error', (err)=>{ console.error('⛔', err.stack); }); }); } From 76c4e7696e07275ed69a78e92edeeb8a6736bdbe Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Mon, 24 Apr 2017 13:24:41 -0400 Subject: [PATCH 115/143] Fixed socket.io errore messages --- config/sockets.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/config/sockets.js b/config/sockets.js index 6f9882d..2854f7b 100644 --- a/config/sockets.js +++ b/config/sockets.js @@ -29,7 +29,11 @@ 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']; + socket.ua = socket.client.request.headers['user-agent']; /* Log */ //socket.on('log', (text)=>{ @@ -62,10 +66,10 @@ module.exports = { // Check for user and sk32 token if (!loc.usr){ - console.error("⛔", new Error(`Recieved an update from ${socket.id} without a usr!`).message); + console.error("⛔", new Error(`Recieved an update from ${socket.ip} without a usr!`).message); } else if (!loc.tok){ - console.error("⛔", new Error(`Recieved an update from ${socket.id} for usr ${loc.usr} without an sk32!`).message); + console.error("⛔", new Error(`Recieved an update from ${socket.ip} for usr ${loc.usr} without an sk32!`).message); } else { @@ -73,13 +77,13 @@ module.exports = { User.findById(loc.usr) .then( (user)=>{ if (!user){ - console.error("⛔", new Error(`Recieved an update from ${socket.id} for ${loc.usr}, but no such user was found in the db!`).message); + console.error("⛔", new Error(`Recieved an update from ${socket.ip} for ${loc.usr}, but no such user was found in the db!`).message); } else { // Confirm sk32 token if (loc.tok!=user.sk32) { - console.error("⛔", new Error(`Recieved an update from ${socket.id} for usr ${loc.usr} with tok of ${loc.tok}, but that user's sk32 is ${user.sk32}!`).message); + console.error("⛔", new Error(`Recieved an update from ${socket.ip} for usr ${loc.usr} with tok of ${loc.tok}, but that user's sk32 is ${user.sk32}!`).message); } else { From c2a3b14a90afbf893b6af37cd5c8f54390e30b1d Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Mon, 24 Apr 2017 13:52:36 -0400 Subject: [PATCH 116/143] #57 Finished google and password login endpoints for android --- config/passport.js | 15 ++++++++------- config/routes/auth.js | 16 ++++++++-------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/config/passport.js b/config/passport.js index c10bc02..c8f8a67 100644 --- a/config/passport.js +++ b/config/passport.js @@ -31,6 +31,7 @@ module.exports = (passport)=>{ passwordField: 'password', passReqToCallback: true }, (req,email,password,done)=>{ + //console.log(`Perfoming local login for ${email}`); User.findOne({'email':email}) .then( (user)=>{ @@ -72,13 +73,13 @@ module.exports = (passport)=>{ // Social login function socialLogin(req, service, profileId, done) { - // console.log(`socialLogin() called`); + //console.log(`socialLogin() called`); let query = {}; query['auth.'+service] = profileId; // Intent to log in if (!req.user) { - // console.log(`Logging in with ${service}...`); + //console.log(`Logging in with ${service}...`); User.findOne(query) .then( (user)=>{ @@ -123,7 +124,7 @@ module.exports = (passport)=>{ // No googleId either else { - // console.log(`Couldn't find ${service} user.`); + //console.log(`Couldn't find ${service} user.`); req.session.flashType = 'warning'; req.session.flashMessage = `There's no user for that ${service} account. `; return done(); @@ -132,7 +133,7 @@ module.exports = (passport)=>{ // Successfull social login else { - // console.log(`Found user: ${user}`); + //console.log(`Found user: ${user}`); req.session.flashType = 'success'; req.session.flashMessage = "You have been logged in."; return done(null, user); @@ -147,7 +148,7 @@ module.exports = (passport)=>{ // Intent to connect account else { - // console.log(`Attempting to connect ${service} account...`); + //console.log(`Attempting to connect ${service} account...`); // Check for unique profileId User.findOne(query) @@ -155,7 +156,7 @@ module.exports = (passport)=>{ // Social account already in use if (existingUser) { - // console.log(`${service} account already in use.`); + //console.log(`${service} account already in use.`); req.session.flashType = 'warning'; req.session.flashMessage = `Another user is already connected to that ${service} account. `; return done(); @@ -163,7 +164,7 @@ module.exports = (passport)=>{ // Connect to account else { - // console.log(`Connecting ${service} account.`); + //console.log(`Connecting ${service} account.`); req.user.auth[service] = profileId; req.user.save() .then( ()=>{ diff --git a/config/routes/auth.js b/config/routes/auth.js index a02baf8..d0137d0 100644 --- a/config/routes/auth.js +++ b/config/routes/auth.js @@ -16,14 +16,14 @@ module.exports = (app, passport) => { failureFlash: true }, loginCallback = (req,res)=>{ - // console.log(`Login callback called... redirecting to ${req.session.next}`); + //console.log(`Login callback called... redirecting to ${req.session.next}`); req.flash(req.session.flashType,req.session.flashMessage); req.session.flashType = undefined; req.session.flashMessage = undefined; res.redirect( req.session.next || '/map' ); }, appLoginCallback = (req,res,next)=>{ - // console.log('appLoginCallback called.'); + //console.log('appLoginCallback called.'); if (req.user){ res.send(req.user); } else { let err = new Error("Unauthorized"); @@ -240,12 +240,12 @@ module.exports = (app, passport) => { } ); // Android - app.get('/login/app/', passport.authenticate('local'), appLoginCallback); + app.post('/login/app', passport.authenticate('local'), appLoginCallback); // Token-based (android social) app.get(['/login/app/google','/auth/google/idtoken'], passport.authenticate('google-token'), appLoginCallback); - app.get('/login/app/facebook', passport.authenticate('facebook-token'), appLoginCallback); - app.get('/login/app/twitter', passport.authenticate('twitter-token'), appLoginCallback); + // app.get('/login/app/facebook', passport.authenticate('facebook-token'), appLoginCallback); + // app.get('/login/app/twitter', passport.authenticate('twitter-token'), appLoginCallback); // Social app.get('/login/:service', (req,res,next)=>{ @@ -254,19 +254,19 @@ module.exports = (app, passport) => { // Social login if (!req.user) { - // console.log(`Attempting to login with ${service} with params: ${JSON.stringify(sendParams)}...`); + //console.log(`Attempting to login with ${service} with params: ${JSON.stringify(sendParams)}...`); passport.authenticate(service, sendParams)(req,res,next); } // Connect social account else if (!req.user.auth[service]) { - // console.log(`Attempting to connect ${service} account...`); + //console.log(`Attempting to connect ${service} account...`); passport.authorize(service, sendParams)(req,res,next); } // Disconnect social account else { - // console.log(`Attempting to disconnect ${service} account...`); + //console.log(`Attempting to disconnect ${service} account...`); req.user.auth[service] = undefined; req.user.save() .then(()=>{ From a311f6f9f3ce2b40ca3e0d6e9d379804979047bd Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 25 Apr 2017 09:56:04 -0400 Subject: [PATCH 117/143] Found meaner-looking emoji for errors --- config/middleware.js | 2 +- config/sockets.js | 14 +++++++------- server.js | 8 ++++---- static/js/map-controls.js | 2 +- static/js/map.js | 4 ++-- views/error.html | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/config/middleware.js b/config/middleware.js index 51ac38d..0c19bc9 100644 --- a/config/middleware.js +++ b/config/middleware.js @@ -6,7 +6,7 @@ module.exports = { // Throw error throwErr: (err,req=null)=>{ - console.error(`⛔️ ${err.stack}`); + console.error(`❌️ ${err.stack}`); if (req){ if (env.mode==='production') { req.flash('danger', 'An error occured.
    Would you like to report it?'); diff --git a/config/sockets.js b/config/sockets.js index 2854f7b..56893ff 100644 --- a/config/sockets.js +++ b/config/sockets.js @@ -66,10 +66,10 @@ module.exports = { // Check for user and sk32 token if (!loc.usr){ - console.error("⛔", new Error(`Recieved an update from ${socket.ip} without a usr!`).message); + console.error("❌", new Error(`Recieved an update from ${socket.ip} without a usr!`).message); } else if (!loc.tok){ - console.error("⛔", new Error(`Recieved an update from ${socket.ip} for usr ${loc.usr} without an sk32!`).message); + console.error("❌", new Error(`Recieved an update from ${socket.ip} for usr ${loc.usr} without an sk32!`).message); } else { @@ -77,13 +77,13 @@ module.exports = { User.findById(loc.usr) .then( (user)=>{ if (!user){ - console.error("⛔", new Error(`Recieved an update from ${socket.ip} for ${loc.usr}, but no such user was found in the db!`).message); + console.error("❌", new Error(`Recieved an update from ${socket.ip} for ${loc.usr}, but no such user was found in the db!`).message); } else { // Confirm sk32 token if (loc.tok!=user.sk32) { - console.error("⛔", new Error(`Recieved an update from ${socket.ip} for usr ${loc.usr} with tok of ${loc.tok}, but that user's sk32 is ${user.sk32}!`).message); + console.error("❌", new Error(`Recieved an update from ${socket.ip} for usr ${loc.usr} with tok of ${loc.tok}, but that user's sk32 is ${user.sk32}!`).message); } else { @@ -100,12 +100,12 @@ module.exports = { time: loc.time }; user.save() - .catch( (err)=>{ console.error("⛔", err.stack); }); + .catch( (err)=>{ console.error("❌", err.stack); }); } } }) - .catch( (err)=>{ console.error("⛔", err.stack); }); + .catch( (err)=>{ console.error("❌", err.stack); }); } }); @@ -123,7 +123,7 @@ module.exports = { }); // Log errors - socket.on('error', (err)=>{ console.error('⛔', err.stack); }); + socket.on('error', (err)=>{ console.error('❌', err.stack); }); }); } diff --git a/server.js b/server.js index c484297..33d1902 100755 --- a/server.js +++ b/server.js @@ -35,7 +35,7 @@ const keepAlive:1, connectTimeoutMS:30000 }} }) .then( ()=>{ console.log(`💿 Mongoose connected to mongoDB`); }) - .catch( (err)=>{ console.error(`⛔ ${err.stack}`); }); + .catch( (err)=>{ console.error(`❌ ${err.stack}`); }); } @@ -131,7 +131,7 @@ const // Production handlers if (env.mode!=='development') { app.use( (err,req,res,next)=>{ - if (err.status!==404){ console.error(`⛔ ${err.stack}`); } + if (err.status!==404){ console.error(`❌ ${err.stack}`); } if (res.headersSent) { return next(err); } res.status(err.status||500); res.render('error', { @@ -145,7 +145,7 @@ const else { app.use( (err,req,res,next)=>{ if (err.status!==404) { - console.error(`⛔ ${err.stack}`); + console.error(`❌ ${err.stack}`); } if (res.headersSent) { return next(err); } res.status(err.status||500); @@ -181,7 +181,7 @@ const }); }) .catch( (err)=>{ - console.error(`⛔ ${err.stack}`); + console.error(`❌ ${err.stack}`); }); }); diff --git a/static/js/map-controls.js b/static/js/map-controls.js index e25a5e1..1d06727 100644 --- a/static/js/map-controls.js +++ b/static/js/map-controls.js @@ -29,7 +29,7 @@ $(function(){ // Error callback function(err) { alert("Unable to set location."); - console.error('⛔️',err.message); + console.error('❌️',err.message); }, // Options diff --git a/static/js/map.js b/static/js/map.js index 2032a3f..b015b9d 100644 --- a/static/js/map.js +++ b/static/js/map.js @@ -42,7 +42,7 @@ socket console.log("⬇️ Disconnected!"); }) .on('error', function (err){ - console.error('⛔️',err.message); + console.error('❌️',err.message); }); // Parse location @@ -202,7 +202,7 @@ function getAltitude(loc,elev,cb){ function getStreetViewData(loc,rad,cb) { if (!sv) { var sv=new google.maps.StreetViewService(); } sv.getPanorama({location:{lat:loc.lat,lng:loc.lon},radius:rad},function(data,status){ - if (status!==google.maps.StreetViewStatus.OK){ console.error(new Error('⛔️ Street view not available:',status).message); } + if (status!==google.maps.StreetViewStatus.OK){ console.error(new Error('❌️ Street view not available:',status).message); } else { cb(data); } }); } diff --git a/views/error.html b/views/error.html index 5a54b46..e4df486 100644 --- a/views/error.html +++ b/views/error.html @@ -3,7 +3,7 @@ {% block main %}
    -

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

    +

    ❌️ {% if code %}{{code}} {% 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 52e38948a920a20637490f0efa29ca7b43110c15 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 25 Apr 2017 10:09:01 -0400 Subject: [PATCH 118/143] Minor changes --- server.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/server.js b/server.js index 33d1902..7beffee 100755 --- a/server.js +++ b/server.js @@ -12,7 +12,6 @@ const passport = require('passport'), flash = require('connect-flash-plus'), env = require('./config/env.js'), - mw = require('./config/middleware.js'), User = require('./config/models.js').user, app = express(), http = require('http').Server(app), @@ -166,7 +165,6 @@ const } /* RUNTIME */ { - console.log('🖥 Starting Tracman server...'); // Listen From 1f11339d32b6ccf52215e60ba269e23782846e5a Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 25 Apr 2017 13:44:11 -0400 Subject: [PATCH 119/143] Fixed up map --- static/css/map.css | 52 +++++--- static/js/map-controls.js | 6 +- static/js/map.js | 255 +++++++++++++++++++++----------------- views/map.html | 13 +- 4 files changed, 184 insertions(+), 142 deletions(-) diff --git a/static/css/map.css b/static/css/map.css index ad7fcf7..c8438eb 100644 --- a/static/css/map.css +++ b/static/css/map.css @@ -20,20 +20,34 @@ main { /* Map and streetview */ #map, #pano {position:relative;} #pano {float:right;} -img#panoImg { width:100%; height:100%; } +#panoImg { width:100%; height:100%; } #notset {display:none} /* Tracman logo */ -.map-logo { +#map-logo { margin-left: -75px; + background: #444; background: rgba(0,0,0,.7); padding: 0 10px 0 75px; font-size: 2em; } -.map-logo a { color: #fbc93d; } +#map-logo a:hover { + text-decoration: none; +} +#map-logo img { + position: relative; + top: 3px; + margin-left: 3px; +} +#map-logo .text { + color: #fbc93d; + position: relative; + top: -3px; + margin-left: 3px; +} /* Timestamp */ -.tim { +#timestamp { color: #000; font-size: 12px; padding-left: 5px; @@ -42,65 +56,65 @@ img#panoImg { width:100%; height:100%; } } /* Signs */ -.spd-sign, .alt-sign { +#spd-sign, #alt-sign { text-align: center; 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 (min-width:400px) { - .spd, .alt { + #spd, #alt { height: 40px; font-size: 32px; } - .alt-unit, .spd-unit { + #alt-unit, #spd-unit { font-size: 12px; } - .alt-label, .spd-label { + #alt-label, #spd-label { font-size: 18px; height: 18px; } } @media (min-width:350px) and (max-width:400px) { - .spd, .alt { + #spd, #alt { height: 30px; font-size: 28px; } - .alt-unit, .spd-unit { + #alt-unit, #spd-unit { font-size: 10px; } - .alt-label, .spd-label { + #alt-label, #spd-label { font-size: 14px; height: 14px; } } @media (min-width:300px) and (max-width:350px) { - .spd, .alt { + #spd, #alt { height: 22px; font-size: 20px; } - .alt-unit, .spd-unit { + #alt-unit, #spd-unit { font-size: 9px; } - .alt-label, .spd-label { + #alt-label, #spd-label { font-size: 11px; height: 11px; } } @media (min-width:0px) and (max-width:300px) { - .spd, .alt { + #spd, #alt { height: 20px; font-size: 18px; } - .alt-unit, .spd-unit { + #alt-unit, #spd-unit { font-size: 8px; } - .alt-label, .spd-label { + #alt-label, #spd-label { font-size: 9px; height: 9px; } diff --git a/static/js/map-controls.js b/static/js/map-controls.js index 1d06727..ef2f4a9 100644 --- a/static/js/map-controls.js +++ b/static/js/map-controls.js @@ -7,7 +7,7 @@ $(function(){ // Set location $('#set-loc').click(function(){ - if (!userid==mapuser._id){ alert('You are not logged in! '); } + if (!userid===mapuser._id){ alert('You are not logged in! '); } else { if (!navigator.geolocation){ alert('Geolocation not enabled. '); } else { navigator.geolocation.getCurrentPosition( @@ -41,7 +41,7 @@ $(function(){ // Track location $('#track-loc').click(function(){ - if (!userid==mapuser._id) { alert('You are not logged in! '); } + if (!userid===mapuser._id) { alert('You are not logged in! '); } else { // Start tracking @@ -92,7 +92,7 @@ $(function(){ // Clear location $('#clear-loc').click(function(){ - if (!userid==mapuser._id) { alert('You are not logged in! '); } + if (!userid===mapuser._id) { alert('You are not logged in! '); } else { // Stop tracking if (wpid) { diff --git a/static/js/map.js b/static/js/map.js index b015b9d..636c486 100644 --- a/static/js/map.js +++ b/static/js/map.js @@ -3,16 +3,16 @@ // Variables -var map, pano, marker, elevator, - mapElem = document.getElementById('map'), - panoElem = document.getElementById('pano'); -const socket = io('//'+window.location.hostname); +var map, pano, marker, elevator; +const mapElem = document.getElementById('map'), + panoElem = document.getElementById('pano'), + socket = io('//'+window.location.hostname); function waitForElements(vars,cb){ - if (vars.every(function(v){ return v!==undefined; })){ + if ( vars.every(function(v){ return v!==undefined; }) ){ cb(); } else { - setTimeout(waitForElements, 100); + setTimeout(waitForElements(vars,cb), 100); } } @@ -75,92 +75,106 @@ $(function() { // Google maps API callback window.gmapsCb = function() { - - // Create map - if (disp!='1'||!mapuser.settings.showStreetview) { - map = new google.maps.Map( mapElem, { - center: new google.maps.LatLng( mapuser.last.lat, mapuser.last.lon ), - panControl: false, - scaleControl: mapuser.settings.showScale, - draggable: false, - zoom: mapuser.settings.defaultZoom, - streetViewControl: false, - zoomControlOptions: {position: google.maps.ControlPosition.LEFT_TOP}, - mapTypeId: (mapuser.settings.defaultMap=='road')?google.maps.MapTypeId.ROADMAP:google.maps.MapTypeId.HYBRID - }); - marker = new google.maps.Marker({ - position: { lat:mapuser.last.lat, lng:mapuser.last.lon }, - title: mapuser.name, - map: map, - draggable: false - }); - map.addListener('zoom_changed',function(){ - map.setCenter(marker.getPosition()); - }); + // Make sure everything's ready... + waitForElements([mapuser,disp,noHeader], function() { + //console.log("gmapsCb() called"); - // Create iFrame logo - if (noHeader.length) { - var logoDiv = document.createElement('div'); - logoDiv.className = 'map-logo'; - logoDiv.innerHTML = ''+ - '[]'+ - 'Tracman'; - map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(logoDiv); - } - - // Create update time block - var timeDiv = document.createElement('div'); - timeDiv.className = 'tim'; - if (mapuser.last.time) { - timeDiv.innerHTML = 'location updated '+new Date(mapuser.last.time).toLocaleString(); - } - map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(timeDiv); - - // Create speed block - if (mapuser.settings.showSpeed) { - const speedSign = document.createElement('div'), - speedLabel = document.createElement('div'), - speedText = document.createElement('div'), - speedUnit = document.createElement('div'); - speedLabel.className = 'spd-label'; - speedLabel.innerHTML = 'SPEED'; - speedText.className = 'spd'; - speedText.innerHTML = (mapuser.settings.units=='standard')?(parseFloat(mapuser.last.spd)*2.23694).toFixed():mapuser.last.spd.toFixed(); - speedUnit.className = 'spd-unit'; - speedUnit.innerHTML = (mapuser.settings.units=='standard')?'m.p.h.':'k.p.h.'; - speedSign.className = 'spd-sign'; - speedSign.appendChild(speedLabel); - speedSign.appendChild(speedText); - speedSign.appendChild(speedUnit); - map.controls[google.maps.ControlPosition.TOP_RIGHT].push(speedSign); - } - - // Create altitude block - if (mapuser.settings.showAlt) { - var elevator = new google.maps.ElevationService; - const altitudeSign = document.createElement('div'), - altitudeLabel = document.createElement('div'), - altitudeText = document.createElement('div'), - altitudeUnit = document.createElement('div'); - altitudeLabel.className = 'alt-label'; - altitudeText.className = 'alt'; - altitudeUnit.className = 'alt-unit'; - altitudeSign.className = 'alt-sign'; - altitudeText.innerHTML = ''; - altitudeLabel.innerHTML = 'ALTITUDE'; - getAltitude(new google.maps.LatLng(mapuser.last.lat,mapuser.last.lon), elevator, function(alt) { - if (alt) { altitudeText.innerHTML = (mapuser.settings.units=='standard')?(alt*3.28084).toFixed():alt.toFixed(); } + // Create map + if (disp!=='1') { + //console.log("Creating map..."); + map = new google.maps.Map( mapElem, { + center: new google.maps.LatLng( mapuser.last.lat, mapuser.last.lon ), + panControl: false, + scaleControl: mapuser.settings.showScale, + draggable: false, + zoom: mapuser.settings.defaultZoom, + streetViewControl: false, + zoomControlOptions: {position: google.maps.ControlPosition.LEFT_TOP}, + mapTypeId: (mapuser.settings.defaultMap=='road')?google.maps.MapTypeId.ROADMAP:google.maps.MapTypeId.HYBRID }); - altitudeUnit.innerHTML = (mapuser.settings.units=='standard')?'feet':'meters'; - altitudeSign.appendChild(altitudeLabel); - altitudeSign.appendChild(altitudeText); - altitudeSign.appendChild(altitudeUnit); - map.controls[google.maps.ControlPosition.TOP_RIGHT].push(altitudeSign); + marker = new google.maps.Marker({ + position: { lat:mapuser.last.lat, lng:mapuser.last.lon }, + title: mapuser.name, + map: map, + draggable: false + }); + map.addListener('zoom_changed',function(){ + map.setCenter(marker.getPosition()); + }); + + // Create iFrame logo + if (noHeader!=='0') { + //console.log("Creating iFrame logo..."); + const logoDiv = document.createElement('div'); + logoDiv.id = 'map-logo'; + logoDiv.innerHTML = ''+ + '[]'+ + "Tracman"; + map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(logoDiv); + } + + // Create update time block + //console.log("Creating time block..."); + const timeDiv = document.createElement('div'); + timeDiv.id = 'timestamp'; + if (mapuser.last.time) { + timeDiv.innerHTML = 'location updated '+new Date(mapuser.last.time).toLocaleString(); + } + map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(timeDiv); + + // Create speed block + if (mapuser.settings.showSpeed) { + //console.log("Creating speed sign..."); + const speedSign = document.createElement('div'), + speedLabel = document.createElement('div'), + speedText = document.createElement('div'), + speedUnit = document.createElement('div'); + speedLabel.id = 'spd-label'; + speedLabel.innerHTML = 'SPEED'; + speedText.id = 'spd'; + speedText.innerHTML = (mapuser.settings.units=='standard')?(parseFloat(mapuser.last.spd)*2.23694).toFixed():mapuser.last.spd.toFixed(); + speedUnit.id = 'spd-unit'; + speedUnit.innerHTML = (mapuser.settings.units=='standard')?'m.p.h.':'k.p.h.'; + speedSign.id = 'spd-sign'; + speedSign.appendChild(speedLabel); + speedSign.appendChild(speedText); + speedSign.appendChild(speedUnit); + map.controls[google.maps.ControlPosition.TOP_RIGHT].push(speedSign); + } + + // Create altitude block + if (mapuser.settings.showAlt) { + //console.log("Creating altitude sign..."); + var elevator = new google.maps.ElevationService; + const altitudeSign = document.createElement('div'), + altitudeLabel = document.createElement('div'), + altitudeText = document.createElement('div'), + altitudeUnit = document.createElement('div'); + altitudeLabel.id = 'alt-label'; + altitudeText.id = 'alt'; + altitudeUnit.id = 'alt-unit'; + altitudeSign.id = 'alt-sign'; + altitudeText.innerHTML = ''; + altitudeLabel.innerHTML = 'ALTITUDE'; + getAltitude(new google.maps.LatLng(mapuser.last.lat,mapuser.last.lon), elevator, function(alt) { + if (alt) { altitudeText.innerHTML = (mapuser.settings.units=='standard')?(alt*3.28084).toFixed():alt.toFixed(); } + }); + altitudeUnit.innerHTML = (mapuser.settings.units=='standard')?'feet':'meters'; + altitudeSign.appendChild(altitudeLabel); + altitudeSign.appendChild(altitudeText); + altitudeSign.appendChild(altitudeUnit); + map.controls[google.maps.ControlPosition.TOP_RIGHT].push(altitudeSign); + } + + } + + // Create streetview + if (disp!=='0' && mapuser.settings.showStreetview) { + //console.log("Creating streetview..."); + updateStreetView(parseLoc(mapuser.last),10); } - } - // Create streetview - updateStreetView(parseLoc(mapuser.last),10); + }); }; // Get location @@ -169,7 +183,7 @@ socket.on('get', function(loc) { loc = parseLoc(loc); // Update street view - if (disp!='1' || !mapuser.settings.showStreetview) { + if (disp!=='0' && mapuser.settings.showStreetview) { $('.tim').text('location updated '+loc.time); if (mapuser.settings.showSpeed) { $('.spd').text(loc.spd.toFixed()); } if (mapuser.settings.showAlt) { @@ -180,14 +194,15 @@ socket.on('get', function(loc) { toggleMaps(loc); map.setCenter({lat:loc.lat,lng:loc.lon}); marker.setPosition({lat:loc.lat,lng:loc.lon}); + updateStreetView(loc,10); } - updateStreetView(loc,10); console.log("🌐️ Got location:",loc.lat+", "+loc.lon); }); // Check altitude function getAltitude(loc,elev,cb){ + //console.log("Getting altitude..."); elev = elev || new google.maps.ElevationService; elev.getElevationForLocations({ 'locations': [loc] @@ -199,41 +214,57 @@ function getAltitude(loc,elev,cb){ } // Get street view imagery +//TODO: Use global loc object? function getStreetViewData(loc,rad,cb) { if (!sv) { var sv=new google.maps.StreetViewService(); } - sv.getPanorama({location:{lat:loc.lat,lng:loc.lon},radius:rad},function(data,status){ - if (status!==google.maps.StreetViewStatus.OK){ console.error(new Error('❌️ Street view not available:',status).message); } - else { cb(data); } - }); + sv.getPanorama({ + location: { + lat: loc.lat, + lng: loc.lon + }, + radius:rad + }, function(data,status){ switch (status){ + // Success + case google.maps.StreetViewStatus.OK: + cb(data); + break; + // No results in that radius + case google.maps.StreetViewStatus.ZERO_RESULTS: + // Square the radius and try again + getStreetViewData(loc,rad*rad*.5,cb); + break; + // Error + default: + console.error(new Error('❌️ Street view not available: '+status).message); + } }); } // Update streetview function updateStreetView(loc) { + //console.log("Updating streetview..."); // Moving if (loc.spd>1) { - if (mapuser.settings.showStreetview && disp!='0') { - var imgElem = document.getElementById('panoImg'); - getStreetViewData(loc, 50, function(data){ - if (!imgElem) { - // Create image - pano = undefined; - $('#pano').empty(); - $('#pano').append($('',{ - alt: 'Street view image', - src: 'https://maps.googleapis.com/maps/api/streetview?size=800x800&location='+loc.lat+','+loc.lon+'&fov=90&heading='+loc.dir+'&key={{api}}', - id: 'panoImg' - })); - } - // Set image - $('#panoImg').attr('src','https://maps.googleapis.com/maps/api/streetview?size='+$('#pano').width()+'x'+$('#pano').height()+'&location='+data.location.latLng.lat()+','+data.location.latLng.lng()+'&fov=90&heading='+loc.dir+'&key={{api}}'); - }); - } + var imgElem = document.getElementById('panoImg'); + getStreetViewData(loc, 50, function(data){ + if (!imgElem) { + // Create image + pano = undefined; + $('#pano').empty(); + $('#pano').append($('',{ + alt: 'Street view image', + src: 'https://maps.googleapis.com/maps/api/streetview?size=800x800&location='+loc.lat+','+loc.lon+'&fov=90&heading='+loc.dir+'&key={{api}}', + id: 'panoImg' + })); + } + // Set image + $('#panoImg').attr('src','https://maps.googleapis.com/maps/api/streetview?size='+$('#pano').width()+'x'+$('#pano').height()+'&location='+data.location.latLng.lat()+','+data.location.latLng.lng()+'&fov=90&heading='+loc.dir+'&key={{api}}'); + }); } // Not moving and pano not set else if (pano==null) { - getStreetViewData(loc, 50, function(data){ + getStreetViewData(loc, 10, function(data){ // Create panorama $('#pano').empty(); const panoOptions = { diff --git a/views/map.html b/views/map.html index 959129b..c2e4daf 100644 --- a/views/map.html +++ b/views/map.html @@ -5,7 +5,7 @@ {{super()}} +{% endblock %} {% block main %}
    @@ -12,20 +20,23 @@

    Glad you're enjoying my website and app. I made the whole thing, from front to backend, and I'm really proud of it! However, I'm a long-haul trucker by day and coding is just a hobby. I don't make any money off this website, and I pay the server fees out of my own pocket. Do you pity me enough to donate some money or bitcoin?

    -

    To make a little money off this service, I'm going to be offering a pro version with more features. It'll be cheap, probably $1 or $2 per month. However, while Tracman is in beta, you can beta test the pro version too. Be sure to inform me about any bugs you encounter. And keep in mind that at some point, when we launch out of beta, Tracman Pro will not be free and you will lose your pro membership unless start paying for it. +

    To make a little money off this service, I'm going to be offering a pro version with more features. It'll be cheap, probably $1 or $2 per month. While Tracman is in beta, you can test the pro version for free. Be sure to inform me about any bugs you encounter. And keep in mind that at some point, when we launch out of beta, Tracman Pro will not be free and you will lose your pro membership unless start paying for it.

    -

    That said, just click the button below to test out the pro features. Keep in mind, they are as unstable as the rest of this product. +

    That said, just click the button below to try pro out.

    Cheers,
    Keith Irwin

    -
    + {% if user.isPro %} -
    You are already pro!
    - go to map +
    + + You are already pro! +
    + go back {% else %} - - go home + + go home {% endif %}
    From ea34e5b0956766738088caa5c5927f8aa444cfbd Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 25 Apr 2017 20:01:35 -0400 Subject: [PATCH 122/143] #61 Cleanup, check for injection attacks --- config/routes/map.js | 2 +- config/routes/settings.js | 22 +++++++++++----------- config/sockets.js | 38 ++++++++++++++++---------------------- 3 files changed, 28 insertions(+), 34 deletions(-) diff --git a/config/routes/map.js b/config/routes/map.js index 33bcc5d..d5a20f1 100644 --- a/config/routes/map.js +++ b/config/routes/map.js @@ -13,7 +13,7 @@ router.get('/', mw.ensureAuth, (req,res)=>{ // Show map router.get('/:slug?', (req,res,next)=>{ - + User.findOne({slug:req.params.slug}) .then( (mapuser)=>{ if (!mapuser){ next(); } //404 diff --git a/config/routes/settings.js b/config/routes/settings.js index 502608f..ae7b4ed 100644 --- a/config/routes/settings.js +++ b/config/routes/settings.js @@ -44,18 +44,18 @@ router.route('/') showStreetview: (req.body.showStreet)?true:false }; - // Save user and send response - req.user.save() - .then( ()=>{ - req.flash('success', 'Settings updated. '); - res.redirect('/settings'); - }) - .catch( (err)=>{ - mw.throwErr(err,req); - res.redirect('/settings'); - }); + // Save user and send response + req.user.save() + .then( ()=>{ + req.flash('success', 'Settings updated. '); + res.redirect('/settings'); + }) + .catch( (err)=>{ + mw.throwErr(err,req); + res.redirect('/settings'); + }); - } + } // Validations if (req.body.slug==='') { diff --git a/config/sockets.js b/config/sockets.js index 56893ff..6d0f849 100644 --- a/config/sockets.js +++ b/config/sockets.js @@ -75,34 +75,28 @@ module.exports = { // Get loc.usr User.findById(loc.usr) + .where('sk32').equals(loc.tok) .then( (user)=>{ if (!user){ - console.error("❌", new Error(`Recieved an update from ${socket.ip} for ${loc.usr}, but no such user was found in the db!`).message); + console.error("❌", new Error(`Recieved an update from ${socket.ip} for ${loc.usr} with tok of ${loc.tok}, but no such user was found in the db!`).message); } else { - // Confirm sk32 token - if (loc.tok!=user.sk32) { - console.error("❌", new Error(`Recieved an update from ${socket.ip} for usr ${loc.usr} with tok of ${loc.tok}, but that user's sk32 is ${user.sk32}!`).message); - } - else { + // Broadcast location + io.to(loc.usr).emit('get', loc); + //console.log(`Broadcasting ${loc.lat}, ${loc.lon} to ${loc.usr}`); + + // Save in db as last seen + user.last = { + lat: parseFloat(loc.lat), + lon: parseFloat(loc.lon), + dir: parseFloat(loc.dir||0), + spd: parseFloat(loc.spd||0), + time: loc.time + }; + user.save() + .catch( (err)=>{ console.error("❌", err.stack); }); - // Broadcast location - io.to(loc.usr).emit('get', loc); - //console.log(`Broadcasting ${loc.lat}, ${loc.lon} to ${loc.usr}`); - - // Save in db as last seen - user.last = { - lat: parseFloat(loc.lat), - lon: parseFloat(loc.lon), - dir: parseFloat(loc.dir||0), - spd: parseFloat(loc.spd||0), - time: loc.time - }; - user.save() - .catch( (err)=>{ console.error("❌", err.stack); }); - - } } }) .catch( (err)=>{ console.error("❌", err.stack); }); From 29bade1914d712862705aac7abf78f740c01a7f5 Mon Sep 17 00:00:00 2001 From: Keith Irwin Date: Tue, 25 Apr 2017 20:22:18 -0400 Subject: [PATCH 123/143] 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 124/143] 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 125/143] 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 126/143] 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 127/143] 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 128/143] 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 129/143] 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 130/143] 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 131/143] 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 132/143] 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 133/143] 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 134/143] 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 135/143] 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 136/143] 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 137/143] 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 138/143] #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 139/143] 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 140/143] 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 141/143] 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 142/143] 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 143/143] 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