149 lines
3.1 KiB
JavaScript
149 lines
3.1 KiB
JavaScript
import config from 'config';
|
|
import util from 'util';
|
|
import crypto from 'crypto';
|
|
import bhttp from 'bhttp';
|
|
|
|
import initLogger from './logger';
|
|
import { HttpError } from './errors';
|
|
import knex from './knex';
|
|
|
|
const logger = initLogger();
|
|
const scrypt = util.promisify(crypto.scrypt);
|
|
|
|
function curateDatabaseUser(user) {
|
|
return {
|
|
id: user.id,
|
|
username: user.username,
|
|
};
|
|
}
|
|
|
|
async function hashPassword(password) {
|
|
const salt = crypto.randomBytes(16).toString('hex');
|
|
const hash = await scrypt(password, salt, 64);
|
|
|
|
return `${salt}/${hash.toString('hex')}`;
|
|
}
|
|
|
|
async function verifyPassword(password, storedPassword) {
|
|
const [salt, hash] = storedPassword.split('/');
|
|
const hashedPassword = await scrypt(password, salt, 64);
|
|
|
|
if (hashedPassword.toString('hex') !== hash) {
|
|
throw new HttpError({
|
|
statusCode: 400,
|
|
statusMessage: 'Username or password incorrect',
|
|
});
|
|
}
|
|
}
|
|
|
|
async function verifyCaptcha(captchaToken) {
|
|
if (!captchaToken) {
|
|
throw new HttpError({
|
|
statusCode: 400,
|
|
statusMessage: 'No CAPTCHA provided',
|
|
});
|
|
}
|
|
|
|
const res = await bhttp.post('https://hcaptcha.com/siteverify', {
|
|
response: captchaToken,
|
|
secret: config.auth.captcha.secret,
|
|
});
|
|
|
|
if (res.statusCode !== 200 || !res.body.success) {
|
|
throw new HttpError({
|
|
statusCode: 498,
|
|
statusMessage: 'Invalid CAPTCHA',
|
|
});
|
|
}
|
|
}
|
|
|
|
async function login(credentials) {
|
|
if (!credentials.username) {
|
|
throw new HttpError({
|
|
statusCode: 400,
|
|
statusMessage: 'You must provide a username',
|
|
});
|
|
}
|
|
|
|
if (!credentials.password) {
|
|
throw new HttpError({
|
|
statusCode: 400,
|
|
statusMessage: 'You must provide a password',
|
|
});
|
|
}
|
|
|
|
const user = await knex('users')
|
|
.where('username', credentials.username)
|
|
.orWhere('email', credentials.username)
|
|
.first();
|
|
|
|
if (!user) {
|
|
throw new HttpError({
|
|
statusCode: 400,
|
|
statusMessage: 'Username or password incorrect',
|
|
});
|
|
}
|
|
|
|
await verifyPassword(credentials.password, user.password);
|
|
|
|
return curateDatabaseUser(user);
|
|
}
|
|
|
|
async function createUser(credentials, context) {
|
|
if (!credentials.username) {
|
|
throw new HttpError({
|
|
statusCode: 400,
|
|
statusMessage: 'You must provide a username',
|
|
});
|
|
}
|
|
|
|
if (!credentials.password) {
|
|
throw new HttpError({
|
|
statusCode: 400,
|
|
statusMessage: 'You must provide a password',
|
|
});
|
|
}
|
|
|
|
if (config.auth.captcha.enabled) {
|
|
await verifyCaptcha(credentials.captchaToken);
|
|
}
|
|
|
|
const hashedPassword = await hashPassword(credentials.password);
|
|
|
|
try {
|
|
const [userEntry] = await knex('users')
|
|
.insert({
|
|
username: credentials.username,
|
|
email: credentials.email,
|
|
password: hashedPassword,
|
|
ip: context.ip,
|
|
})
|
|
.returning('*');
|
|
|
|
const user = curateDatabaseUser(userEntry);
|
|
|
|
logger.info(`Registered user ${user.username} (${user.id}, ${user.email}, ${userEntry.ip})`);
|
|
|
|
return user;
|
|
} catch (error) {
|
|
if (error.code === '23505') {
|
|
throw new HttpError({
|
|
statusCode: 409,
|
|
statusMessage: 'Username or e-mail already registered',
|
|
});
|
|
}
|
|
|
|
logger.error(error.message);
|
|
|
|
throw new HttpError({
|
|
statusCode: 500,
|
|
statusMessage: 'Sign-up failed',
|
|
});
|
|
}
|
|
}
|
|
|
|
export {
|
|
createUser,
|
|
login,
|
|
};
|