1
0
James Ketrenos cf83d2b95b Added location element
Fixed TTL in DB to match maxAge of session

Signed-off-by: James Ketrenos <james_eikona@ketrenos.com>
2022-04-09 15:40:29 -07:00

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;