Implement passport auth against mongo

This commit is contained in:
Elliot DeNolf
2018-09-26 22:08:08 -04:00
parent ac9958b44c
commit 423f56f978
14 changed files with 391 additions and 66 deletions

View File

@@ -1 +1 @@
export { Payload } from './lib/payload';
export { init } from './lib/payload';

View File

@@ -0,0 +1,68 @@
import httpStatus from 'http-status';
import passport from 'passport';
import APIError from '../helpers/APIError';
import User from '../models/user.model';
/**
* Returns passport login response (cookie) when valid username and password is provided
* @param req
* @param res
* @returns {*}
*/
function login(req, res) {
return res.json(req.user);
}
/**
* Returns User when succesfully registered
* @param req
* @param res
* @param next
* @returns {*}
*/
function register(req, res, next) {
User.register(new User({ email: req.body.email }), req.body.password, (err, user) => {
if (err) {
const error = new APIError('Authentication error', httpStatus.UNAUTHORIZED);
return next(error);
}
passport.authenticate('local')(req, res, () => {
res.json({ user });
});
});
}
/**
* Returns User if user session is still open
* @param req
* @param res
* @param next
* @returns {*}
*/
function me(req, res, next) {
if (!req.user) {
const error = new APIError('Authentication error', httpStatus.UNAUTHORIZED);
next(error);
}
res.json(req.user);
}
/**
* Middleware to check user is authorised to access endpoint.
* @param req
* @param res
* @param next
* @returns {*}
*/
function checkAuth(req, res, next) {
if (!req.user) {
const error = new APIError('Authentication error', httpStatus.UNAUTHORIZED);
next(error);
}
next();
}
export default { login, register, me, checkAuth };

View File

@@ -0,0 +1,27 @@
const page = require('../../models/page.model');
const pageController = {
get: (req, res, next) => {
page.find((err, pages, next) => {
if (err) {
return next(err);
}
return res.json(pages);
});
},
post: (req, res, next) => {
const newPage = new page(req.body);
newPage.save((err, page, next) => {
if (err) {
return next(err);
}
return res.json(page);
});
},
};
module.exports = pageController;

View File

@@ -0,0 +1,34 @@
import httpStatus from 'http-status';
/**
* @extends Error
*/
class ExtendableError extends Error {
constructor(message, status, isPublic) {
super(message);
this.name = this.constructor.name;
this.message = message;
this.status = status;
this.isPublic = isPublic;
this.isOperational = true; // This is required since bluebird 4 doesn't append it anymore.
Error.captureStackTrace(this, this.constructor.name);
}
}
/**
* Class representing an API error.
* @extends ExtendableError
*/
class APIError extends ExtendableError {
/**
* Creates an API error.
* @param {string} message - Error message.
* @param {number} status - HTTP status code of error.
* @param {boolean} isPublic - Whether the message should be visible to user or not.
*/
constructor(message, status = httpStatus.INTERNAL_SERVER_ERROR, isPublic = false) {
super(message, status, isPublic);
}
}
export default APIError;

View File

@@ -0,0 +1,70 @@
import mongoose from 'mongoose';
import httpStatus from 'http-status';
import passportLocalMongoose from 'passport-local-mongoose';
import APIError from '../helpers/APIError';
/**
* User Schema
*/
const UserSchema = new mongoose.Schema({
email: { type: String, unique: true },
push: { type: String, unique: true },
createdAt: { type: Date, default: Date.now },
role: { type: String, enum: [ 'user', 'agent', 'admin' ], default: 'user' },
});
/**
* Add your
* - pre-save hooks
* - validations
* - virtuals
*/
/**
* Methods
*/
UserSchema.method({});
/**
* Statics
*/
UserSchema.statics = {
/**
* Get user
* @param {ObjectId} id - The objectId of user.
* @returns {Promise<User, APIError>}
*/
get(id) {
return this.findById(id)
.exec()
.then(user => {
if (user) {
return user;
}
const err = new APIError('No such user exists!', httpStatus.NOT_FOUND);
return Promise.reject(err);
});
},
/**
* List users in descending order of 'createdAt' timestamp.
* @param {number} skip - Number of users to be skipped.
* @param {number} limit - Limit number of users to be returned.
* @returns {Promise<User[]>}
*/
list({ skip = 0, limit = 50 } = {}) {
return this.find()
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit)
.exec();
},
};
UserSchema.plugin(passportLocalMongoose, { usernameField: 'email' });
/**
* @typedef User
*/
export default mongoose.model('User', UserSchema);

View File

@@ -1,33 +1,18 @@
/**
* Module dependencies.
*/
import routes from './routes/index.route';
import passport from 'passport';
import User from './models/user.model';
const api = require('./api');
// class Payload {
export function init(app, mongoose, options) {
// baseURL = options.baseURL;
class Payload {
constructor(options) {
this.express = options.express;
this.mongoose = options.mongoose;
this.baseURL = options.baseURL;
this.models = [];
// configure passport for Auth
app.use(passport.initialize());
app.use(passport.session());
// TODO: Investigate creating an API controller to encapsulate
this.express.get('/api', (req, res) => {
// TODO: Possible return basic API info and/or HATEOAS info to other routes
res.status(200).send({ models: this.models });
});
}
passport.use(User.createStrategy());
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());
loadModel(modelName) {
console.log(`Loading ${modelName} model...`);
// TODO: Require file, validate schema, mount routes instead of just adding to array
let model = { [modelName]: {} };
if (!this.models[modelName]) {
this.models.push(model);
console.log(`${modelName} Loaded.`);
}
}
app.use(routes);
}
export { Payload };

View File

@@ -0,0 +1,17 @@
import express from 'express';
import validate from 'express-validation';
import passport from 'passport';
import paramValidation from './auth.validations';
import authCtrl from '../../controllers/auth.controller';
const router = express.Router(); // eslint-disable-line new-cap
router
.route('/login')
.post(validate(paramValidation.login), passport.authenticate('local'), authCtrl.login);
router
.route('/register')
.post(validate(paramValidation.register), authCtrl.register);
module.exports = router;

View File

@@ -0,0 +1,18 @@
import Joi from 'joi';
export default {
// POST /auth/register
register: {
body: {
email: Joi.string().email().required(),
},
},
// POST /auth/login
login: {
body: {
email: Joi.string().email().required(),
password: Joi.string().required(),
},
},
};

View File

@@ -0,0 +1,10 @@
const express = require('express');
const authRoutes = require('./auth/auth.route');
const router = express.Router({}); // eslint-disable-line new-cap
/** GET /health-check - Check service health */
router.get('/health-check', (req, res) => res.send('OK'));
router.use('', authRoutes);
module.exports = router;

View File

@@ -0,0 +1,11 @@
const express = require('express');
const pageController = require('../../controllers/page.controller');
const router = express.Router(); // eslint-disable-line new-cap
router
.route('') // TODO: not sure how to incorporate url params like `:pageId`
.get(pageController.get)
.post(pageController.post);
module.exports = router;