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
|
* Error Handler
|
||||||
*/
|
*/
|
||||||
app.use(function (err, req, res) {
|
app.use(function (err, req, res, next) {
|
||||||
console.error(err);
|
if (res.headersSent) {
|
||||||
res.status(500).json({
|
return next(err);
|
||||||
error: `Internal Serverless Error - "${err.message}"`,
|
}
|
||||||
|
res.status(err.code || 500);
|
||||||
|
res.json({
|
||||||
|
error: err.message || `Internal Server Error - "${err.message}"`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,29 +3,18 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const jwt = require(`jsonwebtoken`);
|
const jwt = require(`jsonwebtoken`);
|
||||||
const { users } = require(`../models`);
|
const users = require(`../models/users`);
|
||||||
const { comparePassword } = require(`../utils`);
|
const { comparePassword } = require(`../utils`);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save
|
* Save
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
* @param {*} res
|
* @param {*} res
|
||||||
* @param {*} next
|
|
||||||
*/
|
*/
|
||||||
const register = async (req, res, next) => {
|
const register = async (req, res) => {
|
||||||
try {
|
await users.create(req.body);
|
||||||
await users.register(req.body);
|
|
||||||
} catch (error) {
|
|
||||||
return res.status(400).json({ error: error.message });
|
|
||||||
}
|
|
||||||
|
|
||||||
let user;
|
let user = await users.getByEmail(req.body.email);
|
||||||
try {
|
|
||||||
user = await users.getByEmail(req.body.email);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
return next(error, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = jwt.sign(
|
const token = jwt.sign(
|
||||||
users.convertToPublicFormat(user),
|
users.convertToPublicFormat(user),
|
||||||
@@ -45,15 +34,9 @@ const register = async (req, res, next) => {
|
|||||||
* Sign a user in
|
* Sign a user in
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
* @param {*} res
|
* @param {*} res
|
||||||
* @param {*} next
|
|
||||||
*/
|
*/
|
||||||
const login = async (req, res, next) => {
|
const login = async (req, res) => {
|
||||||
let user;
|
let user = await users.getByEmail(req.body.email);
|
||||||
try {
|
|
||||||
user = await users.getByEmail(req.body.email);
|
|
||||||
} catch (error) {
|
|
||||||
return next(error, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return res
|
return res
|
||||||
@@ -86,7 +69,6 @@ const login = async (req, res, next) => {
|
|||||||
* Get a user
|
* Get a user
|
||||||
* @param {*} req
|
* @param {*} req
|
||||||
* @param {*} res
|
* @param {*} res
|
||||||
* @param {*} next
|
|
||||||
*/
|
*/
|
||||||
const get = async (req, res) => {
|
const get = async (req, res) => {
|
||||||
const user = users.convertToPublicFormat(req.user);
|
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
|
* Model: Users
|
||||||
*/
|
*/
|
||||||
|
const utils = require(`../../utils`);
|
||||||
const utils = require(`../utils`);
|
const db = require(`../db`);
|
||||||
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.email User email
|
||||||
* @param {string} user.password User password
|
* @param {string} user.password User password
|
||||||
*/
|
*/
|
||||||
const register = async (user = {}) => {
|
const create = async (user = {}) => {
|
||||||
if (!user.email) {
|
validation.validateOrThrow(schemas.create, user);
|
||||||
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`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if user is already registered
|
|
||||||
const existingUser = await getByEmail(user.email);
|
const existingUser = await getByEmail(user.email);
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
throw new Error(
|
throw utils.customError(
|
||||||
`A user with email "${user.email}" is already registered`
|
`A user with email "${user.email}" is already registered`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -40,16 +33,10 @@ const register = async (user = {}) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get user by email address
|
* Get user by email address
|
||||||
* @param {string} email
|
* @param {string} email Email address of user to retrieve
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const getByEmail = async (email) => {
|
const getByEmail = async (email) => {
|
||||||
if (!email) {
|
validation.validateOrThrow(sharedSchemas.email, email);
|
||||||
throw new Error(`"email" is required`);
|
|
||||||
}
|
|
||||||
if (!utils.validateEmailAddress(email)) {
|
|
||||||
throw new Error(`"${email}" is not a valid email address`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = await db.getByKey(email);
|
let user = await db.getByKey(email);
|
||||||
|
|
||||||
@@ -61,11 +48,8 @@ const getByEmail = async (email) => {
|
|||||||
* Get user by id
|
* Get user by id
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const getById = async (id) => {
|
const getById = async (id) => {
|
||||||
if (!id) {
|
validation.validateOrThrow(sharedSchemas.id, id);
|
||||||
throw new Error(`"id" is required`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = await db.getById(`user`, id);
|
let user = await db.getById(`user`, id);
|
||||||
|
|
||||||
@@ -97,7 +81,7 @@ const convertToPublicFormat = (user = {}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
register,
|
create,
|
||||||
getByEmail,
|
getByEmail,
|
||||||
getById,
|
getById,
|
||||||
convertToPublicFormat,
|
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": "",
|
"description": "",
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"ajv": "^6.12.6",
|
||||||
"aws-sdk": "^2.792.0",
|
"aws-sdk": "^2.792.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
|||||||
@@ -5,16 +5,30 @@
|
|||||||
const bcrypt = require(`bcryptjs`);
|
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) => {
|
const customError = (message, code) => {
|
||||||
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,}))$/;
|
const error = new Error(message);
|
||||||
return re.test(String(email).toLowerCase());
|
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
|
* Hash password
|
||||||
* @param {*} user
|
* @param {string} password Password to hash
|
||||||
*/
|
*/
|
||||||
const hashPassword = (password) => {
|
const hashPassword = (password) => {
|
||||||
const salt = bcrypt.genSaltSync(10);
|
const salt = bcrypt.genSaltSync(10);
|
||||||
@@ -23,13 +37,16 @@ const hashPassword = (password) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare password
|
* Compare password
|
||||||
|
* @param {string} candidatePassword Hashed password supplied by user
|
||||||
|
* @param {string} trustedPassword Hashed password on record for user
|
||||||
*/
|
*/
|
||||||
const comparePassword = (candidatePassword, trustedPassword) => {
|
const comparePassword = (candidatePassword, trustedPassword) => {
|
||||||
return bcrypt.compareSync(candidatePassword, trustedPassword);
|
return bcrypt.compareSync(candidatePassword, trustedPassword);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
customError,
|
||||||
|
validationError,
|
||||||
hashPassword,
|
hashPassword,
|
||||||
comparePassword,
|
comparePassword,
|
||||||
validateEmailAddress,
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user