diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd76eb8..d514c26 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
# Tracman Server Changelog
###### v 0.7.12
+#### develop
+* [#110](https://github.com/Tracman-org/Server/issues/110) Implemented [StandardJS](https://standardjs.com/)
#### v0.7.12
* Fixed altitude sign
diff --git a/README.md b/README.md
index 3add2ef..b3c2de2 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
node.js application to display a sharable map with user's location.
+[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
## Installation
@@ -52,6 +53,9 @@ Tracman will be updated according to [this branching model](http://nvie.com/post
[view full changelog](CHANGELOG.md)
+#### develop
+* [#110](https://github.com/Tracman-org/Server/issues/110) Implemented [StandardJS](https://standardjs.com/)
+
#### v0.7.12
* Fixed altitude sign
diff --git a/config/demo.js b/config/demo.js
index 5404759..0f9317f 100644
--- a/config/demo.js
+++ b/config/demo.js
@@ -1,38 +1,34 @@
-'use strict';
+'use strict'
// Imports
-const fs = require('fs'),
- debug = require('debug')('tracman-demo');
+const fs = require('fs')
+const path = require('path')
+const debug = require('debug')('tracman-demo')
-module.exports = (io)=>{
-
- // File is space-seperated: delay, lat, lon, dir, spd
- fs.readFile(__dirname+'/demo.txt', (err,data)=>{
- if (err){ console.error(`❌ ${err.stack}`); }
-
- const lines = data.toString().split('\n');
-
- (function sendLoc(ln) {
- if (ln>20754){ sendLoc(0) }
- else {
-
- let loc = lines[ln].split(' ');
- debug(`Sending demo location: ${loc[1]}, ${loc[2]}`);
- io.to('demo').emit('get', {
- tim: new Date(),
- lat: loc[1],
- lon: loc[2],
- dir: loc[3],
- spd: loc[4]
- });
-
- // Repeat after delay in milliseconds
- setTimeout(()=>{
- sendLoc(ln+1); // next line of file
- }, loc[0]);
-
- }
- })(5667);
-
- });
-};
\ No newline at end of file
+module.exports = (io) => {
+ // File is space-seperated: delay, lat, lon, dir, spd
+ fs.readFile(path.join(__dirname, '/demo.txt'), (err, data) => {
+ if (err) { console.error(`❌ ${err.stack}`) }
+
+ const lines = data.toString().split('\n');
+
+ (function sendLoc (ln) {
+ if (ln > 20754) { sendLoc(0) } else {
+ let loc = lines[ln].split(' ')
+ debug(`Sending demo location: ${loc[1]}, ${loc[2]}`)
+ io.to('demo').emit('get', {
+ tim: new Date(),
+ lat: loc[1],
+ lon: loc[2],
+ dir: loc[3],
+ spd: loc[4]
+ })
+
+ // Repeat after delay in milliseconds
+ setTimeout(() => {
+ sendLoc(ln + 1) // next line of file
+ }, loc[0])
+ }
+ })(5667)
+ })
+}
diff --git a/config/env/sample.js b/config/env/sample.js
index afdee14..df97fbc 100644
--- a/config/env/sample.js
+++ b/config/env/sample.js
@@ -1,46 +1,46 @@
-'use strict';
+'use strict'
module.exports = {
-
- // Local variables
- mode: 'development', // or production
-
- // Random strings to prevent hijacking
- session: 'SomeSecret',
- cookie: 'SomeOtherSecret',
-
- // Location of your mongoDB
- mongoSetup: 'mongodb://localhost:27017/tracman',
- // Or use the test database from mLab
- //mongoSetup: 'mongodb://tracman:MUPSLXQ34f9cQTc5@ds133961.mlab.com:33961/tracman',
- // You can log in there with:
- // mongo ds133961.mlab.com:33961/tracman-dev -u contributor -p opensourcerules
-
- // URL and port where this will run
- url: 'https://localhost:8080',
- port: 8080,
-
- // Mailserver
- mailserver: 'example.org',
- mailport: 587,
- mailauth: {
- user: 'mailusername',
- pass: 'XXXXXXXXXX',
- },
-
- // OAuth API keys
- facebookAppId: 'XXXXXXXXXXXXXXXX',
- facebookAppSecret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
- twitterConsumerKey: 'XXXXXXXXXXXXXXXXXXXXXXXXX',
- twitterConsumerSecret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
- googleClientId: '############-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com',
- googleClientSecret: 'XXXXXXXXX_XXXXXXXXXXXXXX',
-
- // Google maps API key
- googleMapsAPI: 'XXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXX',
-
- // reCaptcha API key
- recaptchaSitekey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
- recaptchaSecret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
-
-};
+
+ // Local variables
+ mode: 'development', // or production
+
+ // Random strings to prevent hijacking
+ session: 'SomeSecret',
+ cookie: 'SomeOtherSecret',
+
+ // Location of your mongoDB
+ mongoSetup: 'mongodb://localhost:27017/tracman',
+ // Or use the test database from mLab
+ // mongoSetup: 'mongodb://tracman:MUPSLXQ34f9cQTc5@ds133961.mlab.com:33961/tracman',
+ // You can log in there with:
+ // mongo ds133961.mlab.com:33961/tracman-dev -u contributor -p opensourcerules
+
+ // URL and port where this will run
+ url: 'https://localhost:8080',
+ port: 8080,
+
+ // Mailserver
+ mailserver: 'example.org',
+ mailport: 587,
+ mailauth: {
+ user: 'mailusername',
+ pass: 'XXXXXXXXXX'
+ },
+
+ // OAuth API keys
+ facebookAppId: 'XXXXXXXXXXXXXXXX',
+ facebookAppSecret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ twitterConsumerKey: 'XXXXXXXXXXXXXXXXXXXXXXXXX',
+ twitterConsumerSecret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ googleClientId: '############-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com',
+ googleClientSecret: 'XXXXXXXXX_XXXXXXXXXXXXXX',
+
+ // Google maps API key
+ googleMapsAPI: 'XXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXX',
+
+ // reCaptcha API key
+ recaptchaSitekey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ recaptchaSecret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
+
+}
diff --git a/config/mail.js b/config/mail.js
index 6d9acfe..5818897 100644
--- a/config/mail.js
+++ b/config/mail.js
@@ -1,41 +1,41 @@
-'use strict';
+'use strict'
-const nodemailer = require('nodemailer'),
- env = require('./env/env.js');
+const nodemailer = require('nodemailer')
+const env = require('./env/env.js')
let transporter = nodemailer.createTransport({
- host: env.mailserver,
- port: env.mailport,
- secure: false,
- requireTLS: true,
- auth: env.mailauth,
+ host: env.mailserver,
+ port: env.mailport,
+ secure: false,
+ requireTLS: true,
+ auth: env.mailauth
// logger: true,
// debug: true
-});
+})
module.exports = {
-
- verify: ()=>{
- transporter.verify( (err,success)=>{
- if (err){ console.error(`SMTP Error: ${err}`); }
- console.log(`📧 SMTP ${!success?'not ':''}ready`);
- } );
- },
-
- send: transporter.sendMail.bind(transporter),
-
- 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: (text)=>{
- return `
${text}Do not reply to this email. For information about why you recieved this email, see our privacy policy.
`;
- },
-
- noReply: `"Tracman" `,
-
- to: (user)=>{
- return `"${user.name}" <${user.email}>`;
- }
-
-};
\ No newline at end of file
+
+ verify: () => {
+ transporter.verify((err, success) => {
+ if (err) { console.error(`SMTP Error: ${err}`) }
+ console.log(`📧 SMTP ${!success ? 'not ' : ''}ready`)
+ })
+ },
+
+ send: transporter.sendMail.bind(transporter),
+
+ 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: (text) => {
+ return `${text}Do not reply to this email. For information about why you recieved this email, see our privacy policy.
`
+ },
+
+ noReply: `"Tracman" `,
+
+ to: (user) => {
+ return `"${user.name}" <${user.email}>`
+ }
+
+}
diff --git a/config/middleware.js b/config/middleware.js
index 3d9853d..2c2d5ba 100644
--- a/config/middleware.js
+++ b/config/middleware.js
@@ -1,47 +1,45 @@
-'use strict';
+'use strict'
-const env = require('./env/env.js');
+const env = require('./env/env.js')
module.exports = {
- // Throw error
- 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);
- }
- }
- },
+ // Throw error
+ 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: (str)=>{
- return str.charAt(0).toUpperCase() + str.slice(1);
- },
-
- // Validate an email address
- 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);
- },
+ // Capitalize the first letter of a string
+ capitalize: (str) => {
+ return str.charAt(0).toUpperCase() + str.slice(1)
+ },
- // Ensure authentication
- ensureAuth: (req,res,next)=>{
- if (req.isAuthenticated()) { return next(); }
- else { res.redirect('/login'); }
- },
+ // Validate an email address
+ 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)
+ },
- // Ensure administrator
- ensureAdmin: (req,res,next)=>{
- if (req.isAuthenticated() && req.user.isAdmin){ return next(); }
- else {
- let err = new Error("Unauthorized");
- err.status = 401;
- next(err);
- }
- //TODO: test this by logging in as !isAdmin and go to /admin
- }
+ // Ensure authentication
+ ensureAuth: (req, res, next) => {
+ if (req.isAuthenticated()) { return next() } else { res.redirect('/login') }
+ },
-};
\ No newline at end of file
+ // Ensure administrator
+ ensureAdmin: (req, res, next) => {
+ if (req.isAuthenticated() && req.user.isAdmin) { return next() } 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/models.js b/config/models.js
index 833c678..373ab4b 100644
--- a/config/models.js
+++ b/config/models.js
@@ -1,150 +1,142 @@
-'use strict';
+'use strict'
-const mongoose = require('mongoose'),
- unique = require('mongoose-unique-validator'),
- bcrypt = require('bcrypt'),
- crypto = require('crypto'),
- debug = require('debug')('tracman-models');
+const mongoose = require('mongoose')
+const unique = require('mongoose-unique-validator')
+const bcrypt = require('bcrypt')
+const crypto = require('crypto')
+const debug = require('debug')('tracman-models')
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,
- passTokenExpires: Date,
- google: String,
- facebook: String,
- twitter: String,
- },
- isAdmin: {type:Boolean, required:true, default:false},
- isPro: {type:Boolean, required:true, default:false},
- created: {type:Date, required:true},
- lastLogin: Date,
- settings: {
- 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},
- marker: {type:String, default:'red'}
- },
- last: {
- time: Date,
- lat: {type:Number, default:0},
- lon: {type:Number, default:0},
- dir: {type:Number, default:0},
- alt: {type:Number},
- spd: {type:Number, default:0}
- },
- sk32: {type:String, required:true, unique:true}
-}).plugin(unique);
+ name: {type: String},
+ email: {type: String, unique: true},
+ newEmail: String,
+ emailToken: String,
+ slug: {type: String, required: true, unique: true},
+ auth: {
+ password: String,
+ passToken: String,
+ passTokenExpires: Date,
+ google: String,
+ facebook: String,
+ twitter: String
+ },
+ isAdmin: {type: Boolean, required: true, default: false},
+ isPro: {type: Boolean, required: true, default: false},
+ created: {type: Date, required: true},
+ lastLogin: Date,
+ settings: {
+ 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},
+ marker: {type: String, default: 'red'}
+ },
+ last: {
+ time: Date,
+ lat: {type: Number, default: 0},
+ lon: {type: Number, default: 0},
+ dir: {type: Number, default: 0},
+ alt: {type: Number},
+ spd: {type: Number, default: 0}
+ },
+ sk32: {type: String, required: true, unique: true}
+}).plugin(unique)
-/* 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){ // next(err,token)
- debug('user.createEmailToken() called');
- var user = this;
-
- crypto.randomBytes(16, (err,buf)=>{
- if (err){ next(err,null); }
- if (buf){
- debug(`Buffer ${buf.toString('hex')} created`);
- user.emailToken = buf.toString('hex');
- user.save()
- .then( ()=>{
- return next(null,user.emailToken);
- })
- .catch( (err)=>{
- return next(err,null);
- });
-
- }
- });
-
- };
+/* User methods */
+// TODO: Return promises instead of taking callbacks
+// See https://gist.github.com/7h1b0/5154fda207e68ad1cefc#file-random-js
+// For an example
- // 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() ){
- debug(`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 {
- debug(`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( ()=>{
- debug('successfully saved user in createPassToken');
- return next(null,user.auth.passToken,user.auth.passTokenExpires);
- })
- .catch( (err)=>{
- debug('error saving user in createPassToken');
- return next(err,null,null);
- });
- }
- });
- }
-
- };
-
- // Generate hash for new password and save it to the database
- userSchema.methods.generateHashedPassword = function(password,next){
- // next(err);
-
- // Delete token
- this.auth.passToken = undefined;
- this.auth.passTokenExpires = undefined;
-
- // Generate hash
- bcrypt.genSalt(8, (err,salt)=>{
- if (err){ return next(err); }
- bcrypt.hash(password, salt, (err,hash)=>{
- if (err){ return next(err); }
- this.auth.password = hash;
- this.save();
- next();
- });
- });
-
- };
-
- // Check for valid password
- userSchema.methods.validPassword = function(password,next){
- // next(err,res);
- // res = true/false
- bcrypt.compare(password, this.auth.password, next);
- };
-
+// Create email confirmation token
+userSchema.methods.createEmailToken = function (next) { // next(err,token)
+ debug('user.createEmailToken() called')
+ var user = this
+
+ crypto.randomBytes(16, (err, buf) => {
+ if (err) { next(err, null) }
+ if (buf) {
+ debug(`Buffer ${buf.toString('hex')} created`)
+ user.emailToken = buf.toString('hex')
+ 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()) {
+ debug(`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 {
+ debug(`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(() => {
+ debug('successfully saved user in createPassToken')
+ return next(null, user.auth.passToken, user.auth.passTokenExpires)
+ })
+ .catch((err) => {
+ debug('error saving user in createPassToken')
+ return next(err, null, null)
+ })
+ }
+ })
+ }
+}
+
+// Generate hash for new password and save it to the database
+userSchema.methods.generateHashedPassword = function (password, next) {
+ // next(err);
+
+ // Delete token
+ this.auth.passToken = undefined
+ this.auth.passTokenExpires = undefined
+
+ // Generate hash
+ bcrypt.genSalt(8, (err, salt) => {
+ if (err) { return next(err) }
+ bcrypt.hash(password, salt, (err, hash) => {
+ if (err) { return next(err) }
+ this.auth.password = hash
+ this.save()
+ next()
+ })
+ })
+}
+
+// Check for valid password
+userSchema.methods.validPassword = function (password, next) {
+ // next(err,res);
+ // res = true/false
+ bcrypt.compare(password, this.auth.password, next)
}
module.exports = {
- 'user': mongoose.model('User', userSchema)
-};
+ 'user': mongoose.model('User', userSchema)
+}
diff --git a/config/passport.js b/config/passport.js
index 5b19619..c01e34b 100644
--- a/config/passport.js
+++ b/config/passport.js
@@ -1,252 +1,229 @@
-'use strict';
+'use strict'
-const
- LocalStrategy = require('passport-local').Strategy,
- 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'),
- debug = require('debug')('tracman-passport'),
- env = require('./env/env.js'),
- mw = require('./middleware.js'),
- User = require('./models.js').user;
-
-module.exports = (passport)=>{
-
- // Serialize/deserialize users
- passport.serializeUser((user,done)=>{
- done(null, user.id);
- });
- passport.deserializeUser((id,done)=>{
- User.findById(id, (err,user)=>{
- if(!err){ done(null, user); }
- else { done(err, null); }
- });
- });
-
- // Local
- passport.use('local', new LocalStrategy({
- usernameField: 'email',
- passwordField: 'password',
- passReqToCallback: true
- }, (req,email,password,done)=>{
- debug(`Perfoming local login for ${email}`);
- User.findOne({'email':email})
- .then( (user)=>{
-
- // No user with that email
- if (!user) {
- req.session.next = undefined;
- return done( null, false, req.flash('warning','Incorrect email or password.') );
- }
-
- // User exists
- else {
-
- // Check password
- user.validPassword( password, (err,res)=>{
- if (err){ return done(err); }
-
- // Password incorrect
- if (!res) {
- req.session.next = undefined;
- return done( null, false, req.flash('warning','Incorrect email or password.') );
- }
-
- // Successful login
- else {
- user.lastLogin = Date.now();
- user.save();
- return done(null,user);
- }
-
- } );
- }
-
- })
- .catch( (err)=>{
- return done(err);
- });
- }
- ));
-
- // Social login
- function socialLogin(req, service, profileId, done) {
- debug(`socialLogin() called for ${service} account ${profileId}`);
- let query = {};
- query['auth.'+service] = profileId;
-
- // Intent to log in
- if (!req.user) {
- debug(`Searching for user with query ${query}...`);
- User.findOne(query)
- .then( (user)=>{
-
- // Can't find user
- if (!user){
-
- // Lazy update from old googleId field
- if (service==='google') {
- User.findOne({ 'googleID': parseInt(profileId,10) })
- .then( (user)=>{
-
- // User exists with old schema
- if (user) {
- debug(`User ${user.id} exists with old schema. Lazily updating...`);
- user.auth.google = profileId;
- user.googleId = undefined;
- user.save()
- .then( ()=>{
- debug(`Lazily updated ${user.id}...`);
- req.session.flashType = 'success';
- req.session.flashMessage = "You have been logged in. ";
- return done(null, user);
- })
- .catch( (err)=>{
- debug(`Failed to save user that exists with old googleId schema!`);
- mw.throwErr(err,req);
- return done(err);
- });
- }
-
- // No such user
- else {
- debug(`User with ${service} account of ${profileId} not found.`);
- req.flash('warning', `There's no user for that ${service} account. `);
- return done();
- }
-
- })
- .catch ( (err)=>{
- debug(`Failed to search for user with old googleID of ${profileId}. `);
- mw.throwErr(err,req);
- return done(err);
- });
- }
-
- // No googleId either
- else {
- debug(`Couldn't find ${service} user with profileID ${profileId}.`);
- req.flash('warning', `There's no user for that ${service} account. `);
- return done();
- }
- }
-
- // Successfull social login
- else {
- debug(`Found user: ${user.id}; logging in...`);
- req.session.flashType = 'success';
- req.session.flashMessage = "You have been logged in.";
- return done(null, user);
- }
-
- })
- .catch( (err)=>{
- debug(`Failed to find user with query: ${query}`);
- mw.throwErr(err,req);
- return done(err);
- });
- }
-
- // Intent to connect account
- else {
- debug(`Attempting to connect ${service} account to ${req.user.id}...`);
-
- // Check for unique profileId
- debug(`Checking for unique account with query ${query}...`);
- User.findOne(query)
- .then( (existingUser)=>{
-
- // Social account already in use
- if (existingUser) {
- debug(`${service} account already in use with user ${existingUser.id}`);
- req.session.flashType = 'warning';
- req.session.flashMessage = `Another user is already connected to that ${service} account. `;
- return done();
- }
-
- // Connect to account
- else {
- debug(`${service} account (${profileId}) is unique; Connecting to ${req.user.id}...`);
- req.user.auth[service] = profileId;
- req.user.save()
- .then( ()=>{
- debug(`Successfully connected ${service} account to ${req.user.id}`);
- req.session.flashType = 'success';
- req.session.flashMessage = `${mw.capitalize(service)} account connected. `;
- return done(null,req.user);
- } )
- .catch( (err)=>{
- debug(`Failed to connect ${service} account to ${req.user.id}!`);
- return done(err);
- } );
- }
-
- })
- .catch( (err)=>{
- debug(`Failed to check for unique ${service} profileId of ${profileId}!`);
- mw.throwErr(err,req);
- return done(err);
- });
-
- }
-
- }
+const LocalStrategy = require('passport-local').Strategy
+const GoogleStrategy = require('passport-google-oauth20').Strategy
+const FacebookStrategy = require('passport-facebook').Strategy
+const TwitterStrategy = require('passport-twitter').Strategy
+const GoogleTokenStrategy = require('passport-google-id-token')
+const FacebookTokenStrategy = require('passport-facebook-token')
+const TwitterTokenStrategy = require('passport-twitter-token')
+const debug = require('debug')('tracman-passport')
+const env = require('./env/env.js')
+const mw = require('./middleware.js')
+const User = require('./models.js').user
- // Google
- passport.use('google', new GoogleStrategy({
- clientID: env.googleClientId,
- clientSecret: env.googleClientSecret,
- callbackURL: env.url+'/login/google/cb',
- passReqToCallback: true
- }, (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
- passport.use('facebook', new FacebookStrategy({
- clientID: env.facebookAppId,
- clientSecret: env.facebookAppSecret,
- callbackURL: env.url+'/login/facebook/cb',
- passReqToCallback: true
- }, (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
- passport.use(new TwitterStrategy({
- consumerKey: env.twitterConsumerKey,
- consumerSecret: env.twitterConsumerSecret,
- callbackURL: env.url+'/login/twitter/cb',
- passReqToCallback: true
- }, (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);
- }
- ));
+module.exports = (passport) => {
+ // Serialize/deserialize users
+ passport.serializeUser((user, done) => {
+ done(null, user.id)
+ })
+ passport.deserializeUser((id, done) => {
+ User.findById(id, (err, user) => {
+ if (!err) { done(null, user) } else { done(err, null) }
+ })
+ })
- return passport;
-};
\ No newline at end of file
+ // Local
+ passport.use('local', new LocalStrategy({
+ usernameField: 'email',
+ passwordField: 'password',
+ passReqToCallback: true
+ }, (req, email, password, done) => {
+ debug(`Perfoming local login for ${email}`)
+ User.findOne({'email': email})
+ .then((user) => {
+ // No user with that email
+ if (!user) {
+ req.session.next = undefined
+ return done(null, false, req.flash('warning', 'Incorrect email or password.'))
+
+ // User exists
+ } else {
+ // Check password
+ user.validPassword(password, (err, res) => {
+ if (err) { return done(err) }
+
+ // Password incorrect
+ if (!res) {
+ req.session.next = undefined
+ return done(null, false, req.flash('warning', 'Incorrect email or password.'))
+
+ // Successful login
+ } else {
+ user.lastLogin = Date.now()
+ user.save()
+ return done(null, user)
+ }
+ })
+ }
+ })
+ .catch((err) => {
+ return done(err)
+ })
+ }
+ ))
+
+ // Social login
+ function socialLogin (req, service, profileId, done) {
+ debug(`socialLogin() called for ${service} account ${profileId}`)
+ let query = {}
+ query['auth.' + service] = profileId
+
+ // Intent to log in
+ if (!req.user) {
+ debug(`Searching for user with query ${query}...`)
+ User.findOne(query)
+ .then((user) => {
+ // Can't find user
+ if (!user) {
+ // Lazy update from old googleId field
+ if (service === 'google') {
+ User.findOne({ 'googleID': parseInt(profileId, 10) })
+ .then((user) => {
+ // User exists with old schema
+ if (user) {
+ debug(`User ${user.id} exists with old schema. Lazily updating...`)
+ user.auth.google = profileId
+ user.googleId = undefined
+ user.save()
+ .then(() => {
+ debug(`Lazily updated ${user.id}...`)
+ req.session.flashType = 'success'
+ req.session.flashMessage = 'You have been logged in. '
+ return done(null, user)
+ })
+ .catch((err) => {
+ debug(`Failed to save user that exists with old googleId schema!`)
+ mw.throwErr(err, req)
+ return done(err)
+ })
+
+ // No such user
+ } else {
+ debug(`User with ${service} account of ${profileId} not found.`)
+ req.flash('warning', `There's no user for that ${service} account. `)
+ return done()
+ }
+ })
+ .catch((err) => {
+ debug(`Failed to search for user with old googleID of ${profileId}. `)
+ mw.throwErr(err, req)
+ return done(err)
+ })
+
+ // No googleId either
+ } else {
+ debug(`Couldn't find ${service} user with profileID ${profileId}.`)
+ req.flash('warning', `There's no user for that ${service} account. `)
+ return done()
+ }
+
+ // Successfull social login
+ } else {
+ debug(`Found user: ${user.id}; logging in...`)
+ req.session.flashType = 'success'
+ req.session.flashMessage = 'You have been logged in.'
+ return done(null, user)
+ }
+ })
+ .catch((err) => {
+ debug(`Failed to find user with query: ${query}`)
+ mw.throwErr(err, req)
+ return done(err)
+ })
+
+ // Intent to connect account
+ } else {
+ debug(`Attempting to connect ${service} account to ${req.user.id}...`)
+
+ // Check for unique profileId
+ debug(`Checking for unique account with query ${query}...`)
+ User.findOne(query)
+ .then((existingUser) => {
+ // Social account already in use
+ if (existingUser) {
+ debug(`${service} account already in use with user ${existingUser.id}`)
+ req.session.flashType = 'warning'
+ req.session.flashMessage = `Another user is already connected to that ${service} account. `
+ return done()
+
+ // Connect to account
+ } else {
+ debug(`${service} account (${profileId}) is unique; Connecting to ${req.user.id}...`)
+ req.user.auth[service] = profileId
+ req.user.save()
+ .then(() => {
+ debug(`Successfully connected ${service} account to ${req.user.id}`)
+ req.session.flashType = 'success'
+ req.session.flashMessage = `${mw.capitalize(service)} account connected. `
+ return done(null, req.user)
+ })
+ .catch((err) => {
+ debug(`Failed to connect ${service} account to ${req.user.id}!`)
+ return done(err)
+ })
+ }
+ })
+ .catch((err) => {
+ debug(`Failed to check for unique ${service} profileId of ${profileId}!`)
+ mw.throwErr(err, req)
+ return done(err)
+ })
+ }
+ }
+
+ // Google
+ passport.use('google', new GoogleStrategy({
+ clientID: env.googleClientId,
+ clientSecret: env.googleClientSecret,
+ callbackURL: env.url + '/login/google/cb',
+ passReqToCallback: true
+ }, (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
+ passport.use('facebook', new FacebookStrategy({
+ clientID: env.facebookAppId,
+ clientSecret: env.facebookAppSecret,
+ callbackURL: env.url + '/login/facebook/cb',
+ passReqToCallback: true
+ }, (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
+ passport.use(new TwitterStrategy({
+ consumerKey: env.twitterConsumerKey,
+ consumerSecret: env.twitterConsumerSecret,
+ callbackURL: env.url + '/login/twitter/cb',
+ passReqToCallback: true
+ }, (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/admin.js b/config/routes/admin.js
index 3622e11..4cd3b0a 100644
--- a/config/routes/admin.js
+++ b/config/routes/admin.js
@@ -1,39 +1,35 @@
-'use strict';
+'use strict'
-const router = require('express').Router(),
- mw = require('../middleware.js'),
- debug = require('debug')('tracman-routes-admin'),
- User = require('../models.js').user;
+const router = require('express').Router()
+const mw = require('../middleware.js')
+const debug = require('debug')('tracman-routes-admin')
+const User = require('../models.js').user
-router.get('/', mw.ensureAdmin, (req,res)=>{
-
- User.find({}).sort({lastLogin:-1})
- .then( (found)=>{
- res.render('admin', {
- active: 'admin',
- noFooter: '1',
- users: found,
- total: found.length
- });
- })
- .catch( (err)=>{ mw.throwErr(err,req); });
-
-});
-
-router.get('/delete/:usrid', mw.ensureAdmin, (req,res,next)=>{
-
- debug(`/delete/${req.params.usrid} called`);
-
- User.findOneAndRemove({'_id':req.params.usrid})
- .then( (user)=>{
- req.flash('success', `${req.params.usrid} deleted.`);
- res.redirect('/admin');
- })
- .catch( (err)=>{
- mw.throwErr(err,req);
- res.redirect('/admin');
- });
-
-});
+router.get('/', mw.ensureAdmin, (req, res) => {
+ User.find({}).sort({lastLogin: -1})
+ .then((found) => {
+ res.render('admin', {
+ active: 'admin',
+ noFooter: '1',
+ users: found,
+ total: found.length
+ })
+ })
+ .catch((err) => { mw.throwErr(err, req) })
+})
-module.exports = router;
+router.get('/delete/:usrid', mw.ensureAdmin, (req, res, next) => {
+ debug(`/delete/${req.params.usrid} called`)
+
+ User.findOneAndRemove({'_id': req.params.usrid})
+ .then((user) => {
+ req.flash('success', `${req.params.usrid} deleted.`)
+ res.redirect('/admin')
+ })
+ .catch((err) => {
+ mw.throwErr(err, req)
+ res.redirect('/admin')
+ })
+})
+
+module.exports = router
diff --git a/config/routes/auth.js b/config/routes/auth.js
index 31eb927..b2129fd 100644
--- a/config/routes/auth.js
+++ b/config/routes/auth.js
@@ -1,345 +1,309 @@
-'use strict';
+'use strict'
-const
- mw = require('../middleware.js'),
- mail = require('../mail.js'),
- User = require('../models.js').user,
- crypto = require('crypto'),
- moment = require('moment'),
- slugify = require('slug'),
- debug = require('debug')('tracman-routes-auth'),
- env = require('../env/env.js');
+const mw = require('../middleware.js')
+const mail = require('../mail.js')
+const User = require('../models.js').user
+const crypto = require('crypto')
+const moment = require('moment')
+const slugify = require('slug')
+const debug = require('debug')('tracman-routes-auth')
+const env = require('../env/env.js')
module.exports = (app, passport) => {
+ // Methods for success and failure
+ const loginOutcome = {
+ failureRedirect: '/login',
+ failureFlash: true
+ }
+ const loginCallback = (req, res) => {
+ debug(`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')
+ }
+ const appLoginCallback = (req, res, next) => {
+ debug('appLoginCallback called.')
+ if (req.user) { res.send(req.user) } else {
+ let err = new Error('Unauthorized')
+ err.status = 401
+ next(err)
+ }
+ }
- // Methods for success and failure
- const
- loginOutcome = {
- failureRedirect: '/login',
- failureFlash: true
- },
- loginCallback = (req,res)=>{
- debug(`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)=>{
- debug('appLoginCallback called.');
- if (req.user){ res.send(req.user); }
- else {
- let err = new Error("Unauthorized");
- err.status = 401;
- next(err);
- }
- };
-
- // 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.`);
- 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){
- debug(`sendToken() called for user ${user.id}`);
-
- // Create a password token
- user.createPassToken( (err,token,expires)=>{
- if (err){
- debug(`Error creating password token for user ${user.id}!`);
- mw.throwErr(err,req);
- res.redirect('/login#signup');
- }
- else {
- debug(`Created password token for user ${user.id} successfully`);
-
- // 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";
-
- // Email the instructions to continue
- debug(`Emailing new user ${user.id} at ${user.email} instructions to create a password...`);
- mail.send({
- from: mail.noReply,
- 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}\n\nThis link will expire at ${expirationTimeString}. `),
- html: mail.html(`Welcome to Tracman!
To complete your registration, follow this link and set your password:
${env.url}/settings/password/${token}
This link will expire at ${expirationTimeString}.
`)
- })
- .then(()=>{
- debug(`Successfully emailed new user ${user.id} instructions to continue`);
- req.flash('success', `An email has been sent to ${user.email}. Check your inbox and follow the link to complete your registration. (Your registration link will expire in one hour). `);
- res.redirect('/login');
- })
- .catch((err)=>{
- debug(`Failed to email new user ${user.id} instructions to continue!`);
- mw.throwErr(err,req);
- res.redirect('/login#signup');
- });
-
- }
- });
-
- }
-
- // Validate email
- req.checkBody('email', 'Please enter a valid email address.').isEmail();
-
- // Check if somebody already has that email
- debug(`Searching for user with email ${req.body.email}...`);
- User.findOne({'email':req.body.email})
- .then( (user)=>{
-
- // User already exists
- if (user && user.auth.password) {
- debug(`User ${user.id} has email ${req.body.email} and has a 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) {
- debug(`User ${user.id} has email ${req.body.email} but doesn't have a password`);
- // Send another token (or the same one if it hasn't expired)
- sendToken(user);
- }
-
- // Create user
- else {
- debug(`User with email ${req.body.email} doesn't exist; creating one`);
-
- user = new User();
- user.created = Date.now();
- user.email = req.body.email;
- user.slug = slugify(user.email.substring(0, user.email.indexOf('@')));
-
- // Generate unique slug
- const slug = new Promise((resolve,reject) => {
- debug(`Creating new slug for user...`);
-
- (function checkSlug(s,cb){
-
- debug(`Checking to see if slug ${s} is taken...`);
- User.findOne({slug:s})
- .then((existingUser)=>{
-
- // Slug in use: generate a random one and retry
- if (existingUser){
- debug(`Slug ${s} is taken; generating another...`);
- crypto.randomBytes(6, (err,buf)=>{
- if (err) {
- debug('Failed to create random bytes for slug!');
- mw.throwErr(err,req);
- reject();
- }
- if (buf) {
- checkSlug(buf.toString('hex'),cb);
- }
- });
- }
-
- // Unique slug: proceed
- else {
- debug(`Slug ${s} is unique`);
- cb(s);
- }
-
- })
- .catch((err)=>{
- debug('Failed to create slug!');
- mw.throwErr(err,req);
- reject();
- });
-
- })(user.slug, (newSlug)=>{
- debug(`Successfully created slug: ${newSlug}`);
- user.slug = newSlug;
- resolve();
- });
- });
-
- // Generate sk32
- const sk32 = new Promise((resolve,reject) => {
- debug('Creating sk32 for user...');
- crypto.randomBytes(32, (err,buf)=>{
- if (err) {
- debug('Failed to create sk32!');
- mw.throwErr(err,req);
- reject();
- }
- if (buf) {
- user.sk32 = buf.toString('hex');
- debug(`Successfully created sk32: ${user.sk32}`);
- resolve();
- }
- });
- });
-
- // Save user and send the token by email
- Promise.all([slug, sk32])
- .then( ()=>{ sendToken(user); })
- .catch( (err)=>{
- debug('Failed to save user after creating slug and sk32!');
- mw.throwErr(err,req);
- res.redirect('/login#signup');
- });
-
- }
-
- })
- .catch( (err)=>{
- debug(`Failed to check if somebody already has the email ${req.body.email}`);
- mw.throwErr(err,req);
- res.redirect('/signup');
- });
-
- });
-
- // Forgot password
- app.route('/login/forgot')
-
- // Check if user is already logged in
- .all( (req,res,next)=>{
- if (req.isAuthenticated()){ loginCallback(req,res); }
- else { next(); }
- } )
-
- // Show forgot password page
- .get( (req,res,next)=>{
- res.render('forgot', {email:req.query.email});
- } )
-
- // Submitted forgot password form
- .post( (req,res,next)=>{
-
- // Validate email
- req.checkBody('email', 'Please enter a valid email address.').isEmail();
-
- // Check if somebody has that email
- 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.createPassToken( (err,token)=>{
- if (err){ next(err); }
-
- // Email reset link
- mail.send({
- from: mail.noReply,
- 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)=>{
- debug(`Failed to send reset link to ${user.email}`);
- mw.throwErr(err,req);
- res.redirect('/login');
- });
-
- });
-
- }
-
- }).catch( (err)=>{
- debug(`Failed to check for if somebody has that email (in reset request)!`);
- mw.throwErr(err,req);
- res.redirect('/login/forgot');
- });
-
- } );
-
- // Android
- 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);
-
- // Social
- app.get('/login/:service', (req,res,next)=>{
- let service = req.params.service,
- sendParams = (service==='google')?{scope:['https://www.googleapis.com/auth/userinfo.profile']}:null;
-
- // Social login
- if (!req.user) {
- debug(`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]) {
- debug(`Attempting to connect ${service} account...`);
- passport.authorize(service, sendParams)(req,res,next);
- }
-
- // Disconnect social account
- else {
- debug(`Attempting to disconnect ${service} account...`);
-
- // Make sure the user has a password before they disconnect their google login account
- // This is because login used to only be through google, and some people might not have
- // set passwords yet...
- if (!req.user.auth.password && service==='google') {
- req.flash('warning',`Hey, you need to set a password before you can disconnect your google account. Otherwise, you won't be able to log in! `);
- res.redirect('/settings');
- }
-
- else {
- req.user.auth[service] = undefined;
- req.user.save()
- .then(()=>{
- req.flash('success', `${mw.capitalize(service)} account disconnected. `);
- res.redirect('/settings');
- })
- .catch((err)=>{
- debug(`Failed to save user after disconnecting ${service} account!`);
- mw.throwErr(err,req);
- res.redirect('/settings');
- });
- }
-
- }
-
- });
- 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 );
-
-};
+ // 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.`)
+ 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) {
+ debug(`sendToken() called for user ${user.id}`)
+
+ // Create a password token
+ user.createPassToken((err, token, expires) => {
+ if (err) {
+ debug(`Error creating password token for user ${user.id}!`)
+ mw.throwErr(err, req)
+ res.redirect('/login#signup')
+ } else {
+ debug(`Created password token for user ${user.id} successfully`)
+
+ // 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'
+
+ // Email the instructions to continue
+ debug(`Emailing new user ${user.id} at ${user.email} instructions to create a password...`)
+ mail.send({
+ from: mail.noReply,
+ 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}\n\nThis link will expire at ${expirationTimeString}. `),
+ html: mail.html(`Welcome to Tracman!
To complete your registration, follow this link and set your password:
${env.url}/settings/password/${token}
This link will expire at ${expirationTimeString}.
`)
+ })
+ .then(() => {
+ debug(`Successfully emailed new user ${user.id} instructions to continue`)
+ req.flash('success', `An email has been sent to ${user.email}. Check your inbox and follow the link to complete your registration. (Your registration link will expire in one hour). `)
+ res.redirect('/login')
+ })
+ .catch((err) => {
+ debug(`Failed to email new user ${user.id} instructions to continue!`)
+ mw.throwErr(err, req)
+ res.redirect('/login#signup')
+ })
+ }
+ })
+ }
+
+ // Validate email
+ req.checkBody('email', 'Please enter a valid email address.').isEmail()
+
+ // Check if somebody already has that email
+ debug(`Searching for user with email ${req.body.email}...`)
+ User.findOne({'email': req.body.email})
+ .then((user) => {
+ // User already exists
+ if (user && user.auth.password) {
+ debug(`User ${user.id} has email ${req.body.email} and has a 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) {
+ debug(`User ${user.id} has email ${req.body.email} but doesn't have a password`)
+ // Send another token (or the same one if it hasn't expired)
+ sendToken(user)
+
+ // Create user
+ } else {
+ debug(`User with email ${req.body.email} doesn't exist; creating one`)
+
+ user = new User()
+ user.created = Date.now()
+ user.email = req.body.email
+ user.slug = slugify(user.email.substring(0, user.email.indexOf('@')))
+
+ // Generate unique slug
+ const slug = new Promise((resolve, reject) => {
+ debug(`Creating new slug for user...`);
+
+ (function checkSlug (s, cb) {
+ debug(`Checking to see if slug ${s} is taken...`)
+ User.findOne({slug: s})
+ .then((existingUser) => {
+ // Slug in use: generate a random one and retry
+ if (existingUser) {
+ debug(`Slug ${s} is taken; generating another...`)
+ crypto.randomBytes(6, (err, buf) => {
+ if (err) {
+ debug('Failed to create random bytes for slug!')
+ mw.throwErr(err, req)
+ reject()
+ }
+ if (buf) {
+ checkSlug(buf.toString('hex'), cb)
+ }
+ })
+
+ // Unique slug: proceed
+ } else {
+ debug(`Slug ${s} is unique`)
+ cb(s)
+ }
+ })
+ .catch((err) => {
+ debug('Failed to create slug!')
+ mw.throwErr(err, req)
+ reject()
+ })
+ })(user.slug, (newSlug) => {
+ debug(`Successfully created slug: ${newSlug}`)
+ user.slug = newSlug
+ resolve()
+ })
+ })
+
+ // Generate sk32
+ const sk32 = new Promise((resolve, reject) => {
+ debug('Creating sk32 for user...')
+ crypto.randomBytes(32, (err, buf) => {
+ if (err) {
+ debug('Failed to create sk32!')
+ mw.throwErr(err, req)
+ reject()
+ }
+ if (buf) {
+ user.sk32 = buf.toString('hex')
+ debug(`Successfully created sk32: ${user.sk32}`)
+ resolve()
+ }
+ })
+ })
+
+ // Save user and send the token by email
+ Promise.all([slug, sk32])
+ .then(() => { sendToken(user) })
+ .catch((err) => {
+ debug('Failed to save user after creating slug and sk32!')
+ mw.throwErr(err, req)
+ res.redirect('/login#signup')
+ })
+ }
+ })
+ .catch((err) => {
+ debug(`Failed to check if somebody already has the email ${req.body.email}`)
+ mw.throwErr(err, req)
+ res.redirect('/signup')
+ })
+ })
+
+ // Forgot password
+ app.route('/login/forgot')
+
+ // Check if user is already logged in
+ .all((req, res, next) => {
+ if (req.isAuthenticated()) { loginCallback(req, res) } else { next() }
+ })
+
+ // Show forgot password page
+ .get((req, res, next) => {
+ res.render('forgot', {email: req.query.email})
+ })
+
+ // Submitted forgot password form
+ .post((req, res, next) => {
+ // Validate email
+ req.checkBody('email', 'Please enter a valid email address.').isEmail()
+
+ // Check if somebody has that email
+ 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.createPassToken((err, token) => {
+ if (err) { next(err) }
+
+ // Email reset link
+ mail.send({
+ from: mail.noReply,
+ 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) => {
+ debug(`Failed to send reset link to ${user.email}`)
+ mw.throwErr(err, req)
+ res.redirect('/login')
+ })
+ })
+ }
+ }).catch((err) => {
+ debug(`Failed to check for if somebody has that email (in reset request)!`)
+ mw.throwErr(err, req)
+ res.redirect('/login/forgot')
+ })
+ })
+
+ // Android
+ 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);
+
+ // Social
+ app.get('/login/:service', (req, res, next) => {
+ let service = req.params.service
+ let sendParams = (service === 'google') ? {scope: ['https://www.googleapis.com/auth/userinfo.profile']} : null
+
+ // Social login
+ if (!req.user) {
+ debug(`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]) {
+ debug(`Attempting to connect ${service} account...`)
+ passport.authorize(service, sendParams)(req, res, next)
+
+ // Disconnect social account
+ } else {
+ debug(`Attempting to disconnect ${service} account...`)
+
+ // Make sure the user has a password before they disconnect their google login account
+ // This is because login used to only be through google, and some people might not have
+ // set passwords yet...
+ if (!req.user.auth.password && service === 'google') {
+ req.flash('warning', `Hey, you need to set a password before you can disconnect your google account. Otherwise, you won't be able to log in! `)
+ res.redirect('/settings')
+ } else {
+ req.user.auth[service] = undefined
+ req.user.save()
+ .then(() => {
+ req.flash('success', `${mw.capitalize(service)} account disconnected. `)
+ res.redirect('/settings')
+ })
+ .catch((err) => {
+ debug(`Failed to save user after disconnecting ${service} account!`)
+ mw.throwErr(err, req)
+ res.redirect('/settings')
+ })
+ }
+ }
+ })
+ 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/contact.js b/config/routes/contact.js
index eb20543..aa44faa 100644
--- a/config/routes/contact.js
+++ b/config/routes/contact.js
@@ -1,92 +1,78 @@
-'use strict';
+'use strict'
-const env = require('../env/env.js'),
- request = require('request'),
- mw = require('../middleware.js'),
- mail = require('../mail.js'),
- router = require('express').Router();
+const env = require('../env/env.js')
+const request = require('request')
+const mw = require('../middleware.js')
+const mail = require('../mail.js')
+const router = require('express').Router()
module.exports = router
// Display contact form
-.get('/', (req,res)=>{
- res.render('contact', {active:'contact',
- sitekey: env.recaptchaSitekey
- });
+.get('/', (req, res) => {
+ res.render('contact', {active: 'contact',
+ sitekey: env.recaptchaSitekey
+ })
})
-.post('/', (req,res,next)=>{
-
- // Check email
- if (req.body.email==='') {
- req.flash('warning', `You need to enter an email address. `);
- res.redirect('/contact');
- }
- else if (!mw.validateEmail(req.body.email)) {
- req.flash('warning', `${req.body.email} is not a valid email address. `);
- res.redirect('/contact');
- }
-
- // Check for message
- else if (req.body.message==='') {
- req.flash('warning', `You need to enter a message. `);
- res.redirect('/contact');
- }
-
-
- // Passed validations
- else {
-
- // Confirm captcha
- request.post( 'https://www.google.com/recaptcha/api/siteverify', {form:{
- secret: env.recaptchaSecret,
- response: req.body['g-recaptcha-response'],
- remoteip: req.ip
- }}, (err, response, body)=>{
-
- // Check for errors
- if (err){
- mw.throwErr(err,req);
- res.redirect('/contact');
- }
- if (response.statusCode!==200) {
- let err = new Error('Bad response from reCaptcha service');
- mw.throwErr(err,req);
- res.redirect('/contact');
- }
-
- // No errors
- else {
-
- // Captcha failed
- if (!JSON.parse(body).success){
- let err = new Error('Failed reCaptcha');
- mw.throwErr(err,req);
- res.redirect('/contact');
- }
-
- // Captcha succeeded
- else {
- mail.send({
- from: `${req.body.name} <${req.body.email}>`,
- to: `Tracman Contact `,
- subject: req.body.subject||'A message',
- text: req.body.message
- })
- .then(()=>{
- req.flash('success', `Your message has been sent. `);
- res.redirect(req.session.next || '/');
- })
- .catch((err)=>{
- mw.throwErr(err,req);
- res.redirect('/contact');
- });
- }
-
- }
-
- });
-
- }
-
-});
+.post('/', (req, res, next) => {
+ // Check email
+ if (req.body.email === '') {
+ req.flash('warning', `You need to enter an email address. `)
+ res.redirect('/contact')
+ } else if (!mw.validateEmail(req.body.email)) {
+ req.flash('warning', `${req.body.email} is not a valid email address. `)
+ res.redirect('/contact')
+
+ // Check for message
+ } else if (req.body.message === '') {
+ req.flash('warning', `You need to enter a message. `)
+ res.redirect('/contact')
+
+ // Passed validations
+ } else {
+ // Confirm captcha
+ request.post('https://www.google.com/recaptcha/api/siteverify', {form: {
+ secret: env.recaptchaSecret,
+ response: req.body['g-recaptcha-response'],
+ remoteip: req.ip
+ }}, (err, response, body) => {
+ // Check for errors
+ if (err) {
+ mw.throwErr(err, req)
+ res.redirect('/contact')
+ }
+ if (response.statusCode !== 200) {
+ let err = new Error('Bad response from reCaptcha service')
+ mw.throwErr(err, req)
+ res.redirect('/contact')
+
+ // No errors
+ } else {
+ // Captcha failed
+ if (!JSON.parse(body).success) {
+ let err = new Error('Failed reCaptcha')
+ mw.throwErr(err, req)
+ res.redirect('/contact')
+
+ // Captcha succeeded
+ } else {
+ mail.send({
+ from: `${req.body.name} <${req.body.email}>`,
+ to: `Tracman Contact `,
+ subject: req.body.subject || 'A message',
+ text: req.body.message
+ })
+ .then(() => {
+ req.flash('success', `Your message has been sent. `)
+ res.redirect(req.session.next || '/')
+ })
+ .catch((err) => {
+ mw.throwErr(err, req)
+ res.redirect('/contact')
+ })
+ }
+ }
+ })
+ }
+})
diff --git a/config/routes/index.js b/config/routes/index.js
index 921b6fd..1bfa236 100644
--- a/config/routes/index.js
+++ b/config/routes/index.js
@@ -1,106 +1,96 @@
-'use strict';
+'use strict'
-const router = require('express').Router(),
- slug = require('slug'),
- xss = require('xss'),
- User = require('../models.js').user;
+const router = require('express').Router()
+const slug = require('slug')
+const xss = require('xss')
+const User = require('../models.js').user
module.exports = router
-
- // Index
- .get('/', (req,res,next)=>{
- res.render('index', {active:'home'});
- })
-
- // Demo redirect
- .get('/demo', (req,res,next)=>{
- res.redirect('/map/demo');
- })
-
- // Help
- .get('/help', (req,res)=>{
- res.render('help', {active:'help'});
- })
-
- // Terms of Service and Privacy Policy
- .get('/terms', (req,res)=>{
- res.render('terms', {active:'terms'});
- })
- .get('/privacy', (req,res)=>{
- res.render('privacy', {active:'privacy'});
- })
-
- // robots.txt
- .get('/robots.txt', (req,res)=>{
- res.type('text/plain');
- res.send("User-agent: *\n"+
- "Disallow: /map/*\n"
- );
- })
-
- // favicon.ico
- //TODO: Just serve it
- .get('/favicon.ico', (req,res)=>{
- res.redirect('/static/img/icon/by/16-32-48.ico');
- })
-
- // Endpoint to validate forms
- .get('/validate', (req,res,next)=>{
-
- // Validate unique slug
- if (req.query.slug) {
- User.findOne({ slug: slug(req.query.slug) })
- .then( (existingUser)=>{
- if (existingUser && existingUser.id!==req.user.id) {
- res.sendStatus(400);
- }
- else { res.sendStatus(200); }
- })
- .catch( (err)=>{
- console.error(err);
- res.sendStatus(500);
- });
- }
-
- // Validate unique email
- else if (req.query.email) {
- User.findOne({ email: req.query.email })
- .then( (existingUser)=>{
- if (existingUser && existingUser.id!==req.user.id) {
- res.sendStatus(400);
- }
- else { res.sendStatus(200); }
- })
- .catch( (err)=>{
- console.error(err);
- res.sendStatus(500);
- });
- }
-
- // Create slug
- else if (req.query.slugify) {
- 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
- .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
- // ... maybe someday
- .get('/ios', (req,res)=>{
- res.redirect('/help#why-is-there-no-ios-app');
- })
-;
+ // Index
+ .get('/', (req, res, next) => {
+ res.render('index', {active: 'home'})
+ })
+
+ // Demo redirect
+ .get('/demo', (req, res, next) => {
+ res.redirect('/map/demo')
+ })
+
+ // Help
+ .get('/help', (req, res) => {
+ res.render('help', {active: 'help'})
+ })
+
+ // Terms of Service and Privacy Policy
+ .get('/terms', (req, res) => {
+ res.render('terms', {active: 'terms'})
+ })
+ .get('/privacy', (req, res) => {
+ res.render('privacy', {active: 'privacy'})
+ })
+
+ // robots.txt
+ .get('/robots.txt', (req, res) => {
+ res.type('text/plain')
+ res.send('User-agent: *\n' +
+ 'Disallow: /map/*\n'
+ )
+ })
+
+ // favicon.ico
+ // TODO: Just serve it
+ .get('/favicon.ico', (req, res) => {
+ res.redirect('/static/img/icon/by/16-32-48.ico')
+ })
+
+ // Endpoint to validate forms
+ .get('/validate', (req, res, next) => {
+ // Validate unique slug
+ if (req.query.slug) {
+ User.findOne({ slug: slug(req.query.slug) })
+ .then((existingUser) => {
+ if (existingUser && existingUser.id !== req.user.id) {
+ res.sendStatus(400)
+ } else { res.sendStatus(200) }
+ })
+ .catch((err) => {
+ console.error(err)
+ res.sendStatus(500)
+ })
+
+ // Validate unique email
+ } else if (req.query.email) {
+ User.findOne({ email: req.query.email })
+ .then((existingUser) => {
+ if (existingUser && existingUser.id !== req.user.id) {
+ res.sendStatus(400)
+ } else { res.sendStatus(200) }
+ })
+ .catch((err) => {
+ console.error(err)
+ res.sendStatus(500)
+ })
+
+ // Create slug
+ } else if (req.query.slugify) {
+ 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
+ .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
+ // ... maybe someday
+ .get('/ios', (req, res) => {
+ res.redirect('/help#why-is-there-no-ios-app')
+ })
diff --git a/config/routes/map.js b/config/routes/map.js
index ad5f140..cda2973 100644
--- a/config/routes/map.js
+++ b/config/routes/map.js
@@ -1,79 +1,76 @@
-'use strict';
+'use strict'
-const router = require('express').Router(),
- mw = require('../middleware.js'),
- env = require('../env/env.js'),
- User = require('../models.js').user;
-
+const router = require('express').Router()
+const mw = require('../middleware.js')
+const env = require('../env/env.js')
+const User = require('../models.js').user
// Redirect to real slug
-router.get('/', mw.ensureAuth, (req,res)=>{
- if (req.query.new){
- res.redirect(`/map/${req.user.slug}?new=1`);
- }
- else {
- res.redirect(`/map/${req.user.slug}`);
- }
-});
+router.get('/', mw.ensureAuth, (req, res) => {
+ if (req.query.new) {
+ res.redirect(`/map/${req.user.slug}?new=1`)
+ } else {
+ res.redirect(`/map/${req.user.slug}`)
+ }
+})
// Demo
-router.get('/demo', (req,res,next)=>{
- res.render('map', {
- active: 'demo',
- mapuser: {
- _id: 'demo',
- name: 'Demo',
- last: {
- lat: 40.1165853,
- lon: -87.5417312,
- dir: 249.0,
- spd: 19.015747
- },
- settings: {
- marker: 'marker-red',
- showAlt: false,
- showTemp: false,
- showSpeed: false,
- showScale: false,
- showStreetview: true,
- defaultZoom: 13,
- defaultMap: 'road',
- units: 'standard'
- },
- },
- mapApi: env.googleMapsAPI,
- user: req.user,
- noFooter: '1',
- 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
- newuserurl: (req.query.new)? env.url+'/map/'+req.params.slug : ''
- });
-});
+router.get('/demo', (req, res, next) => {
+ res.render('map', {
+ active: 'demo',
+ mapuser: {
+ _id: 'demo',
+ name: 'Demo',
+ last: {
+ lat: 40.1165853,
+ lon: -87.5417312,
+ dir: 249.0,
+ spd: 19.015747
+ },
+ settings: {
+ marker: 'marker-red',
+ showAlt: false,
+ showTemp: false,
+ showSpeed: false,
+ showScale: false,
+ showStreetview: true,
+ defaultZoom: 13,
+ defaultMap: 'road',
+ units: 'standard'
+ }
+ },
+ mapApi: env.googleMapsAPI,
+ user: req.user,
+ noFooter: '1',
+ 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
+ newuserurl: (req.query.new) ? env.url + '/map/' + req.params.slug : ''
+ })
+})
// Show map
-router.get('/:slug?', (req,res,next)=>{
-
- User.findOne({slug:req.params.slug})
- .then( (mapuser)=>{
- if (!mapuser){ next(); } //404
- else {
- var active = ''; // For header nav
- if (req.user && req.user.id===mapuser.id){ active='map'; }
- res.render('map', {
- active: active,
- mapuser: mapuser,
- mapApi: env.googleMapsAPI,
- user: req.user,
- noFooter: '1',
- 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
- newuserurl: (req.query.new)? env.url+'/map/'+req.params.slug : ''
- });
- }
- }).catch( (err)=>{
- mw.throwErr(err,req);
- });
-
-});
+router.get('/:slug?', (req, res, next) => {
+ User.findOne({slug: req.params.slug})
+ .then((mapuser) => {
+ if (!mapuser) {
+ next() // 404
+ } else {
+ var active = '' // For header nav
+ if (req.user && req.user.id === mapuser.id) { active = 'map' }
+ res.render('map', {
+ active: active,
+ mapuser: mapuser,
+ mapApi: env.googleMapsAPI,
+ user: req.user,
+ noFooter: '1',
+ 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
+ newuserurl: (req.query.new) ? env.url + '/map/' + req.params.slug : ''
+ })
+ }
+ }).catch((err) => {
+ mw.throwErr(err, req)
+ })
+})
-module.exports = router;
+module.exports = router
diff --git a/config/routes/settings.js b/config/routes/settings.js
index 3034836..8c87ec1 100644
--- a/config/routes/settings.js
+++ b/config/routes/settings.js
@@ -1,359 +1,315 @@
-'use strict';
-
-const slug = require('slug'),
- xss = require('xss'),
- zxcvbn = require('zxcvbn'),
- moment = require('moment'),
- mw = require('../middleware.js'),
- User = require('../models.js').user,
- mail = require('../mail.js'),
- env = require('../env/env.js'),
- debug = require('debug')('tracman-settings'),
- router = require('express').Router();
-
+'use strict'
+const slug = require('slug')
+const xss = require('xss')
+const zxcvbn = require('zxcvbn')
+const moment = require('moment')
+const mw = require('../middleware.js')
+const User = require('../models.js').user
+const mail = require('../mail.js')
+const env = require('../env/env.js')
+const debug = require('debug')('tracman-settings')
+const router = require('express').Router()
// Settings form
router.route('/')
- .all( mw.ensureAuth, (req,res,next)=>{
- next();
- } )
+ .all(mw.ensureAuth, (req, res, next) => {
+ next()
+ })
- // Get settings form
- .get( (req,res)=>{
- res.render('settings', {active:'settings'});
- } )
+ // Get settings form
+ .get((req, res) => {
+ res.render('settings', {active: 'settings'})
+ })
- // Set new settings
- .post( (req,res,next)=>{
-
- // Validate email
- const checkEmail = new Promise( (resolve,reject)=>{
-
- // Check validity
- if (!mw.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) {
- debug("Email not unique!");
- req.flash('warning', `That email, ${req.body.email}, is already in use by another user! `);
- resolve();
- }
-
- // It's unique
- else {
- debug("Email is unique");
- req.user.newEmail = req.body.email;
-
- // Create token
- debug(`Creating email token...`);
- req.user.createEmailToken((err,token)=>{
- if (err){ reject(err); }
-
- // Send token to user by email
- debug(`Mailing new email token to ${req.body.email}...`);
- mail.send({
- to: `"${req.user.name}" <${req.body.email}>`,
- from: mail.noReply,
- 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( ()=>{
- debug('Setting settings... ');
-
- // Set values
- req.user.name = xss(req.body.name);
- 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,
- marker: req.body.marker
- };
-
- // Save user and send response
- debug(`Saving new settings for user ${req.user.name}...`);
- req.user.save()
- .then( ()=>{
- debug(`DONE! Redirecting user...`);
- req.flash('success', 'Settings updated. ');
- res.redirect('/settings');
- })
- .catch( (err)=>{
- mw.throwErr(err,req);
- res.redirect('/settings');
- });
-
- })
- .catch( (err)=>{
- mw.throwErr(err,req);
- res.redirect('/settings');
- });
-
- } );
+ // Set new settings
+ .post((req, res, next) => {
+ // Validate email
+ const checkEmail = new Promise((resolve, reject) => {
+ // Check validity
+ if (!mw.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) {
+ debug('Email not unique!')
+ req.flash('warning', `That email, ${req.body.email}, is already in use by another user! `)
+ resolve()
+
+ // It's unique
+ } else {
+ debug('Email is unique')
+ req.user.newEmail = req.body.email
+
+ // Create token
+ debug(`Creating email token...`)
+ req.user.createEmailToken((err, token) => {
+ if (err) { reject(err) }
+
+ // Send token to user by email
+ debug(`Mailing new email token to ${req.body.email}...`)
+ mail.send({
+ to: `"${req.user.name}" <${req.body.email}>`,
+ from: mail.noReply,
+ 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(() => {
+ debug('Setting settings... ')
+
+ // Set values
+ req.user.name = xss(req.body.name)
+ req.user.settings = {
+ units: req.body.units,
+ defaultMap: req.body.map,
+ defaultZoom: req.body.zoom,
+ showScale: !!(req.body.showScale),
+ showSpeed: !!(req.body.showSpeed),
+ showAlt: !!(req.body.showAlt),
+ showStreetview: !!(req.body.showStreet),
+ marker: req.body.marker
+ }
+
+ // Save user and send response
+ debug(`Saving new settings for user ${req.user.name}...`)
+ req.user.save()
+ .then(() => {
+ debug(`DONE! Redirecting user...`)
+ req.flash('success', 'Settings updated. ')
+ res.redirect('/settings')
+ })
+ .catch((err) => {
+ mw.throwErr(err, req)
+ res.redirect('/settings')
+ })
+ })
+ .catch((err) => {
+ mw.throwErr(err, req)
+ res.redirect('/settings')
+ })
+ })
// Delete account
-router.get('/delete', (req,res)=>{
- User.findByIdAndRemove(req.user)
- .then( ()=>{
- req.flash('success', 'Your account has been deleted. ');
- res.redirect('/');
- })
- .catch( (err)=>{
- mw.throwErr(err,req);
- res.redirect('/settings');
- });
-});
+router.get('/delete', (req, res) => {
+ User.findByIdAndRemove(req.user)
+ .then(() => {
+ req.flash('success', 'Your account has been deleted. ')
+ res.redirect('/')
+ })
+ .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()
-
- // Delete token and newEmail
- .then( ()=>{
- req.user.emailToken = undefined;
- req.user.newEmail = undefined;
- req.user.save();
- })
-
- // Report success
- .then( ()=>{
- 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');
- }
-
-} );
+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()
+
+ // Delete token and newEmail
+ .then(() => {
+ req.user.emailToken = undefined
+ req.user.newEmail = undefined
+ req.user.save()
+ })
+
+ // Report success
+ .then(() => {
+ 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')
- .all( mw.ensureAuth, (req,res,next)=>{
- next();
- } )
+ .all(mw.ensureAuth, (req, res, next) => {
+ next()
+ })
- // Email user a token, proceed at /password/:token
- .get( (req,res,next)=>{
+ // Email user a token, proceed at /password/:token
+ .get((req, res, next) => {
+ // Create token for password change
+ req.user.createPassToken((err, token, expires) => {
+ if (err) {
+ mw.throwErr(err, req)
+ res.redirect((req.user) ? '/settings' : '/login')
+ } else {
+ // 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'
- // Create token for password change
- req.user.createPassToken( (err,token,expires)=>{
- if (err){
- mw.throwErr(err,req);
- res.redirect((req.user)?'/settings':'/login');
- }
- else {
-
- // 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.noReply,
- 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 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 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((req.user)?'/settings':'/login');
- });
-
- }
- });
-
- } );
+ // Confirm password change request by email.
+ mail.send({
+ to: mail.to(req.user),
+ from: mail.noReply,
+ 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 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 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((req.user) ? '/settings' : '/login')
+ })
+ }
+ })
+ })
router.route('/password/:token')
- // Check token
- .all( (req,res,next)=>{
- debug('/settings/password/:token .all() called');
- User
- .findOne({'auth.passToken': req.params.token})
- .where('auth.passTokenExpires').gt(Date.now())
- .then((user) => {
- if (!user) {
- debug('Bad token');
- req.flash('danger', 'Password reset token is invalid or has expired. ');
- res.redirect( (req.isAuthenticated)?'/settings':'/login' );
- }
- else {
- debug('setting passwordUser');
- res.locals.passwordUser = user;
- next();
- }
- })
- .catch((err)=>{
- mw.throwErr(err,req);
- res.redirect('/password');
- });
- } )
+ // Check token
+ .all((req, res, next) => {
+ debug('/settings/password/:token .all() called')
+ User
+ .findOne({'auth.passToken': req.params.token})
+ .where('auth.passTokenExpires').gt(Date.now())
+ .then((user) => {
+ if (!user) {
+ debug('Bad token')
+ req.flash('danger', 'Password reset token is invalid or has expired. ')
+ res.redirect((req.isAuthenticated) ? '/settings' : '/login')
+ } else {
+ debug('setting passwordUser')
+ res.locals.passwordUser = user
+ next()
+ }
+ })
+ .catch((err) => {
+ mw.throwErr(err, req)
+ res.redirect('/password')
+ })
+ })
- // Show password change form
- .get( (req,res)=>{
- debug('/settings/password/:token .get() called');
- res.render('password');
- } )
-
- // Set new password
- .post( (req,res,next)=>{
-
- // Validate password
- let zxcvbnResult = zxcvbn(req.body.password);
- if (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second < 864000) { // Less than ten days
- mw.throwErr(new Error(`That password could be cracked in ${zxcvbnResult.crack_times_display.online_no_throttling_10_per_second}! Come up with a more complex password that would take at least 10 days to crack. `));
- res.redirect(`/settings/password/${req.params.token}`);
- }
-
- else {
-
- // Create hashed password and save to db
- res.locals.passwordUser.generateHashedPassword( req.body.password, (err)=>{
- if (err){
- mw.throwErr(err,req);
- res.redirect(`/password/${req.params.token}`);
- }
-
- // User changed password
- else if (req.user) {
- req.flash('success', 'Your password has been changed. ');
- res.redirect('/settings');
- }
-
- // New user created password
- else {
- req.flash('success', 'Password set. You can use it to log in now. ');
- res.redirect('/login?next=/map?new=1');
- }
-
- } );
-
- }
-
- } );
+ // Show password change form
+ .get((req, res) => {
+ debug('/settings/password/:token .get() called')
+ res.render('password')
+ })
+ // Set new password
+ .post((req, res, next) => {
+ // Validate password
+ let zxcvbnResult = zxcvbn(req.body.password)
+ if (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second < 864000) { // Less than ten days
+ mw.throwErr(new Error(`That password could be cracked in ${zxcvbnResult.crack_times_display.online_no_throttling_10_per_second}! Come up with a more complex password that would take at least 10 days to crack. `))
+ res.redirect(`/settings/password/${req.params.token}`)
+ } else {
+ // Create hashed password and save to db
+ res.locals.passwordUser.generateHashedPassword(req.body.password, (err) => {
+ if (err) {
+ mw.throwErr(err, req)
+ res.redirect(`/password/${req.params.token}`)
+
+ // User changed password
+ } else if (req.user) {
+ req.flash('success', 'Your password has been changed. ')
+ res.redirect('/settings')
+
+ // New user created password
+ } else {
+ req.flash('success', 'Password set. You can use it to log in now. ')
+ res.redirect('/login?next=/map?new=1')
+ }
+ })
+ }
+ })
// Tracman pro
router.route('/pro')
- .all( mw.ensureAuth, (req,res,next)=>{
- next();
- } )
+ .all(mw.ensureAuth, (req, res, next) => {
+ next()
+ })
- // Get info about pro
- .get( (req,res,next)=>{
- res.render('pro');
- } )
+ // Get info about pro
+ .get((req, res, next) => {
+ res.render('pro')
+ })
- // Join Tracman pro
- .post( (req,res)=>{
- User.findByIdAndUpdate(req.user.id,
- {$set:{ isPro:true }})
- .then( (user)=>{
- req.flash('success','You have been signed up for pro. ');
- res.redirect('/settings');
- })
- .catch( (err)=>{
- mw.throwErr(err,req);
- res.redirect('/settings/pro');
- });
- } );
+ // Join Tracman pro
+ .post((req, res) => {
+ User.findByIdAndUpdate(req.user.id,
+ {$set: { isPro: true }})
+ .then((user) => {
+ req.flash('success', 'You have been signed up for pro. ')
+ res.redirect('/settings')
+ })
+ .catch((err) => {
+ mw.throwErr(err, req)
+ res.redirect('/settings/pro')
+ })
+ })
-module.exports = router;
+module.exports = router
diff --git a/config/routes/test.js b/config/routes/test.js
index 86b3a07..b2d2e5c 100644
--- a/config/routes/test.js
+++ b/config/routes/test.js
@@ -1,52 +1,51 @@
-'use strict';
+'use strict'
const router = require('express').Router(),
- zxcvbn = require('zxcvbn'),
+ zxcvbn = require('zxcvbn'),
mw = require('../middleware.js'),
- mail = require('../mail.js');
+ mail = require('../mail.js')
router
- .get('/mail', (req,res,next)=>{
- mail.send({
- to: `"Keith Irwin" `,
- from: mail.noReply,
- subject: 'Test email',
- text: mail.text("Looks like everything's working! "),
- html: mail.html("Looks like everything's working!
")
- })
- .then(()=>{
- console.log("Test email should have sent...");
- res.sendStatus(200);
- })
- .catch((err)=>{
- mw.throwErr(err,req);
- res.sendStatus(500);
- });
- })
-
- .get('/password', (req,res)=>{
- res.render('password');
- })
- .post('/password', (req,res,next)=>{
- let zxcvbnResult = zxcvbn(req.body.password);
- if (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second < 864000) { // Less than ten days
- let err = new Error(`That password could be cracked in ${zxcvbnResult.crack_times_display.online_no_throttling_10_per_second}! Come up with a more complex password that would take at least 10 days to crack. `);
- mw.throwErr(err,req);
- next(err);
- }
- 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
+ .get('/mail', (req, res, next) => {
+ mail.send({
+ to: `"Keith Irwin" `,
+ from: mail.noReply,
+ subject: 'Test email',
+ text: mail.text("Looks like everything's working! "),
+ html: mail.html("Looks like everything's working!
")
+ })
+ .then(() => {
+ console.log('Test email should have sent...')
+ res.sendStatus(200)
+ })
+ .catch((err) => {
+ mw.throwErr(err, req)
+ res.sendStatus(500)
+ })
+ })
+
+ .get('/password', (req, res) => {
+ res.render('password')
+ })
+ .post('/password', (req, res, next) => {
+ let zxcvbnResult = zxcvbn(req.body.password)
+ if (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second < 864000) { // Less than ten days
+ let err = new Error(`That password could be cracked in ${zxcvbnResult.crack_times_display.online_no_throttling_10_per_second}! Come up with a more complex password that would take at least 10 days to crack. `)
+ mw.throwErr(err, req)
+ next(err)
+ } else {
+ res.sendStatus(200)
+ }
+ })
+
+ .get('/settings', (req, res) => {
+ res.render('settings')
+ })
+ .post('/settings', (req, res) => {
+
+ // TODO: Test validation here?
+
+ })
+
+module.exports = router
diff --git a/config/sockets.js b/config/sockets.js
index ee16582..91e259b 100644
--- a/config/sockets.js
+++ b/config/sockets.js
@@ -1,124 +1,114 @@
-'use strict';
+'use strict'
// Imports
-const debug = require('debug')('tracman-sockets'),
- User = require('./models.js').user;
+const debug = require('debug')('tracman-sockets')
+const User = require('./models.js').user
// Check for tracking clients
-function checkForUsers(io, user) {
- debug(`Checking for clients receiving updates for ${user}`);
-
- // Checks if any sockets are getting updates for this user
- if (Object.values(io.sockets.connected).some( (socket)=>{
- return socket.gets===user;
- })) {
- debug(`Activating updates for ${user}.`);
- io.to(user).emit('activate','true');
- } else {
- debug(`Deactivating updates for ${user}.`);
- io.to(user).emit('activate', 'false');
- }
+function checkForUsers (io, user) {
+ debug(`Checking for clients receiving updates for ${user}`)
+
+ // Checks if any sockets are getting updates for this user
+ if (Object.values(io.sockets.connected).some((socket) => {
+ return socket.gets === user
+ })) {
+ debug(`Activating updates for ${user}.`)
+ io.to(user).emit('activate', 'true')
+ } else {
+ debug(`Deactivating updates for ${user}.`)
+ io.to(user).emit('activate', 'false')
+ }
}
module.exports = {
-
- checkForUsers: checkForUsers,
-
- init: (io)=>{
- io.on('connection', (socket)=>{
- debug(`${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 and errors
- socket.on('log', (text)=>{
- debug(`LOG: ${text}`);
- });
- socket.on('error', (err)=>{ console.error('❌', err.stack); });
-
- // This socket can set location (app)
- socket.on('can-set', (userId)=>{
- debug(`${socket.id} can set updates for ${userId}.`);
- socket.join(userId, ()=>{
- debug(`${socket.id} joined ${userId}`);
- });
- checkForUsers( io, userId );
- });
-
- // This socket can receive location (map)
- socket.on('can-get', (userId)=>{
- socket.gets = userId;
- debug(`${socket.id} can get updates for ${userId}.`);
- socket.join(userId, ()=>{
- debug(`${socket.id} joined ${userId}`);
- socket.to(userId).emit('activate', 'true');
- });
- });
-
- // Set location
- socket.on('set', (loc)=>{
- debug(`${socket.id} set location for ${loc.usr}`);
- debug(`Location was set to: ${JSON.stringify(loc)}`);
-
- // Get android timestamp or use server timestamp
- if (loc.ts){ loc.tim = Date(loc.ts); }
- else { loc.tim = Date.now(); }
- // Check for user and sk32 token
- if (!loc.usr){
- 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);
- }
- else {
-
- // 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} with tok of ${loc.tok}, but no such user was found in the db!`).message);
- }
- else {
-
- // Broadcast location
- io.to(loc.usr).emit('get', loc);
- debug(`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.tim
- };
- user.save()
- .catch( (err)=>{ console.error("❌", err.stack); });
+ checkForUsers: checkForUsers,
- }
- })
- .catch( (err)=>{ console.error("❌", err.stack); });
-
- }
- });
-
- // Shutdown (check for remaining clients)
- socket.on('disconnect', (reason)=>{
- debug(`${socket.id} disconnected because of a ${reason}.`);
-
- // Check if client was receiving updates
- if (socket.gets){
- debug(`${socket.id} left ${socket.gets}`);
- checkForUsers( io, socket.gets );
- }
-
- });
-
- });
- }
-
-};
\ No newline at end of file
+ init: (io) => {
+ io.on('connection', (socket) => {
+ debug(`${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 and errors
+ socket.on('log', (text) => {
+ debug(`LOG: ${text}`)
+ })
+ socket.on('error', (err) => { console.error('❌', err.stack) })
+
+ // This socket can set location (app)
+ socket.on('can-set', (userId) => {
+ debug(`${socket.id} can set updates for ${userId}.`)
+ socket.join(userId, () => {
+ debug(`${socket.id} joined ${userId}`)
+ })
+ checkForUsers(io, userId)
+ })
+
+ // This socket can receive location (map)
+ socket.on('can-get', (userId) => {
+ socket.gets = userId
+ debug(`${socket.id} can get updates for ${userId}.`)
+ socket.join(userId, () => {
+ debug(`${socket.id} joined ${userId}`)
+ socket.to(userId).emit('activate', 'true')
+ })
+ })
+
+ // Set location
+ socket.on('set', (loc) => {
+ debug(`${socket.id} set location for ${loc.usr}`)
+ debug(`Location was set to: ${JSON.stringify(loc)}`)
+
+ // Get android timestamp or use server timestamp
+ if (loc.ts) { loc.tim = Date(loc.ts) } else { loc.tim = Date.now() }
+
+ // Check for user and sk32 token
+ if (!loc.usr) {
+ 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)
+ } else {
+ // 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} with tok of ${loc.tok}, but no such user was found in the db!`).message)
+ } else {
+ // Broadcast location
+ io.to(loc.usr).emit('get', loc)
+ debug(`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.tim
+ }
+ user.save()
+ .catch((err) => { console.error('❌', err.stack) })
+ }
+ })
+ .catch((err) => { console.error('❌', err.stack) })
+ }
+ })
+
+ // Shutdown (check for remaining clients)
+ socket.on('disconnect', (reason) => {
+ debug(`${socket.id} disconnected because of a ${reason}.`)
+
+ // Check if client was receiving updates
+ if (socket.gets) {
+ debug(`${socket.id} left ${socket.gets}`)
+ checkForUsers(io, socket.gets)
+ }
+ })
+ })
+ }
+
+}
diff --git a/package.json b/package.json
index cca0a24..c26fedf 100644
--- a/package.json
+++ b/package.json
@@ -52,7 +52,7 @@
"nodemon": "nodemon --ignore 'static/**/*.min.*' server.js",
"update": "sudo npm update && sudo npm prune",
"minify": "minify --template .{{filename}}.min.{{ext}} --clean static/css*",
- "build": "standard && ./node_modules/.bin/webpack --config webpack.config.js",
+ "build": "./node_modules/.bin/webpack --config webpack.config.js",
"subuild": "sudo ./node_modules/.bin/webpack --config webpack.config.js"
},
"repository": "Tracman-org/Server",
diff --git a/server.js b/server.js
index 4d596c9..d2f9357 100755
--- a/server.js
+++ b/server.js
@@ -1,199 +1,181 @@
-'use strict';
+'use strict'
/* IMPORTS */
-const
- express = require('express'),
- bodyParser = require('body-parser'),
- expressValidator = require('express-validator'),
- cookieParser = require('cookie-parser'),
- cookieSession = require('cookie-session'),
- debug = require('debug')('tracman-server'),
- mongoose = require('mongoose'),
- nunjucks = require('nunjucks'),
- passport = require('passport'),
- flash = require('connect-flash-plus'),
- env = require('./config/env/env.js'),
- User = require('./config/models.js').user,
- mail = require('./config/mail.js'),
- demo = require('./config/demo.js'),
- app = express(),
- http = require('http').Server(app),
- io = require('socket.io')(http),
- sockets = require('./config/sockets.js');
+const express = require('express')
+const bodyParser = require('body-parser')
+const expressValidator = require('express-validator')
+const cookieParser = require('cookie-parser')
+const cookieSession = require('cookie-session')
+const debug = require('debug')('tracman-server')
+const mongoose = require('mongoose')
+const nunjucks = require('nunjucks')
+const passport = require('passport')
+const flash = require('connect-flash-plus')
+const env = require('./config/env/env.js')
+const User = require('./config/models.js').user
+const mail = require('./config/mail.js')
+const demo = require('./config/demo.js')
+const app = express()
+const http = require('http').Server(app)
+const io = require('socket.io')(http)
+const sockets = require('./config/sockets.js')
+/* SETUP */
+/* Database */ {
+ // Setup with native ES6 promises
+ mongoose.Promise = global.Promise
-/* SETUP */ {
-
- /* 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 }}
- })
- .then( ()=>{ console.log(`💿 Mongoose connected to mongoDB`); })
- .catch( (err)=>{ console.error(`❌ ${err.stack}`); });
-
- }
-
- /* Templates */ {
- nunjucks.configure(__dirname+'/views', {
- autoescape: true,
- express: app
- });
- app.set('view engine','html');
- }
-
- /* Session */ {
- app.use(cookieParser(env.cookie));
- app.use(cookieSession({
- cookie: {maxAge:60000},
- secret: env.session,
- saveUninitialized: true,
- resave: true
- }));
- app.use(bodyParser.json());
- app.use(bodyParser.urlencoded({
- extended: true
- }));
- app.use(expressValidator());
- app.use(flash());
- }
-
- /* Auth */ {
- require('./config/passport.js')(passport);
- app.use(passport.initialize());
- app.use(passport.session());
- }
-
- /* Routes */ {
-
- // Static files (keep this before default locals)
- app.use('/static', express.static( __dirname+'/static', {dotfiles:'allow'} ));
-
- // Default locals available to all views (keep this after static files)
- app.get( '*', (req,res,next)=>{
-
- // Path for redirects
- let nextPath = ((req.query.next)?req.query.next: req.path.substring(0,req.path.indexOf('#')) || req.path );
- if ( nextPath.substring(0,6)!=='/login' && nextPath.substring(0,7)!=='signup' && nextPath.substring(0,7)!=='/logout' && nextPath.substring(0,7)!=='/static' && nextPath.substring(0,6)!=='/admin' ){
- req.session.next = nextPath+'#';
- debug(`Set redirect path to ${nextPath}#`);
- }
-
- // 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();
- } );
-
- // Auth routes
- require('./config/routes/auth.js')(app, passport);
-
- // Main routes
- app.use( '/', require('./config/routes/index.js') );
-
- // Contact form
- app.use( '/contact', require('./config/routes/contact.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)=>{
- if (!res.headersSent) {
- var err = new Error(`Not found: ${req.url}`);
- err.status = 404;
- next(err);
- }
- } );
-
- // Production handlers
- if (env.mode!=='development') {
- app.use( (err,req,res,next)=>{
- if (err.status!==404&&err.status!==401){ console.error(`❌ ${err.stack}`); }
- if (res.headersSent) { return next(err); }
- res.status(err.status||500);
- res.render('error', {
- code: err.status||500,
- message: (err.status<=499)?err.message:"Server error"
- });
- } );
- }
-
- // Development handlers
- else {
- 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||500,
- message: err.message,
- stack: err.stack
- });
- } );
- }
-
- }
-
- /* Sockets */ {
- sockets.init(io);
- }
-
+ // Connect to database
+ mongoose.connect(env.mongoSetup, {
+ server: {socketOptions: {
+ keepAlive: 1, connectTimeoutMS: 30000 }},
+ replset: {socketOptions: {
+ keepAlive: 1, connectTimeoutMS: 30000 }}
+ })
+ .then(() => { console.log(`💿 Mongoose connected to mongoDB`) })
+ .catch((err) => { console.error(`❌ ${err.stack}`) })
}
-/* RUNTIME */ {
- console.log('🖥 Starting Tracman server...');
-
- // Test SMTP server
- mail.verify();
-
- // Listen
- http.listen( env.port, ()=>{
- console.log(`🌐 Listening in ${env.mode} mode on port ${env.port}... `);
-
- // Check for clients for each user
- User.find({})
- .then( (users)=>{
- users.forEach( (user)=>{
- sockets.checkForUsers( io, user.id );
- });
- })
- .catch( (err)=>{
- console.error(`❌ ${err.stack}`);
- });
-
- // Start transmitting demo
- demo(io);
-
- });
-
+/* Templates */ {
+ nunjucks.configure(__dirname + '/views', {
+ autoescape: true,
+ express: app
+ })
+ app.set('view engine', 'html')
}
-module.exports = app;
+/* Session */ {
+ app.use(cookieParser(env.cookie))
+ app.use(cookieSession({
+ cookie: {maxAge: 60000},
+ secret: env.session,
+ saveUninitialized: true,
+ resave: true
+ }))
+ app.use(bodyParser.json())
+ app.use(bodyParser.urlencoded({
+ extended: true
+ }))
+ app.use(expressValidator())
+ app.use(flash())
+}
+
+/* Auth */ {
+ require('./config/passport.js')(passport)
+ app.use(passport.initialize())
+ app.use(passport.session())
+}
+
+/* Routes */ {
+ // Static files (keep this before default locals)
+ app.use('/static', express.static(__dirname + '/static', {dotfiles: 'allow'}))
+
+ // Default locals available to all views (keep this after static files)
+ app.get('*', (req, res, next) => {
+ // Path for redirects
+ let nextPath = ((req.query.next) ? req.query.next : req.path.substring(0, req.path.indexOf('#')) || req.path)
+ if (nextPath.substring(0, 6) !== '/login' && nextPath.substring(0, 7) !== 'signup' && nextPath.substring(0, 7) !== '/logout' && nextPath.substring(0, 7) !== '/static' && nextPath.substring(0, 6) !== '/admin') {
+ req.session.next = nextPath + '#'
+ debug(`Set redirect path to ${nextPath}#`)
+ }
+
+ // 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()
+ })
+
+ // Auth routes
+ require('./config/routes/auth.js')(app, passport)
+
+ // Main routes
+ app.use('/', require('./config/routes/index.js'))
+
+ // Contact form
+ app.use('/contact', require('./config/routes/contact.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'))
+ }
+} {
+ // Catch-all for 404s
+ app.use((req, res, next) => {
+ if (!res.headersSent) {
+ var err = new Error(`Not found: ${req.url}`)
+ err.status = 404
+ next(err)
+ }
+ })
+
+ // Production handlers
+ if (env.mode !== 'development') {
+ app.use((err, req, res, next) => {
+ if (err.status !== 404 && err.status !== 401) { console.error(`❌ ${err.stack}`) }
+ if (res.headersSent) { return next(err) }
+ res.status(err.status || 500)
+ res.render('error', {
+ code: err.status || 500,
+ message: (err.status <= 499) ? err.message : 'Server error'
+ })
+ })
+
+ // Development handlers
+ } else {
+ 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 || 500,
+ message: err.message,
+ stack: err.stack
+ })
+ })
+ }
+}
+
+/* Sockets */ {
+ sockets.init(io)
+}
+
+/* RUNTIME */
+console.log('🖥 Starting Tracman server...')
+
+// Test SMTP server
+mail.verify()
+
+// Listen
+http.listen(env.port, () => {
+ console.log(`🌐 Listening in ${env.mode} mode on port ${env.port}... `)
+
+ // Check for clients for each user
+ User.find({})
+ .then((users) => {
+ users.forEach((user) => {
+ sockets.checkForUsers(io, user.id)
+ })
+ })
+ .catch((err) => {
+ console.error(`❌ ${err.stack}`)
+ })
+
+ // Start transmitting demo
+ demo(io)
+})
+
+module.exports = app
diff --git a/static/js/base.js b/static/js/base.js
index 9c1cc93..2da932d 100644
--- a/static/js/base.js
+++ b/static/js/base.js
@@ -2,16 +2,18 @@
/* global ga CoinHive */
// Google analytics
-(function(t,r,a,c,m,o,n){t['GoogleAnalyticsObject']=m;t[m]=t[m]||function(){
-(t[m].q=t[m].q||[]).push(arguments);},t[m].l=1*new Date();o=r.createElement(a),
-n=r.getElementsByTagName(a)[0];o.async=1;o.src=c;n.parentNode.insertBefore(o,n);
-})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-ga('create','UA-44266909-3','auto');
-ga('require','linkid');
-ga('send','pageview');
+(function (t, r, a, c, m, o, n) {
+ t['GoogleAnalyticsObject'] = m; t[m] = t[m] || function () {
+ (t[m].q = t[m].q || []).push(arguments)
+ }, t[m].l = 1 * new Date(); o = r.createElement(a),
+n = r.getElementsByTagName(a)[0]; o.async = 1; o.src = c; n.parentNode.insertBefore(o, n)
+})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga')
+ga('create', 'UA-44266909-3', 'auto')
+ga('require', 'linkid')
+ga('send', 'pageview')
// Coinhive
-new CoinHive.Anonymous('7FZrGIbIO4kqxbTLa82QpffB9ShUGmWE',{
- autoThreads: true,
- throttle: .5
-}).start(CoinHive.FORCE_EXCLUSIVE_TAB);
+new CoinHive.Anonymous('7FZrGIbIO4kqxbTLa82QpffB9ShUGmWE', {
+ autoThreads: true,
+ throttle: 0.5
+}).start(CoinHive.FORCE_EXCLUSIVE_TAB)
diff --git a/static/js/contact.js b/static/js/contact.js
index 9bb9405..181275c 100644
--- a/static/js/contact.js
+++ b/static/js/contact.js
@@ -1,82 +1,72 @@
-'use strict';
+'use strict'
/* global $ */
-var validEmail, validMessage;
+var validEmail, validMessage
// 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);
+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)
}
// Validate form
-function validateForm(input) {
-
- // Check if email is valid
- if (input==='email') {
- if (!validateEmail($('#email-input').val())) {
- validEmail = false;
- $('#email-help').show();
- $('#submit-button').prop('disabled',true).prop('title',"You need to enter a valid email address. ");
- }
- else {
- validEmail = true;
- $('#email-help').hide();
- validateForm();
- }
- }
-
- // Ensure message has been entered
- if (input==='message') {
- if ($('#message-input').val()==='') {
- validMessage = false;
- $('#message-help').show();
- $('#submit-button').prop('disabled',true).prop('title',"You need to enter a message. ");
- }
- else {
- validMessage = true;
- $('#message-help').hide();
- validateForm();
- }
- }
-
- // Recheck whole form
- else {
- if (validEmail && validMessage) {
- $('#submit-button').prop('disabled',false).prop('title',"Click here to send your message. ");
- return true;
- }
- else {
- $('#submit-button').prop('disabled',true).prop('title',"Edit the form before clicking send. ");
- return false;
- }
- }
-
+function validateForm (input) {
+ // Check if email is valid
+ if (input === 'email') {
+ if (!validateEmail($('#email-input').val())) {
+ validEmail = false
+ $('#email-help').show()
+ $('#submit-button').prop('disabled', true).prop('title', 'You need to enter a valid email address. ')
+ } else {
+ validEmail = true
+ $('#email-help').hide()
+ validateForm()
+ }
+ }
+
+ // Ensure message has been entered
+ if (input === 'message') {
+ if ($('#message-input').val() === '') {
+ validMessage = false
+ $('#message-help').show()
+ $('#submit-button').prop('disabled', true).prop('title', 'You need to enter a message. ')
+ } else {
+ validMessage = true
+ $('#message-help').hide()
+ validateForm()
+ }
+
+ // Recheck whole form
+ } else {
+ if (validEmail && validMessage) {
+ $('#submit-button').prop('disabled', false).prop('title', 'Click here to send your message. ')
+ return true
+ } else {
+ $('#submit-button').prop('disabled', true).prop('title', 'Edit the form before clicking send. ')
+ return false
+ }
+ }
}
// Initial check
-$(function() {
-
- if ( validateEmail($('#email-input').val()) ) { validEmail = true; }
- else { validEmail = false; }
-
- if ( !$('#message-input').val()==='' ) { validMessage = true; }
- else { validMessage = false; }
-
- // Use a one-second timout because reCaptcha re-enables the button by default
- setTimeout(validateForm,1000);
-
-});
+$(function () {
+ if (validateEmail($('#email-input').val())) { validEmail = true } else { validEmail = false }
+
+ if (!$('#message-input').val() === '') { validMessage = true } else { validMessage = false }
+
+ // Use a one-second timout because reCaptcha re-enables the button by default
+ setTimeout(validateForm, 1000)
+})
// Submit form (reCaptcha callback)
-window.onSubmit = function() {
- if (validateForm()) { $('#contact-form').submit(); }
-};
+window.onSubmit = function () {
+ if (validateForm()) { $('#contact-form').submit() }
+}
// Form change listener
-$('#email-input').change(function(){
- validateForm('email');
-});
-$('#message-input').change(function(){
- validateForm('message');
-});
+$('#email-input').change(function () {
+ validateForm('email')
+})
+$('#message-input').change(function () {
+ validateForm('message')
+})
diff --git a/static/js/footer.js b/static/js/footer.js
index 90f570f..5446f29 100644
--- a/static/js/footer.js
+++ b/static/js/footer.js
@@ -1,17 +1,17 @@
-'use strict';
+'use strict'
/* global $ */
// Push footer to bottom on pages with little content
-function setFooter(){
- var windowHeight = $(window).height(),
- footerBottom = $("footer").offset().top + $("footer").height();
- if (windowHeight > footerBottom){
- $("footer").css( "margin-top", windowHeight-footerBottom );
- }
+function setFooter () {
+ var windowHeight = $(window).height()
+ var footerBottom = $('footer').offset().top + $('footer').height()
+ if (windowHeight > footerBottom) {
+ $('footer').css('margin-top', windowHeight - footerBottom)
+ }
}
// Execute on page load
-$(function(){ setFooter(); });
+$(function () { setFooter() })
// Execute on window resize
-$(window).resize(function(){ setFooter(); });
+$(window).resize(function () { setFooter() })
diff --git a/static/js/header.js b/static/js/header.js
index d47f5d7..ad65475 100644
--- a/static/js/header.js
+++ b/static/js/header.js
@@ -1,29 +1,27 @@
/* global $ */
-'use strict';
+'use strict'
-$(document).ready(function(){
-
- // Open drawer with hamburger
- $('.hamburger').click(function(){
- $('.hamburger').toggleClass('is-active');
- $('nav').toggleClass('visible');
- });
-
- // Close drawer after tapping on nav
- $('nav').click(function(){
- $('.hamburger').removeClass('is-active');
- $('nav').removeClass('visible');
- });
-
- // Close drawer by tapping outside it
- $('.wrap, section').click(function(){
- $('.hamburger').removeClass('is-active');
- $('nav').removeClass('visible');
- });
-
- // Close alerts
- $('.alert-dismissible .close').click(function() {
- $(this).parent().slideUp(500);
- });
-
-});
+$(document).ready(function () {
+ // Open drawer with hamburger
+ $('.hamburger').click(function () {
+ $('.hamburger').toggleClass('is-active')
+ $('nav').toggleClass('visible')
+ })
+
+ // Close drawer after tapping on nav
+ $('nav').click(function () {
+ $('.hamburger').removeClass('is-active')
+ $('nav').removeClass('visible')
+ })
+
+ // Close drawer by tapping outside it
+ $('.wrap, section').click(function () {
+ $('.hamburger').removeClass('is-active')
+ $('nav').removeClass('visible')
+ })
+
+ // Close alerts
+ $('.alert-dismissible .close').click(function () {
+ $(this).parent().slideUp(500)
+ })
+})
diff --git a/static/js/html5shiv.js b/static/js/html5shiv.js
index dcf351c..60aa537 100644
--- a/static/js/html5shiv.js
+++ b/static/js/html5shiv.js
@@ -1,8 +1,32 @@
/*
HTML5 Shiv v3.6.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed
*/
-(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag();
-a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x";
-c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode||
-"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",version:"3.6.2",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment();
-for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;dStop').prop('title',"Click here to stop tracking your location. ");
- wpid = navigator.geolocation.watchPosition(
-
- // Success callback
- function(pos) {
- newloc = {
- ts: Date.now(),
- tok: token,
- usr: userid,
- lat: pos.coords.latitude,
- lon: pos.coords.longitude,
- alt: pos.coords.altitude,
- 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
- $('#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 = {
- ts: Date.now(),
- tok: token,
- usr: userid,
- lat:0, lon:0, spd:0
- }; socket.emit('set',newloc);
-
- // Turn off map
- toggleMaps(newloc);
- console.log('Cleared location');
- }
- });
-
-});
+
+// On page load
+$(function () {
+ toggleMaps(mapuser.last)
+
+ // Controls
+ 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 = {
+ ts: Date.now(),
+ tok: token,
+ usr: userid,
+ alt: pos.coords.altitude,
+ 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 set location.')
+ console.error('❌️', err.message)
+ },
+
+ // 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 = {
+ ts: Date.now(),
+ tok: token,
+ usr: userid,
+ lat: pos.coords.latitude,
+ lon: pos.coords.longitude,
+ alt: pos.coords.altitude,
+ 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
+ $('#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 = {
+ ts: Date.now(),
+ tok: token,
+ usr: userid,
+ lat: 0,
+ lon: 0,
+ spd: 0
+ }; socket.emit('set', newloc)
+
+ // Turn off map
+ toggleMaps(newloc)
+ console.log('Cleared location')
+ }
+ })
+})
// Load google maps API
-loadGoogleMapsAPI({ key:mapKey })
-.then( function(googlemaps) {
+loadGoogleMapsAPI({ key: mapKey })
+.then(function (googlemaps) {
+ // Create map
+ if (disp !== '1') {
+ // Create map and marker elements
+ map = new googlemaps.Map(mapElem, {
+ center: {
+ lat: mapuser.last.lat,
+ lng: mapuser.last.lon
+ },
+ panControl: false,
+ scrollwheel: true,
+ scaleControl: !!(mapuser.settings.showScale),
+ draggable: false,
+ zoom: mapuser.settings.defaultZoom,
+ streetViewControl: false,
+ zoomControlOptions: {position: googlemaps.ControlPosition.LEFT_TOP},
+ mapTypeId: (mapuser.settings.defaultMap === 'road') ? googlemaps.MapTypeId.ROADMAP : googlemaps.MapTypeId.HYBRID
+ })
+ marker = new googlemaps.Marker({
+ position: { lat: mapuser.last.lat, lng: mapuser.last.lon },
+ title: mapuser.name,
+ icon: (mapuser.settings.marker) ? '/static/img/marker/' + mapuser.settings.marker + '.png' : '/static/img/marker/red.png',
+ map: map,
+ draggable: false
+ })
+ map.addListener('zoom_changed', function () {
+ map.setCenter(marker.getPosition())
+ })
- // Create map
- if (disp!=='1') {
-
- // Create map and marker elements
- map = new googlemaps.Map( mapElem, {
- center: {
- lat: mapuser.last.lat,
- lng: mapuser.last.lon
- },
- panControl: false,
- scrollwheel: true,
- scaleControl: (mapuser.settings.showScale)?true:false,
- draggable: false,
- zoom: mapuser.settings.defaultZoom,
- streetViewControl: false,
- zoomControlOptions: {position: googlemaps.ControlPosition.LEFT_TOP},
- mapTypeId: (mapuser.settings.defaultMap=='road')?googlemaps.MapTypeId.ROADMAP:googlemaps.MapTypeId.HYBRID
- });
- marker = new googlemaps.Marker({
- position: { lat:mapuser.last.lat, lng:mapuser.last.lon },
- title: mapuser.name,
- icon: (mapuser.settings.marker)?'/static/img/marker/'+mapuser.settings.marker+'.png':'/static/img/marker/red.png',
- map: map,
- draggable: false
- });
- map.addListener('zoom_changed',function(){
- map.setCenter(marker.getPosition());
- });
-
- // Create iFrame logo
- if (noHeader!=='0' && mapuser._id!=='demo') {
- const logoDiv = document.createElement('div');
- logoDiv.id = 'map-logo';
- logoDiv.innerHTML = ''+
- ''+
- "Tracman";
- map.controls[googlemaps.ControlPosition.BOTTOM_LEFT].push(logoDiv);
- }
-
- // Create update 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[googlemaps.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.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[googlemaps.ControlPosition.TOP_RIGHT].push(speedSign);
- }
-
- // Create altitude block
- if (mapuser.settings.showAlt) {
- elevator = new googlemaps.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';
- parseAlt(mapuser.last).then( function(alt){
- altitudeText.innerHTML = metersToFeet(alt)
- }).catch( function(err){
- console.error("Could not load altitude from last known location: ",err)
- });
- altitudeUnit.innerHTML = (mapuser.settings.units=='standard')?'feet':'meters';
- altitudeSign.appendChild(altitudeLabel);
- altitudeSign.appendChild(altitudeText);
- altitudeSign.appendChild(altitudeUnit);
- map.controls[googlemaps.ControlPosition.TOP_RIGHT].push(altitudeSign);
- }
-
- }
-
- // Create streetview
- if (disp!=='0' && mapuser.settings.showStreetview) {
- updateStreetView(parseLoc(mapuser.last),10);
- }
-
- // Get altitude from Google API
- function getAlt(loc){
- return new Promise( function(resolve,reject){
-
- // Get elevator service
- elevator = elevator || new googlemaps.ElevationService;
- return elevator.getElevationForLocations({
-
- // Query API
- 'locations': [{ lat:loc.lat, lng:loc.lon }]
- }, function(results, status, error_message) {
-
- // Success; return altitude
- if (status === googlemaps.ElevationStatus.OK && results[0]) {
- console.log("Altitude was retrieved from Google Elevations API as",results[0].elevation,'m')
- resolve( results[0].elevation )
- }
-
- // Unable to get any altitude
- else {
- reject(Error(error_message))
- }
-
- });
- })
- }
-
- // Parse altitude
- function parseAlt(loc){
- //console.log('parseAlt('+loc+'})')
-
- return new Promise( function(resolve,reject){
-
- // Check if altitude was provided
- if (typeof loc.alt=='number'){
- console.log('Altitude was provided in loc as ',loc.alt,'m')
- resolve(loc.alt)
- }
-
- // No altitude provided
- else {
- console.log('No altitude was provided in loc')
-
- // Query google altitude API
- getAlt(loc).then( function(alt){
- resolve(alt)
- }).catch( function (err) {
- reject(err)
- })
-
- }
-
- })
-
- }
-
- // 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.alt = parseAlt(loc);
- loc.tim = new Date(loc.tim).toLocaleString();
- return loc;
- }
-
- // Got location
- socket.on('get', function(loc) {
- console.log("Got location:",loc.lat+", "+loc.lon);
-
- // Parse location
- newLoc = parseLoc(loc);
-
- // Update map
- if (disp!=='1') {
- //console.log('Updating map...')
-
- // Update time
- $('#timestamp').text('location updated '+newLoc.tim);
-
- // Update marker and map center
- googlemaps.event.trigger(map,'resize');
- map.setCenter({ lat:newLoc.lat, lng:newLoc.lon });
- marker.setPosition({ lat:newLoc.lat, lng:newLoc.lon });
-
- // Update speed
- if (mapuser.settings.showSpeed) {
- $('#spd').text( newLoc.spd.toFixed() );
- }
-
- // Update altitude
- if (mapuser.settings.showAlt) {
- //console.log('updating altitude...');
- parseAlt(loc).then(function(alt){
- $('#alt').text( metersToFeet(alt) )
- }).catch(function(err){
- $('#alt').text( '????' )
- console.error(err);
- })
- }
-
- }
-
- // Update street view
- if (disp!=='0' && mapuser.settings.showStreetview) {
- updateStreetView(newLoc,10);
- }
-
- });
-
- // Get street view imagery
- function getStreetViewData(loc,rad,cb) {
- // Ensure that the location hasn't changed (or this is the initial setting)
- if ( newLoc == null || loc.tim===newLoc.tim ) {
- if (!sv) { var sv=new googlemaps.StreetViewService(); }
- sv.getPanorama({
- location: {
- lat: loc.lat,
- lng: loc.lon
- },
- radius: rad
- }, function(data,status){ switch (status){
- // Success
- case googlemaps.StreetViewStatus.OK:
- cb(data);
- break;
- // No results in that radius
- case googlemaps.StreetViewStatus.ZERO_RESULTS:
- // Try again with a bigger radius
- getStreetViewData(loc,rad*2,cb);
- break;
- // Error
- default:
- console.error(new Error('❌️ Street view not available: '+status).message);
- } });
- }
- }
-
- // Update streetview
- function updateStreetView(loc) {
-
- // Calculate bearing between user and position of streetview image
- // https://stackoverflow.com/a/26609687/3006854
- function getBearing(userLoc, imageLoc) {
- return 90-(
- Math.atan2( userLoc.lat-imageLoc.latLng.lat(), userLoc.lon-imageLoc.latLng.lng() )
- * (180/Math.PI) ) % 360;
- }
-
- // Get dimensions for sv request (images proportional to element up to 640x640)
- function getDimensions(element) {
-
- // Window is smaller than max
- if ( element.width()<640 && element.height()<640 ){
- return element.width().toFixed()+'x'+element.height().toFixed();
- }
-
- // Width must be made proportional to 640
- else if (element.width()>element.height()) {
- return '640x'+(element.height()*640/element.width()).toFixed();
- }
-
- // Height must be made proportional to 640
- else {
- return (element.width()*640/element.height()).toFixed()+'x640';
- }
-
- }
-
- // Set image
- getStreetViewData(loc, 2, function(data){
- $('#viewImg').attr('src','https://maps.googleapis.com/maps/api/streetview?'+
- 'size='+ getDimensions($('#view')) +
- '&location='+ data.location.latLng.lat() +','+ data.location.latLng.lng() +
- '&fov=90' + // Inclination
- // Show direction if moving, point to user if stationary
- '&heading='+ ( (loc.spd>2)? loc.dir: getBearing(loc,data.location) ).toString() +
- '&key='+ mapKey
- );
- });
-
- }
+ // Create iFrame logo
+ if (noHeader !== '0' && mapuser._id !== 'demo') {
+ const logoDiv = document.createElement('div')
+ logoDiv.id = 'map-logo'
+ logoDiv.innerHTML = '' +
+ '' +
+ "Tracman"
+ map.controls[googlemaps.ControlPosition.BOTTOM_LEFT].push(logoDiv)
+ }
+
+ // Create update 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[googlemaps.ControlPosition.RIGHT_BOTTOM].push(timeDiv)
+
+ // Create speed block
+ if (mapuser.settings.showSpeed) {
+ const speedSign = document.createElement('div')
+ const speedLabel = document.createElement('div')
+ const speedText = document.createElement('div')
+ const 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[googlemaps.ControlPosition.TOP_RIGHT].push(speedSign)
+ }
+
+ // Create altitude block
+ if (mapuser.settings.showAlt) {
+ elevator = new googlemaps.ElevationService()
+ const altitudeSign = document.createElement('div')
+ const altitudeLabel = document.createElement('div')
+ const altitudeText = document.createElement('div')
+ const altitudeUnit = document.createElement('div')
+ altitudeLabel.id = 'alt-label'
+ altitudeText.id = 'alt'
+ altitudeUnit.id = 'alt-unit'
+ altitudeSign.id = 'alt-sign'
+ altitudeText.innerHTML = ''
+ altitudeLabel.innerHTML = 'ALTITUDE'
+ parseAlt(mapuser.last).then(function (alt) {
+ altitudeText.innerHTML = metersToFeet(alt)
+ }).catch(function (err) {
+ console.error('Could not load altitude from last known location: ', err)
+ })
+ altitudeUnit.innerHTML = (mapuser.settings.units === 'standard') ? 'feet' : 'meters'
+ altitudeSign.appendChild(altitudeLabel)
+ altitudeSign.appendChild(altitudeText)
+ altitudeSign.appendChild(altitudeUnit)
+ map.controls[googlemaps.ControlPosition.TOP_RIGHT].push(altitudeSign)
+ }
+ }
+
+ // Create streetview
+ if (disp !== '0' && mapuser.settings.showStreetview) {
+ updateStreetView(parseLoc(mapuser.last), 10)
+ }
+
+ // Get altitude from Google API
+ function getAlt (loc) {
+ return new Promise(function (resolve, reject) {
+ // Get elevator service
+ elevator = elevator || new googlemaps.ElevationService()
+ return elevator.getElevationForLocations({
+
+ // Query API
+ 'locations': [{ lat: loc.lat, lng: loc.lon }]
+ }, function (results, status, errorMessage) {
+ // Success; return altitude
+ if (status === googlemaps.ElevationStatus.OK && results[0]) {
+ console.log('Altitude was retrieved from Google Elevations API as', results[0].elevation, 'm')
+ resolve(results[0].elevation)
+
+ // Unable to get any altitude
+ } else {
+ reject(Error(errorMessage))
+ }
+ })
+ })
+ }
+
+ // Parse altitude
+ function parseAlt (loc) {
+ // console.log('parseAlt('+loc+'})')
+
+ return new Promise(function (resolve, reject) {
+ // Check if altitude was provided
+ if (typeof loc.alt === 'number') {
+ console.log('Altitude was provided in loc as ', loc.alt, 'm')
+ resolve(loc.alt)
+
+ // No altitude provided
+ } else {
+ console.log('No altitude was provided in loc')
+
+ // Query google altitude API
+ getAlt(loc).then(function (alt) {
+ resolve(alt)
+ }).catch(function (err) {
+ reject(err)
+ })
+ }
+ })
+ }
+
+ // 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.alt = parseAlt(loc);
+ loc.tim = new Date(loc.tim).toLocaleString()
+ return loc
+ }
+
+ // Got location
+ socket.on('get', function (loc) {
+ console.log('Got location:', loc.lat + ', ' + loc.lon)
+
+ // Parse location
+ newLoc = parseLoc(loc)
+
+ // Update map
+ if (disp !== '1') {
+ // console.log('Updating map...')
+
+ // Update time
+ $('#timestamp').text('location updated ' + newLoc.tim)
+
+ // Update marker and map center
+ googlemaps.event.trigger(map, 'resize')
+ map.setCenter({ lat: newLoc.lat, lng: newLoc.lon })
+ marker.setPosition({ lat: newLoc.lat, lng: newLoc.lon })
+
+ // Update speed
+ if (mapuser.settings.showSpeed) {
+ $('#spd').text(newLoc.spd.toFixed())
+ }
+
+ // Update altitude
+ if (mapuser.settings.showAlt) {
+ // console.log('updating altitude...');
+ parseAlt(loc).then(function (alt) {
+ $('#alt').text(metersToFeet(alt))
+ }).catch(function (err) {
+ $('#alt').text('????')
+ console.error(err)
+ })
+ }
+ }
+
+ // Update street view
+ if (disp !== '0' && mapuser.settings.showStreetview) {
+ updateStreetView(newLoc, 10)
+ }
+ })
+
+ // Get street view imagery
+ function getStreetViewData (loc, rad, cb) {
+ // Ensure that the location hasn't changed (or this is the initial setting)
+ if (newLoc == null || loc.tim === newLoc.tim) {
+ if (!sv) { var sv = new googlemaps.StreetViewService() }
+ sv.getPanorama({
+ location: {
+ lat: loc.lat,
+ lng: loc.lon
+ },
+ radius: rad
+ }, function (data, status) {
+ switch (status) {
+ // Success
+ case googlemaps.StreetViewStatus.OK:
+ cb(data)
+ break
+ // No results in that radius
+ case googlemaps.StreetViewStatus.ZERO_RESULTS:
+ // Try again with a bigger radius
+ getStreetViewData(loc, rad * 2, cb)
+ break
+ // Error
+ default:
+ console.error(new Error('❌️ Street view not available: ' + status).message)
+ }
+ })
+ }
+ }
+
+ // Update streetview
+ function updateStreetView (loc) {
+ // Calculate bearing between user and position of streetview image
+ // https://stackoverflow.com/a/26609687/3006854
+ function getBearing (userLoc, imageLoc) {
+ return 90 - (
+ Math.atan2(userLoc.lat - imageLoc.latLng.lat(), userLoc.lon - imageLoc.latLng.lng()) *
+ (180 / Math.PI)) % 360
+ }
+
+ // Get dimensions for sv request (images proportional to element up to 640x640)
+ function getDimensions (element) {
+ // Window is smaller than max
+ if (element.width() < 640 && element.height() < 640) {
+ return element.width().toFixed() + 'x' + element.height().toFixed()
+
+ // Width must be made proportional to 640
+ } else if (element.width() > element.height()) {
+ return '640x' + (element.height() * 640 / element.width()).toFixed()
+
+ // Height must be made proportional to 640
+ } else {
+ return (element.width() * 640 / element.height()).toFixed() + 'x640'
+ }
+ }
+
+ // Set image
+ getStreetViewData(loc, 2, function (data) {
+ $('#viewImg').attr('src', 'https://maps.googleapis.com/maps/api/streetview?' +
+ 'size=' + getDimensions($('#view')) +
+ '&location=' + data.location.latLng.lat() + ',' + data.location.latLng.lng() +
+ '&fov=90' + // Inclination
+ // Show direction if moving, point to user if stationary
+ '&heading=' + ((loc.spd > 2) ? loc.dir : getBearing(loc, data.location)).toString() +
+ '&key=' + mapKey
+ )
+ })
+ }
// Error loading gmaps API
-}).catch( function(err) {
- console.error(err);
-});
+}).catch(function (err) {
+ console.error(err)
+})
diff --git a/static/js/password.js b/static/js/password.js
index b22ffd7..d5ebc02 100644
--- a/static/js/password.js
+++ b/static/js/password.js
@@ -1,87 +1,74 @@
-'use strict';
+'use strict'
/* global $ */
-const zxcvbn = require('zxcvbn');
+const zxcvbn = require('zxcvbn')
-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. ");
- }
-
+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 zxcvbnResult = zxcvbn($('#p1').val());
-
- // Not good enough
- if (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second < 3600) { // Less than an hour
- $('#password-help').text("That password is way too common or simple. 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 (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second < 86400) { // Less than a day
- $('#password-help').text("That password is pretty bad. It could be cracked in "+zxcvbnResult.crack_times_display.online_no_throttling_10_per_second+". 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 (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second < 864000) { // Less than ten days
- $('#password-help').text("That password isn't good enough. It could be cracked in "+zxcvbnResult.crack_times_display.online_no_throttling_10_per_second+". 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. ");
- }
-
- // Good enough
- else if (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second <= 2592000) { // Less than thirty days
- $('#password-help').text("That password is good enough, but it could still be cracked in "+zxcvbnResult.crack_times_display.online_no_throttling_10_per_second+". ").css({'color':'#eee'});
- checkMatch();
- }
- else if (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second <= 1314000) { // Less than a year
- $('#password-help').text("That password is good. It would take "+zxcvbnResult.crack_times_display.online_no_throttling_10_per_second+" to crack. ").css({'color':'#8ae137'});
- checkMatch();
- }
- else { // Long ass time
- $('#password-help').text("That password is great! It could take "+zxcvbnResult.crack_times_display.online_no_throttling_10_per_second+" 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');
- }
- });
-
-});
+$(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 zxcvbnResult = zxcvbn($('#p1').val())
+
+ // Not good enough
+ if (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second < 3600) { // Less than an hour
+ $('#password-help').text('That password is way too common or simple. 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 (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second < 86400) { // Less than a day
+ $('#password-help').text('That password is pretty bad. It could be cracked in ' + zxcvbnResult.crack_times_display.online_no_throttling_10_per_second + '. 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 (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second < 864000) { // Less than ten days
+ $('#password-help').text("That password isn't good enough. It could be cracked in " + zxcvbnResult.crack_times_display.online_no_throttling_10_per_second + '. 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. ')
+
+ // Good enough
+ } else if (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second <= 2592000) { // Less than thirty days
+ $('#password-help').text('That password is good enough, but it could still be cracked in ' + zxcvbnResult.crack_times_display.online_no_throttling_10_per_second + '. ').css({'color': '#eee'})
+ checkMatch()
+ } else if (zxcvbnResult.crack_times_seconds.online_no_throttling_10_per_second <= 1314000) { // Less than a year
+ $('#password-help').text('That password is good. It would take ' + zxcvbnResult.crack_times_display.online_no_throttling_10_per_second + ' to crack. ').css({'color': '#8ae137'})
+ checkMatch()
+ } else { // Long ass time
+ $('#password-help').text('That password is great! It could take ' + zxcvbnResult.crack_times_display.online_no_throttling_10_per_second + ' 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')
+ }
+ })
+})
diff --git a/static/js/settings.js b/static/js/settings.js
index 4be5c27..217e116 100644
--- a/static/js/settings.js
+++ b/static/js/settings.js
@@ -1,155 +1,136 @@
-'use strict';
-/* global location $ */
+'use strict'
+/* global $ confirm */
// 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);
+function validateEmail (email) {
+ let 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)
}
// Replace inputed value with response
-function replaceFromEndpoint(type, selector, cb) {
- $.get('/validate?'+type+'='+$(selector).val())
- .done(function(data){
- $(selector).val(data);
- cb();
- });
+function replaceFromEndpoint (type, selector, cb) {
+ $.get('/validate?' + type + '=' + $(selector).val())
+ .done(function (data) {
+ $(selector).val(data)
+ cb()
+ })
}
// On page load
-$(function(){
- 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! ")) {
- window.location.href = "/settings/delete";
- }
- });
-
- 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);
- });
-
-});
+$(function () {
+ 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! ')) {
+ window.location.href = '/settings/delete'
+ }
+ })
+
+ 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
+ $.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)
+ })
+})
diff --git a/test.js b/test.js
index ac04079..9545405 100755
--- a/test.js
+++ b/test.js
@@ -1,151 +1,146 @@
-const chai = require('chai'),
- chaiHttp = require('chai-http'),
- request = require('supertest'),
- server = require('./server');
-chai.use(chaiHttp);
+const chai = require('chai')
+const chaiHttp = require('chai-http')
+const request = require('supertest')
+const server = require('./server')
+chai.use(chaiHttp)
+describe('Public', function () {
+ it('Displays homepage', function (done) {
+ request(server).get('/')
+ .expect(200)
+ .end(function (err, res) { done() })
+ })
-describe('Public', function() {
-
- it('Displays homepage', function(done){
- request(server).get('/')
- .expect(200)
- .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)
- .expect('Content-Type', /text/)
- .end(function(err,res){ done(); });
- });
-
- it('Displays demo map', function(done){
- request(server).get('/map/keith')
- .expect(200)
- .end(function(err,res){ done(); });
- });
-
-});
+ it('Displays help page', function (done) {
+ request(server).get('/help')
+ .expect(200)
+ .end(function (err, res) { done() })
+ })
-describe('User', function() {
-
- it('Creates an account', function(done){
- request(server).post('/signup',{"email":"test@tracman.org"})
- .expect(200)
- .end(function(err,res){ done(); });
- });
-
- //TODO: it('Creates a password', function(done){
-
- // });
-
- //TODO: it('Logs in', function(done){
-
- // });
-
- //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){
-
- // });
-
- //TODO: it('Deletes account', function(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)
+ .expect('Content-Type', /text/)
+ .end(function (err, res) { done() })
+ })
+
+ it('Displays demo map', function (done) {
+ request(server).get('/map/keith')
+ .expect(200)
+ .end(function (err, res) { done() })
+ })
+})
+
+describe('User', function () {
+ it('Creates an account', function (done) {
+ request(server).post('/signup', {'email': 'test@tracman.org'})
+ .expect(200)
+ .end(function (err, res) { done() })
+ })
+
+ // TODO: it('Creates a password', function(done){
+
+ // })
+
+ // TODO: it('Logs in', function(done){
+
+ // })
+
+ // 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){
+
+ // })
+
+ // TODO: it('Deletes account', function(done){
+
+ // })
+})
diff --git a/webpack.config.js b/webpack.config.js
index ec7d00d..8c9aa3c 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,34 +1,34 @@
-const path = require('path'),
- env = require('./config/env/env.js'),
- uglifyJsPlugin = require('uglifyjs-webpack-plugin');
+const path = require('path')
+const env = require('./config/env/env.js')
+const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
-
- // Javascript files to be bundled
- entry: {
- base: './static/js/base.js',
- header: './static/js/header.js',
- footer: './static/js/footer.js',
- contact: './static/js/contact.js',
- login: './static/js/login.js',
- map: './static/js/map.js',
- // controls: './static/js/controls.js',
- settings: './static/js/settings.js',
- password: './static/js/password.js'
- },
-
- // Sourcemaps
- devtool: (env.mode=='development')?'inline-source-map':false,
-
- // Output format
- output: {
- filename: '.[name].bun.js',
- path: path.resolve(__dirname, 'static/js')
- },
-
- plugins: [
- // Minimize JS
- new uglifyJsPlugin()
- ]
-
-};
+
+ // Javascript files to be bundled
+ entry: {
+ base: './static/js/base.js',
+ header: './static/js/header.js',
+ footer: './static/js/footer.js',
+ contact: './static/js/contact.js',
+ login: './static/js/login.js',
+ map: './static/js/map.js',
+ // controls: './static/js/controls.js',
+ settings: './static/js/settings.js',
+ password: './static/js/password.js'
+ },
+
+ // Sourcemaps
+ devtool: (env.mode === 'development') ? 'inline-source-map' : false,
+
+ // Output format
+ output: {
+ filename: '.[name].bun.js',
+ path: path.resolve(__dirname, 'static/js')
+ },
+
+ plugins: [
+ // Minimize JS
+ new UglifyJsPlugin()
+ ]
+
+}