var oauth2orize = require('oauth2orize'), passport = require('passport'), models = require('../models'), errors = require('../errors'), authUtils = require('./utils'), spamPrevention = require('../middleware/api/spam-prevention'), i18n = require('../i18n'), oauthServer, oauth; function exchangeRefreshToken(client, refreshToken, scope, body, authInfo, done) { models.Refreshtoken.findOne({token: refreshToken}) .then(function then(model) { if (!model) { return done(new errors.NoPermissionError({message: i18n.t('errors.middleware.oauth.invalidRefreshToken')}), false); } else { var token = model.toJSON(); if (token.expires > Date.now()) { spamPrevention.userLogin.reset(authInfo.ip, body.refresh_token + 'login'); authUtils.createTokens({ clientId: token.client_id, userId: token.user_id, oldAccessToken: authInfo.accessToken, oldRefreshToken: refreshToken }).then(function (response) { return done(null, response.access_token, {expires_in: response.expires_in}); }).catch(function handleError(error) { return done(error, false); }); } else { done(new errors.UnauthorizedError({message: i18n.t('errors.middleware.oauth.refreshTokenExpired')}), false); } } }); } // We are required to pass in authInfo in order to reset spam counter for user login function exchangePassword(client, username, password, scope, body, authInfo, done) { models.Client.findOne({slug: client.slug}) .then(function then(client) { if (!client) { return done(new errors.NoPermissionError({ message: i18n.t('errors.middleware.oauth.invalidClient') }), false); } // Validate the user return models.User.check({email: username, password: password}) .then(function then(user) { return authUtils.createTokens({ clientId: client.id, userId: user.id }); }) .then(function then(response) { spamPrevention.userLogin.reset(authInfo.ip, username + 'login'); return done(null, response.access_token, response.refresh_token, {expires_in: response.expires_in}); }); }) .catch(function handleError(error) { return done(error, false); }); } function exchangeAuthorizationCode(req, res, next) { if (!req.body.authorizationCode) { return next(new errors.UnauthorizedError({ message: i18n.t('errors.middleware.auth.accessDenied') })); } req.query.code = req.body.authorizationCode; passport.authenticate('ghost', {session: false, failWithError: false}, function authenticate(err, user) { if (err) { return next(new errors.UnauthorizedError({ err: err })); } if (!user) { return next(new errors.UnauthorizedError({ message: i18n.t('errors.middleware.auth.accessDenied') })); } spamPrevention.userLogin.reset(req.authInfo.ip, req.body.authorizationCode + 'login'); authUtils.createTokens({ clientId: req.client.id, userId: user.id }).then(function then(response) { res.json({ access_token: response.access_token, refresh_token: response.refresh_token, expires_in: response.expires_in }); }).catch(function (err) { next(err); }); })(req, res, next); } oauth = { init: function init() { oauthServer = oauth2orize.createServer(); // remove all expired accesstokens on startup models.Accesstoken.destroyAllExpired(); // remove all expired refreshtokens on startup models.Refreshtoken.destroyAllExpired(); // Exchange user id and password for access tokens. The callback accepts the // `client`, which is exchanging the user's name and password from the // authorization request for verification. If these values are validated, the // application issues an access token on behalf of the user who authorized the code. oauthServer.exchange(oauth2orize.exchange.password({userProperty: 'client'}, exchangePassword)); // Exchange the refresh token to obtain an access token. The callback accepts the // `client`, which is exchanging a `refreshToken` previously issued by the server // for verification. If these values are validated, the application issues an // access token on behalf of the user who authorized the code. oauthServer.exchange(oauth2orize.exchange.refreshToken({userProperty: 'client'}, exchangeRefreshToken)); /** * Exchange authorization_code for an access token. * We forward to authorization code to Ghost.org. * * oauth2orize offers a default implementation via exchange.authorizationCode, but this function * wraps the express request and response. So no chance to get access to it. * We use passport to communicate with Ghost.org. Passport's module design requires the express req/res. * * For now it's OK to not use exchange.authorizationCode. You can read through the implementation here: * https://github.com/jaredhanson/oauth2orize/blob/master/lib/exchange/authorizationCode.js * As you can see, it does some validation and set's some headers, not very very important, * but it's part of the oauth2 spec. * * @TODO: How to use exchange.authorizationCode in combination of passport? */ oauthServer.exchange('authorization_code', exchangeAuthorizationCode); }, // ### Generate access token Middleware // register the oauth2orize middleware for password and refresh token grants generateAccessToken: function generateAccessToken(req, res, next) { /** * TODO: * https://github.com/jaredhanson/oauth2orize/issues/182 * oauth2orize only offers the option to forward request information via authInfo object * * Important: only used for resetting the brute count (access to req.ip) */ req.authInfo = { ip: req.ip, accessToken: authUtils.getBearerAutorizationToken(req) }; return oauthServer.token()(req, res, next); } }; module.exports = oauth;