'use strict'; const express = require('express'), { sendVerifyMail, sendPasswordChangedMail, sendVerifiedMail } = require('../lib/mail'), crypto = require('crypto'); const router = express.Router(); const autoAuthenticate = 1; router.get('/', (req, res/*, next*/) => { return getSessionUser(req).then((user) => { if (typeof user === 'string') { return res.status(403).send({ message: user }); } return res.status(200).send(user); }); }); router.put('/password', async (req, res) => { const db = req.app.locals.db; const changes = { currentPassword: req.query.c || req.body.c, newPassword: req.query.n || req.body.n }; if (!changes.currentPassword || !changes.newPassword) { return res.status(400).send( 'Missing current password and/or new password.'); } if (changes.currentPassword == changes.newPassword) { return res.status(400).send( 'Attempt to set new password to current password.'); } const user = await getSessionUser(req); if (typeof user === 'string') { return res.status(403).send({ message: user }); } return db.sequelize.query('SELECT id FROM users ' + 'WHERE uid=:username AND password=:password', { replacements: { username: user.username, password: crypto.createHash('sha256').update(changes.currentPassword).digest('base64') }, type: db.Sequelize.QueryTypes.SELECT, raw: true }).then(function(users) { if (users.length != 1) { return null; } return user; }).then(function(user) { if (!user) { console.log('Invalid password'); /* Invalid password */ res.status(401).send('Invalid password'); return null; } return db.sequelize.query('UPDATE users SET password=:password WHERE uid=:username', { replacements: { username: user.username, password: crypto.createHash('sha256').update(changes.newPassword).digest('base64') } }).then(function() { console.log('Password changed for user ' + user.username + ' to \'' + changes.newPassword + '\'.'); res.status(200).send(user); user.id = req.session.userId; return sendPasswordChangedMail(db, req, user); }); }); }); router.get('/csrf', (req, res) => { const token = req.csrfToken(); console.log( `${req.method} ${req.path} - token`, token); res.json({ csrfToken: token }); }); router.post('/signup', function(req, res) { const db = req.app.locals.db; const user = { uid: req.body.email, familyName: req.body.familyName, firstName: req.body.firstName, password: req.body.password, email: req.body.email, }; if (!user.uid || !user.email || !user.password || !user.familyName || !user.firstName) { return res.status(400).send({ message: 'Missing email address, password, and/or name.' }); } user.password = crypto.createHash('sha256') .update(user.password).digest('base64'); user.md5 = crypto.createHash('md5') .update(user.email).digest('base64'); return db.sequelize.query('SELECT * FROM users WHERE uid=:uid', { replacements: user, type: db.Sequelize.QueryTypes.SELECT, raw: true }).then(async function(results) { if (results.length != 0 && results[0].mailVerified) { return res.status(400).send({ message: 'Email address already used.' }); } let re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; if (!re.exec(user.email)) { const error = `Invalid email address: ${user.email}.`; console.log(error); return res.status(401).send({ message: error }); } try { if (results.length != 0) { await db.sequelize.query('UPDATE users ' + 'SET mailVerified=0'); req.session.userId = results[0].id; } else { let [, metadata] = await db.sequelize.query('INSERT INTO users ' + '(uid,firstName,familyName,password,email,memberSince,' + 'authenticated,md5) ' + 'VALUES(:uid,:firstName,:familyName,:password,' + `:email,CURRENT_TIMESTAMP,${autoAuthenticate},:md5)`, { replacements: user }); req.session.userId = metadata.lastID; } return getSessionUser(req).then(function(user) { if (typeof user === 'string') { return res.status(403).send({ message: user }); } res.status(200).send(user); user.id = req.session.userId; return sendVerifyMail(db, req, user); }); } catch (error) { console.error(error); } }); }); const getSessionUser = async (req) => { const db = req.app.locals.db; if (!req.session || !req.session.userId) { return 'Unauthorized. You must be logged in.'; } let query = 'SELECT ' + 'uid AS username,firstName,familyName,mailVerified,authenticated,memberSince,email,md5 ' + 'FROM users WHERE id=:id'; const results = await db.sequelize.query(query, { replacements: { id: req.session.userId }, type: db.Sequelize.QueryTypes.SELECT, raw: true }); if (results.length != 1) { return 'Invalid account.'; } const user = results[0]; if (!user.mailVerified) { user.restriction = user.restriction || 'Email address not verified.'; } else if (!user.authenticated) { user.restriction = user.restriction || 'Accout not authorized.'; } /* Strip out any fields that shouldn't be there. * The allowed fields are: */ const allowed = [ 'maintainer', 'username', 'firstName', 'familyName', 'mailVerified', 'authenticated', 'name', 'email', 'restriction', 'md5' ]; for (let field in user) { if (allowed.indexOf(field) == -1) { delete user[field]; } } return user; }; router.post('/verify-email', async (req, res) => { const key = req.body.token; const db = req.app.locals.db; let results = await db.sequelize.query( 'SELECT * FROM authentications WHERE key=:key', { replacements: { key }, type: db.sequelize.QueryTypes.SELECT } ); let token; if (results.length == 0) { console.log('Invalid key. Ignoring.'); return res.status(400).send({ message: 'Invalid authentication token.' }); } token = results[0]; console.log(token); console.log('Matched token: ' + JSON.stringify(token, null, 2)); switch (token.type) { case 'account-setup': return db.sequelize.query( 'UPDATE users SET mailVerified=1 WHERE id=:userId', { replacements: token } ).then(function () { return db.sequelize.query( 'DELETE FROM authentications WHERE key=:key', { replacements: { key } } ); }).then(function () { return db.sequelize.query( 'SELECT * FROM users WHERE id=:userId', { replacements: token, type: db.sequelize.QueryTypes.SELECT } ); }).then(function (results) { if (results.length == 0) { return res.status(500).send({ message: 'Internal authentication error.' }); } return results[0]; }).then((user) => { sendVerifiedMail(db, req, user); req.session.userId = user.id; }).then(() => { return getSessionUser(req).then((user) => { if (typeof user === 'string') { return res.status(403).send({ message: user }); } return res.status(200).send(user); }); }); } }); router.post('/signin', (req, res) => { const db = req.app.locals.db; let { email, password } = req.body; if (!email || !password) { return res.status(400).send({ message: 'Missing email and/or password' }); } console.log('Looking up user in DB.'); let query = 'SELECT ' + 'id,mailVerified,authenticated,' + 'uid AS username,' + 'familyName,firstName,email ' + 'FROM users WHERE uid=:username AND password=:password'; return db.sequelize.query(query, { replacements: { username: email, password: crypto.createHash('sha256').update(password).digest('base64') }, type: db.Sequelize.QueryTypes.SELECT }).then(function(users) { if (users.length != 1) { return null; } let user = users[0]; req.session.userId = user.id; return user; }).then(function(user) { if (!user) { console.log(email + ' not found (or invalid password.)'); req.session.userId = null; return res.status(401).send({ message: 'Invalid sign in credentials' }); } let message = 'Logged in as ' + user.email + ' (' + user.id + ')'; if (!user.mailVerified) { console.log(message + ', who is not verified email.'); } else if (!user.authenticated) { console.log(message + ', who is not authenticated.'); } else { console.log(message); } return getSessionUser(req).then((user) => { if (typeof user === 'string') { return res.status(403).send({ message: user }); } return res.status(200).send(user); }); }); }); router.post('/signout', (req, res) => { if (req.session && req.session.userId) { req.session.userId = null; } res.status(200).send({}); }); module.exports = router;