Add Account controller and create account endpoint

This commit is contained in:
Robbie Antenesse 2019-09-27 17:14:24 -06:00
parent d213ce6f75
commit 0cad0adfdb
2 changed files with 166 additions and 31 deletions

View File

@ -0,0 +1,114 @@
const crypto = require('crypto');
class Account {
constructor (userModel) {
this.model = userModel;
}
// hashPassword and validatePassword modified from https://stackoverflow.com/a/17201493
static hashPassword (password) {
var salt = crypto.randomBytes(128).toString('base64');
var iterations = 10000;
var hash = crypto.pbkdf2Sync(password, salt, iterations, 128, 'sha512').toString('base64');
return {
salt,
hash,
iterations,
};
}
static verifyPassword (savedHash, savedSalt, passwordAttempt) {
const attemptedHash = crypto.pbkdf2Sync(passwordAttempt, savedSalt, 10000, 128, 'sha512').toString('base64');
return savedHash == attemptedHash;
}
static createAccountDataIsValid (createAccountData) {
if (typeof createAccountData.email === 'undefined'
|| typeof createAccountData.username === 'undefined'
|| typeof createAccountData.password === 'undefined'
|| createAccountData.password === '') {
return {
error: true,
message: 'api.account_create_required_data_missing',
};
}
if (createAccountData.email.length < 5 || !/^.+@.+\..+$/.test(createAccountData.email)) {
return {
error: true,
message: 'api.account_create_invalid_email',
};
}
if (createAccountData.username.length < 2 || !/^[a-z0-9_]+$/i.test(createAccountData.username)) {
return {
error: true,
message: 'api.account_create_invalid_username',
};
}
return true;
}
static cleanCreateAccountFormData (formData) {
return {
email: formData.email.trim(),
username: formData.username.toString().trim(),
displayName: typeof formData.displayName !== 'undefined' ? formData.displayName.toString().trim() : 'A Bee',
password: formData.password,
}
}
async emailExists (email) {
const existingUser = await this.model.find({
attributes: ['id'],
where: {
email,
},
});
return existingUser != null;
}
async usernameExists (username) {
const existingUser = await this.model.find({
attributes: ['id'],
where: {
username,
},
});
return existingUser != null;
}
async canCreateUser (email, username) {
const emailExists = await this.emailExists(email);
const usernameExists = await this.usernameExists(username);
if (emailExists) {
return {
error: true,
message: 'api.account_email_exists',
};
}
if (usernameExists) {
return {
error: true,
message: 'api.account_username_exists',
};
}
return true;
}
async createUser (email, username, displayName, password) {
const hashData = Account.hashPassword(password);
// The data should already have gone through Account.cleanCreateAccountFormData()
return await this.model.create({
email,
username,
displayName,
passwordHash: hashData.hash,
passwordSalt: hashData.salt,
});
}
}
module.exports = Account;

View File

@ -1,39 +1,60 @@
const Sequelize = require('sequelize');
const faker = require('faker');
const Account = require('../controllers/account');
async function routes(fastify, options) {
fastify.get('/api/test-db-connect', async (request, reply) => {
const User = fastify.sequelize.define('user', {
email: {
type: Sequelize.STRING,
allowNull: false,
},
test: {
type: Sequelize.STRING,
allowNull: true,
}
});
User.sync();
return await User.findAll()
.catch(async ex => {
return await User.sync().then(() => {
return [];
});
});
// return await User.sync().then(() => {
// return User.create({
// email: faker.internet.email(),
// })
// });
fastify.get('/api/accounts/test', async (request, reply) => {
return false;
});
/*fastify.get('/login', async (request, reply) => {
fastify.post('/api/account/create', async (request, reply) => {
if (request.isLoggedInUser) {
return reply.code(400).send({
error: true,
message: 'api.account_already_logged_in',
});
}
const formDataIsValid = Account.createAccountDataIsValid(request.body);
if (formDataIsValid !== true) {
return reply.code(400).send(formDataIsValid);
}
const formData = Account.cleanCreateAccountFormData(request.body);
const account = new Account(fastify.models.User);
const canCreateUser = await account.canCreateUser(formData.email, formData.username);
if (canCreateUser !== true) {
return reply.code(400).send(canCreateUser);
}
const result = await account.createUser(formData.email, formData.username, formData.displayName, formData.password);
if (typeof result.error !== 'undefined') {
return reply.code(400).send(result);
}
const token = fastify.jwt.sign({ id: result.id });
const expireTime = fastify.siteConfig.tokenExpireDays * (24 * 60 * 60e3); // The section in parentheses is milliseconds in a day
return reply
.setCookie('token', token, {
path: '/',
expires: new Date(Date.now() + expireTime),
maxAge: new Date(Date.now() + expireTime), // Both are set as a "just in case"
httpOnly: true, // Prevents JavaScript on the front end from grabbing it
sameSite: true, // Prevents the cookie from being used outside of this site
})
.send({
error: false,
message: 'api.account_create_success',
});
});
fastify.get('/api/login', async (request, reply) => {
reply.view('login.hbs', { text: request.isLoggedInUser ? JSON.stringify(fastify.jwt.decode(request.cookies.token)) : 'you are NOT logged in' });
});
fastify.post('/login-validate', async (request, reply) => {
fastify.post('/api/login-validate', async (request, reply) => {
if (typeof request.body.email === "undefined" || typeof request.body.password === "undefined") {
reply.redirect('/login', 400);
}
@ -51,9 +72,9 @@ async function routes(fastify, options) {
.redirect('/', 200);
});
fastify.get('/logout', async (request, reply) => {
fastify.get('/api/logout', async (request, reply) => {
reply.clearCookie('token', { path: '/' }).redirect('/?loggedout');
});*/
});
}
module.exports = routes;