Ghost/core/server/api/v2/session.js
Naz c84866dda7
Improved password reset and session invalidation for "locked" users (#11790)
- Fixed session invalidation for "locked" user
  - Currently Ghost API was returning 404 for users having status set to "locked". This lead the user to be stuck in Ghost-Admin with "Rousource Not Found" error message.
  - By returning 401 for non-"active" users it allows for the Ghost-Admin to redirect the user to "signin" screen where they would be instructed to reset their password

- Fixed error message returned by session API
  - Instead of returning generic 'access' denied message when error happens during `User.check` we want to return more specific error thrown inside of the method, e.g.: 'accountLocked' or 'accountSuspended'
  - Fixed messaging for 'accountLocked' i18n, which not corresponds to the
actual UI available to the end user

- Added automatic password reset email to locked users on sign-in
  - uses alternative email for required password reset so it's clear that this is a security related reset and not a user-requested reset

- Backported the auto sending of required password reset email to v2 sign-in route
  - used by 3rd party clients where the email is necessary for users to know why login is failing

Co-authored-by: Kevin Ansfield <kevin@lookingsideways.co.uk>
2020-05-05 19:37:53 +01:00

67 lines
2.1 KiB
JavaScript

const Promise = require('bluebird');
const common = require('../../lib/common');
const models = require('../../models');
const auth = require('../../services/auth');
const api = require('./index');
const session = {
read(frame) {
/*
* TODO
* Don't query db for user, when new api http wrapper is in we can
* have direct access to req.user, we can also get access to some session
* inofrmation too and send it back
*/
return models.User.findOne({id: frame.options.context.user});
},
add(frame) {
const object = frame.data;
if (!object || !object.username || !object.password) {
return Promise.reject(new common.errors.UnauthorizedError({
message: common.i18n.t('errors.middleware.auth.accessDenied')
}));
}
return models.User.check({
email: object.username,
password: object.password
}).then((user) => {
return Promise.resolve((req, res, next) => {
req.brute.reset(function (err) {
if (err) {
return next(err);
}
req.user = user;
auth.session.createSession(req, res, next);
});
});
}).catch(async (err) => {
if (!common.errors.utils.isIgnitionError(err)) {
throw new common.errors.UnauthorizedError({
message: common.i18n.t('errors.middleware.auth.accessDenied'),
err
});
}
if (err.errorType === 'PasswordResetRequiredError') {
await api.authentication.generateResetToken({
passwordreset: [{
email: object.username
}],
required: true
}, frame.options.context);
}
throw err;
});
},
delete() {
return Promise.resolve((req, res, next) => {
auth.session.destroySession(req, res, next);
});
}
};
module.exports = session;