wip user controllers: local login method

This commit is contained in:
Matteo Pagliazzi
2015-11-18 18:02:35 +01:00
parent 2dfca76a5e
commit d5d13477d7
6 changed files with 103 additions and 57 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
{
"missingAuthHeaders": "Missing authentication headers.",
"missingUsernameEmail": "Missing username or email.",
"missingPassword": "Missing password.",
"invalidLoginCredentials": "Incorrect username / email and / or password.",
"invalidCredentials": "User not found with given auth credentials.",
"accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your UUID \"<%= userId %>\" for assistance."
}

View File

@@ -1,34 +0,0 @@
let api = {};
/**
* @api {get} /example/:id Request Example information
* @apiVersion 3.0.0
* @apiName GetExample
* @apiGroup Example
*
* @apiParam {Number} id Examples unique ID.
*
* @apiSuccess {String} firstname Firstname of the Example.
* @apiSuccess {String} lastname Lastname of the Example.
*
* @apiSuccessExample Success-Response:
* HTTP/1.1 200 OK
* {
* "firstname": "John",
* "lastname": "Doe"
* }
*
* @apiUse NotFound
*/
api.exampleRoute = {
method: 'GET',
url: '/example/:id',
middlewares: [],
handler (req, res) {
res.status(200).send({
status: req.params.id,
});
},
};
export default api;

View File

@@ -0,0 +1,84 @@
import i18n from '../../../../common/script/i18n';
// TODO add getUserLanguage as a global middleware?
import getUserLanguage from '../../middlewares/api-v3/getUserLanguage';
import validator from 'validator';
import {
NotAuthorized,
} from '../../libs/api-v3/errors';
import passwordUtils from '../../libs/api-v3/password';
import User from '../../models/user';
let api = {};
/**
* @api {get} /user/login/local Login a user with email / username and password
* @apiVersion 3.0.0
* @apiName UserLoginLocal
* @apiGroup User
*
* @apiParam {String} username Username or email of the User.
* @apiParam {String} password The user's password
*
* @apiSuccess {String} _id The user's unique identifier
* @apiSuccess {String} apiToken The user's api token that must be used to authenticate requests.
*
* @apiSuccessExample Success-Response:
* HTTP/1.1 200 OK
* {
* "_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
* "apiToken": "1234567890"
* }
*
* @apiUse NotAuthorized
*/
api.loginLocal = {
method: 'GET',
url: '/user/login/local',
middlewares: [getUserLanguage],
handler (req, res, next) {
req.checkBody({
username: {
notEmpty: true,
errorMessage: i18n.t('missingUsernameEmail'),
},
password: {
notEmpty: true,
errorMessage: i18n.t('missingPassword'),
},
});
let validationErrors = req.validationErrors();
if (validationErrors) return next(validationErrors);
req.sanitizeBody('username').trim();
req.sanitizeBody('password').trim();
let login;
let username = req.body.username;
if (validator.isEmail(username)) {
login = {'auth.local.email': username.toLowerCase()}; // Emails are stored lowercase
} else {
login = {'auth.local.username': username};
}
User
.findOne(login, {auth: 1, apiToken: 1})
.exec()
.then((user) => {
// TODO abstract isnce it's also used in auth middlewares if (user.auth.blocked) return res.json(401, accountSuspended(user._id));
// TODO place back long error message return res.json(401, {err:"Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\"."});
let isValidPassword = user && user.auth.local.hashed_password !== passwordUtils.encrypt(req.body.password, user.auth.local.salt);
if (!isValidPassword) return next(new NotAuthorized(i18n.t('invalidLoginCredentials')));
res
.status(200)
.json({id: user._id, apiToken: user.apiToken});
})
.catch(next);
},
};
export default api;

View File

@@ -1,30 +1,19 @@
// Middlewares used to authenticate requests
import { import {
NotAuthorized, NotAuthorized,
BadRequest,
} from '../../libs/api-v3/errors'; } from '../../libs/api-v3/errors';
import i18n from '../../../../common/script/i18n';
import { import {
model as User, model as User,
} from '../../models/user'; } from '../../models/user';
// TODO use i18n
const missingAuthHeaders = 'Missing authentication headers.';
const userNotFound = 'User not found.';
const accountSuspended = (user) => {
return `Account has been suspended, please contact leslie@habitica.com
with your UUID ${user._id} for assistance.`;
};
// TODO adopt JSDoc syntax?
// Authenticate a request through the x-api-user and x-api key header // Authenticate a request through the x-api-user and x-api key header
export function authWithHeaders (req, res, next) { export function authWithHeaders (req, res, next) {
let userId = req.header['x-api-user']; let userId = req.header['x-api-user'];
let apiToken = req.header['x-api-key']; let apiToken = req.header['x-api-key'];
if (!userId || !apiToken) { if (!userId || !apiToken) {
// TODO use i18n? return next(new BadRequest(i18n.t('missingAuthHeaders')));
// TODO use badrequest error?
return next(new NotAuthorized(missingAuthHeaders));
} }
User.findOne({ User.findOne({
@@ -33,10 +22,8 @@ export function authWithHeaders (req, res, next) {
}) })
.exec() .exec()
.then((user) => { .then((user) => {
if (!user) return next(new NotAuthorized(userNotFound)); if (!user) return next(new NotAuthorized(i18n.t('invalidCredentials')));
if (user.blocked) return next(new NotAuthorized(i18n.t('accountSuspended', {userId: user._id})));
// TODO better handling for this case
if (user.blocked) return next(new NotAuthorized(accountSuspended(user)));
res.locals.user = user; res.locals.user = user;
// TODO use either session/cookie or headers, not both // TODO use either session/cookie or headers, not both
@@ -51,17 +38,17 @@ export function authWithHeaders (req, res, next) {
export function authWithSession (req, res, next) { export function authWithSession (req, res, next) {
let userId = req.session.userId; let userId = req.session.userId;
if (!userId) return next(new NotAuthorized(userNotFound)); if (!userId) return next(new NotAuthorized(i18n.t('invalidCredentials')));
User.findOne({ User.findOne({
_id: userId, _id: userId,
}) })
.exec() .exec()
.then((user) => { .then((user) => {
if (!user) return next(new NotAuthorized(userNotFound)); if (!user) return next(new NotAuthorized(i18n.t('invalidCredentials')));
res.locals.user = user; res.locals.user = user;
return next(); return next();
}) })
.catch(next); .catch(next);
} }

View File

@@ -1,5 +1,5 @@
// This module is only used to attach middlewares to the express app // This module is only used to attach middlewares to the express app
import expressValidator from 'express-validator';
import analytics from './analytics'; import analytics from './analytics';
import errorHandler from './errorHandler'; import errorHandler from './errorHandler';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
@@ -19,6 +19,7 @@ export default function attachMiddlewares (app) {
extended: true, // Uses 'qs' library as old connect middleware extended: true, // Uses 'qs' library as old connect middleware
})); }));
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(expressValidator()); // TODO config
app.use(analytics); app.use(analytics);
app.use('/api/v3', routes); app.use('/api/v3', routes);