2016-09-30 14:45:59 +03:00
|
|
|
var oauth2orize = require('oauth2orize'),
|
2016-10-17 13:45:50 +03:00
|
|
|
passport = require('passport'),
|
2016-09-30 14:45:59 +03:00
|
|
|
models = require('../models'),
|
|
|
|
errors = require('../errors'),
|
2017-03-01 13:12:03 +03:00
|
|
|
authUtils = require('./utils'),
|
2016-10-11 11:36:00 +03:00
|
|
|
spamPrevention = require('../middleware/api/spam-prevention'),
|
2016-09-30 14:45:59 +03:00
|
|
|
i18n = require('../i18n'),
|
|
|
|
oauthServer,
|
|
|
|
oauth;
|
|
|
|
|
2016-11-17 16:02:56 +03:00
|
|
|
function exchangeRefreshToken(client, refreshToken, scope, body, authInfo, done) {
|
2016-09-30 14:45:59 +03:00
|
|
|
models.Refreshtoken.findOne({token: refreshToken})
|
|
|
|
.then(function then(model) {
|
|
|
|
if (!model) {
|
2016-10-06 15:27:35 +03:00
|
|
|
return done(new errors.NoPermissionError({message: i18n.t('errors.middleware.oauth.invalidRefreshToken')}), false);
|
2016-09-30 14:45:59 +03:00
|
|
|
} else {
|
2017-03-01 13:12:03 +03:00
|
|
|
var token = model.toJSON();
|
2016-09-30 14:45:59 +03:00
|
|
|
|
|
|
|
if (token.expires > Date.now()) {
|
2017-03-13 23:07:12 +03:00
|
|
|
spamPrevention.userLogin().reset(authInfo.ip, body.refresh_token + 'login');
|
2017-01-24 00:44:39 +03:00
|
|
|
|
2017-03-01 13:12:03 +03:00
|
|
|
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});
|
2016-09-30 14:45:59 +03:00
|
|
|
}).catch(function handleError(error) {
|
|
|
|
return done(error, false);
|
|
|
|
});
|
|
|
|
} else {
|
2016-10-06 15:27:35 +03:00
|
|
|
done(new errors.UnauthorizedError({message: i18n.t('errors.middleware.oauth.refreshTokenExpired')}), false);
|
2016-09-30 14:45:59 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2016-11-08 14:33:19 +03:00
|
|
|
// 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) {
|
2016-09-30 14:45:59 +03:00
|
|
|
models.Client.findOne({slug: client.slug})
|
|
|
|
.then(function then(client) {
|
|
|
|
if (!client) {
|
2017-01-24 00:44:39 +03:00
|
|
|
return done(new errors.NoPermissionError({
|
|
|
|
message: i18n.t('errors.middleware.oauth.invalidClient')
|
|
|
|
}), false);
|
2016-09-30 14:45:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Validate the user
|
|
|
|
return models.User.check({email: username, password: password})
|
|
|
|
.then(function then(user) {
|
2017-03-01 13:12:03 +03:00
|
|
|
return authUtils.createTokens({
|
|
|
|
clientId: client.id,
|
|
|
|
userId: user.id
|
|
|
|
});
|
2016-09-30 14:45:59 +03:00
|
|
|
})
|
|
|
|
.then(function then(response) {
|
2017-03-13 23:07:12 +03:00
|
|
|
spamPrevention.userLogin().reset(authInfo.ip, username + 'login');
|
2016-09-30 14:45:59 +03:00
|
|
|
return done(null, response.access_token, response.refresh_token, {expires_in: response.expires_in});
|
|
|
|
});
|
|
|
|
})
|
|
|
|
.catch(function handleError(error) {
|
|
|
|
return done(error, false);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-10-17 13:45:50 +03:00
|
|
|
function exchangeAuthorizationCode(req, res, next) {
|
|
|
|
if (!req.body.authorizationCode) {
|
|
|
|
return next(new errors.UnauthorizedError({
|
|
|
|
message: i18n.t('errors.middleware.auth.accessDenied')
|
|
|
|
}));
|
|
|
|
}
|
2017-03-01 13:12:03 +03:00
|
|
|
|
2016-10-17 13:45:50 +03:00
|
|
|
req.query.code = req.body.authorizationCode;
|
|
|
|
|
|
|
|
passport.authenticate('ghost', {session: false, failWithError: false}, function authenticate(err, user) {
|
|
|
|
if (err) {
|
2017-03-13 15:03:26 +03:00
|
|
|
return next(err);
|
2016-10-17 13:45:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!user) {
|
|
|
|
return next(new errors.UnauthorizedError({
|
|
|
|
message: i18n.t('errors.middleware.auth.accessDenied')
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2017-03-13 23:07:12 +03:00
|
|
|
spamPrevention.userLogin().reset(req.authInfo.ip, req.body.authorizationCode + 'login');
|
2016-11-17 16:02:56 +03:00
|
|
|
|
2017-03-01 13:12:03 +03:00
|
|
|
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
|
2016-10-17 13:45:50 +03:00
|
|
|
});
|
2017-03-01 13:12:03 +03:00
|
|
|
}).catch(function (err) {
|
|
|
|
next(err);
|
|
|
|
});
|
2016-10-17 13:45:50 +03:00
|
|
|
})(req, res, next);
|
|
|
|
}
|
|
|
|
|
2016-09-30 14:45:59 +03:00
|
|
|
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));
|
2016-10-17 13:45:50 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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);
|
2016-09-30 14:45:59 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
// ### Generate access token Middleware
|
|
|
|
// register the oauth2orize middleware for password and refresh token grants
|
|
|
|
generateAccessToken: function generateAccessToken(req, res, next) {
|
2017-01-24 00:44:39 +03:00
|
|
|
/**
|
|
|
|
* 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 = {
|
2017-03-01 13:12:03 +03:00
|
|
|
ip: req.ip,
|
|
|
|
accessToken: authUtils.getBearerAutorizationToken(req)
|
2017-01-24 00:44:39 +03:00
|
|
|
};
|
|
|
|
|
2016-09-30 14:45:59 +03:00
|
|
|
return oauthServer.token()(req, res, next);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = oauth;
|