2017-03-01 13:12:03 +03:00
|
|
|
var Promise = require('bluebird'),
|
|
|
|
_ = require('lodash'),
|
|
|
|
debug = require('ghost-ignition').debug('auth:utils'),
|
2017-12-14 05:01:23 +03:00
|
|
|
models = require('../../models'),
|
2017-12-14 15:52:20 +03:00
|
|
|
security = require('../../lib/security'),
|
2017-12-14 05:01:23 +03:00
|
|
|
globalUtils = require('../../utils'),
|
|
|
|
knex = require('../../data/db').knex,
|
2017-03-01 13:12:03 +03:00
|
|
|
_private = {};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The initial idea was to delete all old tokens connected to a user and a client.
|
|
|
|
* But if multiple browsers/apps are using the same client, we would log out them out.
|
|
|
|
* So the idea is to always decrease the expiry of the old access token if available.
|
|
|
|
* This access token auto expires and get's cleaned up on bootstrap (see oauth.js).
|
|
|
|
*/
|
|
|
|
_private.decreaseOldAccessTokenExpiry = function decreaseOldAccessTokenExpiry(data, options) {
|
2017-09-18 18:23:47 +03:00
|
|
|
debug('decreaseOldAccessTokenExpiry', data);
|
2017-03-01 13:12:03 +03:00
|
|
|
|
|
|
|
if (!data.token) {
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
return models.Accesstoken.findOne(data, options)
|
|
|
|
.then(function (oldAccessToken) {
|
|
|
|
if (!oldAccessToken) {
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
return models.Accesstoken.edit({
|
|
|
|
expires: Date.now() + globalUtils.FIVE_MINUTES_MS
|
|
|
|
}, _.merge({id: oldAccessToken.id}, options));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2017-11-14 02:38:54 +03:00
|
|
|
_private.handleOldRefreshToken = function handleOldRefreshToken(data, options) {
|
|
|
|
debug('handleOldRefreshToken', data.oldRefreshToken);
|
|
|
|
|
|
|
|
if (!data.oldRefreshToken) {
|
|
|
|
return models.Refreshtoken.add({
|
|
|
|
token: data.newRefreshToken,
|
|
|
|
user_id: data.userId,
|
|
|
|
client_id: data.clientId,
|
|
|
|
expires: data.refreshExpires
|
|
|
|
}, options);
|
2017-03-01 13:12:03 +03:00
|
|
|
}
|
|
|
|
|
2017-11-14 02:38:54 +03:00
|
|
|
// extend refresh token expiry
|
|
|
|
return models.Refreshtoken.edit({
|
|
|
|
expires: data.refreshExpires
|
|
|
|
}, _.merge({id: data.oldRefreshId}, options));
|
|
|
|
};
|
|
|
|
|
|
|
|
_private.handleTokenCreation = function handleTokenCreation(data, options) {
|
|
|
|
var oldAccessToken = data.oldAccessToken,
|
|
|
|
oldRefreshToken = data.oldRefreshToken,
|
|
|
|
oldRefreshId = data.oldRefreshId,
|
2017-12-14 15:52:20 +03:00
|
|
|
newAccessToken = security.identifier.uid(191),
|
|
|
|
newRefreshToken = security.identifier.uid(191),
|
2017-11-14 02:38:54 +03:00
|
|
|
accessExpires = Date.now() + globalUtils.ONE_MONTH_MS,
|
|
|
|
refreshExpires = Date.now() + globalUtils.SIX_MONTH_MS,
|
|
|
|
clientId = data.clientId,
|
|
|
|
userId = data.userId;
|
|
|
|
|
|
|
|
return _private.decreaseOldAccessTokenExpiry({token: oldAccessToken}, options)
|
|
|
|
.then(function () {
|
|
|
|
return _private.handleOldRefreshToken({
|
|
|
|
userId: userId,
|
|
|
|
clientId: clientId,
|
|
|
|
oldRefreshToken: oldRefreshToken,
|
|
|
|
oldRefreshId: oldRefreshId,
|
|
|
|
newRefreshToken: newRefreshToken,
|
|
|
|
refreshExpires: refreshExpires
|
|
|
|
}, options);
|
|
|
|
})
|
|
|
|
.then(function (refreshToken) {
|
|
|
|
return models.Accesstoken.add({
|
|
|
|
token: newAccessToken,
|
|
|
|
user_id: userId,
|
|
|
|
client_id: clientId,
|
|
|
|
issued_by: refreshToken.id,
|
|
|
|
expires: accessExpires
|
|
|
|
}, options);
|
|
|
|
})
|
|
|
|
.then(function () {
|
|
|
|
return {
|
|
|
|
access_token: newAccessToken,
|
|
|
|
refresh_token: newRefreshToken,
|
|
|
|
expires_in: globalUtils.ONE_MONTH_S
|
|
|
|
};
|
|
|
|
});
|
2017-03-01 13:12:03 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A user can have one token per client at a time.
|
|
|
|
* If the user requests a new pair of tokens, we decrease the expiry of the old access token
|
|
|
|
* and re-add the refresh token (this happens because this function is used for 3 different cases).
|
|
|
|
* If the operation fails in between, the user can still use e.g. the refresh token and try again.
|
|
|
|
*/
|
2017-11-14 02:38:54 +03:00
|
|
|
module.exports.createTokens = function createTokens(data, modelOptions) {
|
|
|
|
data = data || {};
|
|
|
|
modelOptions = modelOptions || {};
|
|
|
|
|
2017-03-01 13:12:03 +03:00
|
|
|
debug('createTokens');
|
|
|
|
|
2017-11-14 02:38:54 +03:00
|
|
|
if (modelOptions.transacting) {
|
|
|
|
return _private.handleTokenCreation(data, modelOptions);
|
|
|
|
}
|
2017-03-01 13:12:03 +03:00
|
|
|
|
|
|
|
return knex.transaction(function (transaction) {
|
2017-11-14 02:38:54 +03:00
|
|
|
modelOptions.transacting = transaction;
|
|
|
|
|
|
|
|
return _private.handleTokenCreation(data, modelOptions);
|
2017-03-01 13:12:03 +03:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports.getBearerAutorizationToken = function (req) {
|
|
|
|
var parts,
|
|
|
|
scheme,
|
|
|
|
token;
|
|
|
|
|
|
|
|
if (req.headers && req.headers.authorization) {
|
|
|
|
parts = req.headers.authorization.split(' ');
|
|
|
|
scheme = parts[0];
|
|
|
|
|
|
|
|
if (/^Bearer$/i.test(scheme)) {
|
|
|
|
token = parts[1];
|
|
|
|
}
|
|
|
|
} else if (req.query && req.query.access_token) {
|
|
|
|
token = req.query.access_token;
|
|
|
|
}
|
|
|
|
|
|
|
|
return token;
|
|
|
|
};
|
🐛 Fixed error for password authentication with Bearer Token (#9227)
refs #8613, refs #9228
- if you send a request to /authentication/token with `grant_type:password` and a Bearer token, Ghost was not able to handle this combination
- because it skipped the client authentication, see https://github.com/TryGhost/Ghost/blob/1.17.0/core/server/auth/authenticate.js#L13
- and OAuth detects the `grant_type: password` and jumps in the target implementation
- the target implementation for password authentication **again** tried to fetch the client and failed, because it relied on the previous client authentication
- see https://github.com/TryGhost/Ghost/blob/1.17.0/core/server/auth/oauth.js#L40 (client.slug is undefined if client authentication is skipped)
- ^ so this is the bug
- we **can** skip client authentication for requests to the API to fetch data for example e.g. GET /posts (including Bearer)
- so when is a client authentication required?
- RFC (https://tools.ietf.org/html/rfc6749#page-38) differentiates between confidential and public clients, Ghost has no implementation for this at the moment
- so in theory, public clients don't have to be authenticated, only if the credentials are included
- to not invent a breaking change, i decided to only make the client authentication required for password authentication
- we could change this in Ghost 2.0
I have removed the extra client request to the database for the password authentication, this is not needed. We already do client password authentication [here](https://github.com/TryGhost/Ghost/blob/1.17.0/core/server/auth/auth-strategies.js#L19);
If a Bearer token is present and you have not send a `grant_type` (which signalises OAuth to do authentication), you can skip the client authentication.
2017-11-09 17:11:29 +03:00
|
|
|
|
|
|
|
module.exports.hasGrantType = function hasGrantType(req, type) {
|
|
|
|
return req.body && req.body.hasOwnProperty('grant_type') && req.body.grant_type === type
|
|
|
|
|| req.query && req.query.hasOwnProperty('grant_type') && req.query.grant_type === type;
|
|
|
|
};
|