const base64 = require('base-64'); const sha512 = require('js-sha512'); const shortid = require('shortid'); const db = require('./db'); const buildSessionCookie = ({ username, sessionToken, isLogout }) => { if (!isLogout) { const tokenData = { username, sessionToken, }; const tokenDataStr = JSON.stringify(tokenData); const encodedToken = base64.encode(tokenDataStr); return `sessionCookie=${encodedToken}; Domain=localhost; HttpOnly; SameSite=strict;`; } else { return `sessionCookie=deleted; Domain=localhost; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; SameSite=strict;`; } }; const authMiddleware = async (req, res, next) => { const cookies = req.cookies; const encodedCookie = cookies.sessionCookie; if (!encodedCookie) { res.status(403); return res.json({ message: 'Log in first!', }); } const sessionCookieStr = base64.decode(encodedCookie); const sessionCookie = JSON.parse(sessionCookieStr); const { username, sessionToken } = sessionCookie; const user = await db('users').where({ username }).first(); if (!user) { res.status(400); res.set('Set-Cookie', buildSessionCookie({ isLogout: true })); return res.json({ message: 'Invalid session token.', sessionCookie, }); } const salt = user.salt; const sessionTokenHash = sha512(salt + sessionToken); const token = await db('tokens').where({ type: 'session', value: sessionTokenHash }); if (!token) { res.status(400); res.set('Set-Cookie', buildSessionCookie({ isLogout: true })); return res.json({ message: 'Invalid session token.', sessionCookie, }); } req.sessionTokenHash = sessionTokenHash; req.user = user; next(); }; const registerRoute = async (req, res) => { const { username, password } = req.body; if (!username || !password) { res.status(400); return res.json({ error: 'Username or password missing.', }); } const existingUser = await db('users').where({ username }).first(); if (existingUser) { res.status(400); return res.json({ error: 'Username already taken.', }); } const salt = shortid.generate(); const pwHash = sha512(salt + password); const sessionToken = shortid.generate(); const sessionTokenHash = sha512(salt + sessionToken); const user = (await db('users').insert({ username, password: pwHash, salt }).returning('*'))[0]; await db('tokens').insert({ user_id: user.id, type: 'session', value: sessionTokenHash }); res.set('Set-Cookie', buildSessionCookie({ username, sessionToken, remember: false, isLogout: false })); return res.json({ username, }); }; const loginRoute = async (req, res) => { const { username, password, remember } = req.body; if (!username || !password) { res.status(400); return res.json({ error: 'Username or password missing.', }); } const user = await db('users').where({ username }).first(); if (user) { const sessionToken = shortid.generate(); const sessionTokenHash = sha512(user.salt + sessionToken); await db('tokens').insert({ user_id: user.id, type: 'session', value: sessionTokenHash }); res.set('Set-Cookie', buildSessionCookie({ username, sessionToken, remember, isLogout: false })); return res.json({ username, }); } res.status(403); res.json({ error: 'Wrong username or password', }); }; const logoutRoute = async (req, res) => { await db('tokens').where({ type: 'session', user_id: req.user.id, value: req.sessionTokenHash }).delete(); res.status(204); res.set('Set-Cookie', buildSessionCookie({ isLogout: true })); res.send(); }; module.exports = { buildSessionCookie, authMiddleware, registerRoute, loginRoute, logoutRoute, };