#110 Implemented StandardJS
parent
f137835870
commit
016378d807
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
});
|
||||
};
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
||||
}
|
||||
|
|
|
@ -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 `<h1><a href="/" style="text-decoration:none;"><span style="color:#000;font-family:sans-serif;font-size:36px;font-weight:bold"><img src="${env.url}/static/img/icon/by/32.png" alt="+" style="margin-right:10px">Tracman</span></a></h1>${text}<p style="font-size:8px;">Do not reply to this email. For information about why you recieved this email, see our <a href="${env.url}/privacy#email">privacy policy</a>. </p>`;
|
||||
},
|
||||
|
||||
noReply: `"Tracman" <NoReply@tracman.org>`,
|
||||
|
||||
to: (user)=>{
|
||||
return `"${user.name}" <${user.email}>`;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
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 `<h1><a href="/" style="text-decoration:none;"><span style="color:#000;font-family:sans-serif;font-size:36px;font-weight:bold"><img src="${env.url}/static/img/icon/by/32.png" alt="+" style="margin-right:10px">Tracman</span></a></h1>${text}<p style="font-size:8px;">Do not reply to this email. For information about why you recieved this email, see our <a href="${env.url}/privacy#email">privacy policy</a>. </p>`
|
||||
},
|
||||
|
||||
noReply: `"Tracman" <NoReply@tracman.org>`,
|
||||
|
||||
to: (user) => {
|
||||
return `"${user.name}" <${user.email}>`
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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. <br>Would you like to <a href="https://github.com/Tracman-org/Server/issues/new">report it</a>?');
|
||||
} 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. <br>Would you like to <a href="https://github.com/Tracman-org/Server/issues/new">report it</a>?')
|
||||
} 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') }
|
||||
},
|
||||
|
||||
};
|
||||
// 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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
276
config/models.js
276
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)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
// 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
|
||||
}
|
||||
|
|
|
@ -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', `<i>${req.params.usrid}</i> 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', `<i>${req.params.usrid}</i> deleted.`)
|
||||
res.redirect('/admin')
|
||||
})
|
||||
.catch((err) => {
|
||||
mw.throwErr(err, req)
|
||||
res.redirect('/admin')
|
||||
})
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -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(`<p>Welcome to Tracman! </p><p>To complete your registration, follow this link and set your password:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a></p><p>This link will expire at ${expirationTimeString}. </p>`)
|
||||
})
|
||||
.then(()=>{
|
||||
debug(`Successfully emailed new user ${user.id} instructions to continue`);
|
||||
req.flash('success', `An email has been sent to <u>${user.email}</u>. 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 <a href="/login/forgot?email=${req.body.email}">reset it here</a>.`);
|
||||
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 <u>${req.body.email}</u>, 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(`<p>Hi, </p><p>Did you request to reset your Tracman password? If so, follow this link to do so:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a></p><p>If you didn't initiate this request, just ignore this email. </p>`)
|
||||
}).then(()=>{
|
||||
req.flash('success', `If an account exists with the email <u>${req.body.email}</u>, 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 <a href="/settings/password">set a password</a> 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(`<p>Welcome to Tracman! </p><p>To complete your registration, follow this link and set your password:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a></p><p>This link will expire at ${expirationTimeString}. </p>`)
|
||||
})
|
||||
.then(() => {
|
||||
debug(`Successfully emailed new user ${user.id} instructions to continue`)
|
||||
req.flash('success', `An email has been sent to <u>${user.email}</u>. 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 <a href="/login/forgot?email=${req.body.email}">reset it here</a>.`)
|
||||
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 <u>${req.body.email}</u>, 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(`<p>Hi, </p><p>Did you request to reset your Tracman password? If so, follow this link to do so:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a></p><p>If you didn't initiate this request, just ignore this email. </p>`)
|
||||
}).then(() => {
|
||||
req.flash('success', `If an account exists with the email <u>${req.body.email}</u>, 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 <a href="/settings/password">set a password</a> 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)
|
||||
}
|
||||
|
|
|
@ -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', `<u>${req.body.email}</u> 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 <contact@tracman.org>`,
|
||||
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', `<u>${req.body.email}</u> 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 <contact@tracman.org>`,
|
||||
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')
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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', `<u>${req.body.email}</u> 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, <u>${req.body.email}</u>, 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(`<p>A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it. </p><p>To confirm your email, follow this link:<br><a href="${env.url}/settings/email/${token}">${env.url}/settings/email/${token}</a>. </p>`)
|
||||
})
|
||||
.then( ()=>{
|
||||
req.flash('warning',`An email has been sent to <u>${req.body.email}</u>. 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, <u>${req.body.slug}</u>, 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', `<u>${req.body.email}</u> 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, <u>${req.body.email}</u>, 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(`<p>A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it. </p><p>To confirm your email, follow this link:<br><a href="${env.url}/settings/email/${token}">${env.url}/settings/email/${token}</a>. </p>`)
|
||||
})
|
||||
.then(() => {
|
||||
req.flash('warning', `An email has been sent to <u>${req.body.email}</u>. 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, <u>${req.body.slug}</u>, 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 <u>${req.user.email}</u>. `);
|
||||
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 <u>${req.user.email}</u>. `)
|
||||
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(`<p>A request has been made to change your tracman password. If you did not initiate this request, please contact support at <a href="mailto:keith@tracman.org">keith@tracman.org</a>. </p><p>To change your password, follow this link:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a>. </p><p>This request will expire at ${expirationTimeString}. </p>`)
|
||||
})
|
||||
.then( ()=>{
|
||||
// Alert user to check email.
|
||||
req.flash('success',`An link has been sent to <u>${req.user.email}</u>. 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(`<p>A request has been made to change your tracman password. If you did not initiate this request, please contact support at <a href="mailto:keith@tracman.org">keith@tracman.org</a>. </p><p>To change your password, follow this link:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a>. </p><p>This request will expire at ${expirationTimeString}. </p>`)
|
||||
})
|
||||
.then(() => {
|
||||
// Alert user to check email.
|
||||
req.flash('success', `An link has been sent to <u>${req.user.email}</u>. 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
|
||||
|
|
|
@ -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" <hypergeek14@gmail.com>`,
|
||||
from: mail.noReply,
|
||||
subject: 'Test email',
|
||||
text: mail.text("Looks like everything's working! "),
|
||||
html: mail.html("<p>Looks like everything's working! </p>")
|
||||
})
|
||||
.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;
|
||||
.get('/mail', (req, res, next) => {
|
||||
mail.send({
|
||||
to: `"Keith Irwin" <hypergeek14@gmail.com>`,
|
||||
from: mail.noReply,
|
||||
subject: 'Test email',
|
||||
text: mail.text("Looks like everything's working! "),
|
||||
html: mail.html("<p>Looks like everything's working! </p>")
|
||||
})
|
||||
.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
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
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)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
364
server.js
364
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
|
|
|
@ -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() })
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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<style>article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}</style>";
|
||||
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="<xyz></xyz>";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;d<h;d++)c.createElement(e[d]);return c}};l.html5=e;q(f)})(this,document);
|
||||
(function (l, f) {
|
||||
function m () { var a = e.elements; return typeof a === 'string' ? 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<style>article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}</style>'
|
||||
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 = '<xyz></xyz>'; j = 'hidden' in a; var b; if (!(b = a.childNodes.length == 1)) {
|
||||
f.createElement('a'); var c = f.createDocumentFragment(); b = typeof c.cloneNode === 'undefined' ||
|
||||
typeof c.createDocumentFragment === 'undefined' || typeof c.createElement === 'undefined'
|
||||
}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; d < h; d++)c.createElement(e[d]); return c
|
||||
}}; l.html5 = e; q(f)
|
||||
})(this, document)
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
'use strict';
|
||||
'use strict'
|
||||
/* global $ */
|
||||
|
||||
$(function(){
|
||||
|
||||
// On clocking 'show'
|
||||
$('#show').click(function(){
|
||||
if ($('#password').attr('type')==="password") {
|
||||
$('#password').attr('type','text');
|
||||
} else {
|
||||
$('#password').attr('type','password');
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
$(function () {
|
||||
// On clocking 'show'
|
||||
$('#show').click(function () {
|
||||
if ($('#password').attr('type') === 'password') {
|
||||
$('#password').attr('type', 'text')
|
||||
} else {
|
||||
$('#password').attr('type', 'password')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
869
static/js/map.js
869
static/js/map.js
|
@ -1,464 +1,439 @@
|
|||
'use strict';
|
||||
/* global mapuser userid disp noHeader mapKey navigator $ token */
|
||||
'use strict'
|
||||
/* global alert mapuser userid disp noHeader mapKey navigator token */
|
||||
|
||||
import io from 'socket.io-client';
|
||||
import $ from 'jquery';
|
||||
import loadGoogleMapsAPI from 'load-google-maps-api';
|
||||
import io from 'socket.io-client'
|
||||
import $ from 'jquery'
|
||||
import loadGoogleMapsAPI from 'load-google-maps-api'
|
||||
|
||||
// Variables
|
||||
var map, marker, elevator, newLoc;
|
||||
const mapElem = document.getElementById('map'),
|
||||
socket = io('//'+window.location.hostname);
|
||||
|
||||
var map, marker, elevator, newLoc
|
||||
const mapElem = document.getElementById('map')
|
||||
const socket = io('//' + window.location.hostname)
|
||||
|
||||
// Convert to feet if needed
|
||||
function metersToFeet(meters){
|
||||
//console.log('metersToFeet('+meters+')')
|
||||
return (mapuser.settings.units=='standard')? (meters*3.28084).toFixed(): meters.toFixed()
|
||||
function metersToFeet (meters) {
|
||||
// console.log('metersToFeet('+meters+')')
|
||||
return (mapuser.settings.units === 'standard') ? (meters * 3.28084).toFixed() : meters.toFixed()
|
||||
}
|
||||
|
||||
// socket.io stuff
|
||||
socket
|
||||
.on('connect', function(){
|
||||
console.log("Connected!");
|
||||
|
||||
// Can get location
|
||||
socket.emit('can-get', mapuser._id );
|
||||
.on('connect', function () {
|
||||
console.log('Connected!')
|
||||
|
||||
// Can set location too
|
||||
if (mapuser._id===userid) {
|
||||
socket.emit('can-set', userid );
|
||||
}
|
||||
|
||||
})
|
||||
.on('disconnect', function(){
|
||||
console.log("Disconnected!");
|
||||
})
|
||||
.on('error', function (err){
|
||||
console.error('❌️',err.message);
|
||||
});
|
||||
// Can get location
|
||||
socket.emit('can-get', mapuser._id)
|
||||
|
||||
// Can set location too
|
||||
if (mapuser._id === userid) {
|
||||
socket.emit('can-set', userid)
|
||||
}
|
||||
}).on('disconnect', function () {
|
||||
console.log('Disconnected!')
|
||||
}).on('error', function (err) {
|
||||
console.error('❌️', err.message)
|
||||
})
|
||||
|
||||
// Show/hide map if location is set/unset
|
||||
function toggleMaps(loc) {
|
||||
if (loc.lat===0&&loc.lon===0) {
|
||||
$('#map').hide();
|
||||
$('#view').hide();
|
||||
$('#notset').show();
|
||||
}
|
||||
else {
|
||||
$('#map').show();
|
||||
$('#view').show();
|
||||
$('#notset').hide();
|
||||
}
|
||||
function toggleMaps (loc) {
|
||||
if (loc.lat === 0 && loc.lon === 0) {
|
||||
$('#map').hide()
|
||||
$('#view').hide()
|
||||
$('#notset').show()
|
||||
} else {
|
||||
$('#map').show()
|
||||
$('#view').show()
|
||||
$('#notset').hide()
|
||||
}
|
||||
}
|
||||
// Toggle maps 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('<i class="fa fa-crosshairs fa-spin"></i>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('<i class="fa fa-crosshairs"></i>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('<i class="fa fa-crosshairs"></i>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('<i class="fa fa-crosshairs fa-spin"></i>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('<i class="fa fa-crosshairs"></i>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('<i class="fa fa-crosshairs"></i>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 = '<a href="https://tracman.org/">'+
|
||||
'<img src="https://tracman.org/static/img/style/logo-28.png" alt="[]">'+
|
||||
"<span class='text'>Tracman</span></a>";
|
||||
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 = '<a href="https://tracman.org/">' +
|
||||
'<img src="https://tracman.org/static/img/style/logo-28.png" alt="[]">' +
|
||||
"<span class='text'>Tracman</span></a>"
|
||||
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)
|
||||
})
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
291
test.js
291
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){
|
||||
|
||||
// })
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
]
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue