Many fixes, added emailing capability

master
Keith Irwin 2017-04-10 03:00:56 -04:00
parent 4abf003a11
commit 016c6a6cdd
No known key found for this signature in database
GPG Key ID: 378933C743E2BBC0
10 changed files with 241 additions and 240 deletions

View File

@ -55,11 +55,11 @@ module.exports = function(app, passport) {
newUser.createToken(function(err,token){
if (err){ next(err); }
mail({
from: '"Trackmap" <accounts@trackmap.tech>',
from: '"Tracman" <NoReply@tracman.org>',
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
subject: 'Complete your Tracman registration',
text: `Welcome to Tracman! \n\nTo complete your registration, follow this link and set your password:\n${env.url}/settings/password/${token}`,
html: `<p>Welcome to Tracman! </p><p>To complete your registration, follow this link and set your password:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a></p>`
}).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('/');
@ -100,11 +100,11 @@ module.exports = function(app, passport) {
// 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>`
from: '"Tracman" <NoReply@tracman.org>',
to: `"${user.name}"" <${user.email}>`,
subject: 'Reset your Tracman password',
text: `Hi, \n\nDid you request to reset your Tracman password? If so, follow this link to do so:\n${env.url}/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 Tracman 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('/');

View File

@ -1,6 +1,8 @@
'use strict';
module.exports = require('nodemailer').createTransport({
const nodemailer = require('nodemailer');
let transporter = nodemailer.createTransport({
host: 'keithirwin.us',
port: 587,
secure: false,
@ -9,8 +11,30 @@ module.exports = require('nodemailer').createTransport({
user: 'NoReply@tracman.org',
pass: 'Ei0UwfrZuE'
},
// logger: true,
// debug: true
logger: true,
debug: true
});
// require('./mail.js').(mailData, context).then(...).catch(...);
/* Confirm login */
// transporter.verify(function(err, success) {
// if (err){ console.error(`SMTP Error: ${err}`); }
// if (success){
// console.log("SMTP ready...");
// } else {
// console.error("SMTP not ready!");
// }
// });
/* Send test email */
transporter.sendMail({
to: `"Keith Irwin" <mail@keithirwin.us>`,
from: '"Tracman" <NoReply@tracman.org>',
subject: 'Test email',
text: "Looks like everything's working",
}).then(function(){
console.log("Email should have sent...");
}).catch(function(err){
console.error(err);
});
module.exports = transporter.sendMail.bind(transporter);

View File

@ -2,7 +2,8 @@
const mongoose = require('mongoose'),
unique = require('mongoose-unique-validator'),
bcrypt = require('bcrypt-nodejs');
bcrypt = require('bcrypt-nodejs'),
crypto = require('crypto');
const userSchema = new mongoose.Schema({
name: {type:String, required:true},
@ -52,8 +53,9 @@ const userSchema = new mongoose.Schema({
// Create password reset token
userSchema.methods.createToken = function(next){
//TODO: Use the same token if there already is one that's not yet expired.
var user = this;
require('crypto').randomBytes(16, function(err,buf){
crypto.randomBytes(16, function(err,buf){
if (err){ next(err,null); }
else {
user.auth.passToken = buf.toString('hex');

View File

@ -1,91 +1,15 @@
'use strict';
const slug = require('slug'),
xss = require('xss'),
mw = require('../middleware.js'),
User = require('../models.js').user,
router = require('express').Router();
const mw = require('../middleware.js'),
router = require('express').Router(),
slug = require('slug'),
User = require('../models.js').user;
// Index
router.get('/', function(req,res,next) {
res.render('index');
});
// Settings
router.route('/settings').all(mw.ensureAuth, function(req,res,next){
next();
})
// Get settings form
.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');
});
})
// Set new settings
.post(function(req,res,next){
User.findByIdAndUpdate(req.session.passport.user, {$set:{
name: xss(req.body.name),
slug: slug(xss(req.body.slug)),
email: req.body.email,
settings: {
units: req.body.units,
defaultMap: req.body.map,
defaultZoom: req.body.zoom,
showSpeed: (req.body.showSpeed)?true:false,
showAlt: (req.body.showAlt)?true:false,
showStreetview: (req.body.showStreet)?true:false
}
}}, function(err, user){
if (err) { console.log('Error updating user settings:',err); mw.throwErr(req,err); }
else { req.flash('success', 'Settings updated. '); }
res.redirect('/settings');
});
})
// Delete user account
.delete(function(req,res,next){
User.findByIdAndRemove( req.session.passport.user,
function(err) {
if (err) {
console.log('Error deleting user:',err);
mw.throwErr(req,err);
} else {
req.flash('success', 'Your account has been deleted. ');
res.redirect('/');
}
}
);
});
// Tracman pro
router.route('/pro').all(mw.ensureAuth, function(req,res,next){
next();
})
// Get info about pro
.get(function(req,res,next){
User.findById(req.session.passport.user, function(err, user){
if (err){ mw.throwErr(req,err); }
if (!user){ next(); }
else { res.render('pro'); }
});
})
// Join Tracman pro
.post(function(req,res){
User.findByIdAndUpdate(req.session.passport.user,
{$set:{ isPro:true }},
function(err, user){
if (err){ mw.throwErr(req,err); }
else { req.flash('success','You have been signed up for pro. '); }
res.redirect('/map');
}
);
});
// Help
router.get('/help', mw.ensureAuth, function(req,res){
res.render('help');
@ -98,4 +22,39 @@ router.get('/terms', function(req,res){
res.render('privacy');
});
// robots.txt
router.get('/robots.txt', function(req,res){
res.type('text/plain');
res.send("User-agent: *\n"+
"Disallow: /map/*\n"
);
});
// favicon.ico
router.get('/favicon.ico', function(req,res){
res.redirect('/static/img/icon/by/16-32-48.ico');
});
// Endpoint to validate forms
router.get('/validate', function(req,res){
if (req.query.slug) { // validate unique slug
User.findOne({slug:slug(req.query.slug)}, function(err, existingUser){
if (err) { console.log('/validate error:',err); }
if (existingUser && existingUser.id!==req.session.passport.user) { res.sendStatus(400); }
else { res.sendStatus(200); }
});
}
});
// Link to androidapp in play store
router.get('/android', function(req,res){
res.redirect('https://play.google.com/store/apps/details?id=us.keithirwin.tracman');
});
// Link to iphone app in the apple store
router.get('/ios', function(req,res){
res.sendStatus(404);
//TODO: Add link to info about why there's no ios app
});
module.exports = router;

View File

@ -1,36 +0,0 @@
'use strict';
const router = require('express').Router(),
slug = require('slug'),
User = require('../models.js').user;
// robots.txt
router.get('/robots.txt', function(req,res){
res.type('text/plain');
res.send("User-agent: *\n"+
"Disallow: /map/*\n"
);
});
// favicon.ico
router.get('/favicon.ico', function(req,res){
res.redirect('/static/img/icon/by/16-32-48.ico');
});
// Endpoint to validate forms
router.get('/validate', function(req,res){
if (req.query.slug) { // validate unique slug
User.findOne({slug:slug(req.query.slug)}, function(err, existingUser){
if (err) { console.log('/validate error:',err); }
if (existingUser && existingUser.id!==req.session.passport.user) { res.sendStatus(400); }
else { res.sendStatus(200); }
});
}
});
// Link to android app in play store
router.get('/android', function(req,res){
res.redirect('https://play.google.com/store/apps/details?id=us.keithirwin.tracman');
});
module.exports = router;

View File

@ -92,16 +92,16 @@ const
});
// Main routes
app.use('/',
require('./config/routes/index.js'),
require('./config/routes/misc.js')
);
app.use( '/', require('./config/routes/index.js') );
// Settings
app.use( '/settings', require('./config/routes/settings.js') );
// Map
app.use(['/map','/trac'], require('./config/routes/map.js'));
app.use( ['/map','/trac'], require('./config/routes/map.js') );
// Admin
app.use('/admin', require('./config/routes/admin.js'));
// Site administration
app.use( '/admin', require('./config/routes/admin.js') );
}
@ -147,12 +147,6 @@ 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(env.port, function(){
console.log(

View File

@ -1,6 +1,6 @@
form {
margin: auto;
max-width: 600px;
max-width: 800px;
}
.form-group {
@ -27,6 +27,10 @@ form input, form textarea, form select {
border: 1px solid #666;
border-radius: .3vw;
}
form input:not(.input-addon):not(.input-with-addon):not([type="radio"]):not([type="checkbox"]),
form .input-with-addon-group {
min-width: 50%;
}
form input:active:not(.input-addon), form textarea:active, form select:active,
form input:focus:not(.input-addon), form textarea:focus, form select:focus {
outline: none;

122
test.js
View File

@ -1,11 +1,10 @@
var chai = require('chai'),
const chai = require('chai'),
chaiHttp = require('chai-http'),
request = require('supertest'),
server = require('./server'),
should = chai.should(),
expect = chai.expect();
server = require('./server');
chai.use(chaiHttp);
describe('Index', function() {
// I think this restarts the server after each try?
// var server;
@ -37,54 +36,54 @@ describe('Index', function() {
});
describe('Auth', function() {
// describe('Auth', function() {
it('Creates an account', function(done){
request(server).get('/login')
.expect(200)
.end(function(err,res){
//TODO: google authentication
it('Logs out', function(done){
request(server).get('/logout')
.expect(200)
.end(function(err,res){
it('Logs in', function(done){
request(server).get('/logout')
.expect(200)
.end(function(err,res){
cbc=2;
var deletesAccount = function(done){
it('Deletes own account', function(){
//TODO: Delete account via GUI
});
}
it('Shows own map', function(done){
request(server).get('/map')
.expect(200)
//TODO: Expect no js errors
.end(function(err,res){
if (cbc<2){ deletesAccount(); }
else { cbc--; }
done();
});
});
// it('Creates an account', function(done){
// request(server).get('/login')
// .expect(200)
// .end(function(err,res){
// //TODO: google authentication
// it('Logs out', function(done){
// request(server).get('/logout')
// .expect(200)
// .end(function(err,res){
// it('Logs in', function(done){
// request(server).get('/logout')
// .expect(200)
// .end(function(err,res){
// cbc=2;
// var deletesAccount = function(done){
// it('Deletes own account', function(){
// //TODO: Delete account via GUI
// });
// }
// it('Shows own map', function(done){
// request(server).get('/map')
// .expect(200)
// //TODO: Expect no js errors
// .end(function(err,res){
// if (cbc<2){ deletesAccount(); }
// else { cbc--; }
// done();
// });
// });
it('Has the correct account info', function(done){
//TODO: Check account info
if (cbc<2){ deletesAccount(); }
else { cbc--; }
done();
});
// it('Has the correct account info', function(done){
// //TODO: Check account info
// if (cbc<2){ deletesAccount(); }
// else { cbc--; }
// done();
// });
done();
});
});
done();
});
});
done();
});
});
// done();
// });
// });
// done();
// });
// });
// done();
// });
// });
//TODO: it('Has the correct account info', function(done){
@ -106,9 +105,9 @@ describe('Auth', function() {
// });
});
// });
describe('Map controls', function() {
// describe('Map controls', function() {
//TODO: it('Sets location', function(done){
@ -126,24 +125,5 @@ describe('Map controls', function() {
// });
});
// });
describe('Map popups', function() {
//TODO: it('Opens Share popup', function(done){
// });
//TODO: it('Closes Share popup', function(done){
// });
//TODO: it('Opens Settings popup', function(done){
// });
//TODO: it('Closes Settings popup', function(done){
// });
});

View File

@ -341,19 +341,6 @@
}
}
// Delete account
function deleteAccount() {
if (confirm("Are you sure you want to delete your account? This CANNOT be undone! ")) {
$.ajax({
url: "/map",
type: "DELETE",
success: function(){
location.reload();
}
})
}
}
{% endif %}
// Check altitude

View File

@ -1,13 +1,9 @@
{% extends 'templates/base.html' %}
{% block title %}{{super()}} | Settings{% endblock %}
{% block head %}
{{super()}}
<link rel="stylesheet" type="text/css" href="/static/css/form.css">
<style>
/*#slug-input {*/
/* padding-left: calc(146px + 1.6%);*/
/*}*/
</style>
{% endblock %}
{% block main %}
@ -30,12 +26,80 @@
<input class='form-control' name="email" type="email" value="{{user.email}}" maxlength="160">
</div>
<!--TODO: add password field-->
<div id='social-connect' class='form-group'>
<style>
#social-connect {
flex-wrap: wrap;
}
#social-connect > .btn {
display: flex;
align-items: center;
text-align: center;
margin-left: 1vw;
margin-right: 1vw;
flex-grow: 1;
flex-basis: 0;
font-size: .9em;
}
#social-connect > .btn:hover {
color: #fff;
}
#social-connect > .btn .fa {
font-size: 1.1em;
margin-left: 0;
margin-right: 5%;
}
/* Connected social button styles */
#social-connect > .btn.gp.connected {
border: 2px solid rgb(206,77,57);
}
#social-connect > .btn.fb.connected {
border: 2px solid rgb(48,88,145);
}
#social-connect > .btn.tw.connected {
border: 2px solid rgb(44,168,210);
}
/* Unconnected social button styles */
#social-connect > .btn.gp:not(.connected) {
background: rgb(206,77,57);
}
#social-connect > .btn.gp:not(.connected):hover {
background: rgb(251,122,102);
}
#social-connect > .btn.fb:not(.connected) {
background: rgb(48,88,145);
}
#social-connect > .btn.fb:not(.connected):hover {
background: rgb(93,133,190);
}
#social-connect > .btn.tw:not(.connected) {
background: rgb(44,168,210);
}
#social-connect > .btn.tw:not(.connected):hover {
background: rgb(89,213,255);
}
</style>
<a href="/login/google" class='btn gp{% if user.auth.google %} connected{% endif %}'>
<i class="fa fa-google-plus"></i>
{% if user.auth.google %}Disconnect{% else %}Connect{% endif %} Google
</a>
<a href="/login/facebook" class='btn fb{% if user.auth.facebook %} connected{% endif %}'>
<i class="fa fa-facebook"></i>
{% if user.auth.facebook %}Disconnect{% else %}Connect{% endif %} Facebook
</a>
<a href="/login/twitter" class='btn tw{% if user.auth.twitter %} connected{% endif %}'>
<i class="fa fa-twitter"></i>
{% if user.auth.twitter %}Disconnect{% else %}Connect{% endif %} Twitter
</a>
</div>
<!--TODO: add social login buttons-->
<div id='delete' class='form-group' title="Permently delete your Tracman account. ">
<a class='red' style='width:100%; text-align:right' href="" onclick="deleteAccount()">Delete account</a>
<div id='password-delete' class='form-group'>
<a href="/settings/password?next=/settings" title="Click here to {% if user.auth.password %}change{% else %}set{% endif %} your password. ">{% if user.auth.password %}Change{% else %}Set{% endif %} password</a>
<a class='red' style="text-align:right" href="#" onclick="deleteAccount()" title="Permently delete your Tracman account. ">Delete account</a>
</div>
<h2>Map settings</h2>
@ -124,9 +188,32 @@
</form>
{% if not user.isPro %}<p style="clear:both">Want to try <a href="/pro">Tracman Pro</a>? It's free during beta testing. </p>{% endif %}
{% if not user.isPro %}<p style="clear:both">Want to try <a href="/settings/pro">Tracman Pro</a>? It's free during beta testing. </p>{% endif %}
<p style="clear:both">Would you like to <a href="https://github.com/Tracman-org/Server/issues/new">submit a suggestion or bug report</a>? </p>
</section>
{% endblock %}
{% block javascript %}
{{super()}}
<script>
// Delete account
function deleteAccount() {
if (confirm("Are you sure you want to delete your account? This CANNOT be undone! ")) {
$.ajax({
url: "/settings",
type: "DELETE",
success: function(){
location.reload();
},
fail: function(){
alert("Failed to delete account!");
}
})
}
}
</script>
{% endblock %}