Implement passport auth against mongo
This commit is contained in:
@@ -1 +1 @@
|
||||
export { Payload } from './lib/payload';
|
||||
export { init } from './lib/payload';
|
||||
|
||||
68
src/lib/controllers/auth.controller.js
Normal file
68
src/lib/controllers/auth.controller.js
Normal 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 };
|
||||
27
src/lib/controllers/page.controller.js
Normal file
27
src/lib/controllers/page.controller.js
Normal 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;
|
||||
34
src/lib/helpers/APIError.js
Normal file
34
src/lib/helpers/APIError.js
Normal 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;
|
||||
70
src/lib/models/user.model.js
Normal file
70
src/lib/models/user.model.js
Normal 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);
|
||||
@@ -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 };
|
||||
|
||||
17
src/lib/routes/auth/auth.route.js
Normal file
17
src/lib/routes/auth/auth.route.js
Normal 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;
|
||||
18
src/lib/routes/auth/auth.validations.js
Normal file
18
src/lib/routes/auth/auth.validations.js
Normal 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(),
|
||||
},
|
||||
},
|
||||
};
|
||||
10
src/lib/routes/index.route.js
Normal file
10
src/lib/routes/index.route.js
Normal 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;
|
||||
11
src/lib/routes/page/page.route.js
Normal file
11
src/lib/routes/page/page.route.js
Normal 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;
|
||||
Reference in New Issue
Block a user