-
-
Notifications
You must be signed in to change notification settings - Fork 10.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
issue #7452 Remote oauth2 authentication with Ghost.org. This PR supports: - oauth2 login or local login - authentication on blog setup - authentication on invite - normal authentication - does not contain many, many tests, but we'll improve in the next alpha weeks
- Loading branch information
1 parent
3e727d0
commit 6473c9e
Showing
26 changed files
with
743 additions
and
274 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
var models = require('../models'), | ||
utils = require('../utils'), | ||
i18n = require('../i18n'), | ||
errors = require('../errors'), | ||
_ = require('lodash'), | ||
strategies; | ||
|
||
strategies = { | ||
|
||
/** | ||
* ClientPasswordStrategy | ||
* | ||
* This strategy is used to authenticate registered OAuth clients. It is | ||
* employed to protect the `token` endpoint, which consumers use to obtain | ||
* access tokens. The OAuth 2.0 specification suggests that clients use the | ||
* HTTP Basic scheme to authenticate (not implemented yet). | ||
* Use of the client password strategy is implemented to support ember-simple-auth. | ||
*/ | ||
clientPasswordStrategy: function clientPasswordStrategy(clientId, clientSecret, done) { | ||
return models.Client.findOne({slug: clientId}, {withRelated: ['trustedDomains']}) | ||
.then(function then(model) { | ||
if (model) { | ||
var client = model.toJSON({include: ['trustedDomains']}); | ||
if (client.status === 'enabled' && client.secret === clientSecret) { | ||
return done(null, client); | ||
} | ||
} | ||
return done(null, false); | ||
}); | ||
}, | ||
|
||
/** | ||
* BearerStrategy | ||
* | ||
* This strategy is used to authenticate users based on an access token (aka a | ||
* bearer token). The user must have previously authorized a client | ||
* application, which is issued an access token to make requests on behalf of | ||
* the authorizing user. | ||
*/ | ||
bearerStrategy: function bearerStrategy(accessToken, done) { | ||
return models.Accesstoken.findOne({token: accessToken}) | ||
.then(function then(model) { | ||
if (model) { | ||
var token = model.toJSON(); | ||
if (token.expires > Date.now()) { | ||
return models.User.findOne({id: token.user_id}) | ||
.then(function then(model) { | ||
if (model) { | ||
var user = model.toJSON(), | ||
info = {scope: '*'}; | ||
return done(null, {id: user.id}, info); | ||
} | ||
return done(null, false); | ||
}); | ||
} else { | ||
return done(null, false); | ||
} | ||
} else { | ||
return done(null, false); | ||
} | ||
}); | ||
}, | ||
|
||
/** | ||
* Ghost Strategy | ||
* patronusRefreshToken: will be null for now, because we don't need it right now | ||
* | ||
* CASES: | ||
* - via invite token | ||
* - via normal auth | ||
* - via setup | ||
* | ||
* @TODO: validate patronus profile? | ||
*/ | ||
ghostStrategy: function ghostStrategy(req, patronusAccessToken, patronusRefreshToken, profile, done) { | ||
var inviteToken = req.body.inviteToken, | ||
options = {context: {internal: true}}, | ||
handleInviteToken, handleSetup; | ||
|
||
handleInviteToken = function handleInviteToken() { | ||
var user, invite; | ||
inviteToken = utils.decodeBase64URLsafe(inviteToken); | ||
|
||
return models.Invite.findOne({token: inviteToken}, options) | ||
.then(function addInviteUser(_invite) { | ||
invite = _invite; | ||
|
||
if (!invite) { | ||
throw new errors.NotFoundError(i18n.t('errors.api.invites.inviteNotFound')); | ||
} | ||
|
||
if (invite.get('expires') < Date.now()) { | ||
throw new errors.NotFoundError(i18n.t('errors.api.invites.inviteExpired')); | ||
} | ||
|
||
return models.User.add({ | ||
email: profile.email_address, | ||
name: profile.email_address, | ||
password: utils.uid(50), | ||
roles: invite.toJSON().roles | ||
}, options); | ||
}) | ||
.then(function destroyInvite(_user) { | ||
user = _user; | ||
return invite.destroy(options); | ||
}) | ||
.then(function () { | ||
return user; | ||
}); | ||
}; | ||
|
||
handleSetup = function handleSetup() { | ||
return models.User.findOne({slug: 'ghost-owner', status: 'all'}, options) | ||
.then(function fetchedOwner(owner) { | ||
if (!owner) { | ||
throw new errors.NotFoundError(i18n.t('errors.models.user.userNotFound')); | ||
} | ||
|
||
return models.User.edit({ | ||
email: profile.email_address, | ||
status: 'active' | ||
}, _.merge({id: owner.id}, options)); | ||
}); | ||
}; | ||
|
||
models.User.getByEmail(profile.email_address, options) | ||
.then(function fetchedUser(user) { | ||
if (user) { | ||
return user; | ||
} | ||
|
||
if (inviteToken) { | ||
return handleInviteToken(); | ||
} | ||
|
||
return handleSetup(); | ||
}) | ||
.then(function updatePatronusToken(user) { | ||
options.id = user.id; | ||
return models.User.edit({patronus_access_token: patronusAccessToken}, options); | ||
}) | ||
.then(function returnResponse(user) { | ||
done(null, user, profile); | ||
}) | ||
.catch(done); | ||
} | ||
}; | ||
|
||
module.exports = strategies; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
var errors = require('../errors'), | ||
labs = require('../utils/labs'), | ||
i18n = require('../i18n'), | ||
authorize; | ||
|
||
authorize = { | ||
// Workaround for missing permissions | ||
// TODO: rework when https://github.com/TryGhost/Ghost/issues/3911 is done | ||
requiresAuthorizedUser: function requiresAuthorizedUser(req, res, next) { | ||
if (req.user && req.user.id) { | ||
return next(); | ||
} else { | ||
return errors.handleAPIError(new errors.NoPermissionError(i18n.t('errors.middleware.auth.pleaseSignIn')), req, res, next); | ||
} | ||
}, | ||
|
||
// ### Require user depending on public API being activated. | ||
requiresAuthorizedUserPublicAPI: function requiresAuthorizedUserPublicAPI(req, res, next) { | ||
if (labs.isSet('publicAPI') === true) { | ||
return next(); | ||
} else { | ||
if (req.user && req.user.id) { | ||
return next(); | ||
} else { | ||
return errors.handleAPIError(new errors.NoPermissionError(i18n.t('errors.middleware.auth.pleaseSignIn')), req, res, next); | ||
} | ||
} | ||
} | ||
}; | ||
|
||
module.exports = authorize; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
var passport = require('./passport'), | ||
authorize = require('./authorize'), | ||
authenticate = require('./authenticate'), | ||
oauth = require('./oauth'); | ||
|
||
exports.init = function (options) { | ||
oauth.init(); | ||
|
||
return passport.init(options) | ||
.then(function (response) { | ||
return {auth: response.passport}; | ||
}); | ||
}; | ||
|
||
exports.oauth = oauth; | ||
exports.authorize = authorize; | ||
exports.authenticate = authenticate; |
Oops, something went wrong.