let express = require('express'); let bcrypt = require('bcrypt'); let uuid = require('uuid'); let errorMiddleware = require('./util/error-middleware'); let { validateUser } = require('./util/validate-user'); let { getAccountDb } = require('./account-db'); let app = express(); app.use(errorMiddleware); function init() { // eslint-disable-previous-line @typescript-eslint/no-empty-function } function hashPassword(password) { return bcrypt.hashSync(password, 12); } // Non-authenticated endpoints: // // /boostrap (special endpoint for setting up the instance, cant call again) // /login app.get('/needs-bootstrap', (req, res) => { let accountDb = getAccountDb(); let rows = accountDb.all('SELECT * FROM auth'); res.send({ status: 'ok', data: { bootstrapped: rows.length > 0 } }); }); app.post('/bootstrap', (req, res) => { let { password } = req.body; let accountDb = getAccountDb(); let rows = accountDb.all('SELECT * FROM auth'); if (rows.length !== 0) { res.status(400).send({ status: 'error', reason: 'already-bootstrapped' }); return; } if (password == null || password === '') { res.status(400).send({ status: 'error', reason: 'invalid-password' }); return; } // Hash the password. There's really not a strong need for this // since this is a self-hosted instance owned by the user. // However, just in case we do it. let hashed = hashPassword(password); accountDb.mutate('INSERT INTO auth (password) VALUES (?)', [hashed]); let token = uuid.v4(); accountDb.mutate('INSERT INTO sessions (token) VALUES (?)', [token]); res.send({ status: 'ok', data: { token } }); }); app.post('/login', (req, res) => { let { password } = req.body; let accountDb = getAccountDb(); let row = accountDb.first('SELECT * FROM auth'); let confirmed = row && bcrypt.compareSync(password, row.password); let token = null; if (confirmed) { // Right now, tokens are permanent and there's just one in the // system. In the future this should probably evolve to be a // "session" that times out after a long time or something, and // maybe each device has a different token let row = accountDb.first('SELECT * FROM sessions'); token = row.token; } res.send({ status: 'ok', data: { token } }); }); app.post('/change-password', (req, res) => { let user = validateUser(req, res); if (!user) return; let accountDb = getAccountDb(); let { password } = req.body; if (password == null || password === '') { res.send({ status: 'error', reason: 'invalid-password' }); return; } let hashed = hashPassword(password); // Note that this doesn't have a WHERE. This table only ever has 1 // row (maybe that will change in the future? if this this will not work) accountDb.mutate('UPDATE auth SET password = ?', [hashed]); res.send({ status: 'ok', data: {} }); }); app.get('/validate', (req, res) => { let user = validateUser(req, res); if (user) { res.send({ status: 'ok', data: { validated: true } }); } }); app.use(errorMiddleware); module.exports.handlers = app; module.exports.init = init;