Added login screen

master
Keith Irwin 2017-04-01 13:03:05 -04:00
parent 76ccaab5c4
commit 7153f7bb9b
No known key found for this signature in database
GPG Key ID: 378933C743E2BBC0
21 changed files with 688 additions and 552 deletions

View File

@ -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.

View File

@ -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) $ 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 ```javascript
'use strict'; 'use strict';
module.exports = { module.exports = {
env: 'development', // or 'production' mode: 'development', // or 'production'
// Random strings to prevent hijacking // Random strings to prevent hijacking
session: 'this is a secret', session: 'this is a secret',
@ -25,8 +25,12 @@ module.exports = {
// Client IDs for authentication // Client IDs for authentication
googleClientId: '############-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com', googleClientId: '############-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com',
googleClientSecret: 'XXXXXXXXX_XXXXXXXXXXXXXX', googleClientSecret: 'XXXXXXXXX_XXXXXXXXXXXXXX',
facebookAppId: 'XXXXXXXXXXXXXXXX',
facebookAppSecret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
twitterConsumerKey: 'XXXXXXXXXXXXXXXXXXXXXXXXX',
twitterConsumerSecret: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
// A google maps API // A google maps API key
googleMapsAPI: 'XXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXX', googleMapsAPI: 'XXXXXXXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXX',
// Location of your mongoDB // 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). 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 ## Running

View File

@ -1,108 +1,270 @@
'use strict'; 'use strict';
const passport = require('passport'), const mw = require('./middleware.js'),
slug = require('slug'), mail = require('./mail.js'),
crypto = require('crypto'), User = require('./models.js').user,
secret = require('./secrets.js'), env = require('./env.js');
User = require('./models/user.js'),
GoogleStrategy = require('passport-google-oauth2').Strategy,
GoogleTokenStrategy = require('passport-google-id-token');
passport.use(new GoogleStrategy({ module.exports = function(app, passport) {
clientID: secret.googleClientId,
clientSecret: secret.googleClientSecret, // Methods for success and failure
callbackURL: secret.url+'/auth/google/callback', var loginOutcome = {
passReqToCallback: true failureRedirect: '/login',
}, function(req, accessToken, refreshToken, profile, done) { 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 // Signup
User.findOne({googleID: profile.id}, function(err, user){ app.post('/signup', function(req,res,next){
User.findOne({'email':req.body.email}, function(err,user){
// Error if (err){ next(err); }
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 // User already exists
(function checkSlug(s,cb) { else if (user){
//console.log('checking ',s); req.flash('warning','A user with that email already exists! If you forgot your password, use <a href="/login/forgot">this form</a>.');
User.findOne({slug:s}, function(err, existingUser){ res.redirect('/login');
if (err) { console.log('No user found for ',slug,':',err); } } else {
if (existingUser){
s = ''; // Create user
while (s.length<6) { var newUser = new User();
s+='abcdefghijkmnpqrtuvwxy346789'.charAt(Math.floor(Math.random()*28)); newUser.email = req.body.email;
} newUser.created = Date.now();
checkSlug(s,cb);
} else { cb(s); } 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 //TODO: Validate and sanitize email
crypto.randomBytes(32, function(err,buf) { // req.assert('email', 'Please enter a valid email address.').isEmail();
if (err) {console.log('Unable to get random bytes:',err);} // req.sanitize('email').normalizeEmail({ remove_dots: false });
if (!buf) {console.log('Unable to get random buffer');}
else { User.findOne({'email':req.body.email},function(err,user){
user.sk32 = buf.toString('hex'); if (err){ next(err); }
user.save(function(err) { else if (!user) {
if (err) { req.flash('danger', `No user has <u>${req.body.email}</u> set as their email address. `);
console.log('Error saving new user '+err); res.redirect('/login/forgot');
var failMessage = 'Something went wrong creating your account. Would you like to <a href="/bug">report this error</a>?'; } else {
} 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--; } // Set reset token to user
else { done(null, user, { success:successMessage, failure:failMessage }); } 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) { // Social
User.findOne({googleID:googleId}, function(err, user) { app.get('/login/:service', function(req,res,next){
if (err) { var service = req.params.service;
console.log('Error finding user for gToken login with google profile ID: '+googleId+'\n'+err); } if (service==='google'){
if (!err && user !== null) { // Log in var sendParams = {scope:['profile']};
user.lastLogin = Date.now(); }
user.save(function (err) { if (!req.user) { // Social login
if (err) { passport.authenticate(service, sendParams)(req,res,next);
console.log('Error saving user\'s lastLogin for gToken login with google profile ID: '+googleId+'\n'+err); } } 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);
// }
// });
// }));

View File

@ -1,39 +1,36 @@
'use strict'; 'use strict';
const secret = require('./secrets.js'); const env = require('./env.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
// });
// }
});
};
module.exports = { module.exports = {
throwErr,
ensureAuth, // Throw error
ensureAdmin 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
}
}; };

View File

@ -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);

View File

@ -2,7 +2,7 @@
const router = require('express').Router(), const router = require('express').Router(),
mw = require('../middleware.js'), mw = require('../middleware.js'),
User = require('../models/user.js'); User = require('../models.js').user;
router.route('/') router.route('/')
.all(mw.ensureAdmin, function(req,res,next){ .all(mw.ensureAdmin, function(req,res,next){
@ -17,7 +17,7 @@ router.route('/')
} }
if (cbc<1){ cbc++; } if (cbc<1){ cbc++; }
else { // done else { // done
res.render('admin.html', { res.render('admin', {
noFooter: '1', noFooter: '1',
success:req.flash('success')[0], success:req.flash('success')[0],
error:req.flash('error')[0] error:req.flash('error')[0]

View File

@ -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;

View File

@ -3,12 +3,12 @@
const slug = require('slug'), const slug = require('slug'),
xss = require('xss'), xss = require('xss'),
mw = require('../middleware.js'), mw = require('../middleware.js'),
User = require('../models/user.js'), User = require('../models.js').user,
router = require('express').Router(); router = require('express').Router();
// Index // Index
router.get('/', function(req,res,next) { router.get('/', function(req,res,next) {
res.render('index.html'); res.render('index');
}); });
// Settings // Settings
@ -20,7 +20,7 @@ router.route('/settings').all(mw.ensureAuth, function(req,res,next){
.get(function(req,res,next){ .get(function(req,res,next){
User.findById(req.session.passport.user, function(err,user){ User.findById(req.session.passport.user, function(err,user){
if (err){ console.log('Error finding settings for user:',err); mw.throwErr(req,err); } 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){ User.findById(req.session.passport.user, function(err, user){
if (err){ mw.throwErr(req,err); } if (err){ mw.throwErr(req,err); }
if (!user){ next(); } 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 // Help
router.route('/help').get(mw.ensureAuth, function(req,res){ router.get('/help', mw.ensureAuth, function(req,res){
res.render('help.html'); res.render('help');
}); });
// Terms of Service // Terms of Service and Privacy Policy
router.get('/terms', function(req,res){ router.get('/terms', function(req,res){
res.render('terms.html'); res.render('terms');
}).get('/privacy', function(req,res){
res.render('privacy');
}); });
module.exports = router; module.exports = router;

View File

@ -2,8 +2,13 @@
const router = require('express').Router(), const router = require('express').Router(),
mw = require('../middleware.js'), mw = require('../middleware.js'),
secrets = require('../secrets.js'), env = require('../env.js'),
User = require('../models/user.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 // Show map
router.get('/:slug?', function(req,res,next){ router.get('/:slug?', function(req,res,next){
@ -44,9 +49,9 @@ router.get('/:slug?', function(req,res,next){
res.redirect('/'); res.redirect('/');
} else { } else {
if (user && !mapuser) { mapuser = user; } if (user && !mapuser) { mapuser = user; }
res.render('map.html', { res.render('map', {
mapuser: mapuser, mapuser: mapuser,
mapApi: secrets.googleMapsAPI, mapApi: env.googleMapsAPI,
user: user, user: user,
noFooter: '1', noFooter: '1',
noHeader: (req.query.noheader)?req.query.noheader.match(/\d/)[0]:'', noHeader: (req.query.noheader)?req.query.noheader.match(/\d/)[0]:'',

View File

@ -2,7 +2,7 @@
const router = require('express').Router(), const router = require('express').Router(),
slug = require('slug'), slug = require('slug'),
User = require('../models/user.js'); User = require('../models.js').user;
// robots.txt // robots.txt
router.get('/robots.txt', function(req,res){ router.get('/robots.txt', function(req,res){

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
// Imports // Imports
const User = require('./models/user.js'); const User = require('./models.js').user;
// Check for tracking clients // Check for tracking clients
function checkForUsers(io, user) { function checkForUsers(io, user) {

View File

@ -15,10 +15,15 @@
"mongodb": "^2.1.4", "mongodb": "^2.1.4",
"mongoose": "^4.9.0", "mongoose": "^4.9.0",
"node-jose": "^0.8.0", "node-jose": "^0.8.0",
"nodemailer": "^3.1.8",
"nunjucks": "^2.3.0", "nunjucks": "^2.3.0",
"passport": "^0.3.2", "passport": "^0.3.2",
"passport-facebook": "^2.1.1",
"passport-google-id-token": "^0.4.0", "passport-google-id-token": "^0.4.0",
"passport-google-oauth2": "^0.1.6", "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", "slug": "^0.9.1",
"socket.io": "^1.4.4" "socket.io": "^1.4.4"
}, },
@ -38,7 +43,6 @@
"test": "mocha test.js", "test": "mocha test.js",
"start": "node server.js", "start": "node server.js",
"dev": "nodemon server.js", "dev": "nodemon server.js",
"deploy": "ssh khp deploy-tracman",
"update": "sudo n stable && sudo npm update --save && sudo npm prune" "update": "sudo n stable && sudo npm update --save && sudo npm prune"
}, },
"repository": { "repository": {

View File

@ -10,8 +10,8 @@ const
nunjucks = require('nunjucks'), nunjucks = require('nunjucks'),
passport = require('passport'), passport = require('passport'),
flash = require('connect-flash'), flash = require('connect-flash'),
secret = require('./config/secrets.js'), env = require('./config/env.js'),
User = require('./config/models/user.js'), User = require('./config/models.js').user,
app = express(), app = express(),
http = require('http').Server(app), http = require('http').Server(app),
io = require('socket.io')(http), io = require('socket.io')(http),
@ -19,24 +19,27 @@ const
/* SETUP */ { /* SETUP */ {
/* Database */ mongoose.connect(secret.mongoSetup, {
/* Database */ mongoose.connect(env.mongoSetup, {
server:{socketOptions:{ server:{socketOptions:{
keepAlive:1, connectTimeoutMS:30000 }}, keepAlive:1, connectTimeoutMS:30000 }},
replset:{socketOptions:{ replset:{socketOptions:{
keepAlive:1, connectTimeoutMS:30000 }} keepAlive:1, connectTimeoutMS:30000 }}
}); });
/* Templates */ nunjucks.configure(__dirname+'/views', { /* Templates */ {
autoescape: true, nunjucks.configure(__dirname+'/views', {
express: app autoescape: true,
}); express: app
});
app.set('view engine','html');
}
/* Session */ { /* Session */ {
app.use(cookieParser(secret.cookie)); app.use(cookieParser(env.cookie));
// app.use(expressSession({
app.use(cookieSession({ app.use(cookieSession({
cookie: {maxAge:60000}, cookie: {maxAge:60000},
secret: secret.session, secret: env.session,
saveUninitialized: true, saveUninitialized: true,
resave: true resave: true
})); }));
@ -48,18 +51,22 @@ const
} }
/* Auth */ { /* Auth */ {
require('./config/passport.js')(passport);
app.use(passport.initialize()); app.use(passport.initialize());
app.use(passport.session()); app.use(passport.session());
require('./config/auth.js'); require('./config/auth.js')(app, passport);
passport.serializeUser(function(user,done) { // app.use(passport.initialize());
done(null, user.id); // app.use(passport.session());
}); // require('./config/auth.js');
passport.deserializeUser(function(id,done) { // passport.serializeUser(function(user,done) {
User.findById(id, function(err, user) { // done(null, user.id);
if(!err) done(null, user); // });
else done(err, null); // passport.deserializeUser(function(id,done) {
}); // User.findById(id, function(err, user) {
}); // if(!err) done(null, user);
// else done(err, null);
// });
// });
} }
/* Routes */ { /* Routes */ {
@ -87,7 +94,6 @@ const
// Main routes // Main routes
app.use('/', app.use('/',
require('./config/routes/index.js'), require('./config/routes/index.js'),
require('./config/routes/auth.js'),
require('./config/routes/misc.js') require('./config/routes/misc.js')
); );
@ -110,11 +116,11 @@ const
}); });
// Handlers // Handlers
if (secret.env=='production') { if (env.mode=='production') {
app.use(function(err,req,res,next) { app.use(function(err,req,res,next) {
if (res.headersSent) { return next(err); } if (res.headersSent) { return next(err); }
res.status(err.status||500); res.status(err.status||500);
res.render('error.html', { res.render('error', {
code: err.status code: err.status
}); });
}); });
@ -124,7 +130,7 @@ const
console.log(err); console.log(err);
if (res.headersSent) { return next(err); } if (res.headersSent) { return next(err); }
res.status(err.status||500); res.status(err.status||500);
res.render('error.html', { res.render('error', {
code: err.status, code: err.status,
message: err.message, message: err.message,
error: err error: err
@ -141,11 +147,17 @@ const
/* RUNTIME */ { /* 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 // Listen
http.listen(secret.port, function(){ http.listen(env.port, function(){
console.log( console.log(
'==========================================\n'+ '==========================================\n'+
'Listening at '+secret.url+ 'Listening at '+env.url+
'\n==========================================' '\n=========================================='
); );
@ -158,6 +170,7 @@ const
}); });
}); });
} }
module.exports = app; module.exports = app;

View File

@ -1,105 +1,41 @@
/* Resets, Clears & Defaults */ /* Global */
*, *:after, *:before { div, footer, .fa,
.container, .container:before, .container:after {
box-sizing: border-box; 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 { body, input, textarea {
padding: 0; margin: 0; padding: 0; margin: 0;
font-family: 'Open Sans', sans-serif; font-family: 'Open Sans', sans-serif;
font-size: 18px; font-size: 18px;
color: #eee; color: #eee;
font-weight: 600;
} }
.flexbox { body {
width:100%; background-color: #080808;
display:flex;
justify-content:space-around;
} }
.flexbox.stretch { justify-content:space-between; } ::-webkit-scrollbar {
pre { width: 5vw;
white-space: pre-wrap; min-width:10px;
white-space: -moz-pre-wrap; max-width:40px;
white-space: -pre-wrap; }::-webkit-scrollbar-track {
white-space: -o-pre-wrap; background-color: #080808;
word-wrap: break-word; background-color: rgba(8,8,8,0);
}::-webkit-scrollbar-thumb {
border-radius: .2vw;
background: #333;
} }
.dark pre { ::selection {
-moz-box-shadow: 2px 2px 4px #000; background: #999;
-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;
} }
.dark .form-control:disabled, .dark .form-control:disabled { ::-moz-selection {
background-color: rgba(255,255,255,0.1); background: #999;
}
.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;
} }
input:focus, textarea:focus { /* Elements */
outline: 0; h1, h2, h3 {
} margin: 0 0 5% 0;
h1, h2, h3, p {
margin: 0 0 20px 0;
position: relative; position: relative;
z-index: 6; z-index: 6;
} }
h1,h2,h3,h4 { font-weight: 600; } h1,h2,h3,h4 { font-weight: 600; }
h1 { h1 {
font-size: 48px; font-size: 48px;
@ -109,27 +45,20 @@ h2 {
line-height: 36px; } line-height: 36px; }
h3 { font-size: 28px; } h3 { font-size: 28px; }
h4 { font-size: 20px; } h4 { font-size: 20px; }
p {
.red { color: #fb6e3d; } margin-top: 0;
.shadow { margin-bottom: 10vh;
-moz-box-shadow: .18vw .18vw .36vw #000;
-webkit-box-shadow: .18vw .18vw .36vw #000;
box-shadow: .18vw .18vw .36vw #000;
} .shadow:active {
-moz-box-shadow: none;
-webkit-box-shadow: none;
box-shadow: none;
} }
a { a {
color: #fbc93d; color: #fbc93d;
text-decoration: none; text-decoration: none;
} }
a:hover { main a:hover:not(.btn) {
color: #fbc93d; color: #fbc93d;
text-decoration: underline; text-decoration: underline;
} }
.light a { .light a:not(.btn) {
color:#111; color:#111;
text-decoration: underline; text-decoration: underline;
} }
@ -138,6 +67,10 @@ a:hover {
text-decoration: none; text-decoration: none;
} }
hr {
width: 90%;
margin: 10% auto;
}
img { img {
max-width: 100%; max-width: 100%;
} }
@ -145,27 +78,47 @@ p img {
display: block; display: block;
margin: auto; margin: auto;
} }
pre {
input[type="checkbox"] { white-space: pre-wrap;
width: auto; white-space: -moz-pre-wrap;
margin: 8px; white-space: -pre-wrap;
} white-space: -o-pre-wrap;
.with-errors { word-wrap: break-word;
color: #d9534f;
} }
::selection { .hide { display:none }
background: #999; .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 { .inline { display: inline-block; }
background: #999; .flex {
} width: 100%;
display: flex;
/* End Resets, Clears & Defaults */ justify-content: space-around;
}
.container { .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%; width: 100%;
max-width: 1000px;
margin: 0 auto; margin: 0 auto;
} }
.container:after { .container:after {
@ -174,56 +127,122 @@ input[type="checkbox"] {
clear: both; clear: both;
} }
section { 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 { .btn {
text-decoration: none; text-decoration: none;
text-align: center;
font-weight:600; font-weight:600;
display: inline-block; display: inline-block;
padding: 15px 30px; padding: 15px 30px;
transition: 200ms; transition: 100ms;
background: transparent;
cursor: pointer; 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); background: rgba(255,255,255,0.1);
}.dark.btn:active:not(.disabled) { color: #eee;
-moz-box-shadow: 0; border: 1px solid #999;
-webkit-box-shadow: 0; border-radius: .5vw;
box-shadow: 0; } .btn:not(.disabled) {
} -moz-box-shadow:
.light .btn { inset .11vw .18vw .52vw rgba(255,255,255,.2),
color: #222; 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; text-decoration: none;
border: 1px solid #222; background: rgba(255,255,255,0.2);
} } .btn:active:not(.disabled) {
.light .btn:hover:not(.disabled), -moz-box-shadow:
.light .btn:active:not(.disabled), inset .11vw .18vw .52vw rgba(0,0,0,.4),
.light .btn:focus:not(.disabled) { inset -.11vw -.18vw .52vw rgba(255,255,255,.2);
background: rgba(0,0,0,0.1); -webkit-box-shadow:
} inset .11vw .18vw .52vw rgba(0,0,0,.4),
.btn.yellow { inset -.11vw -.18vw .52vw rgba(255,255,255,.2);
color: #fbc93d; 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; border: 1px solid #fbc93d;
} }
.dark .btn.yellow:hover:not(.disabled), .btn.main {
.dark .btn.yellow:active:not(.disabled), color: #fbc93d;
.dark .btn.yellow:focus:not(.disabled) {
background: rgba(251,201,61,0.1);
}
.btn.smaller {
padding: 10px 25px;
} }
.btn .fa { .btn .fa {
margin-left: 10px; margin-left: 10px;
} }
.group {
width: 100%;
}
.group div {
display: flex;
margin-bottom: 10vh;
}

View File

@ -1,10 +1,13 @@
footer { footer {
font-weight: 300; font-weight: 300;
width:100%; width: 100%;
overflow:auto; overflow: auto;
background: #111; background: #111;
color: #ccc; color: #ccc;
padding: 0 20px; 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 { footer .left {
float: left; float: left;
@ -33,7 +36,6 @@ footer a .fa {
footer .fa a:hover, footer .fa a:focus { footer .fa a:hover, footer .fa a:focus {
color: inherit; color: inherit;
} }
@media (max-width: 800px) { @media (max-width: 800px) {
footer { footer {
padding: 0 10px; padding: 0 10px;
@ -49,4 +51,4 @@ footer .fa a:hover, footer .fa a:focus {
footer .right { footer .right {
padding-top: 0; padding-top: 0;
} }
} }

View File

@ -5,9 +5,10 @@ header {
top: 0; left: 0; top: 0; left: 0;
width: 100%; width: 100%;
z-index: 200; z-index: 200;
} header a:hover, header a:focus {
color: #fbc93d;
} }
header .logo { header .logo {
float: left; float: left;
font-family: 'Open Sans', sans-serif; font-family: 'Open Sans', sans-serif;
padding: 13px 23px; padding: 13px 23px;
@ -16,53 +17,39 @@ header .logo {
font-size: 22px; font-size: 22px;
line-height: 30px; line-height: 30px;
margin: 0; margin: 0;
} } header .logo a {
header a:hover, header a:focus {
color: #fbc93d;
}
header .logo a {
color:inherit; color:inherit;
font:inherit; font:inherit;
text-decoration:inherit; text-decoration:inherit;
cursor: pointer; cursor: pointer;
} } header .logo img {
header .logo img {
margin-right: 10px; margin-right: 10px;
position: relative; vertical-align: middle;
width:28px; } header .logo:hover {
height:28px; text-decoration: none;
background: rgba(255,255,255,0.1);
} }
header nav { header nav {
float: right; float: right;
} } header nav ul {
header nav ul {
padding: 0; padding: 0;
margin: 0; margin: 0;
} } header nav ul li {
header nav ul li {
display: inline-block; display: inline-block;
float: left; 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; text-decoration:inherit;
display: inline-block; display: inline-block;
padding: 15px 20px; padding: 15px 20px;
color: #fff; color: #fff;
transition: 200ms; transition: 100ms;
} } header nav ul li a:hover,
header nav ul li a:hover,
header nav ul li a:focus, header nav ul li a:focus,
header nav ul li a.active, header nav ul li a.active,
header .logo:hover { header .logo:hover {
text-decoration: none; text-decoration: none;
background: rgba(255,255,255,0.1); background: rgba(255,255,255,0.1);
} }
.alert.header {
position: relative;
border-radius: 0;
top: 58px;
width: 100%;
}
header .hamburger { header .hamburger {
display: none; display: none;
padding: 5px; padding: 5px;
@ -70,18 +57,20 @@ header .hamburger {
transition-property: opacity, -webkit-filter; transition-property: opacity, -webkit-filter;
transition-property: opacity, filter; transition-property: opacity, filter;
transition-property: opacity, filter, -webkit-filter; transition-property: opacity, filter, -webkit-filter;
transition-duration: 0.15s; transition-duration: 150ms;
transition-timing-function: linear; } transition-timing-function: linear;
.hamburger:hover { } header .hamburger:hover {
opacity: 0.7; } opacity: 0.7;
header .hamburger-box { } header .hamburger-box {
width: 40px; width: 40px;
height: 24px; height: 24px;
position: relative; } position: relative;
header .hamburger-inner { } header .hamburger-inner {
top: 50%; top: 50%;
margin-top: -2px; } margin-top: -2px;
header .hamburger-inner, header .hamburger-inner::before, header .hamburger-inner::after { } header .hamburger-inner,
header .hamburger-inner::before,
header .hamburger-inner::after {
width: 40px; width: 40px;
height: 4px; height: 4px;
background-color: #fff; background-color: #fff;
@ -90,42 +79,47 @@ header .hamburger-inner {
transition-property: -webkit-transform; transition-property: -webkit-transform;
transition-property: transform; transition-property: transform;
transition-property: transform, -webkit-transform; transition-property: transform, -webkit-transform;
transition-duration: 0.15s; transition-duration: 150ms;
transition-timing-function: ease; } transition-timing-function: ease;
header .hamburger-inner::before, header .hamburger-inner::after { } header .hamburger-inner::before, header .hamburger-inner::after {
content: ""; content: "";
display: block; } display: block;
header .hamburger-inner::before { } header .hamburger-inner::before {
top: -10px; } top: -10px;
header .hamburger-inner::after { } header .hamburger-inner::after {
bottom: -10px; } bottom: -10px;
} header .hamburger--slider .hamburger-inner {
header .hamburger--slider .hamburger-inner { top: 0;
top: 0; } } header .hamburger--slider .hamburger-inner::before {
header .hamburger--slider .hamburger-inner::before {
top: 10px; top: 10px;
transition-property: opacity, -webkit-transform; transition-property: opacity, -webkit-transform;
transition-property: transform, opacity; transition-property: transform, opacity;
transition-property: transform, opacity, -webkit-transform; transition-property: transform, opacity, -webkit-transform;
transition-timing-function: ease; transition-timing-function: ease;
transition-duration: 0.2s; } transition-duration: 200ms;
header .hamburger--slider .hamburger-inner::after { } header .hamburger--slider .hamburger-inner::after {
top: 20px; } top: 20px;
} header .hamburger--slider.is-active .hamburger-inner {
header .hamburger--slider.is-active .hamburger-inner { -webkit-transform: translate3d(0, 10px, 0) rotate(45deg);
-webkit-transform: translate3d(0, 10px, 0) rotate(45deg); -moz-transform: translate3d(0, 10px, 0) rotate(45deg);
transform: translate3d(0, 10px, 0) rotate(45deg); } -md-transform: translate3d(0, 10px, 0) rotate(45deg);
header .hamburger--slider.is-active .hamburger-inner::before { -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); -webkit-transform: rotate(-45deg) translate3d(-5.71429px, -6px, 0);
transform: rotate(-45deg) translate3d(-5.71429px, -6px, 0); -moz-transform: rotate(-45deg) translate3d(-5.71429px, -6px, 0);
opacity: 0; } -ms-transform: rotate(-45deg) translate3d(-5.71429px, -6px, 0);
header .hamburger--slider.is-active .hamburger-inner::after { -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); -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) { @media (max-width: 800px) {
header { padding:0; }
header nav ul li a { padding:15px; } header nav ul li a { padding:15px; }
} }
@media (max-width: 600px) { @media (max-width: 600px) {
@ -138,17 +132,14 @@ header .hamburger--slider.is-active .hamburger-inner {
width: 100%; width: 100%;
max-width: 300px; max-width: 300px;
background: #333; background: #333;
transition: 200ms; transition: 100ms;
} } header nav.visible {
header nav.visible {
right: 0px; right: 0px;
} } header nav ul li {
header nav ul li {
display: block; display: block;
float: none; float: none;
width: 100%; width: 100%;
} } header nav ul li a {
header nav ul li a {
display: block; display: block;
width: 100%; width: 100%;
border-bottom: 1px solid rgba(255,255,255,0.1); border-bottom: 1px solid rgba(255,255,255,0.1);
@ -160,4 +151,4 @@ header .hamburger--slider.is-active .hamburger-inner {
right: 10px; right: 10px;
top: 13px; top: 13px;
} }
} }

View File

@ -1,3 +1,4 @@
/* global $ */
'use strict'; 'use strict';
$(document).ready(function(){ $(document).ready(function(){
@ -20,4 +21,9 @@ $(document).ready(function(){
$('nav').removeClass('visible'); $('nav').removeClass('visible');
}); });
// Close alerts
$('.alert-dismissible .close').click(function() {
$(this).parent().slideUp(500);
});
}); });

View File

@ -1,11 +1,11 @@
{% extends 'templates/base.html' %} {% extends 'templates/base.html' %}
{% block head %} {% block head %}
{{ super() }} {{ super() }}
<link href="/static/css/index.css" rel="stylesheet"> <link href="/static/css/index.css" rel="stylesheet">
{% endblock %} {% endblock %}
{% block main %} {% block main %}
<script src="/static/js/index.js"></script> <script src="/static/js/index.js"></script>
<section class='splash dark' id='splash'> <section class='splash dark' id='splash'>

View File

@ -1,38 +1,59 @@
{% extends 'templates/base.html' %} {% extends 'templates/base.html' %}
{% block title %}{{ super() }} | Login{% endblock %} {% 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 %} {% block main %}
<section class='dark'>
<section class='container'>
<h1>Welcome!</h1>
<div class='container wrap'> <div class='flex'>
<h1>Login</h1> <div class='login'>
<ul>
<li><a id='google-login' style="cursor:pointer">Google</a></li>
</ul>
</div>
<script src="https://www.gstatic.com/firebasejs/3.7.0/firebase.js"></script> <h3>Login</h3>
<script> <form method="post">
firebase.initializeApp({
apiKey: "AIzaSyDPYY_Fw3FXLm0hKfIfc8qlrc98zZiN4IY", <div id='social-login' class='flex'>
authDomain: "tracman-b894f.firebaseapp.com", <a href="/login/google" class='gp btn'><i class="fa fa-google-plus"></i></a>
databaseURL: "https://tracman-b894f.firebaseio.com", <a href="/login/facebook" class='fb btn'><i class="fa fa-facebook"></i></a>
storageBucket: "tracman-b894f.appspot.com", <a href="/login/twitter" class='tw btn'><i class="fa fa-twitter"></i></a>
messagingSenderId: "483494341936" </div>
});
$('#google-login').click(function() { <input type="email" placeholder="Email" name="email" required>
var googleProvider = new firebase.auth.GoogleAuthProvider(); <input type="password" placeholder="Password" name="password" required>
firebase.auth().signInWithPopup(googleProvider).then(function(result) {
console.log(`Successfully logged in ${result.user}`); <input type="submit" value="Sign in" class='btn main'>
}).catch(function(error) {
console.error(error.message); <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 %} {% endblock %}

View File

@ -5,10 +5,10 @@
<title>{% block title %}Tracman{% endblock %}</title> <title>{% block title %}Tracman{% endblock %}</title>
<link rel="manifest" href="/static/manifest.webmanifest"> <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 charset="UTF-8">
<meta name="author" content="Keith Irwin"> <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="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="description" content="Tracman lets you see and share your phone's exact realtime location">
<meta name="theme-color" content="#222"> <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="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="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 rel="stylesheet" type="text/css" href="/static/css/base.css">
<link href="/static/css/base.css" rel="stylesheet"> <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
<link rel="stylesheet" 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" 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"> <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://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 %} {% endblock %}
{% if not noHeader %}<link href="/static/css/header.css" rel="stylesheet">{% endif %} {% if not noHeader %}<link href="/static/css/header.css" rel="stylesheet">{% endif %}
{% if not noFooter %}<link href="/static/css/footer.css" rel="stylesheet">{% endif %} {% if not noFooter %}<link href="/static/css/footer.css" rel="stylesheet">{% endif %}
@ -40,11 +38,16 @@
<body> <body>
{% if not noHeader %}{% include 'templates/header.html' %}{% endif %} {% if not noHeader %}{% include 'templates/header.html' %}{% endif %}
{% block main %}Loading... {% endblock %} <main>
{% if not noFooter %}{% include 'templates/footer.html' %}{% endif %} {% block main %}{% endblock %}
{% if not noFooter %}{% include 'templates/footer.html' %}{% endif %}
</main>
<!-- Google Analytics --> <!-- Javascript -->
{% block javascript %}
<script> <script>
// Google analytics
(function(t,r,a,c,m,o,n){t['GoogleAnalyticsObject']=m;t[m]=t[m]||function(){ (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), (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); 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('create','UA-44266909-3','auto');
ga('require','linkid'); ga('require','linkid');
ga('send','pageview'); ga('send','pageview');
</script> </script>
{% endblock %}
<!-- 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>
</body> </body>
</html> </html>

View File

@ -13,28 +13,25 @@
</div> </div>
<!-- Navigation --> <!-- Navigation -->
<nav id='navigation'> <nav id='navigation'><ul>
<ul> {% if user %}
{% if user %} <li><a href="/map">Map</a></li>
<li><a href="/map/{{user.slug}}">Map</a></li> <li><a href="/settings">Settings</a></li>
<li><a href="/settings">Settings</a></li> {% if user.isAdmin %}<li><a href="/admin">Admin</a></li>{% endif %}
{% if user.isAdmin %}<li><a href="/admin">Admin</a></li>{% endif %} <li><a href="/help">Help</a></li>
<li><a href="/help">Help</a></li> <li><a href="/logout">Logout</a></li>
<li><a href="/logout">Logout</a></li> {% else %}
{% else %} <li><a href="/">About</a></li>
<li><a href="/#overview">About</a></li> <li><a href="/map/keith">Demo</a></li>
<li><a href="/map/keith">Demo</a></li> <li><a href="/login">Login</a></li>
<li><a href="/#join">Join</a></li> {% endif %}
<li><a href="/login">Login</a></li> </ul></nav>
{% endif %}
</ul>
</nav>
</header> </header>
<!-- Flash messages --> <!-- Flash messages -->
<noscript> <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. <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> <a href="#" class='close' data-dismiss="alert" aria-label="close"><i class='fa fa-times'></i></a>
</div> </div>