Use AJV, refactor a lot
This commit is contained in:
11
api/app.js
11
api/app.js
@@ -76,10 +76,13 @@ app.get(`/*`, (req, res) => {
|
||||
/**
|
||||
* Error Handler
|
||||
*/
|
||||
app.use(function (err, req, res) {
|
||||
console.error(err);
|
||||
res.status(500).json({
|
||||
error: `Internal Serverless Error - "${err.message}"`,
|
||||
app.use(function (err, req, res, next) {
|
||||
if (res.headersSent) {
|
||||
return next(err);
|
||||
}
|
||||
res.status(err.code || 500);
|
||||
res.json({
|
||||
error: err.message || `Internal Server Error - "${err.message}"`,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,29 +3,18 @@
|
||||
*/
|
||||
|
||||
const jwt = require(`jsonwebtoken`);
|
||||
const { users } = require(`../models`);
|
||||
const users = require(`../models/users`);
|
||||
const { comparePassword } = require(`../utils`);
|
||||
|
||||
/**
|
||||
* Save
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
* @param {*} next
|
||||
*/
|
||||
const register = async (req, res, next) => {
|
||||
try {
|
||||
await users.register(req.body);
|
||||
} catch (error) {
|
||||
return res.status(400).json({ error: error.message });
|
||||
}
|
||||
|
||||
let user;
|
||||
try {
|
||||
user = await users.getByEmail(req.body.email);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return next(error, null);
|
||||
}
|
||||
const register = async (req, res) => {
|
||||
await users.create(req.body);
|
||||
|
||||
let user = await users.getByEmail(req.body.email);
|
||||
|
||||
const token = jwt.sign(
|
||||
users.convertToPublicFormat(user),
|
||||
@@ -45,15 +34,9 @@ const register = async (req, res, next) => {
|
||||
* Sign a user in
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
* @param {*} next
|
||||
*/
|
||||
const login = async (req, res, next) => {
|
||||
let user;
|
||||
try {
|
||||
user = await users.getByEmail(req.body.email);
|
||||
} catch (error) {
|
||||
return next(error, null);
|
||||
}
|
||||
const login = async (req, res) => {
|
||||
let user = await users.getByEmail(req.body.email);
|
||||
|
||||
if (!user) {
|
||||
return res
|
||||
@@ -86,7 +69,6 @@ const login = async (req, res, next) => {
|
||||
* Get a user
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
* @param {*} next
|
||||
*/
|
||||
const get = async (req, res) => {
|
||||
const user = users.convertToPublicFormat(req.user);
|
||||
|
||||
10
api/models/schemas.js
Normal file
10
api/models/schemas.js
Normal file
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"format": "email"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
}
|
||||
}
|
||||
@@ -2,30 +2,23 @@
|
||||
/**
|
||||
* Model: Users
|
||||
*/
|
||||
|
||||
const utils = require(`../utils`);
|
||||
const db = require(`./db`);
|
||||
const utils = require(`../../utils`);
|
||||
const db = require(`../db`);
|
||||
const schemas = require(`./schemas`);
|
||||
const sharedSchemas = require(`../schemas`);
|
||||
const validation = require(`../validation`);
|
||||
|
||||
/**
|
||||
* Register user
|
||||
* Create a new user
|
||||
* @param {string} user.email User email
|
||||
* @param {string} user.password User password
|
||||
*/
|
||||
const register = async (user = {}) => {
|
||||
if (!user.email) {
|
||||
throw new Error(`"email" is required`);
|
||||
}
|
||||
if (!user.password) {
|
||||
throw new Error(`"password" is required`);
|
||||
}
|
||||
if (!utils.validateEmailAddress(user.email)) {
|
||||
throw new Error(`"${user.email}" is not a valid email address`);
|
||||
}
|
||||
const create = async (user = {}) => {
|
||||
validation.validateOrThrow(schemas.create, user);
|
||||
|
||||
// Check if user is already registered
|
||||
const existingUser = await getByEmail(user.email);
|
||||
if (existingUser) {
|
||||
throw new Error(
|
||||
throw utils.customError(
|
||||
`A user with email "${user.email}" is already registered`
|
||||
);
|
||||
}
|
||||
@@ -40,16 +33,10 @@ const register = async (user = {}) => {
|
||||
|
||||
/**
|
||||
* Get user by email address
|
||||
* @param {string} email
|
||||
* @param {string} email Email address of user to retrieve
|
||||
*/
|
||||
|
||||
const getByEmail = async (email) => {
|
||||
if (!email) {
|
||||
throw new Error(`"email" is required`);
|
||||
}
|
||||
if (!utils.validateEmailAddress(email)) {
|
||||
throw new Error(`"${email}" is not a valid email address`);
|
||||
}
|
||||
validation.validateOrThrow(sharedSchemas.email, email);
|
||||
|
||||
let user = await db.getByKey(email);
|
||||
|
||||
@@ -61,11 +48,8 @@ const getByEmail = async (email) => {
|
||||
* Get user by id
|
||||
* @param {string} id
|
||||
*/
|
||||
|
||||
const getById = async (id) => {
|
||||
if (!id) {
|
||||
throw new Error(`"id" is required`);
|
||||
}
|
||||
validation.validateOrThrow(sharedSchemas.id, id);
|
||||
|
||||
let user = await db.getById(`user`, id);
|
||||
|
||||
@@ -97,7 +81,7 @@ const convertToPublicFormat = (user = {}) => {
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
register,
|
||||
create,
|
||||
getByEmail,
|
||||
getById,
|
||||
convertToPublicFormat,
|
||||
19
api/models/users/schemas.js
Normal file
19
api/models/users/schemas.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const sharedSchemas = require(`../schemas`);
|
||||
|
||||
module.exports = {
|
||||
"create": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Create User",
|
||||
"description": "An object to create a new user",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": sharedSchemas.email,
|
||||
"password": {
|
||||
"type": "string",
|
||||
"minLength": 10,
|
||||
"maxLength": 50
|
||||
}
|
||||
},
|
||||
"required": ["email", "password"]
|
||||
}
|
||||
};
|
||||
16
api/models/validation.js
Normal file
16
api/models/validation.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const Ajv = require(`ajv`);
|
||||
|
||||
const { validationError } = require(`../utils`);
|
||||
|
||||
const validateOrThrow = (schema, data) => {
|
||||
const ajv = new Ajv();
|
||||
const valid = ajv.validate(schema, data);
|
||||
|
||||
if (!valid) {
|
||||
throw validationError(ajv.errors);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
validateOrThrow,
|
||||
};
|
||||
@@ -4,6 +4,7 @@
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.6",
|
||||
"aws-sdk": "^2.792.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"express": "^4.17.1",
|
||||
|
||||
@@ -5,16 +5,30 @@
|
||||
const bcrypt = require(`bcryptjs`);
|
||||
|
||||
/**
|
||||
* Validate email address
|
||||
* Build a custom error
|
||||
* @param {string} message Error message
|
||||
* @param {number} code Error code, should be HTTP status code
|
||||
*/
|
||||
const validateEmailAddress = (email) => {
|
||||
var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
return re.test(String(email).toLowerCase());
|
||||
const customError = (message, code) => {
|
||||
const error = new Error(message);
|
||||
error.code = code;
|
||||
return error;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a custom validation error
|
||||
* @param {array[string]} validationErrors Validation errors
|
||||
*/
|
||||
const validationError = (validationErrors) => {
|
||||
let error = new Error("Validation error(s)");
|
||||
error.validationErrors = validationErrors;
|
||||
error.code = 400;
|
||||
return error;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hash password
|
||||
* @param {*} user
|
||||
* @param {string} password Password to hash
|
||||
*/
|
||||
const hashPassword = (password) => {
|
||||
const salt = bcrypt.genSaltSync(10);
|
||||
@@ -23,13 +37,16 @@ const hashPassword = (password) => {
|
||||
|
||||
/**
|
||||
* Compare password
|
||||
* @param {string} candidatePassword Hashed password supplied by user
|
||||
* @param {string} trustedPassword Hashed password on record for user
|
||||
*/
|
||||
const comparePassword = (candidatePassword, trustedPassword) => {
|
||||
return bcrypt.compareSync(candidatePassword, trustedPassword);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
customError,
|
||||
validationError,
|
||||
hashPassword,
|
||||
comparePassword,
|
||||
validateEmailAddress,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user