Added user sign up and login.

This commit is contained in:
DebaucheryLibrarian
2021-03-13 04:26:24 +01:00
parent 99cfd3dc3f
commit 816529b0ca
42 changed files with 741 additions and 8 deletions

80
src/auth.js Normal file
View File

@@ -0,0 +1,80 @@
'use strict';
const util = require('util');
const crypto = require('crypto');
const knex = require('./knex');
const { curateUser } = 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) {
const user = await knex('users')
.select('users.*', 'users_roles.abilities as role_abilities')
.where('username', credentials.username)
.orWhere('email', credentials.username)
.leftJoin('users_roles', 'users_roles.role', 'users.role')
.first();
if (!user) {
throw new HttpError('Username or password incorrect', 401);
}
await verifyPassword(credentials.password, user.password);
return curateUser(user);
}
async function signup(credentials) {
if (!credentials.username) {
throw new HttpError('Username required', 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', credentials.username)
.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 [user] = await knex('users')
.insert({
username: credentials.username,
email: credentials.email,
password: storedPassword,
})
.returning('*');
return curateUser(user);
}
module.exports = {
login,
signup,
};

40
src/users.js Normal file
View File

@@ -0,0 +1,40 @@
'use strict';
const knex = require('./knex');
function curateUser(user) {
if (!user) {
return null;
}
const ability = [...(user.role_abilities || []), ...(user.abilities || [])];
const curatedUser = {
id: user.id,
username: user.username,
email: user.email,
emailVerified: user.email_verified,
identityVerified: user.identity_verified,
ability,
createdAt: user.created_at,
};
return curatedUser;
}
async function fetchUser(userId) {
const user = await knex('users')
.select('users.*', 'users_roles.abilities as role_abilities')
.where('id', userId)
.orWhere('username', userId)
.orWhere('email', userId)
.leftJoin('users_roles', 'users_roles.role', 'users.role')
.first();
return curateUser(user);
}
module.exports = {
curateUser,
fetchUser,
};

45
src/web/auth.js Normal file
View File

@@ -0,0 +1,45 @@
'use strict';
const { login, signup } = require('../auth');
const { fetchUser } = require('../users');
async function loginApi(req, res) {
const user = await login(req.body);
req.session.user = user;
res.send(user);
}
async function logoutApi(req, res) {
req.session.destroy((error) => {
if (error) {
res.status(500).send();
}
res.status(204).send();
});
}
async function fetchMeApi(req, res) {
if (req.session.user) {
req.session.user = await fetchUser(req.session.user.id, req.session.user);
res.send(req.session.user);
return;
}
res.status(401).send();
}
async function signupApi(req, res) {
const user = await signup(req.body);
res.send(user);
}
module.exports = {
login: loginApi,
logout: logoutApi,
fetchMe: fetchMeApi,
signup: signupApi,
};

View File

@@ -15,6 +15,13 @@ const errorHandler = require('./error');
const pg = require('./postgraphile');
const {
login,
logout,
signup,
fetchMe,
} = require('./auth');
const {
fetchScene,
fetchScenes,
@@ -60,6 +67,12 @@ async function initServer() {
next();
});
router.get('/api/session', fetchMe);
router.post('/api/session', login);
router.delete('/api/session', logout);
router.post('/api/users', signup);
router.get('/api/scenes', fetchScenes);
router.get('/api/scenes/:releaseId', fetchScene);
router.get('/api/scenes/:releaseId/poster', fetchScenePoster);