diff --git a/lib/model/meteor-model.js b/lib/model/meteor-model.js index 6b8b334..3cdadd9 100644 --- a/lib/model/meteor-model.js +++ b/lib/model/meteor-model.js @@ -101,7 +101,7 @@ export const saveAuthorizationCode = bind(function saveAuthCode (code, client, u redirectUri, scope: code.scope, client: { - id: client.client_id + id: client.clientId }, user: { id: user.id @@ -139,5 +139,14 @@ export const saveRefreshToken = bind(function (token, clientId, expires, user) { * @private used by OAuthMeteorModel.prototype.getRefreshToken */ export const getRefreshToken = bind(function (refreshToken) { - return collections.RefreshTokens.findOne({ refreshToken }) + return collections.AccessTokens.findOne({ refreshToken }) +}) + +export const revokeToken = bind(function (token) { + const docCount = collections.AccessTokens.find({ refreshToken: token.refreshToken }).count() + if (docCount === 0) { + return true + } + + return collections.AccessTokens.remove({ refreshToken: token.refreshToken }) === docCount }) diff --git a/lib/model/model.js b/lib/model/model.js index 55b99e1..8231e22 100644 --- a/lib/model/model.js +++ b/lib/model/model.js @@ -10,7 +10,8 @@ import { saveAuthorizationCode, saveRefreshToken, saveToken, - getAccessToken + getAccessToken, + revokeToken } from './meteor-model' /** @@ -174,6 +175,14 @@ class OAuthMeteorModel { this.log('grantTypeAllowed (clientId:', clientId, ', grantType:', grantType + ')') return ['authorization_code', 'refresh_token'].includes(grantType) } + + /** + * revokeToken(refreshToken) is required and should return true + */ + async revokeToken (refreshToken) { + this.log(`revokeToken (refreshToken: ${refreshToken})`) + return revokeToken(refreshToken) + } } export { OAuthMeteorModel } diff --git a/lib/oauth.js b/lib/oauth.js index 79e84b5..e5e7930 100644 --- a/lib/oauth.js +++ b/lib/oauth.js @@ -17,6 +17,7 @@ import { validateParams } from './validation/validateParams' import { requiredAuthorizeGetParams } from './validation/requiredAuthorizeGetParams' import { requiredAuthorizePostParams } from './validation/requiredAuthorizePostParams' import { requiredAccessTokenPostParams } from './validation/requiredAccessTokenPostParams' +import { requiredRefreshTokenPostParams } from './validation/requiredRefreshTokenPostParams' import { UserValidation } from './validation/UserValidation' import { OptionsSchema } from './validation/OptionsSchema' @@ -407,7 +408,7 @@ const initRoutes = (self, { accessTokenUrl = '/oauth/token', authorizeUrl = '/oa // - validate authorization code // - issue accessToken and refreshToken route('post', accessTokenUrl, async function (req, res /*, next */) { - if (!validateParams(req.body, requiredAccessTokenPostParams, self.debug)) { + if (!validateParams(req.body, req.body?.refresh_token ? requiredRefreshTokenPostParams : requiredAccessTokenPostParams, self.debug)) { return errorHandler(res, { status: 400, error: 'invalid_request', diff --git a/lib/utils/isModelInterface.js b/lib/utils/isModelInterface.js index 4db2a16..e963e2f 100644 --- a/lib/utils/isModelInterface.js +++ b/lib/utils/isModelInterface.js @@ -10,7 +10,8 @@ const modelNames = [ 'saveAuthorizationCode', 'saveRefreshToken', 'saveToken', - 'getAccessToken' + 'getAccessToken', + 'revokeToken' ] /** @@ -28,6 +29,7 @@ const modelNames = [ * - 'saveRefreshToken', * - 'saveToken', * - 'getAccessToken' + * - 'revokeToken' * @param model {Object} the model implementation * @return {boolean} true if valid, otherwise false */ diff --git a/lib/validation/requiredRefreshTokenPostParams.js b/lib/validation/requiredRefreshTokenPostParams.js new file mode 100644 index 0000000..59588fc --- /dev/null +++ b/lib/validation/requiredRefreshTokenPostParams.js @@ -0,0 +1,11 @@ +import { Match } from 'meteor/check' +import { nonEmptyString } from './nonEmptyString' + +const isNonEmptyString = Match.Where(nonEmptyString) + +export const requiredRefreshTokenPostParams = { + grant_type: isNonEmptyString, + refresh_token: isNonEmptyString, + client_id: Match.Maybe(String), + client_secret: Match.Maybe(String) +}