mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
commit
28d10a9e2f
12 changed files with 194 additions and 57 deletions
|
@ -401,20 +401,20 @@
|
|||
validator.handleErrors(validationErrors);
|
||||
} else {
|
||||
$.ajax({
|
||||
url: Ghost.paths.subdir + '/ghost/changepw/',
|
||||
type: 'POST',
|
||||
url: Ghost.paths.subdir + '/ghost/api/v0.1/users/password/',
|
||||
type: 'PUT',
|
||||
headers: {
|
||||
'X-CSRF-Token': $("meta[name='csrf-param']").attr('content')
|
||||
},
|
||||
data: {
|
||||
password: oldPassword,
|
||||
newpassword: newPassword,
|
||||
ne2password: ne2Password
|
||||
},
|
||||
data: {password: [{
|
||||
oldPassword: oldPassword,
|
||||
newPassword: newPassword,
|
||||
ne2Password: ne2Password
|
||||
}]},
|
||||
success: function (msg) {
|
||||
Ghost.notifications.addItem({
|
||||
type: 'success',
|
||||
message: msg.msg,
|
||||
message: msg.password[0].message,
|
||||
status: 'passive',
|
||||
id: 'success-98'
|
||||
});
|
||||
|
|
99
core/server/api/authentication.js
Normal file
99
core/server/api/authentication.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
|
||||
var dataProvider = require('../models'),
|
||||
settings = require('./settings'),
|
||||
mail = require('./mail'),
|
||||
utils = require('./utils'),
|
||||
when = require('when'),
|
||||
errors = require('../errors'),
|
||||
config = require('../config'),
|
||||
ONE_DAY = 86400000,
|
||||
authentication;
|
||||
|
||||
/**
|
||||
* ## Authentication API Methods
|
||||
*
|
||||
* **See:** [API Methods](index.js.html#api%20methods)
|
||||
*/
|
||||
authentication = {
|
||||
|
||||
/**
|
||||
* ## Generate Reset Token
|
||||
* generate a reset token for a given email address
|
||||
* @param {{passwordreset}}
|
||||
* @returns {Promise(passwordreset)} message
|
||||
*/
|
||||
generateResetToken: function generateResetToken(object) {
|
||||
var expires = Date.now() + ONE_DAY,
|
||||
email;
|
||||
return utils.checkObject(object, 'passwordreset').then(function (checkedPasswordReset) {
|
||||
if (checkedPasswordReset.passwordreset[0].email) {
|
||||
email = checkedPasswordReset.passwordreset[0].email;
|
||||
} else {
|
||||
return when.reject(new errors.BadRequestError('No email provided.'));
|
||||
}
|
||||
|
||||
return settings.read({context: {internal: true}, key: 'dbHash'}).then(function (response) {
|
||||
var dbHash = response.settings[0].value;
|
||||
|
||||
return dataProvider.User.generateResetToken(email, expires, dbHash).then(function (resetToken) {
|
||||
var baseUrl = config().forceAdminSSL ? (config().urlSSL || config().url) : config().url,
|
||||
siteLink = '<a href="' + baseUrl + '">' + baseUrl + '</a>',
|
||||
resetUrl = baseUrl.replace(/\/$/, '') + '/ghost/reset/' + resetToken + '/',
|
||||
resetLink = '<a href="' + resetUrl + '">' + resetUrl + '</a>',
|
||||
payload = {
|
||||
mail: [{
|
||||
message: {
|
||||
to: email,
|
||||
subject: 'Reset Password',
|
||||
html: '<p><strong>Hello!</strong></p>' +
|
||||
'<p>A request has been made to reset the password on the site ' + siteLink + '.</p>' +
|
||||
'<p>Please follow the link below to reset your password:<br><br>' + resetLink + '</p>' +
|
||||
'<p>Ghost</p>'
|
||||
},
|
||||
options: {}
|
||||
}]
|
||||
};
|
||||
|
||||
return mail.send(payload);
|
||||
}).then(function () {
|
||||
return when.resolve({passwordreset: [{message: 'Check your email for further instructions.'}]});
|
||||
}).otherwise(function (error) {
|
||||
// TODO: This is kind of sketchy, depends on magic string error.message from Bookshelf.
|
||||
if (error && error.message === 'NotFound') {
|
||||
error.message = "Invalid email address";
|
||||
}
|
||||
|
||||
return when.reject(new errors.UnauthorizedError(error.message));
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* ## Reset Password
|
||||
* reset password if a valid token and password (2x) is passed
|
||||
* @param {{passwordreset}}
|
||||
* @returns {Promise(passwordreset)} message
|
||||
*/
|
||||
resetPassword: function resetPassword(object) {
|
||||
var resetToken,
|
||||
newPassword,
|
||||
ne2Password;
|
||||
return utils.checkObject(object, 'passwordreset').then(function (checkedPasswordReset) {
|
||||
resetToken = checkedPasswordReset.passwordreset[0].token;
|
||||
newPassword = checkedPasswordReset.passwordreset[0].newPassword;
|
||||
ne2Password = checkedPasswordReset.passwordreset[0].ne2Password;
|
||||
|
||||
return settings.read({context: {internal: true}, key: 'dbHash'}).then(function (response) {
|
||||
var dbHash = response.settings[0].value;
|
||||
return dataProvider.User.resetPassword(resetToken, newPassword, ne2Password, dbHash);
|
||||
}).then(function () {
|
||||
return when.resolve({passwordreset: [{message: 'Password changed successfully.'}]});
|
||||
}).otherwise(function (error) {
|
||||
return when.reject(new errors.UnauthorizedError(error.message));
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = authentication;
|
|
@ -4,19 +4,20 @@
|
|||
// Ghost's JSON API is integral to the workings of Ghost, regardless of whether you want to access data internally,
|
||||
// from a theme, an app, or from an external app, you'll use the Ghost JSON API to do so.
|
||||
|
||||
var _ = require('lodash'),
|
||||
when = require('when'),
|
||||
config = require('../config'),
|
||||
var _ = require('lodash'),
|
||||
when = require('when'),
|
||||
config = require('../config'),
|
||||
// Include Endpoints
|
||||
db = require('./db'),
|
||||
mail = require('./mail'),
|
||||
notifications = require('./notifications'),
|
||||
posts = require('./posts'),
|
||||
settings = require('./settings'),
|
||||
tags = require('./tags'),
|
||||
themes = require('./themes'),
|
||||
users = require('./users'),
|
||||
slugs = require('./slugs'),
|
||||
db = require('./db'),
|
||||
mail = require('./mail'),
|
||||
notifications = require('./notifications'),
|
||||
posts = require('./posts'),
|
||||
settings = require('./settings'),
|
||||
tags = require('./tags'),
|
||||
themes = require('./themes'),
|
||||
users = require('./users'),
|
||||
slugs = require('./slugs'),
|
||||
authentication = require('./authentication'),
|
||||
|
||||
http,
|
||||
formatHttpErrors,
|
||||
|
@ -254,7 +255,8 @@ module.exports = {
|
|||
tags: tags,
|
||||
themes: themes,
|
||||
users: users,
|
||||
slugs: slugs
|
||||
slugs: slugs,
|
||||
authentication: authentication
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -113,22 +113,63 @@ users = {
|
|||
return when.reject(new errors.NoPermissionError('You do not have permission to add a users.'));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* ### Destroy
|
||||
* @param {{id, context}} options
|
||||
* @returns {Promise(User)}
|
||||
*/
|
||||
destroy: function destroy(options) {
|
||||
return canThis(options.context).remove.user(options.id).then(function () {
|
||||
return users.read(options).then(function (result) {
|
||||
return dataProvider.User.destroy(options).then(function () {
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}, function () {
|
||||
return when.reject(new errors.NoPermissionError('You do not have permission to remove posts.'));
|
||||
});
|
||||
},
|
||||
|
||||
// TODO complete documentation as part of #2822
|
||||
/**
|
||||
* ### Register
|
||||
* Allow to register a user using the API without beeing authenticated in. Needed to set up the first user.
|
||||
* @param {User} object the user to create
|
||||
* @returns {Promise(User}} Newly created user
|
||||
*/
|
||||
// TODO: create a proper API end point and use JSON API format
|
||||
register: function register(object) {
|
||||
// TODO: if we want to prevent users from being created with the signup form this is the right place to do it
|
||||
return users.add(object, {context: {internal: true}});
|
||||
},
|
||||
|
||||
|
||||
check: function check(object) {
|
||||
return dataProvider.User.check(object);
|
||||
},
|
||||
|
||||
changePassword: function changePassword(object) {
|
||||
return dataProvider.User.changePassword(object);
|
||||
/**
|
||||
* ### Change Password
|
||||
* @param {password} object
|
||||
* @param {{context}} options
|
||||
* @returns {Promise(password}} success message
|
||||
*/
|
||||
changePassword: function changePassword(object, options) {
|
||||
var oldPassword,
|
||||
newPassword,
|
||||
ne2Password;
|
||||
return utils.checkObject(object, 'password').then(function (checkedPasswordReset) {
|
||||
oldPassword = checkedPasswordReset.password[0].oldPassword;
|
||||
newPassword = checkedPasswordReset.password[0].newPassword;
|
||||
ne2Password = checkedPasswordReset.password[0].ne2Password;
|
||||
|
||||
return dataProvider.User.changePassword(oldPassword, newPassword, ne2Password, options).then(function () {
|
||||
return when.resolve({password: [{message: 'Password changed successfully.'}]});
|
||||
}).otherwise(function (error) {
|
||||
return when.reject(new errors.UnauthorizedError(error.message));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// TODO: remove when old admin is removed, functionality lives now in api/authentication
|
||||
generateResetToken: function generateResetToken(email) {
|
||||
var expires = Date.now() + ONE_DAY;
|
||||
return settings.read({context: {internal: true}, key: 'dbHash'}).then(function (response) {
|
||||
|
@ -137,6 +178,7 @@ users = {
|
|||
});
|
||||
},
|
||||
|
||||
// TODO: remove when old admin is removed -> not needed anymore
|
||||
validateToken: function validateToken(token) {
|
||||
return settings.read({context: {internal: true}, key: 'dbHash'}).then(function (response) {
|
||||
var dbHash = response.settings[0].value;
|
||||
|
@ -144,6 +186,7 @@ users = {
|
|||
});
|
||||
},
|
||||
|
||||
// TODO: remove when old admin is removed, functionality lives now in api/authentication
|
||||
resetPassword: function resetPassword(token, newPassword, ne2Password) {
|
||||
return settings.read({context: {internal: true}, key: 'dbHash'}).then(function (response) {
|
||||
var dbHash = response.settings[0].value;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
// Shared helpers for working with the API
|
||||
var when = require('when'),
|
||||
_ = require('lodash'),
|
||||
errors = require('../errors'),
|
||||
utils;
|
||||
|
||||
utils = {
|
||||
|
@ -15,7 +16,7 @@ utils = {
|
|||
*/
|
||||
checkObject: function (object, docName) {
|
||||
if (_.isEmpty(object) || _.isEmpty(object[docName]) || _.isEmpty(object[docName][0])) {
|
||||
return when.reject({type: 'BadRequest', message: 'No root key (\'' + docName + '\') provided.'});
|
||||
return when.reject(new errors.BadRequestError('No root key (\'' + docName + '\') provided.'));
|
||||
}
|
||||
return when.resolve(object);
|
||||
}
|
||||
|
|
|
@ -373,6 +373,7 @@ adminControllers = {
|
|||
adminNav: setSelected(adminNavbar, 'login')
|
||||
});
|
||||
},
|
||||
// TODO: remove when old admin is removed, functionality lives now in api/authentication
|
||||
// Route: doForgotten
|
||||
// Path: /ghost/forgotten/
|
||||
// Method: POST
|
||||
|
@ -449,6 +450,7 @@ adminControllers = {
|
|||
});
|
||||
});
|
||||
},
|
||||
// TODO: remove when old admin is removed, functionality lives now in api/authentication
|
||||
// Route: doReset
|
||||
// Path: /ghost/reset/:token
|
||||
// Method: POST
|
||||
|
@ -470,21 +472,6 @@ adminControllers = {
|
|||
}).otherwise(function (err) {
|
||||
res.json(401, {error: err.message});
|
||||
});
|
||||
},
|
||||
// Route: doChangePassword
|
||||
// Path: /ghost/changepw/
|
||||
// Method: POST
|
||||
'doChangePassword': function (req, res) {
|
||||
return api.users.changePassword({
|
||||
currentUser: req.session.user,
|
||||
oldpw: req.body.password,
|
||||
newpw: req.body.newpassword,
|
||||
ne2pw: req.body.ne2password
|
||||
}).then(function () {
|
||||
res.json(200, {msg: 'Password changed successfully'});
|
||||
}, function (error) {
|
||||
res.send(401, {error: error.message});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -115,6 +115,11 @@ var fixtures = {
|
|||
"action_type": "add",
|
||||
"object_type": "user"
|
||||
},
|
||||
{
|
||||
"name": "Remove users",
|
||||
"action_type": "remove",
|
||||
"object_type": "user"
|
||||
},
|
||||
{
|
||||
"name": "Browse settings",
|
||||
"action_type": "browse",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
function UnauthorizedError(message) {
|
||||
this.message = message;
|
||||
this.stack = new Error().stack;
|
||||
this.code = 404;
|
||||
this.code = 401;
|
||||
this.type = this.name;
|
||||
}
|
||||
|
||||
|
|
|
@ -33,25 +33,25 @@ var middleware = {
|
|||
authenticate: function (req, res, next) {
|
||||
var noAuthNeeded = [
|
||||
'/ghost/signin/', '/ghost/signout/', '/ghost/signup/',
|
||||
'/ghost/forgotten/', '/ghost/reset/', '/ghost/ember/',
|
||||
'/ghost/ember/signin'
|
||||
'/ghost/forgotten/', '/ghost/reset/', '/ghost/ember/'
|
||||
],
|
||||
path,
|
||||
subPath;
|
||||
|
||||
// SubPath is the url path starting after any default subdirectories
|
||||
// it is stripped of anything after the two levels `/ghost/.*?/` as the reset link has an argument
|
||||
subPath = req.path.substring(config().paths.subdir.length);
|
||||
path = req.path.substring(config().paths.subdir.length);
|
||||
/*jslint regexp:true, unparam:true*/
|
||||
subPath = subPath.replace(/^(\/.*?\/.*?\/)(.*)?/, function (match, a) {
|
||||
subPath = path.replace(/^(\/.*?\/.*?\/)(.*)?/, function (match, a) {
|
||||
return a;
|
||||
});
|
||||
|
||||
if (res.isAdmin) {
|
||||
if (subPath.indexOf('/ghost/api/') === 0) {
|
||||
if (subPath.indexOf('/ghost/api/') === 0 && path.indexOf('/ghost/api/v0.1/authentication/passwordreset/') !== 0) {
|
||||
return middleware.authAPI(req, res, next);
|
||||
}
|
||||
|
||||
if (noAuthNeeded.indexOf(subPath) < 0) {
|
||||
if (noAuthNeeded.indexOf(subPath) < 0 && subPath.indexOf('/ghost/api/') !== 0) {
|
||||
return middleware.auth(req, res, next);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -267,15 +267,11 @@ User = ghostBookshelf.Model.extend({
|
|||
* Naive change password method
|
||||
* @param {object} _userdata email, old pw, new pw, new pw2
|
||||
*/
|
||||
changePassword: function (_userdata) {
|
||||
changePassword: function (oldPassword, newPassword, ne2Password, options) {
|
||||
var self = this,
|
||||
userid = _userdata.currentUser,
|
||||
oldPassword = _userdata.oldpw,
|
||||
newPassword = _userdata.newpw,
|
||||
ne2Password = _userdata.ne2pw,
|
||||
userid = options.context.user,
|
||||
user = null;
|
||||
|
||||
|
||||
if (newPassword !== ne2Password) {
|
||||
return when.reject(new Error('Your new passwords do not match'));
|
||||
}
|
||||
|
@ -294,7 +290,6 @@ User = ghostBookshelf.Model.extend({
|
|||
return nodefn.call(bcrypt.hash, newPassword, salt);
|
||||
}).then(function (hash) {
|
||||
user.save({password: hash});
|
||||
|
||||
return user;
|
||||
});
|
||||
},
|
||||
|
|
|
@ -47,7 +47,6 @@ adminRoutes = function (middleware) {
|
|||
router.post('/ghost/forgotten/', admin.doForgotten);
|
||||
router.get('/ghost/reset/:token', admin.reset);
|
||||
router.post('/ghost/reset/:token', admin.doReset);
|
||||
router.post('/ghost/changepw/', admin.doChangePassword);
|
||||
|
||||
router.get('/ghost/editor/:id/:action', admin.editor);
|
||||
router.get('/ghost/editor/:id/', admin.editor);
|
||||
|
|
|
@ -20,7 +20,10 @@ apiRoutes = function (middleware) {
|
|||
// ## Users
|
||||
router.get('/ghost/api/v0.1/users/', api.http(api.users.browse));
|
||||
router.get('/ghost/api/v0.1/users/:id/', api.http(api.users.read));
|
||||
router.put('/ghost/api/v0.1/users/password/', api.http(api.users.changePassword));
|
||||
router.put('/ghost/api/v0.1/users/:id/', api.http(api.users.edit));
|
||||
router['delete']('/ghost/api/v0.1/users/:id/', api.http(api.users.destroy));
|
||||
|
||||
// ## Tags
|
||||
router.get('/ghost/api/v0.1/tags/', api.http(api.tags.browse));
|
||||
// ## Themes
|
||||
|
@ -37,8 +40,11 @@ apiRoutes = function (middleware) {
|
|||
// ## Mail
|
||||
router.post('/ghost/api/v0.1/mail', api.http(api.mail.send));
|
||||
router.post('/ghost/api/v0.1/mail/test', api.http(api.mail.sendTest));
|
||||
// #### Slugs
|
||||
// ## Slugs
|
||||
router.get('/ghost/api/v0.1/slugs/:type/:name', api.http(api.slugs.generate));
|
||||
// ## Authentication
|
||||
router.post('/ghost/api/v0.1/authentication/passwordreset', api.http(api.authentication.generateResetToken));
|
||||
router.put('/ghost/api/v0.1/authentication/passwordreset', api.http(api.authentication.resetPassword));
|
||||
|
||||
return router;
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue