const express = require('express'); const crypto = require('crypto'); const Config = require('./config.js'); const Database = require('./db_interface.js'); let router = express.Router(); router.use(express.json()); let session_entropy = {}; user_cache = {}; email_cache = {}; async function get_user_details(id) { if (!id) { return undefined; } console.log(`search for user with id ${id}`); if (!user_cache[id]) { let user = await Database.schemas.user.findOne({where: {id: id}}); if (!user) { return undefined; } user_cache[user.id] = { id: user.id, email: user.email, password_hash: user.password_hash, }; email_cache[user.email] = user.id; } console.log(`returning ${JSON.stringify(user_cache[id])}`); return user_cache[id]; } async function get_user_details_by_email(email) { if (!email) { return undefined; } console.log(`search for user with email ${email}}`); if (!email_cache[email] || !user_cache[email_cache[email]]) { let user = await Database.schemas.user.findOne({where: {email: email}}); if (!user) { return undefined; } user_cache[user.id] = { id: user.id, email: user.email, password_hash: user.password_hash, }; email_cache[user.email] = user.id; } console.log(`returning ${JSON.stringify(user_cache[email_cache[email]])}`); return user_cache[email_cache[email]]; } router.get('/byEmail/:email', async (req, res) => { if (!req.params?.email) { res.status(400).json({ error: 'email is a required parameter', }); } let user = get_user_details_by_email(req.params.email); console.log(user); if (user != null) { res.json({ id: user.id, email: user.email, }); } else { res.sendStatus(404); } }); function hash(secret, password) { let pw_hash = crypto.pbkdf2Sync( password, secret, Config.config.key?.iterations || 1000, Config.config.key?.length || 64, 'sha512' ); return pw_hash.toString('base64'); } function verify(secret, password, hash) { let pw_hash = crypto.pbkdf2Sync( password, secret, Config.config.key?.iterations || 1000, Config.config.key?.length || 64, 'sha512' ); return hash === pw_hash.toString('base64'); } function hash_password(password) { return hash(Config.config.secret, password); } function verify_password(password, hash) { return verify(Config.config.secret, password, hash); } function get_session_token(id, token) { session_entropy[id] = crypto.randomBytes(Config.config.session_entropy || 32); return hash(session_entropy[id], token); } function verify_session_token(id, hash, token) { if (session_entropy[id]) { return verify(session_entropy[id], hash, token); } else { return false; } } async function enforce_session_login(req, res, next) { let userid = req.cookies?.userid; let session_token = req.cookies?._session; console.log('a', userid, session_token); if (!userid || !session_token) { return res.sendStatus(401); } let user = await get_user_details(userid); if (!user) { return res.sendStatus(401); } let verified_session = verify_session_token( userid, user.password_hash, session_token ); if (!verified_session) { return res.sendStatus(401); } return next(); } router.post('/new', async (req, res) => { if (!req.body?.email || !req.body?.password) { return res.status(400).json({ error: 'must have email and password fields', }); } let user = await get_user_details_by_email(req.body.email); console.log(user); if (user != null) { return res.status(403).json({ error: `email ${req.body.email} is already in use.`, }); } else { let user = await Database.schemas.user.create({ email: String(req.body.email), password_hash: hash_password(req.body.password), }); return res.json({ id: user.id, email: user.email, }); } }); router.post('/login', async (req, res) => { if (!req.body?.email || !req.body?.password) { return res.status(400).json({ error: 'must have email and password fields', }); } let user = await get_user_details_by_email(req.body.email); if (!user) { return res.status(401).json({ error: 'incorrect email or password', }); } let verified = verify_password(req.body.password, user.password_hash); if (!verified) { return res.status(401).json({ error: 'incorrect email or password', }); } res.cookie('userid', user.id, { httpOnly: true, secure: true, }); res.cookie('_session', get_session_token(user.id, user.password_hash), { httpOnly: true, secure: true, }); return res.sendStatus(204); }); router.get('/:id([a-f0-9-]+)', async (req, res) => { console.log(req.params); if (!req.params?.id) { return res.status(400).json({ error: 'must have id parameter', }); } let id = req.params?.id; console.log(id); let user = await get_user_details(id); console.log(user); if (user != null) { return res.json({ id: user.id, email: user.email, }); } else { return res.sendStatus(404); } }); router.use('/authorized', enforce_session_login); router.get('/authorized', async (req, res) => { let userid = req.cookies?.userid; let user = await get_user_details(userid); return res.json({ authorized: true, user: { id: user.id, email: user.email, }, }); }); module.exports = { router: router, enforce_session_login: enforce_session_login, };