
Fixed TTL in DB to match maxAge of session Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
331 lines
9.1 KiB
JavaScript
Executable File
331 lines
9.1 KiB
JavaScript
Executable File
'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;
|