const express = require('express'); const sha512 = require('js-sha512'); const shortid = require('shortid'); const base64 = require('base-64'); const db = require('./db'); const buildSessionCookie = ({ username, sessionToken, isLogout, remember }) => { if (!isLogout) { const joinedToken = `${username}&${sessionToken}`; const encodedToken = base64.encode(joinedToken); if (remember) { return `sessionCookie=${encodedToken}; Domain=localhost; HttpOnly; SameSite=strict;`; } else { return `sessionCookie=${encodedToken}; Domain=localhost; HttpOnly; SameSite=strict; Max-Age=86400;`; } } else { return `sessionCookie=deleted; Domain=localhost; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; SameSite=strict;`; } }; const normalRouter = express.Router(); const authedRouter = express.Router(); authedRouter.use(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 sessionCookie = base64.decode(encodedCookie); const [ username, sessionToken ] = sessionCookie.split('&'); 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(); }); normalRouter.get('/', (req, res) => { res.json({ message: 'Backend API', }); }); normalRouter.post('/register', 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, }); }); normalRouter.post('/login', 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', }); }); authedRouter.post('/logout', 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(); }); authedRouter.get('/user', (req, res) => { const user = req.user; return res.json({ message: `Hello user ${user.username}!`, username: user.username, }); }); module.exports = { authedRouter, normalRouter };