Added login screen
parent
76ccaab5c4
commit
7153f7bb9b
13
PRIVACY.md
13
PRIVACY.md
|
@ -1,13 +0,0 @@
|
|||
# Privacy Policy for Tracman 0.3.1
|
||||
|
||||
In lieu of legalease, which I don't speak, here is a quick rundown of what Tracman does with your data (such as location).
|
||||
|
||||
## Location history
|
||||
|
||||
Your location is saved on the database as long as you have it "set" or "tracking". If you "clear" the data, it will be deleted from the database too. This doesn't mean all copies are destroyed. Our servers keep occasional backups, and caches could exist on other servers (google index, wayback archive, etc).
|
||||
|
||||
This means that all public access to your location is essentially deleted when you clear it. But anyone could record your location while it's publicly available and rebroadcast it. Tracman doesn't store location histories (except as mentioned above), but histories may exist elsewhere! If you have (or plan to have) trouble with the law, don't use Tracman. Authorities have easy access to those histories.
|
||||
|
||||
## Email addresses
|
||||
|
||||
Tracman stores email addresses so we can contact users for important stuff (urgent security updates, deletion requests, lost passwords). We will never subscribe you to anything else by default.
|
12
README.md
12
README.md
|
@ -9,14 +9,14 @@ node.js application to display a map with user's location.
|
|||
$ git clone https://github.com/Tracman-org/Server.git && (cd Server && exec npm install)
|
||||
```
|
||||
|
||||
You will need to set up a configuration file at `config/secrets.js`. It should contain the following information:
|
||||
You will need to set up a configuration file at `config/env.js`. It should contain the following information:
|
||||
|
||||
```javascript
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
|
||||
env: 'development', // or 'production'
|
||||
mode: 'development', // or 'production'
|
||||
|
||||
// Random strings to prevent hijacking
|
||||
session: 'this is a secret',
|
||||
|
@ -25,8 +25,12 @@ module.exports = {
|
|||
// Client IDs for authentication
|
||||
googleClientId: '############-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com',
|
||||
googleClientSecret: 'XXXXXXXXX_XXXXXXXXXXXXXX',
|
||||
facebookAppId: 'XXXXXXXXXXXXXXXX',
|
||||
facebookAppSecret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
twitterConsumerKey: 'XXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
twitterConsumerSecret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
|
||||
// A google maps API
|
||||
// A google maps API key
|
||||
googleMapsAPI: 'XXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXX',
|
||||
|
||||
// Location of your mongoDB
|
||||
|
@ -39,6 +43,8 @@ module.exports = {
|
|||
};
|
||||
```
|
||||
|
||||
Use `config/env-sample.js` for help.
|
||||
|
||||
You can get API keys at the [google developer's console](https://console.developers.google.com/apis/credentials). You will need to set up approved hosts and auth callbacks. There is more information in [their documentation](https://support.google.com/googleapi/answer/6158857?hl=en).
|
||||
|
||||
## Running
|
||||
|
|
350
config/auth.js
350
config/auth.js
|
@ -1,108 +1,270 @@
|
|||
'use strict';
|
||||
|
||||
const passport = require('passport'),
|
||||
slug = require('slug'),
|
||||
crypto = require('crypto'),
|
||||
secret = require('./secrets.js'),
|
||||
User = require('./models/user.js'),
|
||||
GoogleStrategy = require('passport-google-oauth2').Strategy,
|
||||
GoogleTokenStrategy = require('passport-google-id-token');
|
||||
const mw = require('./middleware.js'),
|
||||
mail = require('./mail.js'),
|
||||
User = require('./models.js').user,
|
||||
env = require('./env.js');
|
||||
|
||||
passport.use(new GoogleStrategy({
|
||||
clientID: secret.googleClientId,
|
||||
clientSecret: secret.googleClientSecret,
|
||||
callbackURL: secret.url+'/auth/google/callback',
|
||||
passReqToCallback: true
|
||||
}, function(req, accessToken, refreshToken, profile, done) {
|
||||
module.exports = function(app, passport) {
|
||||
|
||||
// Methods for success and failure
|
||||
var loginOutcome = {
|
||||
failureRedirect: '/login',
|
||||
failureFlash: true
|
||||
};
|
||||
var connectOutcome = {
|
||||
failureRedirect: '/account',
|
||||
failureFlash: true
|
||||
};
|
||||
var loginCallback = function(req,res){
|
||||
res.redirect( req.session.returnTo || '/settings' );
|
||||
delete req.session.returnTo;
|
||||
};
|
||||
|
||||
// Login/-out
|
||||
app.route('/login')
|
||||
.get( function(req,res){
|
||||
if (req.isAuthenticated()){
|
||||
res.redirect('/account'); }
|
||||
else { res.render('login'); }
|
||||
})
|
||||
.post( passport.authenticate('local',loginOutcome), loginCallback );
|
||||
app.get('/logout', function(req,res){
|
||||
req.logout();
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
// Check for user
|
||||
User.findOne({googleID: profile.id}, function(err, user){
|
||||
|
||||
// Error
|
||||
if (err) { console.log('Error finding user with google ID: '+profile.id+'\n'+err); }
|
||||
|
||||
// User found
|
||||
if (!err && user !== null) /* Log user in */ {
|
||||
if (!user.name) { user.name=profile.displayName; }
|
||||
user.lastLogin = Date.now();
|
||||
user.save(function (err, raw) {
|
||||
if (err) { throwErr(req,err); }
|
||||
}); done(null, user);
|
||||
}
|
||||
|
||||
// User not found
|
||||
else /* create user */ {
|
||||
user = new User();
|
||||
user.googleID = profile.id;
|
||||
user.name = profile.displayName;
|
||||
user.email = profile.emails[0].value;
|
||||
user.slug = slug(profile.displayName).toLowerCase();
|
||||
user.created = Date.now();
|
||||
user.lastLogin = Date.now();
|
||||
// user.settings = { units:'standard', defaultMap:'road', defaultZoom:11, showSpeed:false, showTemp:false, showAlt:false, showStreetview:false },
|
||||
// user.last = { lat:0, lon:0, dir:0, alt:0, spd:0 },
|
||||
// user.isPro = false;
|
||||
// user.isAdmin = false;
|
||||
var cbc = 2;
|
||||
var successMessage, failMessage;
|
||||
// Signup
|
||||
app.post('/signup', function(req,res,next){
|
||||
User.findOne({'email':req.body.email}, function(err,user){
|
||||
if (err){ next(err); }
|
||||
|
||||
// Generate slug
|
||||
(function checkSlug(s,cb) {
|
||||
//console.log('checking ',s);
|
||||
User.findOne({slug:s}, function(err, existingUser){
|
||||
if (err) { console.log('No user found for ',slug,':',err); }
|
||||
if (existingUser){
|
||||
s = '';
|
||||
while (s.length<6) {
|
||||
s+='abcdefghijkmnpqrtuvwxy346789'.charAt(Math.floor(Math.random()*28));
|
||||
}
|
||||
checkSlug(s,cb);
|
||||
} else { cb(s); }
|
||||
// User already exists
|
||||
else if (user){
|
||||
req.flash('warning','A user with that email already exists! If you forgot your password, use <a href="/login/forgot">this form</a>.');
|
||||
res.redirect('/login');
|
||||
} else {
|
||||
|
||||
// Create user
|
||||
var newUser = new User();
|
||||
newUser.email = req.body.email;
|
||||
newUser.created = Date.now();
|
||||
|
||||
newUser.createToken(function(err,token){
|
||||
if (err){ next(err); }
|
||||
mail({
|
||||
from: '"Trackmap" <accounts@trackmap.tech>',
|
||||
to: req.body.email,
|
||||
subject: 'Complete your Trackmap registration',
|
||||
text: `Welcome to trackmap! \n\nTo complete your registration, follow this link and set your password:\n${env.url}/account/password/${token}`, // plaintext body
|
||||
html: `<p>Welcome to trackmap! </p><p>To complete your registration, follow this link and set your password:<br><a href="${env.url}/account/password/${token}">${env.url}/account/password/${token}</a></p>` // html body
|
||||
}).then(function(){
|
||||
req.flash('success',`An email has been sent to <u>${req.body.email}</u>. Check your inbox to complete your registration.`);
|
||||
res.redirect('/');
|
||||
}).catch(function(err){
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
})(user.slug, function(newSlug){
|
||||
user.slug = newSlug;
|
||||
if (cbc>1) /* waiting on other calls */ { cbc--; }
|
||||
else { done(null, user, { success:successMessage, failure:failMessage }); }
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Forgot password
|
||||
app.route('/login/forgot')
|
||||
.all( function(req,res,next){
|
||||
if (req.isAuthenticated()){ res.redirect('/account'); }
|
||||
else { next(); }
|
||||
})
|
||||
.get( function(req,res,next){
|
||||
res.render('forgot');
|
||||
})
|
||||
.post( function(req,res,next){
|
||||
|
||||
// Generate sk32
|
||||
crypto.randomBytes(32, function(err,buf) {
|
||||
if (err) {console.log('Unable to get random bytes:',err);}
|
||||
if (!buf) {console.log('Unable to get random buffer');}
|
||||
else {
|
||||
user.sk32 = buf.toString('hex');
|
||||
user.save(function(err) {
|
||||
if (err) {
|
||||
console.log('Error saving new user '+err);
|
||||
var failMessage = 'Something went wrong creating your account. Would you like to <a href="/bug">report this error</a>?';
|
||||
} else { successMessage = 'Your account has been created. Next maybe you should download the <a href="/android">android app</a>. ' }
|
||||
if (cbc>1) /* waiting on other calls */ { cbc--; }
|
||||
else { done(null, user, { success:successMessage, failure:failMessage }); }
|
||||
//TODO: Validate and sanitize email
|
||||
// req.assert('email', 'Please enter a valid email address.').isEmail();
|
||||
// req.sanitize('email').normalizeEmail({ remove_dots: false });
|
||||
|
||||
User.findOne({'email':req.body.email},function(err,user){
|
||||
if (err){ next(err); }
|
||||
else if (!user) {
|
||||
req.flash('danger', `No user has <u>${req.body.email}</u> set as their email address. `);
|
||||
res.redirect('/login/forgot');
|
||||
} else {
|
||||
|
||||
// Set reset token to user
|
||||
user.createToken( function(err,token){
|
||||
if (err){ next(err); }
|
||||
|
||||
// Email reset link
|
||||
mail({
|
||||
from: '"Trackmap" <accounts@trackmap.tech>',
|
||||
to: user.email,
|
||||
subject: 'Reset your Trackmap password',
|
||||
text: `Hi, \n\nDid you request to reset your trackmap password? If so, follow this link to do so:\n${env.url}/account/password/${token}\n\nIf you didn't initiate this request, just ignore this email. `,
|
||||
html: `<p>Hi, </p><p>Did you request to reset your trackmap password? If so, follow this link to do so:<br><a href="${env.url}/account/password/${token}">${env.url}/account/password/${token}</a></p><p>If you didn't initiate this request, just ignore this email. </p>`
|
||||
}).then(function(){
|
||||
req.flash('success', `An email has been sent to <u>${req.body.email}</u>. Check your email for instructions to reset your password. `);
|
||||
res.redirect('/');
|
||||
}).catch(function(err){
|
||||
next(err);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}));
|
||||
|
||||
passport.use(new GoogleTokenStrategy({
|
||||
clientID: secret.googleClientId
|
||||
}, function(parsedToken, googleId, done) {
|
||||
User.findOne({googleID:googleId}, function(err, user) {
|
||||
if (err) {
|
||||
console.log('Error finding user for gToken login with google profile ID: '+googleId+'\n'+err); }
|
||||
if (!err && user !== null) { // Log in
|
||||
user.lastLogin = Date.now();
|
||||
user.save(function (err) {
|
||||
if (err) {
|
||||
console.log('Error saving user\'s lastLogin for gToken login with google profile ID: '+googleId+'\n'+err); }
|
||||
});
|
||||
|
||||
// Social
|
||||
app.get('/login/:service', function(req,res,next){
|
||||
var service = req.params.service;
|
||||
if (service==='google'){
|
||||
var sendParams = {scope:['profile']};
|
||||
}
|
||||
if (!req.user) { // Social login
|
||||
passport.authenticate(service, sendParams)(req,res,next);
|
||||
} else if (!req.user.auth[service]) { // Connect social account
|
||||
passport.authorize(service, sendParams)(req,res,next);
|
||||
} else { // Disconnect social account
|
||||
req.user.auth[service] = undefined;
|
||||
req.user.save(function(err){
|
||||
if (err){ return next(err); }
|
||||
else {
|
||||
req.flash('success', `${mw.capitalize(service)} account disconnected. `);
|
||||
res.redirect('/account');
|
||||
}
|
||||
});
|
||||
return done(err, user);
|
||||
} else { // No such user
|
||||
done(null, false);
|
||||
|
||||
}
|
||||
});
|
||||
}));
|
||||
app.get('/login/:service/cb', function(req,res,next){
|
||||
var service = req.params.service;
|
||||
if (!req.user) {
|
||||
passport.authenticate(service, loginOutcome)(req,res,next);
|
||||
} else {
|
||||
req.flash('success', `${mw.capitalize(service)} account connected. `);
|
||||
req.session.returnTo = '/account';
|
||||
passport.authenticate(service, connectOutcome)(req,res,next);
|
||||
}
|
||||
}, loginCallback);
|
||||
|
||||
// Old google auth
|
||||
// app.get('/auth/google', passport.authenticate('google', { scope: [
|
||||
// 'https://www.googleapis.com/auth/plus.login',
|
||||
// 'https://www.googleapis.com/auth/plus.profile.emails.read'
|
||||
// ] }));
|
||||
// app.get('/auth/google/callback', passport.authenticate('google', {
|
||||
// failureRedirect: '/',
|
||||
// failureFlash: true,
|
||||
// successRedirect: '/',
|
||||
// successFlash: true
|
||||
// } ));
|
||||
|
||||
// Android auth
|
||||
//TODO: See if there's a better method
|
||||
app.get('/auth/google/idtoken', passport.authenticate('google-id-token'), function (req,res) {
|
||||
if (!req.user) { res.sendStatus(401); }
|
||||
else { res.send(req.user); }
|
||||
} );
|
||||
|
||||
};
|
||||
|
||||
// passport.use(new GoogleStrategy({
|
||||
// clientID: env.googleClientId,
|
||||
// clientSecret: env.googleClientSecret,
|
||||
// callbackURL: env.url+'/auth/google/callback',
|
||||
// passReqToCallback: true
|
||||
// }, function(req, accessToken, refreshToken, profile, done) {
|
||||
|
||||
// // Check for user
|
||||
// User.findOne({googleID: profile.id}, function(err, user){
|
||||
|
||||
// // Error
|
||||
// if (err) { console.log('Error finding user with google ID: '+profile.id+'\n'+err); }
|
||||
|
||||
// // User found
|
||||
// if (!err && user !== null) /* Log user in */ {
|
||||
// if (!user.name) { user.name=profile.displayName; }
|
||||
// user.lastLogin = Date.now();
|
||||
// user.save(function (err, raw) {
|
||||
// if (err) { throwErr(req,err); }
|
||||
// }); done(null, user);
|
||||
// }
|
||||
|
||||
// // User not found
|
||||
// else /* create user */ {
|
||||
// user = new User();
|
||||
// user.googleID = profile.id;
|
||||
// user.name = profile.displayName;
|
||||
// user.email = profile.emails[0].value;
|
||||
// user.slug = slug(profile.displayName).toLowerCase();
|
||||
// user.created = Date.now();
|
||||
// user.lastLogin = Date.now();
|
||||
// // user.settings = { units:'standard', defaultMap:'road', defaultZoom:11, showSpeed:false, showTemp:false, showAlt:false, showStreetview:false },
|
||||
// // user.last = { lat:0, lon:0, dir:0, alt:0, spd:0 },
|
||||
// // user.isPro = false;
|
||||
// // user.isAdmin = false;
|
||||
// var cbc = 2;
|
||||
// var successMessage, failMessage;
|
||||
|
||||
// // Generate slug
|
||||
// (function checkSlug(s,cb) {
|
||||
// //console.log('checking ',s);
|
||||
// User.findOne({slug:s}, function(err, existingUser){
|
||||
// if (err) { console.log('No user found for ',slug,':',err); }
|
||||
// if (existingUser){
|
||||
// s = '';
|
||||
// while (s.length<6) {
|
||||
// s+='abcdefghijkmnpqrtuvwxy346789'.charAt(Math.floor(Math.random()*28));
|
||||
// }
|
||||
// checkSlug(s,cb);
|
||||
// } else { cb(s); }
|
||||
// });
|
||||
// })(user.slug, function(newSlug){
|
||||
// user.slug = newSlug;
|
||||
// if (cbc>1) /* waiting on other calls */ { cbc--; }
|
||||
// else { done(null, user, { success:successMessage, failure:failMessage }); }
|
||||
// });
|
||||
|
||||
// // Generate sk32
|
||||
// crypto.randomBytes(32, function(err,buf) {
|
||||
// if (err) {console.log('Unable to get random bytes:',err);}
|
||||
// if (!buf) {console.log('Unable to get random buffer');}
|
||||
// else {
|
||||
// user.sk32 = buf.toString('hex');
|
||||
// user.save(function(err) {
|
||||
// if (err) {
|
||||
// console.log('Error saving new user '+err);
|
||||
// var failMessage = 'Something went wrong creating your account. Would you like to <a href="/bug">report this error</a>?';
|
||||
// } else { successMessage = 'Your account has been created. Next maybe you should download the <a href="/android">android app</a>. ' }
|
||||
// if (cbc>1) /* waiting on other calls */ { cbc--; }
|
||||
// else { done(null, user, { success:successMessage, failure:failMessage }); }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// }
|
||||
|
||||
// });
|
||||
|
||||
// }));
|
||||
|
||||
// passport.use(new GoogleTokenStrategy({
|
||||
// clientID: env.googleClientId
|
||||
// }, function(parsedToken, googleId, done) {
|
||||
// User.findOne({googleID:googleId}, function(err, user) {
|
||||
// if (err) {
|
||||
// console.log('Error finding user for gToken login with google profile ID: '+googleId+'\n'+err); }
|
||||
// if (!err && user !== null) { // Log in
|
||||
// user.lastLogin = Date.now();
|
||||
// user.save(function (err) {
|
||||
// if (err) {
|
||||
// console.log('Error saving user\'s lastLogin for gToken login with google profile ID: '+googleId+'\n'+err); }
|
||||
// });
|
||||
// return done(err, user);
|
||||
// } else { // No such user
|
||||
// done(null, false);
|
||||
// }
|
||||
// });
|
||||
// }));
|
||||
|
|
|
@ -1,39 +1,36 @@
|
|||
'use strict';
|
||||
|
||||
const secret = require('./secrets.js');
|
||||
|
||||
var throwErr = function(req,err){
|
||||
console.error('middleware.js:5 '+typeof err);
|
||||
console.error('Middleware error:'+err+'\nfor request:\n'+req);
|
||||
if (secret.env==='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);
|
||||
}
|
||||
};
|
||||
|
||||
var ensureAuth = function(req,res,next){
|
||||
if (req.isAuthenticated()) { return next(); }
|
||||
else { res.redirect('/login'); }
|
||||
};
|
||||
|
||||
var ensureAdmin = function(req,res,next){
|
||||
ensureAuth(req,res,function(){
|
||||
if (req.user.isAdmin){ return next(); }
|
||||
else { next(); }
|
||||
//TODO: test this by logging in as !isAdmin and go to /admin
|
||||
// else if (!res.headersSent) { // 404 to users (not admin)
|
||||
// var err = new Error('404: Not found: '+req.url);
|
||||
// err.status = 404;
|
||||
// res.render('error.html', {
|
||||
// code: err.status
|
||||
// });
|
||||
// }
|
||||
});
|
||||
};
|
||||
const env = require('./env.js');
|
||||
|
||||
module.exports = {
|
||||
throwErr,
|
||||
ensureAuth,
|
||||
ensureAdmin
|
||||
|
||||
// Throw error
|
||||
throwErr: function(req,err){
|
||||
console.error('middleware.js:5 '+typeof err);
|
||||
console.error('Middleware error:'+err+'\nfor request:\n'+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);
|
||||
}
|
||||
},
|
||||
|
||||
// Capitalize the first letter of a string
|
||||
capitalize: function(str){ 'use strict';
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
},
|
||||
|
||||
// Ensure authentication
|
||||
ensureAuth: function(req,res,next){
|
||||
if (req.isAuthenticated()) { return next(); }
|
||||
else { res.redirect('/login'); }
|
||||
},
|
||||
|
||||
// Ensure administrator
|
||||
ensureAdmin: function(req,res,next){
|
||||
if (req.user.isAdmin){ return next(); }
|
||||
else { res.sendStatus(401); }
|
||||
//TODO: test this by logging in as !isAdmin and go to /admin
|
||||
}
|
||||
|
||||
};
|
|
@ -1,35 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
name: {type:String, required:true},
|
||||
email: String,
|
||||
slug: {type:String, required:true, unique:true},
|
||||
requestId: String,
|
||||
isAdmin: {type:Boolean, required:true, default:false},
|
||||
isPro: {type:Boolean, required:true, default:false},
|
||||
created: Date,
|
||||
lastLogin: Date,
|
||||
googleID: {type:Number, unique:true},
|
||||
settings: {
|
||||
units: {type:String, default:'standard'},
|
||||
defaultMap: {type:String, default:'road'},
|
||||
defaultZoom: {type:Number, default:11},
|
||||
showSpeed: {type:Boolean, default:false},
|
||||
showTemp: {type:Boolean, default:false},
|
||||
showAlt: {type:Boolean, default:false},
|
||||
showStreetview: {type:Boolean, default:false}
|
||||
},
|
||||
last: {
|
||||
time: Date,
|
||||
lat: {type:Number, default:0},
|
||||
lon: {type:Number, default:0},
|
||||
dir: {type:Number, default:0},
|
||||
alt: {type:Number, default:0},
|
||||
spd: {type:Number, default:0}
|
||||
},
|
||||
sk32: {type:String, required:true, unique:true}
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('User', userSchema);
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const router = require('express').Router(),
|
||||
mw = require('../middleware.js'),
|
||||
User = require('../models/user.js');
|
||||
User = require('../models.js').user;
|
||||
|
||||
router.route('/')
|
||||
.all(mw.ensureAdmin, function(req,res,next){
|
||||
|
@ -17,7 +17,7 @@ router.route('/')
|
|||
}
|
||||
if (cbc<1){ cbc++; }
|
||||
else { // done
|
||||
res.render('admin.html', {
|
||||
res.render('admin', {
|
||||
noFooter: '1',
|
||||
success:req.flash('success')[0],
|
||||
error:req.flash('error')[0]
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const router = require('express').Router(),
|
||||
passport = require('passport');
|
||||
|
||||
// Routes
|
||||
router.get('/login', function(req,res){
|
||||
res.redirect('/auth/google');
|
||||
});
|
||||
router.get('/logout', function(req,res){
|
||||
req.logout(); // Needs to clear cookies?
|
||||
req.flash('success', 'You have been logged out. ');
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
// Web app auth
|
||||
router.get('/auth/google', passport.authenticate('google', { scope: [
|
||||
'https://www.googleapis.com/auth/plus.login',
|
||||
'https://www.googleapis.com/auth/plus.profile.emails.read'
|
||||
] }));
|
||||
router.get('/auth/google/callback', passport.authenticate('google', {
|
||||
failureRedirect: '/',
|
||||
failureFlash: true,
|
||||
successRedirect: '/',
|
||||
successFlash: true
|
||||
} ));
|
||||
|
||||
// Android auth
|
||||
router.get('/auth/google/idtoken', passport.authenticate('google-id-token'), function (req,res) {
|
||||
if (!req.user) { res.sendStatus(401); }
|
||||
else { res.send(req.user); }
|
||||
} );
|
||||
|
||||
module.exports = router;
|
|
@ -3,12 +3,12 @@
|
|||
const slug = require('slug'),
|
||||
xss = require('xss'),
|
||||
mw = require('../middleware.js'),
|
||||
User = require('../models/user.js'),
|
||||
User = require('../models.js').user,
|
||||
router = require('express').Router();
|
||||
|
||||
// Index
|
||||
router.get('/', function(req,res,next) {
|
||||
res.render('index.html');
|
||||
res.render('index');
|
||||
});
|
||||
|
||||
// Settings
|
||||
|
@ -20,7 +20,7 @@ router.route('/settings').all(mw.ensureAuth, function(req,res,next){
|
|||
.get(function(req,res,next){
|
||||
User.findById(req.session.passport.user, function(err,user){
|
||||
if (err){ console.log('Error finding settings for user:',err); mw.throwErr(req,err); }
|
||||
res.render('settings.html');
|
||||
res.render('settings');
|
||||
});
|
||||
})
|
||||
|
||||
|
@ -70,7 +70,7 @@ router.route('/pro').all(mw.ensureAuth, function(req,res,next){
|
|||
User.findById(req.session.passport.user, function(err, user){
|
||||
if (err){ mw.throwErr(req,err); }
|
||||
if (!user){ next(); }
|
||||
else { res.render('pro.html'); }
|
||||
else { res.render('pro'); }
|
||||
});
|
||||
})
|
||||
|
||||
|
@ -87,13 +87,15 @@ router.route('/pro').all(mw.ensureAuth, function(req,res,next){
|
|||
});
|
||||
|
||||
// Help
|
||||
router.route('/help').get(mw.ensureAuth, function(req,res){
|
||||
res.render('help.html');
|
||||
router.get('/help', mw.ensureAuth, function(req,res){
|
||||
res.render('help');
|
||||
});
|
||||
|
||||
// Terms of Service
|
||||
// Terms of Service and Privacy Policy
|
||||
router.get('/terms', function(req,res){
|
||||
res.render('terms.html');
|
||||
res.render('terms');
|
||||
}).get('/privacy', function(req,res){
|
||||
res.render('privacy');
|
||||
});
|
||||
|
||||
module.exports = router;
|
|
@ -2,8 +2,13 @@
|
|||
|
||||
const router = require('express').Router(),
|
||||
mw = require('../middleware.js'),
|
||||
secrets = require('../secrets.js'),
|
||||
User = require('../models/user.js');
|
||||
env = require('../env.js'),
|
||||
User = require('../models.js').user;
|
||||
|
||||
// Redirect to real slug
|
||||
router.get('/', mw.ensureAuth, function(req,res){
|
||||
res.redirect(`/map/${req.user.slug}`);
|
||||
});
|
||||
|
||||
// Show map
|
||||
router.get('/:slug?', function(req,res,next){
|
||||
|
@ -44,9 +49,9 @@ router.get('/:slug?', function(req,res,next){
|
|||
res.redirect('/');
|
||||
} else {
|
||||
if (user && !mapuser) { mapuser = user; }
|
||||
res.render('map.html', {
|
||||
res.render('map', {
|
||||
mapuser: mapuser,
|
||||
mapApi: secrets.googleMapsAPI,
|
||||
mapApi: env.googleMapsAPI,
|
||||
user: user,
|
||||
noFooter: '1',
|
||||
noHeader: (req.query.noheader)?req.query.noheader.match(/\d/)[0]:'',
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
const router = require('express').Router(),
|
||||
slug = require('slug'),
|
||||
User = require('../models/user.js');
|
||||
User = require('../models.js').user;
|
||||
|
||||
// robots.txt
|
||||
router.get('/robots.txt', function(req,res){
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
// Imports
|
||||
const User = require('./models/user.js');
|
||||
const User = require('./models.js').user;
|
||||
|
||||
// Check for tracking clients
|
||||
function checkForUsers(io, user) {
|
||||
|
|
|
@ -15,10 +15,15 @@
|
|||
"mongodb": "^2.1.4",
|
||||
"mongoose": "^4.9.0",
|
||||
"node-jose": "^0.8.0",
|
||||
"nodemailer": "^3.1.8",
|
||||
"nunjucks": "^2.3.0",
|
||||
"passport": "^0.3.2",
|
||||
"passport-facebook": "^2.1.1",
|
||||
"passport-google-id-token": "^0.4.0",
|
||||
"passport-google-oauth2": "^0.1.6",
|
||||
"passport-google-oauth20": "^1.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"passport-twitter": "^1.0.4",
|
||||
"slug": "^0.9.1",
|
||||
"socket.io": "^1.4.4"
|
||||
},
|
||||
|
@ -38,7 +43,6 @@
|
|||
"test": "mocha test.js",
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js",
|
||||
"deploy": "ssh khp deploy-tracman",
|
||||
"update": "sudo n stable && sudo npm update --save && sudo npm prune"
|
||||
},
|
||||
"repository": {
|
||||
|
|
65
server.js
65
server.js
|
@ -10,8 +10,8 @@ const
|
|||
nunjucks = require('nunjucks'),
|
||||
passport = require('passport'),
|
||||
flash = require('connect-flash'),
|
||||
secret = require('./config/secrets.js'),
|
||||
User = require('./config/models/user.js'),
|
||||
env = require('./config/env.js'),
|
||||
User = require('./config/models.js').user,
|
||||
app = express(),
|
||||
http = require('http').Server(app),
|
||||
io = require('socket.io')(http),
|
||||
|
@ -19,24 +19,27 @@ const
|
|||
|
||||
|
||||
/* SETUP */ {
|
||||
/* Database */ mongoose.connect(secret.mongoSetup, {
|
||||
|
||||
/* Database */ mongoose.connect(env.mongoSetup, {
|
||||
server:{socketOptions:{
|
||||
keepAlive:1, connectTimeoutMS:30000 }},
|
||||
replset:{socketOptions:{
|
||||
keepAlive:1, connectTimeoutMS:30000 }}
|
||||
});
|
||||
|
||||
/* Templates */ nunjucks.configure(__dirname+'/views', {
|
||||
autoescape: true,
|
||||
express: app
|
||||
});
|
||||
/* Templates */ {
|
||||
nunjucks.configure(__dirname+'/views', {
|
||||
autoescape: true,
|
||||
express: app
|
||||
});
|
||||
app.set('view engine','html');
|
||||
}
|
||||
|
||||
/* Session */ {
|
||||
app.use(cookieParser(secret.cookie));
|
||||
// app.use(expressSession({
|
||||
app.use(cookieParser(env.cookie));
|
||||
app.use(cookieSession({
|
||||
cookie: {maxAge:60000},
|
||||
secret: secret.session,
|
||||
secret: env.session,
|
||||
saveUninitialized: true,
|
||||
resave: true
|
||||
}));
|
||||
|
@ -48,18 +51,22 @@ const
|
|||
}
|
||||
|
||||
/* Auth */ {
|
||||
require('./config/passport.js')(passport);
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
require('./config/auth.js');
|
||||
passport.serializeUser(function(user,done) {
|
||||
done(null, user.id);
|
||||
});
|
||||
passport.deserializeUser(function(id,done) {
|
||||
User.findById(id, function(err, user) {
|
||||
if(!err) done(null, user);
|
||||
else done(err, null);
|
||||
});
|
||||
});
|
||||
require('./config/auth.js')(app, passport);
|
||||
// app.use(passport.initialize());
|
||||
// app.use(passport.session());
|
||||
// require('./config/auth.js');
|
||||
// passport.serializeUser(function(user,done) {
|
||||
// done(null, user.id);
|
||||
// });
|
||||
// passport.deserializeUser(function(id,done) {
|
||||
// User.findById(id, function(err, user) {
|
||||
// if(!err) done(null, user);
|
||||
// else done(err, null);
|
||||
// });
|
||||
// });
|
||||
}
|
||||
|
||||
/* Routes */ {
|
||||
|
@ -87,7 +94,6 @@ const
|
|||
// Main routes
|
||||
app.use('/',
|
||||
require('./config/routes/index.js'),
|
||||
require('./config/routes/auth.js'),
|
||||
require('./config/routes/misc.js')
|
||||
);
|
||||
|
||||
|
@ -110,11 +116,11 @@ const
|
|||
});
|
||||
|
||||
// Handlers
|
||||
if (secret.env=='production') {
|
||||
if (env.mode=='production') {
|
||||
app.use(function(err,req,res,next) {
|
||||
if (res.headersSent) { return next(err); }
|
||||
res.status(err.status||500);
|
||||
res.render('error.html', {
|
||||
res.render('error', {
|
||||
code: err.status
|
||||
});
|
||||
});
|
||||
|
@ -124,7 +130,7 @@ const
|
|||
console.log(err);
|
||||
if (res.headersSent) { return next(err); }
|
||||
res.status(err.status||500);
|
||||
res.render('error.html', {
|
||||
res.render('error', {
|
||||
code: err.status,
|
||||
message: err.message,
|
||||
error: err
|
||||
|
@ -141,11 +147,17 @@ const
|
|||
|
||||
/* RUNTIME */ {
|
||||
|
||||
// Check mail transporter
|
||||
require('./config/mail.js').verify(function(err, success) {
|
||||
if (err){ console.error(`SMTP Error: ${err}`); }
|
||||
console.log(success?'SMTP ready...':'SMTP not ready!');
|
||||
});
|
||||
|
||||
// Listen
|
||||
http.listen(secret.port, function(){
|
||||
http.listen(env.port, function(){
|
||||
console.log(
|
||||
'==========================================\n'+
|
||||
'Listening at '+secret.url+
|
||||
'Listening at '+env.url+
|
||||
'\n=========================================='
|
||||
);
|
||||
|
||||
|
@ -158,6 +170,7 @@ const
|
|||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
module.exports = app;
|
||||
|
|
|
@ -1,105 +1,41 @@
|
|||
/* Resets, Clears & Defaults */
|
||||
*, *:after, *:before {
|
||||
/* Global */
|
||||
div, footer, .fa,
|
||||
.container, .container:before, .container:after {
|
||||
box-sizing: border-box;
|
||||
}::-webkit-scrollbar {
|
||||
width: 5vw;
|
||||
min-width:10px;
|
||||
max-width:40px;
|
||||
}::-webkit-scrollbar-track {
|
||||
background-color: #080808;
|
||||
}::-webkit-scrollbar-thumb {
|
||||
border-radius: .2vw;
|
||||
background: #333;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #080808;
|
||||
color: #eee;
|
||||
}
|
||||
body, input, textarea {
|
||||
padding: 0; margin: 0;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 18px;
|
||||
color: #eee;
|
||||
font-weight: 600;
|
||||
}
|
||||
.flexbox {
|
||||
width:100%;
|
||||
display:flex;
|
||||
justify-content:space-around;
|
||||
body {
|
||||
background-color: #080808;
|
||||
}
|
||||
.flexbox.stretch { justify-content:space-between; }
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
white-space: -moz-pre-wrap;
|
||||
white-space: -pre-wrap;
|
||||
white-space: -o-pre-wrap;
|
||||
word-wrap: break-word;
|
||||
::-webkit-scrollbar {
|
||||
width: 5vw;
|
||||
min-width:10px;
|
||||
max-width:40px;
|
||||
}::-webkit-scrollbar-track {
|
||||
background-color: #080808;
|
||||
background-color: rgba(8,8,8,0);
|
||||
}::-webkit-scrollbar-thumb {
|
||||
border-radius: .2vw;
|
||||
background: #333;
|
||||
}
|
||||
.dark pre {
|
||||
-moz-box-shadow: 2px 2px 4px #000;
|
||||
-webkit-box-shadow: 2px 2px 4px #000;
|
||||
box-shadow: 2px 2px 4px #000;
|
||||
background-color: rgba(255,255,255,.03);
|
||||
color: #aaa;
|
||||
padding: 1%;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: .25rem;
|
||||
::selection {
|
||||
background: #999;
|
||||
}
|
||||
.dark .form-control:disabled, .dark .form-control:disabled {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
}
|
||||
.input-group {
|
||||
margin-bottom:30px;
|
||||
}
|
||||
input[type="checkbox"] {
|
||||
margin: 8px 0;
|
||||
}
|
||||
.form-group#buttons {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
input[type="checkbox"] {
|
||||
display: inline-block;
|
||||
}
|
||||
.help-block {margin-top:-20px;}
|
||||
|
||||
.alert {
|
||||
z-index:10;
|
||||
}
|
||||
.alert-header {
|
||||
position: relative;
|
||||
top: 58px;
|
||||
} .alert-header.alert-danger {
|
||||
z-index: 103;
|
||||
} .alert-header.alert-warning {
|
||||
z-index: 102;
|
||||
} .alert-header.alert-success {
|
||||
z-index: 101;
|
||||
}
|
||||
.alert:not(.alert-dismissible) {
|
||||
text-align: center;
|
||||
}
|
||||
.alert a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.alert a:hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
::-moz-selection {
|
||||
background: #999;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
h1, h2, h3, p {
|
||||
margin: 0 0 20px 0;
|
||||
/* Elements */
|
||||
h1, h2, h3 {
|
||||
margin: 0 0 5% 0;
|
||||
position: relative;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
h1,h2,h3,h4 { font-weight: 600; }
|
||||
h1 {
|
||||
font-size: 48px;
|
||||
|
@ -109,27 +45,20 @@ h2 {
|
|||
line-height: 36px; }
|
||||
h3 { font-size: 28px; }
|
||||
h4 { font-size: 20px; }
|
||||
|
||||
.red { color: #fb6e3d; }
|
||||
.shadow {
|
||||
-moz-box-shadow: .18vw .18vw .36vw #000;
|
||||
-webkit-box-shadow: .18vw .18vw .36vw #000;
|
||||
box-shadow: .18vw .18vw .36vw #000;
|
||||
} .shadow:active {
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 10vh;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #fbc93d;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
main a:hover:not(.btn) {
|
||||
color: #fbc93d;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.light a {
|
||||
.light a:not(.btn) {
|
||||
color:#111;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
@ -138,6 +67,10 @@ a:hover {
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
width: 90%;
|
||||
margin: 10% auto;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
@ -145,27 +78,47 @@ p img {
|
|||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
width: auto;
|
||||
margin: 8px;
|
||||
}
|
||||
.with-errors {
|
||||
color: #d9534f;
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
white-space: -moz-pre-wrap;
|
||||
white-space: -pre-wrap;
|
||||
white-space: -o-pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #999;
|
||||
.hide { display:none }
|
||||
.red { color: #fb6e3d; }
|
||||
.shadow {
|
||||
-moz-box-shadow: .18vw .18vw .36vw #000;
|
||||
-webkit-box-shadow: .18vw .18vw .36vw #000;
|
||||
box-shadow: .18vw .18vw .36vw #000;
|
||||
} .shadow:active {
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
::-moz-selection {
|
||||
background: #999;
|
||||
}
|
||||
|
||||
/* End Resets, Clears & Defaults */
|
||||
|
||||
.container {
|
||||
.inline { display: inline-block; }
|
||||
.flex {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.flex.stretch { justify-content: space-between; }
|
||||
.left { float: left; }
|
||||
.right { float: right; }
|
||||
|
||||
main {
|
||||
top: 60px;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.container {
|
||||
padding-right: 5%;
|
||||
padding-left: 5%;
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.container:after {
|
||||
|
@ -174,56 +127,122 @@ input[type="checkbox"] {
|
|||
clear: both;
|
||||
}
|
||||
section {
|
||||
padding: 100px 0 50px;
|
||||
padding: 10vh 0 5vh;
|
||||
}
|
||||
|
||||
/* Alerts */
|
||||
.alert {
|
||||
z-index: 10;
|
||||
padding: 15px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.alert a {
|
||||
z-index: 10;
|
||||
color: inherit;
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.alert a:hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.alert h4 {
|
||||
margin-top: 0;
|
||||
color: inherit;
|
||||
}
|
||||
.alert > p,
|
||||
.alert > ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.alert > p + p {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.alert-dismissable {
|
||||
padding-right: 35px;
|
||||
}
|
||||
.alert .close,
|
||||
.alert-dismissible .close {
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
color: inherit;
|
||||
}
|
||||
.alert-success {
|
||||
color: #dff0d8;
|
||||
background-color: #3c763d;
|
||||
}
|
||||
.alert-info {
|
||||
color: #d9edf7;
|
||||
background-color: #31708f;
|
||||
}
|
||||
.alert-warning {
|
||||
color: #fcf8e3;
|
||||
background-color: #8a6d3b;
|
||||
}
|
||||
.alert-danger {
|
||||
color: #f2dede;
|
||||
background-color: #a94442;
|
||||
}
|
||||
.alert.alert-header {
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
top: 58px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
font-weight:600;
|
||||
display: inline-block;
|
||||
padding: 15px 30px;
|
||||
transition: 200ms;
|
||||
background: transparent;
|
||||
transition: 100ms;
|
||||
cursor: pointer;
|
||||
-moz-box-shadow: 2px 2px 4px #000;
|
||||
-webkit-box-shadow: 2px 2px 4px #000;
|
||||
box-shadow: 2px 2px 4px #000;
|
||||
}
|
||||
.dark .btn {
|
||||
color: #fff;
|
||||
border: 1px solid #fff;
|
||||
}
|
||||
.dark .btn:hover:not(.disabled),
|
||||
.dark .btn:active:not(.disabled),
|
||||
.dark .btn:focus:not(.disabled) {
|
||||
background: rgba(255,255,255,0.1);
|
||||
}.dark.btn:active:not(.disabled) {
|
||||
-moz-box-shadow: 0;
|
||||
-webkit-box-shadow: 0;
|
||||
box-shadow: 0;
|
||||
}
|
||||
.light .btn {
|
||||
color: #222;
|
||||
color: #eee;
|
||||
border: 1px solid #999;
|
||||
border-radius: .5vw;
|
||||
} .btn:not(.disabled) {
|
||||
-moz-box-shadow:
|
||||
inset .11vw .18vw .52vw rgba(255,255,255,.2),
|
||||
inset -.11vw -.18vw .52vw rgba(0,0,0,.4),
|
||||
.11vw .18vw .52vw #000;
|
||||
-webkit-box-shadow:
|
||||
inset .11vw .18vw .52vw rgba(255,255,255,.2),
|
||||
inset -.11vw -.18vw .52vw rgba(0,0,0,.4),
|
||||
.18vw .18vw .36vw #000;
|
||||
box-shadow:
|
||||
inset .11vw .18vw .52vw rgba(255,255,255,.2),
|
||||
inset -.11vw -.18vw .52vw rgba(0,0,0,.4),
|
||||
.18vw .18vw .36vw #000;
|
||||
} .btn:hover:not(.disabled) {
|
||||
text-decoration: none;
|
||||
border: 1px solid #222;
|
||||
}
|
||||
.light .btn:hover:not(.disabled),
|
||||
.light .btn:active:not(.disabled),
|
||||
.light .btn:focus:not(.disabled) {
|
||||
background: rgba(0,0,0,0.1);
|
||||
}
|
||||
.btn.yellow {
|
||||
color: #fbc93d;
|
||||
background: rgba(255,255,255,0.2);
|
||||
} .btn:active:not(.disabled) {
|
||||
-moz-box-shadow:
|
||||
inset .11vw .18vw .52vw rgba(0,0,0,.4),
|
||||
inset -.11vw -.18vw .52vw rgba(255,255,255,.2);
|
||||
-webkit-box-shadow:
|
||||
inset .11vw .18vw .52vw rgba(0,0,0,.4),
|
||||
inset -.11vw -.18vw .52vw rgba(255,255,255,.2);
|
||||
box-shadow:
|
||||
inset .11vw .18vw .52vw rgba(0,0,0,.4),
|
||||
inset -.11vw -.18vw .52vw rgba(255,255,255,.2);
|
||||
} .btn:focus:not(.disabled){
|
||||
border: 1px solid #fbc93d;
|
||||
}
|
||||
.dark .btn.yellow:hover:not(.disabled),
|
||||
.dark .btn.yellow:active:not(.disabled),
|
||||
.dark .btn.yellow:focus:not(.disabled) {
|
||||
background: rgba(251,201,61,0.1);
|
||||
}
|
||||
.btn.smaller {
|
||||
padding: 10px 25px;
|
||||
.btn.main {
|
||||
color: #fbc93d;
|
||||
}
|
||||
.btn .fa {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.group {
|
||||
width: 100%;
|
||||
}
|
||||
.group div {
|
||||
display: flex;
|
||||
margin-bottom: 10vh;
|
||||
}
|
|
@ -1,10 +1,13 @@
|
|||
footer {
|
||||
font-weight: 300;
|
||||
width:100%;
|
||||
overflow:auto;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
background: #111;
|
||||
color: #ccc;
|
||||
padding: 0 20px;
|
||||
-moz-box-shadow: inset 0 .25vw 1vw #222;
|
||||
-webkit-box-shadow: inset 0 .25vw 1vw #222;
|
||||
box-shadow: inset 0 .25vw 1vw #222;
|
||||
}
|
||||
footer .left {
|
||||
float: left;
|
||||
|
@ -33,7 +36,6 @@ footer a .fa {
|
|||
footer .fa a:hover, footer .fa a:focus {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
footer {
|
||||
padding: 0 10px;
|
||||
|
@ -49,4 +51,4 @@ footer .fa a:hover, footer .fa a:focus {
|
|||
footer .right {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,9 +5,10 @@ header {
|
|||
top: 0; left: 0;
|
||||
width: 100%;
|
||||
z-index: 200;
|
||||
} header a:hover, header a:focus {
|
||||
color: #fbc93d;
|
||||
}
|
||||
header .logo {
|
||||
|
||||
float: left;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
padding: 13px 23px;
|
||||
|
@ -16,53 +17,39 @@ header .logo {
|
|||
font-size: 22px;
|
||||
line-height: 30px;
|
||||
margin: 0;
|
||||
}
|
||||
header a:hover, header a:focus {
|
||||
color: #fbc93d;
|
||||
}
|
||||
header .logo a {
|
||||
} header .logo a {
|
||||
color:inherit;
|
||||
font:inherit;
|
||||
text-decoration:inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
header .logo img {
|
||||
} header .logo img {
|
||||
margin-right: 10px;
|
||||
position: relative;
|
||||
width:28px;
|
||||
height:28px;
|
||||
vertical-align: middle;
|
||||
} header .logo:hover {
|
||||
text-decoration: none;
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
header nav {
|
||||
float: right;
|
||||
}
|
||||
header nav ul {
|
||||
} header nav ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
header nav ul li {
|
||||
} header nav ul li {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
}
|
||||
header nav ul li a, header nav ul li span {
|
||||
} header nav ul li a, header nav ul li span {
|
||||
text-decoration:inherit;
|
||||
display: inline-block;
|
||||
padding: 15px 20px;
|
||||
color: #fff;
|
||||
transition: 200ms;
|
||||
}
|
||||
header nav ul li a:hover,
|
||||
transition: 100ms;
|
||||
} header nav ul li a:hover,
|
||||
header nav ul li a:focus,
|
||||
header nav ul li a.active,
|
||||
header .logo:hover {
|
||||
text-decoration: none;
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
.alert.header {
|
||||
position: relative;
|
||||
border-radius: 0;
|
||||
top: 58px;
|
||||
width: 100%;
|
||||
}
|
||||
header .hamburger {
|
||||
display: none;
|
||||
padding: 5px;
|
||||
|
@ -70,18 +57,20 @@ header .hamburger {
|
|||
transition-property: opacity, -webkit-filter;
|
||||
transition-property: opacity, filter;
|
||||
transition-property: opacity, filter, -webkit-filter;
|
||||
transition-duration: 0.15s;
|
||||
transition-timing-function: linear; }
|
||||
.hamburger:hover {
|
||||
opacity: 0.7; }
|
||||
header .hamburger-box {
|
||||
transition-duration: 150ms;
|
||||
transition-timing-function: linear;
|
||||
} header .hamburger:hover {
|
||||
opacity: 0.7;
|
||||
} header .hamburger-box {
|
||||
width: 40px;
|
||||
height: 24px;
|
||||
position: relative; }
|
||||
header .hamburger-inner {
|
||||
position: relative;
|
||||
} header .hamburger-inner {
|
||||
top: 50%;
|
||||
margin-top: -2px; }
|
||||
header .hamburger-inner, header .hamburger-inner::before, header .hamburger-inner::after {
|
||||
margin-top: -2px;
|
||||
} header .hamburger-inner,
|
||||
header .hamburger-inner::before,
|
||||
header .hamburger-inner::after {
|
||||
width: 40px;
|
||||
height: 4px;
|
||||
background-color: #fff;
|
||||
|
@ -90,42 +79,47 @@ header .hamburger-inner {
|
|||
transition-property: -webkit-transform;
|
||||
transition-property: transform;
|
||||
transition-property: transform, -webkit-transform;
|
||||
transition-duration: 0.15s;
|
||||
transition-timing-function: ease; }
|
||||
header .hamburger-inner::before, header .hamburger-inner::after {
|
||||
transition-duration: 150ms;
|
||||
transition-timing-function: ease;
|
||||
} header .hamburger-inner::before, header .hamburger-inner::after {
|
||||
content: "";
|
||||
display: block; }
|
||||
header .hamburger-inner::before {
|
||||
top: -10px; }
|
||||
header .hamburger-inner::after {
|
||||
bottom: -10px; }
|
||||
|
||||
header .hamburger--slider .hamburger-inner {
|
||||
top: 0; }
|
||||
header .hamburger--slider .hamburger-inner::before {
|
||||
display: block;
|
||||
} header .hamburger-inner::before {
|
||||
top: -10px;
|
||||
} header .hamburger-inner::after {
|
||||
bottom: -10px;
|
||||
} header .hamburger--slider .hamburger-inner {
|
||||
top: 0;
|
||||
} header .hamburger--slider .hamburger-inner::before {
|
||||
top: 10px;
|
||||
transition-property: opacity, -webkit-transform;
|
||||
transition-property: transform, opacity;
|
||||
transition-property: transform, opacity, -webkit-transform;
|
||||
transition-timing-function: ease;
|
||||
transition-duration: 0.2s; }
|
||||
header .hamburger--slider .hamburger-inner::after {
|
||||
top: 20px; }
|
||||
|
||||
header .hamburger--slider.is-active .hamburger-inner {
|
||||
-webkit-transform: translate3d(0, 10px, 0) rotate(45deg);
|
||||
transform: translate3d(0, 10px, 0) rotate(45deg); }
|
||||
header .hamburger--slider.is-active .hamburger-inner::before {
|
||||
transition-duration: 200ms;
|
||||
} header .hamburger--slider .hamburger-inner::after {
|
||||
top: 20px;
|
||||
} header .hamburger--slider.is-active .hamburger-inner {
|
||||
-webkit-transform: translate3d(0, 10px, 0) rotate(45deg);
|
||||
-moz-transform: translate3d(0, 10px, 0) rotate(45deg);
|
||||
-md-transform: translate3d(0, 10px, 0) rotate(45deg);
|
||||
-o-transform: translate3d(0, 10px, 0) rotate(45deg);
|
||||
transform: translate3d(0, 10px, 0) rotate(45deg);
|
||||
} header .hamburger--slider.is-active .hamburger-inner::before {
|
||||
-webkit-transform: rotate(-45deg) translate3d(-5.71429px, -6px, 0);
|
||||
transform: rotate(-45deg) translate3d(-5.71429px, -6px, 0);
|
||||
opacity: 0; }
|
||||
header .hamburger--slider.is-active .hamburger-inner::after {
|
||||
-moz-transform: rotate(-45deg) translate3d(-5.71429px, -6px, 0);
|
||||
-ms-transform: rotate(-45deg) translate3d(-5.71429px, -6px, 0);
|
||||
-o-transform: rotate(-45deg) translate3d(-5.71429px, -6px, 0);
|
||||
transform: rotate(-45deg) translate3d(-5.71429px, -6px, 0);
|
||||
opacity: 0;
|
||||
} header .hamburger--slider.is-active .hamburger-inner::after {
|
||||
-webkit-transform: translate3d(0, -20px, 0) rotate(-90deg);
|
||||
transform: translate3d(0, -20px, 0) rotate(-90deg); }
|
||||
|
||||
|
||||
-moz-transform: translate3d(0, -20px, 0) rotate(-90deg);
|
||||
-ms-transform: translate3d(0, -20px, 0) rotate(-90deg);
|
||||
-o-transform: translate3d(0, -20px, 0) rotate(-90deg);
|
||||
transform: translate3d(0, -20px, 0) rotate(-90deg);
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
header { padding:0; }
|
||||
header nav ul li a { padding:15px; }
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
|
@ -138,17 +132,14 @@ header .hamburger--slider.is-active .hamburger-inner {
|
|||
width: 100%;
|
||||
max-width: 300px;
|
||||
background: #333;
|
||||
transition: 200ms;
|
||||
}
|
||||
header nav.visible {
|
||||
transition: 100ms;
|
||||
} header nav.visible {
|
||||
right: 0px;
|
||||
}
|
||||
header nav ul li {
|
||||
} header nav ul li {
|
||||
display: block;
|
||||
float: none;
|
||||
width: 100%;
|
||||
}
|
||||
header nav ul li a {
|
||||
} header nav ul li a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
|
@ -160,4 +151,4 @@ header .hamburger--slider.is-active .hamburger-inner {
|
|||
right: 10px;
|
||||
top: 13px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
/* global $ */
|
||||
'use strict';
|
||||
|
||||
$(document).ready(function(){
|
||||
|
@ -20,4 +21,9 @@ $(document).ready(function(){
|
|||
$('nav').removeClass('visible');
|
||||
});
|
||||
|
||||
// Close alerts
|
||||
$('.alert-dismissible .close').click(function() {
|
||||
$(this).parent().slideUp(500);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{% extends 'templates/base.html' %}
|
||||
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<link href="/static/css/index.css" rel="stylesheet">
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<script src="/static/js/index.js"></script>
|
||||
|
||||
<section class='splash dark' id='splash'>
|
||||
|
|
|
@ -1,38 +1,59 @@
|
|||
{% extends 'templates/base.html' %}
|
||||
{% block title %}{{ super() }} | Login{% endblock %}
|
||||
{% block head %}
|
||||
{{super()}}
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/form.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/login.css">
|
||||
<style>
|
||||
p, input, #social-login {
|
||||
margin-bottom: 5vh;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<section class='dark'>
|
||||
|
||||
<section class='container'>
|
||||
|
||||
<h1>Welcome!</h1>
|
||||
|
||||
<div class='container wrap'>
|
||||
|
||||
<h1>Login</h1>
|
||||
|
||||
<ul>
|
||||
<li><a id='google-login' style="cursor:pointer">Google</a></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<div class='flex'>
|
||||
|
||||
<div class='login'>
|
||||
|
||||
<script src="https://www.gstatic.com/firebasejs/3.7.0/firebase.js"></script>
|
||||
<script>
|
||||
firebase.initializeApp({
|
||||
apiKey: "AIzaSyDPYY_Fw3FXLm0hKfIfc8qlrc98zZiN4IY",
|
||||
authDomain: "tracman-b894f.firebaseapp.com",
|
||||
databaseURL: "https://tracman-b894f.firebaseio.com",
|
||||
storageBucket: "tracman-b894f.appspot.com",
|
||||
messagingSenderId: "483494341936"
|
||||
});
|
||||
$('#google-login').click(function() {
|
||||
var googleProvider = new firebase.auth.GoogleAuthProvider();
|
||||
firebase.auth().signInWithPopup(googleProvider).then(function(result) {
|
||||
console.log(`Successfully logged in ${result.user}`);
|
||||
}).catch(function(error) {
|
||||
console.error(error.message);
|
||||
});
|
||||
});
|
||||
<h3>Login</h3>
|
||||
<form method="post">
|
||||
|
||||
<div id='social-login' class='flex'>
|
||||
<a href="/login/google" class='gp btn'><i class="fa fa-google-plus"></i></a>
|
||||
<a href="/login/facebook" class='fb btn'><i class="fa fa-facebook"></i></a>
|
||||
<a href="/login/twitter" class='tw btn'><i class="fa fa-twitter"></i></a>
|
||||
</div>
|
||||
|
||||
<input type="email" placeholder="Email" name="email" required>
|
||||
<input type="password" placeholder="Password" name="password" required>
|
||||
|
||||
<input type="submit" value="Sign in" class='btn main'>
|
||||
|
||||
<p><a href="/login/forgot">Forgot your password?</a></p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<hr class='hide'>
|
||||
|
||||
<div class='signup'>
|
||||
<h3>Create account</h3>
|
||||
<p>Welcome aboard! </p>
|
||||
<form action="/signup" method="POST">
|
||||
<input type="email" name="email" placeholder="Your Email" required>
|
||||
<p>You will be sent an email confrimation with a link to create a password. </p>
|
||||
<p>By signing up, you agree to our <a href="/terms">terms of service</a> and <a href="/privacy">privacy policy</a>. </p>
|
||||
<input type="submit" value="Sign up" class='btn'>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</script>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -5,10 +5,10 @@
|
|||
<title>{% block title %}Tracman{% endblock %}</title>
|
||||
|
||||
<link rel="manifest" href="/static/manifest.webmanifest">
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<meta http-equiv="Content-type" content="text/html;charset=utf-8">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="author" content="Keith Irwin">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0">
|
||||
<meta name="keywords" content="map, phone, gps, link, location, track, friends, app">
|
||||
<meta name="description" content="Tracman lets you see and share your phone's exact realtime location">
|
||||
<meta name="theme-color" content="#222">
|
||||
|
@ -25,14 +25,12 @@
|
|||
<link rel="icon apple-touch-icon" sizes="228x228" type="image/png" href="/static/img/icon/by/228.png">
|
||||
<link rel="apple-touch-icon-precomposed" type="image/png" href="/static/img/icon/by/152.png">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/bootstrap.css">
|
||||
<link href="/static/css/base.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:300,600">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/base.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Open+Sans:300,600">
|
||||
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Merriweather:300,700">
|
||||
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
{% if not noHeader %}<link href="/static/css/header.css" rel="stylesheet">{% endif %}
|
||||
{% if not noFooter %}<link href="/static/css/footer.css" rel="stylesheet">{% endif %}
|
||||
|
@ -40,11 +38,16 @@
|
|||
<body>
|
||||
|
||||
{% if not noHeader %}{% include 'templates/header.html' %}{% endif %}
|
||||
{% block main %}Loading... {% endblock %}
|
||||
{% if not noFooter %}{% include 'templates/footer.html' %}{% endif %}
|
||||
<main>
|
||||
{% block main %}{% endblock %}
|
||||
{% if not noFooter %}{% include 'templates/footer.html' %}{% endif %}
|
||||
</main>
|
||||
|
||||
<!-- Google Analytics -->
|
||||
<!-- Javascript -->
|
||||
{% block javascript %}
|
||||
<script>
|
||||
|
||||
// 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);
|
||||
|
@ -53,19 +56,9 @@
|
|||
ga('create','UA-44266909-3','auto');
|
||||
ga('require','linkid');
|
||||
ga('send','pageview');
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Firebase -->
|
||||
<script src="https://www.gstatic.com/firebasejs/3.7.0/firebase.js"></script>
|
||||
<script>
|
||||
firebase.initializeApp({
|
||||
apiKey: "AIzaSyDPYY_Fw3FXLm0hKfIfc8qlrc98zZiN4IY",
|
||||
authDomain: "tracman-b894f.firebaseapp.com",
|
||||
databaseURL: "https://tracman-b894f.firebaseio.com",
|
||||
storageBucket: "tracman-b894f.appspot.com",
|
||||
messagingSenderId: "483494341936"
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -13,28 +13,25 @@
|
|||
</div>
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav id='navigation'>
|
||||
<ul>
|
||||
{% if user %}
|
||||
<li><a href="/map/{{user.slug}}">Map</a></li>
|
||||
<li><a href="/settings">Settings</a></li>
|
||||
{% if user.isAdmin %}<li><a href="/admin">Admin</a></li>{% endif %}
|
||||
<li><a href="/help">Help</a></li>
|
||||
<li><a href="/logout">Logout</a></li>
|
||||
{% else %}
|
||||
<li><a href="/#overview">About</a></li>
|
||||
<li><a href="/map/keith">Demo</a></li>
|
||||
<li><a href="/#join">Join</a></li>
|
||||
<li><a href="/login">Login</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
<nav id='navigation'><ul>
|
||||
{% if user %}
|
||||
<li><a href="/map">Map</a></li>
|
||||
<li><a href="/settings">Settings</a></li>
|
||||
{% if user.isAdmin %}<li><a href="/admin">Admin</a></li>{% endif %}
|
||||
<li><a href="/help">Help</a></li>
|
||||
<li><a href="/logout">Logout</a></li>
|
||||
{% else %}
|
||||
<li><a href="/">About</a></li>
|
||||
<li><a href="/map/keith">Demo</a></li>
|
||||
<li><a href="/login">Login</a></li>
|
||||
{% endif %}
|
||||
</ul></nav>
|
||||
|
||||
</header>
|
||||
|
||||
<!-- Flash messages -->
|
||||
<noscript>
|
||||
<div class='alert alert-header alert-danger alert-dismissible shadow'>
|
||||
<div class='alert alert-header alert-danger alert-dismissible shadow'>
|
||||
<strong>Uh-oh!</strong> You don't have javascript enabled! This page won't load correctly without it. You should really enable it, because many websites won't work properly. Ask your grandchildren if you need help.
|
||||
<a href="#" class='close' data-dismiss="alert" aria-label="close"><i class='fa fa-times'></i></a>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue