diff --git a/lib/errors/access-denied-error.js b/lib/errors/access-denied-error.js index ce5c0af..614235c 100644 --- a/lib/errors/access-denied-error.js +++ b/lib/errors/access-denied-error.js @@ -5,7 +5,6 @@ */ const OAuthError = require('./oauth-error'); -const util = require('util'); /** * Constructor. @@ -15,21 +14,18 @@ const util = require('util'); * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 */ -function AccessDeniedError(message, properties) { - properties = Object.assign({ - code: 400, - name: 'access_denied' - }, properties); +class AccessDeniedError extends OAuthError { + constructor(message, properties) { + properties = { + code: 400, + name: 'access_denied', + ...properties + }; - OAuthError.call(this, message, properties); + super(message, properties); + } } -/** - * Inherit prototype. - */ - -util.inherits(AccessDeniedError, OAuthError); - /** * Export constructor. */ diff --git a/lib/errors/insufficient-scope-error.js b/lib/errors/insufficient-scope-error.js index a27ad68..3125c75 100644 --- a/lib/errors/insufficient-scope-error.js +++ b/lib/errors/insufficient-scope-error.js @@ -5,7 +5,6 @@ */ const OAuthError = require('./oauth-error'); -const util = require('util'); /** * Constructor. @@ -15,21 +14,18 @@ const util = require('util'); * @see https://tools.ietf.org/html/rfc6750.html#section-3.1 */ -function InsufficientScopeError(message, properties) { - properties = Object.assign({ - code: 403, - name: 'insufficient_scope' - }, properties); +class InsufficientScopeError extends OAuthError { + constructor(message, properties) { + properties = { + code: 403, + name: 'insufficient_scope', + ...properties + }; - OAuthError.call(this, message, properties); + super(message, properties); + } } -/** - * Inherit prototype. - */ - -util.inherits(InsufficientScopeError, OAuthError); - /** * Export constructor. */ diff --git a/lib/errors/invalid-argument-error.js b/lib/errors/invalid-argument-error.js index 1958caa..9c9cfc5 100644 --- a/lib/errors/invalid-argument-error.js +++ b/lib/errors/invalid-argument-error.js @@ -5,27 +5,23 @@ */ const OAuthError = require('./oauth-error'); -const util = require('util'); /** * Constructor. */ -function InvalidArgumentError(message, properties) { - properties = Object.assign({ - code: 500, - name: 'invalid_argument' - }, properties); +class InvalidArgumentError extends OAuthError { + constructor(message, properties) { + properties = { + code: 500, + name: 'invalid_argument', + ...properties + }; - OAuthError.call(this, message, properties); + super(message, properties); + } } -/** - * Inherit prototype. - */ - -util.inherits(InvalidArgumentError, OAuthError); - /** * Export constructor. */ diff --git a/lib/errors/invalid-client-error.js b/lib/errors/invalid-client-error.js index 1513d57..a1874d8 100644 --- a/lib/errors/invalid-client-error.js +++ b/lib/errors/invalid-client-error.js @@ -5,7 +5,6 @@ */ const OAuthError = require('./oauth-error'); -const util = require('util'); /** * Constructor. @@ -16,21 +15,18 @@ const util = require('util'); * @see https://tools.ietf.org/html/rfc6749#section-5.2 */ -function InvalidClientError(message, properties) { - properties = Object.assign({ - code: 400, - name: 'invalid_client' - }, properties); +class InvalidClientError extends OAuthError { + constructor(message, properties) { + properties = { + code: 400, + name: 'invalid_client', + ...properties + }; - OAuthError.call(this, message, properties); + super(message, properties); + } } -/** - * Inherit prototype. - */ - -util.inherits(InvalidClientError, OAuthError); - /** * Export constructor. */ diff --git a/lib/errors/invalid-grant-error.js b/lib/errors/invalid-grant-error.js index 2c6a568..4beade9 100644 --- a/lib/errors/invalid-grant-error.js +++ b/lib/errors/invalid-grant-error.js @@ -5,7 +5,6 @@ */ const OAuthError = require('./oauth-error'); -const util = require('util'); /** * Constructor. @@ -17,21 +16,18 @@ const util = require('util'); * @see https://tools.ietf.org/html/rfc6749#section-5.2 */ -function InvalidGrantError(message, properties) { - properties = Object.assign({ - code: 400, - name: 'invalid_grant' - }, properties); +class InvalidGrantError extends OAuthError { + constructor(message, properties) { + properties = { + code: 400, + name: 'invalid_grant', + ...properties + }; - OAuthError.call(this, message, properties); + super(message, properties); + } } -/** - * Inherit prototype. - */ - -util.inherits(InvalidGrantError, OAuthError); - /** * Export constructor. */ diff --git a/lib/errors/invalid-request-error.js b/lib/errors/invalid-request-error.js index 56e997e..0b86101 100644 --- a/lib/errors/invalid-request-error.js +++ b/lib/errors/invalid-request-error.js @@ -5,7 +5,6 @@ */ const OAuthError = require('./oauth-error'); -const util = require('util'); /** * Constructor. @@ -16,21 +15,18 @@ const util = require('util'); * @see https://tools.ietf.org/html/rfc6749#section-4.2.2.1 */ -function InvalidRequest(message, properties) { - properties = Object.assign({ - code: 400, - name: 'invalid_request' - }, properties); +class InvalidRequest extends OAuthError { + constructor(message, properties) { + properties = { + code: 400, + name: 'invalid_request', + ...properties + }; - OAuthError.call(this, message, properties); + super(message, properties); + } } -/** - * Inherit prototype. - */ - -util.inherits(InvalidRequest, OAuthError); - /** * Export constructor. */ diff --git a/lib/errors/invalid-scope-error.js b/lib/errors/invalid-scope-error.js index 2f5746d..fec5d82 100644 --- a/lib/errors/invalid-scope-error.js +++ b/lib/errors/invalid-scope-error.js @@ -5,7 +5,6 @@ */ const OAuthError = require('./oauth-error'); -const util = require('util'); /** * Constructor. @@ -15,21 +14,18 @@ const util = require('util'); * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 */ -function InvalidScopeError(message, properties) { - properties = Object.assign({ - code: 400, - name: 'invalid_scope' - }, properties); +class InvalidScopeError extends OAuthError { + constructor(message, properties) { + properties = { + code: 400, + name: 'invalid_scope', + ...properties + }; - OAuthError.call(this, message, properties); + super(message, properties); + } } -/** - * Inherit prototype. - */ - -util.inherits(InvalidScopeError, OAuthError); - /** * Export constructor. */ diff --git a/lib/errors/invalid-token-error.js b/lib/errors/invalid-token-error.js index e79d926..481717b 100644 --- a/lib/errors/invalid-token-error.js +++ b/lib/errors/invalid-token-error.js @@ -5,7 +5,6 @@ */ const OAuthError = require('./oauth-error'); -const util = require('util'); /** * Constructor. @@ -15,21 +14,18 @@ const util = require('util'); * @see https://tools.ietf.org/html/rfc6750#section-3.1 */ -function InvalidTokenError(message, properties) { - properties = Object.assign({ - code: 401, - name: 'invalid_token' - }, properties); +class InvalidTokenError extends OAuthError { + constructor(message, properties) { + properties = { + code: 401, + name: 'invalid_token', + ...properties + }; - OAuthError.call(this, message, properties); + super(message, properties); + } } -/** - * Inherit prototype. - */ - -util.inherits(InvalidTokenError, OAuthError); - /** * Export constructor. */ diff --git a/lib/errors/oauth-error.js b/lib/errors/oauth-error.js index a96a41f..fff9660 100644 --- a/lib/errors/oauth-error.js +++ b/lib/errors/oauth-error.js @@ -3,39 +3,43 @@ /** * Module dependencies. */ -const util = require('util'); const http = require('http'); /** * Constructor. */ -function OAuthError(messageOrError, properties) { - let message = messageOrError instanceof Error ? messageOrError.message : messageOrError; - const error = messageOrError instanceof Error ? messageOrError : null; - if (properties == null || !Object.entries(properties).length ) { - properties = {}; - } +class OAuthError extends Error { + constructor(messageOrError, properties) { + super(messageOrError, properties); - properties = Object.assign({ code: 500 }, properties); + let message = messageOrError instanceof Error ? messageOrError.message : messageOrError; + const error = messageOrError instanceof Error ? messageOrError : null; - if (error) { - properties.inner = error; - } - if (!message || message.length === 0) { - message = http.STATUS_CODES[properties.code]; - } - this.code = this.status = this.statusCode = properties.code; - this.message = message; - for (const key in properties) { - if (key !== 'code') { - this[key] = properties[key]; + if (properties == null || !Object.entries(properties).length) { + properties = {}; + } + + properties = { code: 500, ...properties }; + + if (error) { + properties.inner = error; + } + + if (!message || message.length === 0) { + message = http.STATUS_CODES[properties.code]; + } + + this.code = this.status = this.statusCode = properties.code; + this.message = message; + + for (const key in properties) { + if (key !== 'code') { + this[key] = properties[key]; + } } } - Error.captureStackTrace(this, OAuthError); } -util.inherits(OAuthError, Error); - /** * Export constructor. */ diff --git a/lib/errors/server-error.js b/lib/errors/server-error.js index aee958b..2b9fc8c 100644 --- a/lib/errors/server-error.js +++ b/lib/errors/server-error.js @@ -5,7 +5,6 @@ */ const OAuthError = require('./oauth-error'); -const util = require('util'); /** * Constructor. @@ -15,21 +14,18 @@ const util = require('util'); * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 */ -function ServerError(message, properties) { - properties = Object.assign({ - code: 503, - name: 'server_error' - }, properties); +class ServerError extends OAuthError { + constructor(message, properties) { + properties = { + code: 503, + name: 'server_error', + ...properties + }; - OAuthError.call(this, message, properties); + super(message, properties); + } } -/** - * Inherit prototype. - */ - -util.inherits(ServerError, OAuthError); - /** * Export constructor. */ diff --git a/lib/errors/unauthorized-client-error.js b/lib/errors/unauthorized-client-error.js index fde3cb5..cf29c7c 100644 --- a/lib/errors/unauthorized-client-error.js +++ b/lib/errors/unauthorized-client-error.js @@ -5,7 +5,6 @@ */ const OAuthError = require('./oauth-error'); -const util = require('util'); /** * Constructor. @@ -15,21 +14,18 @@ const util = require('util'); * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 */ -function UnauthorizedClientError(message, properties) { - properties = Object.assign({ - code: 400, - name: 'unauthorized_client' - }, properties); +class UnauthorizedClientError extends OAuthError { + constructor(message, properties) { + properties = { + code: 400, + name: 'unauthorized_client', + ...properties + }; - OAuthError.call(this, message, properties); + super(message, properties); + } } -/** - * Inherit prototype. - */ - -util.inherits(UnauthorizedClientError, OAuthError); - /** * Export constructor. */ diff --git a/lib/errors/unauthorized-request-error.js b/lib/errors/unauthorized-request-error.js index e960489..c861eab 100644 --- a/lib/errors/unauthorized-request-error.js +++ b/lib/errors/unauthorized-request-error.js @@ -5,7 +5,6 @@ */ const OAuthError = require('./oauth-error'); -const util = require('util'); /** * Constructor. @@ -18,21 +17,18 @@ const util = require('util'); * @see https://tools.ietf.org/html/rfc6750#section-3.1 */ -function UnauthorizedRequestError(message, properties) { - properties = Object.assign({ - code: 401, - name: 'unauthorized_request' - }, properties); +class UnauthorizedRequestError extends OAuthError { + constructor(message, properties) { + properties = { + code: 401, + name: 'unauthorized_request', + ...properties + }; - OAuthError.call(this, message, properties); + super(message, properties); + } } -/** - * Inherit prototype. - */ - -util.inherits(UnauthorizedRequestError, OAuthError); - /** * Export constructor. */ diff --git a/lib/errors/unsupported-grant-type-error.js b/lib/errors/unsupported-grant-type-error.js index 586a743..ac7a46c 100644 --- a/lib/errors/unsupported-grant-type-error.js +++ b/lib/errors/unsupported-grant-type-error.js @@ -5,7 +5,6 @@ */ const OAuthError = require('./oauth-error'); -const util = require('util'); /** * Constructor. @@ -15,21 +14,18 @@ const util = require('util'); * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 */ -function UnsupportedGrantTypeError(message, properties) { - properties = Object.assign({ - code: 400, - name: 'unsupported_grant_type' - }, properties); +class UnsupportedGrantTypeError extends OAuthError { + constructor(message, properties) { + properties = { + code: 400, + name: 'unsupported_grant_type', + ...properties + }; - OAuthError.call(this, message, properties); + super(message, properties); + } } -/** - * Inherit prototype. - */ - -util.inherits(UnsupportedGrantTypeError, OAuthError); - /** * Export constructor. */ diff --git a/lib/errors/unsupported-response-type-error.js b/lib/errors/unsupported-response-type-error.js index 539551e..c480e50 100644 --- a/lib/errors/unsupported-response-type-error.js +++ b/lib/errors/unsupported-response-type-error.js @@ -5,7 +5,6 @@ */ const OAuthError = require('./oauth-error'); -const util = require('util'); /** * Constructor. @@ -16,21 +15,18 @@ const util = require('util'); * @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 */ -function UnsupportedResponseTypeError(message, properties) { - properties = Object.assign({ - code: 400, - name: 'unsupported_response_type' - }, properties); +class UnsupportedResponseTypeError extends OAuthError { + constructor(message, properties) { + properties = { + code: 400, + name: 'unsupported_response_type', + ...properties + }; - OAuthError.call(this, message, properties); + super(message, properties); + } } -/** - * Inherit prototype. - */ - -util.inherits(UnsupportedResponseTypeError, OAuthError); - /** * Export constructor. */ diff --git a/lib/grant-types/authorization-code-grant-type.js b/lib/grant-types/authorization-code-grant-type.js index 92193d3..73dec6d 100644 --- a/lib/grant-types/authorization-code-grant-type.js +++ b/lib/grant-types/authorization-code-grant-type.js @@ -12,223 +12,219 @@ const Promise = require('bluebird'); const promisify = require('promisify-any').use(Promise); const ServerError = require('../errors/server-error'); const isFormat = require('@node-oauth/formats'); -const util = require('util'); const pkce = require('../pkce/pkce'); /** * Constructor. */ -function AuthorizationCodeGrantType(options) { - options = options || {}; +class AuthorizationCodeGrantType extends AbstractGrantType { + constructor(options = {}) { + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } + if (!options.model.getAuthorizationCode) { + throw new InvalidArgumentError('Invalid argument: model does not implement `getAuthorizationCode()`'); + } - if (!options.model.getAuthorizationCode) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getAuthorizationCode()`'); - } + if (!options.model.revokeAuthorizationCode) { + throw new InvalidArgumentError('Invalid argument: model does not implement `revokeAuthorizationCode()`'); + } - if (!options.model.revokeAuthorizationCode) { - throw new InvalidArgumentError('Invalid argument: model does not implement `revokeAuthorizationCode()`'); - } + if (!options.model.saveToken) { + throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); + } - if (!options.model.saveToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); + super(options); } - AbstractGrantType.call(this, options); -} - -/** - * Inherit prototype. - */ - -util.inherits(AuthorizationCodeGrantType, AbstractGrantType); - -/** - * Handle authorization code grant. - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 - */ - -AuthorizationCodeGrantType.prototype.handle = function(request, client) { - if (!request) { - throw new InvalidArgumentError('Missing parameter: `request`'); + /** + * Handle authorization code grant. + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ + + handle(request, client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + return Promise.bind(this) + .then(function () { + return this.getAuthorizationCode(request, client); + }) + .tap(function (code) { + return this.validateRedirectUri(request, code); + }) + .tap(function (code) { + return this.revokeAuthorizationCode(code); + }) + .then(function (code) { + return this.saveToken(code.user, client, code.authorizationCode, code.scope); + }); } - if (!client) { - throw new InvalidArgumentError('Missing parameter: `client`'); - } - - return Promise.bind(this) - .then(function() { - return this.getAuthorizationCode(request, client); - }) - .tap(function(code) { - return this.validateRedirectUri(request, code); - }) - .tap(function(code) { - return this.revokeAuthorizationCode(code); - }) - .then(function(code) { - return this.saveToken(code.user, client, code.authorizationCode, code.scope); - }); -}; - -/** - * Get the authorization code. - */ - -AuthorizationCodeGrantType.prototype.getAuthorizationCode = function(request, client) { - if (!request.body.code) { - throw new InvalidRequestError('Missing parameter: `code`'); - } - - if (!isFormat.vschar(request.body.code)) { - throw new InvalidRequestError('Invalid parameter: `code`'); - } - return promisify(this.model.getAuthorizationCode, 1).call(this.model, request.body.code) - .then(function(code) { - if (!code) { - throw new InvalidGrantError('Invalid grant: authorization code is invalid'); - } - - if (!code.client) { - throw new ServerError('Server error: `getAuthorizationCode()` did not return a `client` object'); - } - - if (!code.user) { - throw new ServerError('Server error: `getAuthorizationCode()` did not return a `user` object'); - } - - if (code.client.id !== client.id) { - throw new InvalidGrantError('Invalid grant: authorization code is invalid'); - } - - if (!(code.expiresAt instanceof Date)) { - throw new ServerError('Server error: `expiresAt` must be a Date instance'); - } - - if (code.expiresAt < new Date()) { - throw new InvalidGrantError('Invalid grant: authorization code has expired'); - } - - if (code.redirectUri && !isFormat.uri(code.redirectUri)) { - throw new InvalidGrantError('Invalid grant: `redirect_uri` is not a valid URI'); - } + /** + * Get the authorization code. + */ + + getAuthorizationCode(request, client) { + if (!request.body.code) { + throw new InvalidRequestError('Missing parameter: `code`'); + } + + if (!isFormat.vschar(request.body.code)) { + throw new InvalidRequestError('Invalid parameter: `code`'); + } + return promisify(this.model.getAuthorizationCode, 1) + .call(this.model, request.body.code) + .then((code) => { + if (!code) { + throw new InvalidGrantError('Invalid grant: authorization code is invalid'); + } - // optional: PKCE code challenge + if (!code.client) { + throw new ServerError('Server error: `getAuthorizationCode()` did not return a `client` object'); + } - if (code.codeChallenge) { - if (!request.body.code_verifier) { - throw new InvalidGrantError('Missing parameter: `code_verifier`'); + if (!code.user) { + throw new ServerError('Server error: `getAuthorizationCode()` did not return a `user` object'); } - const hash = pkce.getHashForCodeChallenge({ - method: code.codeChallengeMethod, - verifier: request.body.code_verifier - }); + if (code.client.id !== client.id) { + throw new InvalidGrantError('Invalid grant: authorization code is invalid'); + } - if (!hash) { - // notice that we assume that codeChallengeMethod is already - // checked at an earlier stage when being read from - // request.body.code_challenge_method - throw new ServerError('Server error: `getAuthorizationCode()` did not return a valid `codeChallengeMethod` property'); + if (!(code.expiresAt instanceof Date)) { + throw new ServerError('Server error: `expiresAt` must be a Date instance'); } - if (code.codeChallenge !== hash) { - throw new InvalidGrantError('Invalid grant: code verifier is invalid'); + if (code.expiresAt < new Date()) { + throw new InvalidGrantError('Invalid grant: authorization code has expired'); } - } - else { - if (request.body.code_verifier) { - // No code challenge but code_verifier was passed in. - throw new InvalidGrantError('Invalid grant: code verifier is invalid'); + + if (code.redirectUri && !isFormat.uri(code.redirectUri)) { + throw new InvalidGrantError('Invalid grant: `redirect_uri` is not a valid URI'); } - } - return code; - }); -}; + // optional: PKCE code challenge -/** - * Validate the redirect URI. - * - * "The authorization server MUST ensure that the redirect_uri parameter is - * present if the redirect_uri parameter was included in the initial - * authorization request as described in Section 4.1.1, and if included - * ensure that their values are identical." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 - */ + if (code.codeChallenge) { + if (!request.body.code_verifier) { + throw new InvalidGrantError('Missing parameter: `code_verifier`'); + } -AuthorizationCodeGrantType.prototype.validateRedirectUri = function(request, code) { - if (!code.redirectUri) { - return; - } + const hash = pkce.getHashForCodeChallenge({ + method: code.codeChallengeMethod, + verifier: request.body.code_verifier + }); - const redirectUri = request.body.redirect_uri || request.query.redirect_uri; + if (!hash) { + // notice that we assume that codeChallengeMethod is already + // checked at an earlier stage when being read from + // request.body.code_challenge_method + throw new ServerError('Server error: `getAuthorizationCode()` did not return a valid `codeChallengeMethod` property'); + } - if (!isFormat.uri(redirectUri)) { - throw new InvalidRequestError('Invalid request: `redirect_uri` is not a valid URI'); - } + if (code.codeChallenge !== hash) { + throw new InvalidGrantError('Invalid grant: code verifier is invalid'); + } + } + else { + if (request.body.code_verifier) { + // No code challenge but code_verifier was passed in. + throw new InvalidGrantError('Invalid grant: code verifier is invalid'); + } + } - if (redirectUri !== code.redirectUri) { - throw new InvalidRequestError('Invalid request: `redirect_uri` is invalid'); + return code; + }); } -}; -/** - * Revoke the authorization code. - * - * "The authorization code MUST expire shortly after it is issued to mitigate - * the risk of leaks. [...] If an authorization code is used more than once, - * the authorization server MUST deny the request." - * - * @see https://tools.ietf.org/html/rfc6749#section-4.1.2 - */ + /** + * Validate the redirect URI. + * + * "The authorization server MUST ensure that the redirect_uri parameter is + * present if the redirect_uri parameter was included in the initial + * authorization request as described in Section 4.1.1, and if included + * ensure that their values are identical." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.3 + */ + + validateRedirectUri(request, code) { + if (!code.redirectUri) { + return; + } + + const redirectUri = request.body.redirect_uri || request.query.redirect_uri; + + if (!isFormat.uri(redirectUri)) { + throw new InvalidRequestError('Invalid request: `redirect_uri` is not a valid URI'); + } + + if (redirectUri !== code.redirectUri) { + throw new InvalidRequestError('Invalid request: `redirect_uri` is invalid'); + } + } -AuthorizationCodeGrantType.prototype.revokeAuthorizationCode = function(code) { - return promisify(this.model.revokeAuthorizationCode, 1).call(this.model, code) - .then(function(status) { - if (!status) { - throw new InvalidGrantError('Invalid grant: authorization code is invalid'); - } + /** + * Revoke the authorization code. + * + * "The authorization code MUST expire shortly after it is issued to mitigate + * the risk of leaks. [...] If an authorization code is used more than once, + * the authorization server MUST deny the request." + * + * @see https://tools.ietf.org/html/rfc6749#section-4.1.2 + */ + + revokeAuthorizationCode(code) { + return promisify(this.model.revokeAuthorizationCode, 1) + .call(this.model, code) + .then((status) => { + if (!status) { + throw new InvalidGrantError('Invalid grant: authorization code is invalid'); + } - return code; - }); -}; + return code; + }); + } -/** - * Save token. - */ -AuthorizationCodeGrantType.prototype.saveToken = function(user, client, authorizationCode, scope) { - const fns = [ - this.validateScope(user, client, scope), - this.generateAccessToken(client, user, scope), - this.generateRefreshToken(client, user, scope), - this.getAccessTokenExpiresAt(), - this.getRefreshTokenExpiresAt() - ]; - - return Promise.all(fns) - .bind(this) - .spread(function(scope, accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { - const token = { - accessToken: accessToken, - authorizationCode: authorizationCode, - accessTokenExpiresAt: accessTokenExpiresAt, - refreshToken: refreshToken, - refreshTokenExpiresAt: refreshTokenExpiresAt, - scope: scope - }; - - return promisify(this.model.saveToken, 3).call(this.model, token, client, user); - }); -}; + /** + * Save token. + */ + + saveToken(user, client, authorizationCode, scope) { + const fns = [ + this.validateScope(user, client, scope), + this.generateAccessToken(client, user, scope), + this.generateRefreshToken(client, user, scope), + this.getAccessTokenExpiresAt(), + this.getRefreshTokenExpiresAt(), + ]; + + return Promise.all(fns) + .bind(this) + .spread(function (scope, accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { + const token = { + accessToken: accessToken, + authorizationCode: authorizationCode, + accessTokenExpiresAt: accessTokenExpiresAt, + refreshToken: refreshToken, + refreshTokenExpiresAt: refreshTokenExpiresAt, + scope: scope, + }; + + return promisify(this.model.saveToken, 3).call(this.model, token, client, user); + }); + } +} /** * Export constructor. diff --git a/lib/grant-types/client-credentials-grant-type.js b/lib/grant-types/client-credentials-grant-type.js index d0af0fe..4b1aec5 100644 --- a/lib/grant-types/client-credentials-grant-type.js +++ b/lib/grant-types/client-credentials-grant-type.js @@ -9,100 +9,94 @@ const InvalidArgumentError = require('../errors/invalid-argument-error'); const InvalidGrantError = require('../errors/invalid-grant-error'); const Promise = require('bluebird'); const promisify = require('promisify-any').use(Promise); -const util = require('util'); /** * Constructor. */ -function ClientCredentialsGrantType(options) { - options = options || {}; +class ClientCredentialsGrantType extends AbstractGrantType { + constructor(options = {}) { + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } + if (!options.model.getUserFromClient) { + throw new InvalidArgumentError('Invalid argument: model does not implement `getUserFromClient()`'); + } - if (!options.model.getUserFromClient) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getUserFromClient()`'); - } + if (!options.model.saveToken) { + throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); + } - if (!options.model.saveToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); + super(options); } - AbstractGrantType.call(this, options); -} - -/** - * Inherit prototype. - */ + /** + * Handle client credentials grant. + * + * @see https://tools.ietf.org/html/rfc6749#section-4.4.2 + */ + + handle(request, client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + const scope = this.getScope(request); + + return Promise.bind(this) + .then(function () { + return this.getUserFromClient(client); + }) + .then(function (user) { + return this.saveToken(user, client, scope); + }); + } -util.inherits(ClientCredentialsGrantType, AbstractGrantType); + /** + * Retrieve the user using client credentials. + */ -/** - * Handle client credentials grant. - * - * @see https://tools.ietf.org/html/rfc6749#section-4.4.2 - */ + getUserFromClient(client) { + return promisify(this.model.getUserFromClient, 1) + .call(this.model, client) + .then((user) => { + if (!user) { + throw new InvalidGrantError('Invalid grant: user credentials are invalid'); + } -ClientCredentialsGrantType.prototype.handle = function(request, client) { - if (!request) { - throw new InvalidArgumentError('Missing parameter: `request`'); + return user; + }); } - if (!client) { - throw new InvalidArgumentError('Missing parameter: `client`'); + /** + * Save token. + */ + + saveToken(user, client, scope) { + const fns = [ + this.validateScope(user, client, scope), + this.generateAccessToken(client, user, scope), + this.getAccessTokenExpiresAt(client, user, scope), + ]; + + return Promise.all(fns) + .bind(this) + .spread(function (scope, accessToken, accessTokenExpiresAt) { + const token = { + accessToken: accessToken, + accessTokenExpiresAt: accessTokenExpiresAt, + scope: scope, + }; + + return promisify(this.model.saveToken, 3).call(this.model, token, client, user); + }); } - - const scope = this.getScope(request); - - return Promise.bind(this) - .then(function() { - return this.getUserFromClient(client); - }) - .then(function(user) { - return this.saveToken(user, client, scope); - }); -}; - -/** - * Retrieve the user using client credentials. - */ - -ClientCredentialsGrantType.prototype.getUserFromClient = function(client) { - return promisify(this.model.getUserFromClient, 1).call(this.model, client) - .then(function(user) { - if (!user) { - throw new InvalidGrantError('Invalid grant: user credentials are invalid'); - } - - return user; - }); -}; - -/** - * Save token. - */ - -ClientCredentialsGrantType.prototype.saveToken = function(user, client, scope) { - const fns = [ - this.validateScope(user, client, scope), - this.generateAccessToken(client, user, scope), - this.getAccessTokenExpiresAt(client, user, scope) - ]; - - return Promise.all(fns) - .bind(this) - .spread(function(scope, accessToken, accessTokenExpiresAt) { - const token = { - accessToken: accessToken, - accessTokenExpiresAt: accessTokenExpiresAt, - scope: scope - }; - - return promisify(this.model.saveToken, 3).call(this.model, token, client, user); - }); -}; +} /** * Export constructor. diff --git a/lib/grant-types/password-grant-type.js b/lib/grant-types/password-grant-type.js index b65f9e1..3035cdd 100644 --- a/lib/grant-types/password-grant-type.js +++ b/lib/grant-types/password-grant-type.js @@ -11,120 +11,114 @@ const InvalidRequestError = require('../errors/invalid-request-error'); const Promise = require('bluebird'); const promisify = require('promisify-any').use(Promise); const isFormat = require('@node-oauth/formats'); -const util = require('util'); /** * Constructor. */ -function PasswordGrantType(options) { - options = options || {}; +class PasswordGrantType extends AbstractGrantType { + constructor(options = {}) { + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } + if (!options.model.getUser) { + throw new InvalidArgumentError('Invalid argument: model does not implement `getUser()`'); + } - if (!options.model.getUser) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getUser()`'); - } + if (!options.model.saveToken) { + throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); + } - if (!options.model.saveToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); + super(options); } - AbstractGrantType.call(this, options); -} - -/** - * Inherit prototype. - */ - -util.inherits(PasswordGrantType, AbstractGrantType); - -/** - * Retrieve the user from the model using a username/password combination. - * - * @see https://tools.ietf.org/html/rfc6749#section-4.3.2 - */ - -PasswordGrantType.prototype.handle = function(request, client) { - if (!request) { - throw new InvalidArgumentError('Missing parameter: `request`'); + /** + * Retrieve the user from the model using a username/password combination. + * + * @see https://tools.ietf.org/html/rfc6749#section-4.3.2 + */ + + handle(request, client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + const scope = this.getScope(request); + + return Promise.bind(this) + .then(function () { + return this.getUser(request); + }) + .then(function (user) { + return this.saveToken(user, client, scope); + }); } - if (!client) { - throw new InvalidArgumentError('Missing parameter: `client`'); - } + /** + * Get user using a username/password combination. + */ - const scope = this.getScope(request); + getUser(request) { + if (!request.body.username) { + throw new InvalidRequestError('Missing parameter: `username`'); + } - return Promise.bind(this) - .then(function() { - return this.getUser(request); - }) - .then(function(user) { - return this.saveToken(user, client, scope); - }); -}; + if (!request.body.password) { + throw new InvalidRequestError('Missing parameter: `password`'); + } -/** - * Get user using a username/password combination. - */ + if (!isFormat.uchar(request.body.username)) { + throw new InvalidRequestError('Invalid parameter: `username`'); + } -PasswordGrantType.prototype.getUser = function(request) { - if (!request.body.username) { - throw new InvalidRequestError('Missing parameter: `username`'); - } + if (!isFormat.uchar(request.body.password)) { + throw new InvalidRequestError('Invalid parameter: `password`'); + } - if (!request.body.password) { - throw new InvalidRequestError('Missing parameter: `password`'); - } + return promisify(this.model.getUser, 2) + .call(this.model, request.body.username, request.body.password) + .then((user) => { + if (!user) { + throw new InvalidGrantError('Invalid grant: user credentials are invalid'); + } - if (!isFormat.uchar(request.body.username)) { - throw new InvalidRequestError('Invalid parameter: `username`'); + return user; + }); } - if (!isFormat.uchar(request.body.password)) { - throw new InvalidRequestError('Invalid parameter: `password`'); + /** + * Save token. + */ + + saveToken(user, client, scope) { + const fns = [ + this.validateScope(user, client, scope), + this.generateAccessToken(client, user, scope), + this.generateRefreshToken(client, user, scope), + this.getAccessTokenExpiresAt(), + this.getRefreshTokenExpiresAt(), + ]; + + return Promise.all(fns) + .bind(this) + .spread(function (scope, accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { + const token = { + accessToken: accessToken, + accessTokenExpiresAt: accessTokenExpiresAt, + refreshToken: refreshToken, + refreshTokenExpiresAt: refreshTokenExpiresAt, + scope: scope, + }; + + return promisify(this.model.saveToken, 3).call(this.model, token, client, user); + }); } - - return promisify(this.model.getUser, 2).call(this.model, request.body.username, request.body.password) - .then(function(user) { - if (!user) { - throw new InvalidGrantError('Invalid grant: user credentials are invalid'); - } - - return user; - }); -}; - -/** - * Save token. - */ - -PasswordGrantType.prototype.saveToken = function(user, client, scope) { - const fns = [ - this.validateScope(user, client, scope), - this.generateAccessToken(client, user, scope), - this.generateRefreshToken(client, user, scope), - this.getAccessTokenExpiresAt(), - this.getRefreshTokenExpiresAt() - ]; - - return Promise.all(fns) - .bind(this) - .spread(function(scope, accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { - const token = { - accessToken: accessToken, - accessTokenExpiresAt: accessTokenExpiresAt, - refreshToken: refreshToken, - refreshTokenExpiresAt: refreshTokenExpiresAt, - scope: scope - }; - - return promisify(this.model.saveToken, 3).call(this.model, token, client, user); - }); -}; +} /** * Export constructor. diff --git a/lib/grant-types/refresh-token-grant-type.js b/lib/grant-types/refresh-token-grant-type.js index 9787b08..d94caca 100644 --- a/lib/grant-types/refresh-token-grant-type.js +++ b/lib/grant-types/refresh-token-grant-type.js @@ -12,166 +12,160 @@ const Promise = require('bluebird'); const promisify = require('promisify-any').use(Promise); const ServerError = require('../errors/server-error'); const isFormat = require('@node-oauth/formats'); -const util = require('util'); /** * Constructor. */ -function RefreshTokenGrantType(options) { - options = options || {}; +class RefreshTokenGrantType extends AbstractGrantType { + constructor(options = {}) { + if (!options.model) { + throw new InvalidArgumentError('Missing parameter: `model`'); + } - if (!options.model) { - throw new InvalidArgumentError('Missing parameter: `model`'); - } - - if (!options.model.getRefreshToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `getRefreshToken()`'); - } - - if (!options.model.revokeToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `revokeToken()`'); - } - - if (!options.model.saveToken) { - throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); - } + if (!options.model.getRefreshToken) { + throw new InvalidArgumentError('Invalid argument: model does not implement `getRefreshToken()`'); + } - AbstractGrantType.call(this, options); -} - -/** - * Inherit prototype. - */ - -util.inherits(RefreshTokenGrantType, AbstractGrantType); - -/** - * Handle refresh token grant. - * - * @see https://tools.ietf.org/html/rfc6749#section-6 - */ + if (!options.model.revokeToken) { + throw new InvalidArgumentError('Invalid argument: model does not implement `revokeToken()`'); + } -RefreshTokenGrantType.prototype.handle = function(request, client) { - if (!request) { - throw new InvalidArgumentError('Missing parameter: `request`'); - } + if (!options.model.saveToken) { + throw new InvalidArgumentError('Invalid argument: model does not implement `saveToken()`'); + } - if (!client) { - throw new InvalidArgumentError('Missing parameter: `client`'); + super(options); } - return Promise.bind(this) - .then(function() { - return this.getRefreshToken(request, client); - }) - .tap(function(token) { - return this.revokeToken(token); - }) - .then(function(token) { - return this.saveToken(token.user, client, token.scope); - }); -}; - -/** - * Get refresh token. - */ - -RefreshTokenGrantType.prototype.getRefreshToken = function(request, client) { - if (!request.body.refresh_token) { - throw new InvalidRequestError('Missing parameter: `refresh_token`'); + /** + * Handle refresh token grant. + * + * @see https://tools.ietf.org/html/rfc6749#section-6 + */ + + handle(request, client) { + if (!request) { + throw new InvalidArgumentError('Missing parameter: `request`'); + } + + if (!client) { + throw new InvalidArgumentError('Missing parameter: `client`'); + } + + return Promise.bind(this) + .then(function () { + return this.getRefreshToken(request, client); + }) + .tap(function (token) { + return this.revokeToken(token); + }) + .then(function (token) { + return this.saveToken(token.user, client, token.scope); + }); } - if (!isFormat.vschar(request.body.refresh_token)) { - throw new InvalidRequestError('Invalid parameter: `refresh_token`'); - } + /** + * Get refresh token. + */ - return promisify(this.model.getRefreshToken, 1).call(this.model, request.body.refresh_token) - .then(function(token) { - if (!token) { - throw new InvalidGrantError('Invalid grant: refresh token is invalid'); - } + getRefreshToken(request, client) { + if (!request.body.refresh_token) { + throw new InvalidRequestError('Missing parameter: `refresh_token`'); + } - if (!token.client) { - throw new ServerError('Server error: `getRefreshToken()` did not return a `client` object'); - } + if (!isFormat.vschar(request.body.refresh_token)) { + throw new InvalidRequestError('Invalid parameter: `refresh_token`'); + } - if (!token.user) { - throw new ServerError('Server error: `getRefreshToken()` did not return a `user` object'); - } + return promisify(this.model.getRefreshToken, 1) + .call(this.model, request.body.refresh_token) + .then((token) => { + if (!token) { + throw new InvalidGrantError('Invalid grant: refresh token is invalid'); + } - if (token.client.id !== client.id) { - throw new InvalidGrantError('Invalid grant: refresh token was issued to another client'); - } + if (!token.client) { + throw new ServerError('Server error: `getRefreshToken()` did not return a `client` object'); + } - if (token.refreshTokenExpiresAt && !(token.refreshTokenExpiresAt instanceof Date)) { - throw new ServerError('Server error: `refreshTokenExpiresAt` must be a Date instance'); - } + if (!token.user) { + throw new ServerError('Server error: `getRefreshToken()` did not return a `user` object'); + } - if (token.refreshTokenExpiresAt && token.refreshTokenExpiresAt < new Date()) { - throw new InvalidGrantError('Invalid grant: refresh token has expired'); - } + if (token.client.id !== client.id) { + throw new InvalidGrantError('Invalid grant: refresh token was issued to another client'); + } - return token; - }); -}; + if (token.refreshTokenExpiresAt && !(token.refreshTokenExpiresAt instanceof Date)) { + throw new ServerError('Server error: `refreshTokenExpiresAt` must be a Date instance'); + } -/** - * Revoke the refresh token. - * - * @see https://tools.ietf.org/html/rfc6749#section-6 - */ + if (token.refreshTokenExpiresAt && token.refreshTokenExpiresAt < new Date()) { + throw new InvalidGrantError('Invalid grant: refresh token has expired'); + } -RefreshTokenGrantType.prototype.revokeToken = function(token) { - if (this.alwaysIssueNewRefreshToken === false) { - return Promise.resolve(token); + return token; + }); } - return promisify(this.model.revokeToken, 1).call(this.model, token) - .then(function(status) { - if (!status) { - throw new InvalidGrantError('Invalid grant: refresh token is invalid or could not be revoked'); - } - - return token; - }); -}; - -/** - * Save token. - */ + /** + * Revoke the refresh token. + * + * @see https://tools.ietf.org/html/rfc6749#section-6 + */ + + revokeToken(token) { + if (this.alwaysIssueNewRefreshToken === false) { + return Promise.resolve(token); + } + + return promisify(this.model.revokeToken, 1) + .call(this.model, token) + .then((status) => { + if (!status) { + throw new InvalidGrantError('Invalid grant: refresh token is invalid or could not be revoked'); + } + + return token; + }); + } -RefreshTokenGrantType.prototype.saveToken = function(user, client, scope) { - const fns = [ - this.generateAccessToken(client, user, scope), - this.generateRefreshToken(client, user, scope), - this.getAccessTokenExpiresAt(), - this.getRefreshTokenExpiresAt() - ]; - - return Promise.all(fns) - .bind(this) - .spread(function(accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { - const token = { - accessToken: accessToken, - accessTokenExpiresAt: accessTokenExpiresAt, - scope: scope - }; - - if (this.alwaysIssueNewRefreshToken !== false) { - token.refreshToken = refreshToken; - token.refreshTokenExpiresAt = refreshTokenExpiresAt; - } - - return token; - }) - .then(function(token) { - return promisify(this.model.saveToken, 3).call(this.model, token, client, user) - .then(function(savedToken) { - return savedToken; - }); - }); -}; + /** + * Save token. + */ + + saveToken(user, client, scope) { + const fns = [ + this.generateAccessToken(client, user, scope), + this.generateRefreshToken(client, user, scope), + this.getAccessTokenExpiresAt(), + this.getRefreshTokenExpiresAt(), + ]; + + return Promise.all(fns) + .bind(this) + .spread(function (accessToken, refreshToken, accessTokenExpiresAt, refreshTokenExpiresAt) { + const token = { + accessToken: accessToken, + accessTokenExpiresAt: accessTokenExpiresAt, + scope: scope, + }; + + if (this.alwaysIssueNewRefreshToken !== false) { + token.refreshToken = refreshToken; + token.refreshTokenExpiresAt = refreshTokenExpiresAt; + } + + return token; + }) + .then(function (token) { + return promisify(this.model.saveToken, 3) + .call(this.model, token, client, user) + .then((savedToken) => savedToken); + }); + } +} /** * Export constructor. diff --git a/package.json b/package.json index 11b098b..e95a0ce 100644 --- a/package.json +++ b/package.json @@ -32,11 +32,11 @@ "type-is": "1.6.18" }, "devDependencies": { - "chai": "4.3.4", - "eslint": "8.4.1", - "mocha": "9.2.2", + "chai": "4.3.7", + "eslint": "8.42.0", + "mocha": "10.2.0", "nyc": "15.1.0", - "sinon": "13.0.1" + "sinon": "15.1.0" }, "license": "MIT", "engines": { diff --git a/test/unit/errors/oauth-error_test.js b/test/unit/errors/oauth-error_test.js new file mode 100644 index 0000000..bad86f6 --- /dev/null +++ b/test/unit/errors/oauth-error_test.js @@ -0,0 +1,37 @@ +'use strict'; + +/** + * Module dependencies. + */ + +const { describe, it } = require('mocha'); +const should = require('chai').should(); +const OAuthError = require('../../../lib/errors/oauth-error'); + +/** + * Test `OAuthError`. + */ + +describe('OAuthError', function() { + describe('constructor()', function() { + it('should get `captureStackTrace`', function() { + + const errorFn = function () { throw new OAuthError('test', {name: 'test_error'}); }; + + try { + errorFn(); + + should.fail(); + } catch (e) { + + e.should.be.an.instanceOf(OAuthError); + e.message.should.equal('test'); + e.code.should.equal(500); + e.stack.should.not.be.null; + e.stack.should.not.be.undefined; + e.stack.should.include('oauth-error_test.js'); + e.stack.should.include('19'); //error lineNUmber + } + }); + }); +});