'use strict'; const config = require('config'); const util = require('util'); const crypto = require('crypto'); const knex = require('./knex'); const { curateUser, fetchUser } = require('./users'); const { HttpError } = require('./errors'); const scrypt = util.promisify(crypto.scrypt); async function verifyPassword(password, storedPassword) { const [salt, hash] = storedPassword.split('/'); const hashedPassword = (await scrypt(password, salt, 64)).toString('hex'); if (hashedPassword === hash) { return true; } throw new HttpError('Username or password incorrect', 401); } async function login(credentials) { if (!config.auth.login) { throw new HttpError('Authentication is disabled', 405); } const user = await fetchUser(credentials.username.trim(), true); if (!user) { throw new HttpError('Username or password incorrect', 401); } await verifyPassword(credentials.password, user.password); await knex('users') .update('last_login', 'NOW()') .where('id', user.id); return curateUser(user); } async function signup(credentials) { if (!config.auth.signup) { throw new HttpError('Authentication is disabled', 405); } const curatedUsername = credentials.username.trim(); if (!curatedUsername) { throw new HttpError('Username required', 400); } if (curatedUsername.length < config.auth.usernameLength[0]) { throw new HttpError('Username is too short', 400); } if (curatedUsername.length > config.auth.usernameLength[1]) { throw new HttpError('Username is too long', 400); } if (!config.auth.usernamePattern.test(curatedUsername)) { throw new HttpError('Username contains invalid characters', 400); } if (!credentials.email) { throw new HttpError('E-mail required', 400); } if (credentials.password?.length < 3) { throw new HttpError('Password must be 3 characters or longer', 400); } const existingUser = await knex('users') .where('username', curatedUsername) .orWhere('email', credentials.email) .first(); if (existingUser) { throw new HttpError('Username or e-mail already in use', 409); } const salt = crypto.randomBytes(16).toString('hex'); const hashedPassword = (await scrypt(credentials.password, salt, 64)).toString('hex'); const storedPassword = `${salt}/${hashedPassword}`; const [{ id: userId }] = await knex('users') .insert({ username: curatedUsername, email: credentials.email, password: storedPassword, }) .returning('id'); await knex('stashes').insert({ user_id: userId, name: 'Favorites', slug: 'favorites', public: false, primary: true, }); return fetchUser(userId); } module.exports = { login, signup, };