Socket
Socket
Sign inDemoInstall

@node-oauth/oauth2-server

Package Overview
Dependencies
7
Maintainers
2
Versions
17
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 5.0.0-rc.2 to 5.0.0-rc.3

lib/utils/date-util.js

1

index.d.ts

@@ -272,2 +272,3 @@ // Type definitions for Node OAuth2 Server 5.0

* Invoked during request authentication to check if the provided access token was authorized the requested scopes.
* Optional, if a custom authenticateHandler is used or if there is no scope part of the request.
*

@@ -274,0 +275,0 @@ */

127

lib/grant-types/abstract-grant-type.js

@@ -12,93 +12,90 @@ 'use strict';

/**
* Constructor.
*/
class AbstractGrantType {
constructor (options) {
options = options || {};
function AbstractGrantType(options) {
options = options || {};
if (!options.accessTokenLifetime) {
throw new InvalidArgumentError('Missing parameter: `accessTokenLifetime`');
}
if (!options.accessTokenLifetime) {
throw new InvalidArgumentError('Missing parameter: `accessTokenLifetime`');
}
if (!options.model) {
throw new InvalidArgumentError('Missing parameter: `model`');
}
if (!options.model) {
throw new InvalidArgumentError('Missing parameter: `model`');
this.accessTokenLifetime = options.accessTokenLifetime;
this.model = options.model;
this.refreshTokenLifetime = options.refreshTokenLifetime;
this.alwaysIssueNewRefreshToken = options.alwaysIssueNewRefreshToken;
}
this.accessTokenLifetime = options.accessTokenLifetime;
this.model = options.model;
this.refreshTokenLifetime = options.refreshTokenLifetime;
this.alwaysIssueNewRefreshToken = options.alwaysIssueNewRefreshToken;
}
/**
* Generate access token.
*/
async generateAccessToken (client, user, scope) {
if (this.model.generateAccessToken) {
// We should not fall back to a random accessToken, if the model did not
// return a token, in order to prevent unintended token-issuing.
return this.model.generateAccessToken(client, user, scope);
}
/**
* Generate access token.
*/
AbstractGrantType.prototype.generateAccessToken = async function(client, user, scope) {
if (this.model.generateAccessToken) {
const accessToken = await this.model.generateAccessToken(client, user, scope);
return accessToken || tokenUtil.generateRandomToken();
return tokenUtil.generateRandomToken();
}
return tokenUtil.generateRandomToken();
};
/**
/**
* Generate refresh token.
*/
async generateRefreshToken (client, user, scope) {
if (this.model.generateRefreshToken) {
// We should not fall back to a random refreshToken, if the model did not
// return a token, in order to prevent unintended token-issuing.
return this.model.generateRefreshToken(client, user, scope);
}
AbstractGrantType.prototype.generateRefreshToken = async function(client, user, scope) {
if (this.model.generateRefreshToken) {
const refreshToken = await this.model.generateRefreshToken(client, user, scope);
return refreshToken || tokenUtil.generateRandomToken();
return tokenUtil.generateRandomToken();
}
return tokenUtil.generateRandomToken();
};
/**
/**
* Get access token expiration date.
*/
getAccessTokenExpiresAt() {
return new Date(Date.now() + this.accessTokenLifetime * 1000);
}
AbstractGrantType.prototype.getAccessTokenExpiresAt = function() {
return new Date(Date.now() + this.accessTokenLifetime * 1000);
};
/**
* Get refresh token expiration date.
*/
AbstractGrantType.prototype.getRefreshTokenExpiresAt = function() {
return new Date(Date.now() + this.refreshTokenLifetime * 1000);
};
/**
* Get refresh token expiration date.
*/
getRefreshTokenExpiresAt () {
return new Date(Date.now() + this.refreshTokenLifetime * 1000);
}
/**
* Get scope from the request body.
*/
/**
* Get scope from the request body.
*/
getScope (request) {
if (!isFormat.nqschar(request.body.scope)) {
throw new InvalidArgumentError('Invalid parameter: `scope`');
}
AbstractGrantType.prototype.getScope = function(request) {
if (!isFormat.nqschar(request.body.scope)) {
throw new InvalidArgumentError('Invalid parameter: `scope`');
return request.body.scope;
}
return request.body.scope;
};
/**
* Validate requested scope.
*/
async validateScope (user, client, scope) {
if (this.model.validateScope) {
const validatedScope = await this.model.validateScope(user, client, scope);
/**
* Validate requested scope.
*/
AbstractGrantType.prototype.validateScope = async function(user, client, scope) {
if (this.model.validateScope) {
const validatedScope = await this.model.validateScope(user, client, scope);
if (!validatedScope) {
throw new InvalidScopeError('Invalid scope: Requested scope is invalid');
}
if (!validatedScope) {
throw new InvalidScopeError('Invalid scope: Requested scope is invalid');
return validatedScope;
} else {
return scope;
}
return validatedScope;
} else {
return scope;
}
};
}

@@ -105,0 +102,0 @@ /**

@@ -56,4 +56,4 @@ 'use strict';

const code = await this.getAuthorizationCode(request, client);
await this.revokeAuthorizationCode(code);
await this.validateRedirectUri(request, code);
await this.revokeAuthorizationCode(code);

@@ -191,6 +191,6 @@ return this.saveToken(code.user, client, code.authorizationCode, code.scope);

async saveToken(user, client, authorizationCode, scope) {
const validatedScope = await this.validateScope(user, client, scope);
const accessToken = await this.generateAccessToken(client, user, scope);
const refreshToken = await this.generateRefreshToken(client, user, scope);
async saveToken(user, client, authorizationCode, requestedScope) {
const validatedScope = await this.validateScope(user, client, requestedScope);
const accessToken = await this.generateAccessToken(client, user, validatedScope);
const refreshToken = await this.generateRefreshToken(client, user, validatedScope);
const accessTokenExpiresAt = await this.getAccessTokenExpiresAt();

@@ -200,7 +200,7 @@ const refreshTokenExpiresAt = await this.getRefreshTokenExpiresAt();

const token = {
accessToken: accessToken,
authorizationCode: authorizationCode,
accessTokenExpiresAt: accessTokenExpiresAt,
refreshToken: refreshToken,
refreshTokenExpiresAt: refreshTokenExpiresAt,
accessToken,
authorizationCode,
accessTokenExpiresAt,
refreshToken,
refreshTokenExpiresAt,
scope: validatedScope,

@@ -207,0 +207,0 @@ };

@@ -71,9 +71,9 @@ 'use strict';

async saveToken(user, client, scope) {
const validatedScope = await this.validateScope(user, client, scope);
const accessToken = await this.generateAccessToken(client, user, scope);
const accessTokenExpiresAt = await this.getAccessTokenExpiresAt(client, user, scope);
async saveToken(user, client, requestedScope) {
const validatedScope = await this.validateScope(user, client, requestedScope);
const accessToken = await this.generateAccessToken(client, user, validatedScope);
const accessTokenExpiresAt = await this.getAccessTokenExpiresAt(client, user, validatedScope);
const token = {
accessToken: accessToken,
accessTokenExpiresAt: accessTokenExpiresAt,
accessToken,
accessTokenExpiresAt,
scope: validatedScope,

@@ -80,0 +80,0 @@ };

@@ -89,6 +89,6 @@ 'use strict';

async saveToken(user, client, scope) {
const validatedScope = await this.validateScope(user, client, scope);
const accessToken = await this.generateAccessToken(client, user, scope);
const refreshToken = await this.generateRefreshToken(client, user, scope);
async saveToken(user, client, requestedScope) {
const validatedScope = await this.validateScope(user, client, requestedScope);
const accessToken = await this.generateAccessToken(client, user, validatedScope);
const refreshToken = await this.generateRefreshToken(client, user, validatedScope);
const accessTokenExpiresAt = await this.getAccessTokenExpiresAt();

@@ -98,6 +98,6 @@ const refreshTokenExpiresAt = await this.getRefreshTokenExpiresAt();

const token = {
accessToken: accessToken,
accessTokenExpiresAt: accessTokenExpiresAt,
refreshToken: refreshToken,
refreshTokenExpiresAt: refreshTokenExpiresAt,
accessToken,
accessTokenExpiresAt,
refreshToken,
refreshTokenExpiresAt,
scope: validatedScope,

@@ -104,0 +104,0 @@ };

@@ -133,5 +133,5 @@ 'use strict';

const token = {
accessToken: accessToken,
accessTokenExpiresAt: accessTokenExpiresAt,
scope: scope,
accessToken,
accessTokenExpiresAt,
scope,
};

@@ -138,0 +138,0 @@

@@ -21,236 +21,237 @@ 'use strict';

function AuthenticateHandler(options) {
options = options || {};
class AuthenticateHandler {
constructor (options) {
options = options || {};
if (!options.model) {
throw new InvalidArgumentError('Missing parameter: `model`');
}
if (!options.model) {
throw new InvalidArgumentError('Missing parameter: `model`');
}
if (!options.model.getAccessToken) {
throw new InvalidArgumentError('Invalid argument: model does not implement `getAccessToken()`');
}
if (!options.model.getAccessToken) {
throw new InvalidArgumentError('Invalid argument: model does not implement `getAccessToken()`');
}
if (options.scope && undefined === options.addAcceptedScopesHeader) {
throw new InvalidArgumentError('Missing parameter: `addAcceptedScopesHeader`');
}
if (options.scope && undefined === options.addAcceptedScopesHeader) {
throw new InvalidArgumentError('Missing parameter: `addAcceptedScopesHeader`');
}
if (options.scope && undefined === options.addAuthorizedScopesHeader) {
throw new InvalidArgumentError('Missing parameter: `addAuthorizedScopesHeader`');
}
if (options.scope && undefined === options.addAuthorizedScopesHeader) {
throw new InvalidArgumentError('Missing parameter: `addAuthorizedScopesHeader`');
}
if (options.scope && !options.model.verifyScope) {
throw new InvalidArgumentError('Invalid argument: model does not implement `verifyScope()`');
if (options.scope && !options.model.verifyScope) {
throw new InvalidArgumentError('Invalid argument: model does not implement `verifyScope()`');
}
this.addAcceptedScopesHeader = options.addAcceptedScopesHeader;
this.addAuthorizedScopesHeader = options.addAuthorizedScopesHeader;
this.allowBearerTokensInQueryString = options.allowBearerTokensInQueryString;
this.model = options.model;
this.scope = options.scope;
}
this.addAcceptedScopesHeader = options.addAcceptedScopesHeader;
this.addAuthorizedScopesHeader = options.addAuthorizedScopesHeader;
this.allowBearerTokensInQueryString = options.allowBearerTokensInQueryString;
this.model = options.model;
this.scope = options.scope;
}
/**
* Authenticate Handler.
*/
/**
* Authenticate Handler.
*/
async handle (request, response) {
if (!(request instanceof Request)) {
throw new InvalidArgumentError('Invalid argument: `request` must be an instance of Request');
}
AuthenticateHandler.prototype.handle = async function(request, response) {
if (!(request instanceof Request)) {
throw new InvalidArgumentError('Invalid argument: `request` must be an instance of Request');
}
if (!(response instanceof Response)) {
throw new InvalidArgumentError('Invalid argument: `response` must be an instance of Response');
}
if (!(response instanceof Response)) {
throw new InvalidArgumentError('Invalid argument: `response` must be an instance of Response');
}
try {
const requestToken = await this.getTokenFromRequest(request);
try {
const requestToken = await this.getTokenFromRequest(request);
let accessToken;
accessToken = await this.getAccessToken(requestToken);
accessToken = await this.validateAccessToken(accessToken);
let accessToken;
accessToken = await this.getAccessToken(requestToken);
accessToken = await this.validateAccessToken(accessToken);
if (this.scope) {
await this.verifyScope(accessToken);
}
if (this.scope) {
await this.verifyScope(accessToken);
}
this.updateResponse(response, accessToken);
this.updateResponse(response, accessToken);
return accessToken;
} catch (e) {
// Include the "WWW-Authenticate" response header field if the client
// lacks any authentication information.
//
// @see https://tools.ietf.org/html/rfc6750#section-3.1
if (e instanceof UnauthorizedRequestError) {
response.set('WWW-Authenticate', 'Bearer realm="Service"');
} else if (e instanceof InvalidRequestError) {
response.set('WWW-Authenticate', 'Bearer realm="Service",error="invalid_request"');
} else if (e instanceof InvalidTokenError) {
response.set('WWW-Authenticate', 'Bearer realm="Service",error="invalid_token"');
} else if (e instanceof InsufficientScopeError) {
response.set('WWW-Authenticate', 'Bearer realm="Service",error="insufficient_scope"');
}
return accessToken;
} catch (e) {
// Include the "WWW-Authenticate" response header field if the client
// lacks any authentication information.
//
// @see https://tools.ietf.org/html/rfc6750#section-3.1
if (e instanceof UnauthorizedRequestError) {
response.set('WWW-Authenticate', 'Bearer realm="Service"');
} else if (e instanceof InvalidRequestError) {
response.set('WWW-Authenticate', 'Bearer realm="Service",error="invalid_request"');
} else if (e instanceof InvalidTokenError) {
response.set('WWW-Authenticate', 'Bearer realm="Service",error="invalid_token"');
} else if (e instanceof InsufficientScopeError) {
response.set('WWW-Authenticate', 'Bearer realm="Service",error="insufficient_scope"');
}
if (!(e instanceof OAuthError)) {
throw new ServerError(e);
}
if (!(e instanceof OAuthError)) {
throw new ServerError(e);
throw e;
}
throw e;
}
};
/**
* Get the token from the header or body, depending on the request.
*
* "Clients MUST NOT use more than one method to transmit the token in each request."
*
* @see https://tools.ietf.org/html/rfc6750#section-2
*/
/**
* Get the token from the header or body, depending on the request.
*
* "Clients MUST NOT use more than one method to transmit the token in each request."
*
* @see https://tools.ietf.org/html/rfc6750#section-2
*/
AuthenticateHandler.prototype.getTokenFromRequest = function(request) {
const headerToken = request.get('Authorization');
const queryToken = request.query.access_token;
const bodyToken = request.body.access_token;
getTokenFromRequest (request) {
const headerToken = request.get('Authorization');
const queryToken = request.query.access_token;
const bodyToken = request.body.access_token;
if (!!headerToken + !!queryToken + !!bodyToken > 1) {
throw new InvalidRequestError('Invalid request: only one authentication method is allowed');
}
if (!!headerToken + !!queryToken + !!bodyToken > 1) {
throw new InvalidRequestError('Invalid request: only one authentication method is allowed');
}
if (headerToken) {
return this.getTokenFromRequestHeader(request);
}
if (headerToken) {
return this.getTokenFromRequestHeader(request);
}
if (queryToken) {
return this.getTokenFromRequestQuery(request);
}
if (queryToken) {
return this.getTokenFromRequestQuery(request);
}
if (bodyToken) {
return this.getTokenFromRequestBody(request);
if (bodyToken) {
return this.getTokenFromRequestBody(request);
}
throw new UnauthorizedRequestError('Unauthorized request: no authentication given');
}
throw new UnauthorizedRequestError('Unauthorized request: no authentication given');
};
/**
* Get the token from the request header.
*
* @see http://tools.ietf.org/html/rfc6750#section-2.1
*/
/**
* Get the token from the request header.
*
* @see http://tools.ietf.org/html/rfc6750#section-2.1
*/
getTokenFromRequestHeader (request) {
const token = request.get('Authorization');
const matches = token.match(/^Bearer\s(\S+)/);
AuthenticateHandler.prototype.getTokenFromRequestHeader = function(request) {
const token = request.get('Authorization');
const matches = token.match(/^Bearer\s(\S+)/);
if (!matches) {
throw new InvalidRequestError('Invalid request: malformed authorization header');
}
if (!matches) {
throw new InvalidRequestError('Invalid request: malformed authorization header');
return matches[1];
}
return matches[1];
};
/**
* Get the token from the request query.
*
* "Don't pass bearer tokens in page URLs: Bearer tokens SHOULD NOT be passed in page
* URLs (for example, as query string parameters). Instead, bearer tokens SHOULD be
* passed in HTTP message headers or message bodies for which confidentiality measures
* are taken. Browsers, web servers, and other software may not adequately secure URLs
* in the browser history, web server logs, and other data structures. If bearer tokens
* are passed in page URLs, attackers might be able to steal them from the history data,
* logs, or other unsecured locations."
*
* @see http://tools.ietf.org/html/rfc6750#section-2.3
*/
/**
* Get the token from the request query.
*
* "Don't pass bearer tokens in page URLs: Bearer tokens SHOULD NOT be passed in page
* URLs (for example, as query string parameters). Instead, bearer tokens SHOULD be
* passed in HTTP message headers or message bodies for which confidentiality measures
* are taken. Browsers, web servers, and other software may not adequately secure URLs
* in the browser history, web server logs, and other data structures. If bearer tokens
* are passed in page URLs, attackers might be able to steal them from the history data,
* logs, or other unsecured locations."
*
* @see http://tools.ietf.org/html/rfc6750#section-2.3
*/
getTokenFromRequestQuery (request) {
if (!this.allowBearerTokensInQueryString) {
throw new InvalidRequestError('Invalid request: do not send bearer tokens in query URLs');
}
AuthenticateHandler.prototype.getTokenFromRequestQuery = function(request) {
if (!this.allowBearerTokensInQueryString) {
throw new InvalidRequestError('Invalid request: do not send bearer tokens in query URLs');
return request.query.access_token;
}
return request.query.access_token;
};
/**
* Get the token from the request body.
*
* "The HTTP request method is one for which the request-body has defined semantics.
* In particular, this means that the "GET" method MUST NOT be used."
*
* @see http://tools.ietf.org/html/rfc6750#section-2.2
*/
/**
* Get the token from the request body.
*
* "The HTTP request method is one for which the request-body has defined semantics.
* In particular, this means that the "GET" method MUST NOT be used."
*
* @see http://tools.ietf.org/html/rfc6750#section-2.2
*/
getTokenFromRequestBody (request) {
if (request.method === 'GET') {
throw new InvalidRequestError('Invalid request: token may not be passed in the body when using the GET verb');
}
AuthenticateHandler.prototype.getTokenFromRequestBody = function(request) {
if (request.method === 'GET') {
throw new InvalidRequestError('Invalid request: token may not be passed in the body when using the GET verb');
}
if (!request.is('application/x-www-form-urlencoded')) {
throw new InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded');
}
if (!request.is('application/x-www-form-urlencoded')) {
throw new InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded');
return request.body.access_token;
}
return request.body.access_token;
};
/**
* Get the access token from the model.
*/
/**
* Get the access token from the model.
*/
async getAccessToken (token) {
const accessToken = await this.model.getAccessToken(token);
AuthenticateHandler.prototype.getAccessToken = async function(token) {
const accessToken = await this.model.getAccessToken(token);
if (!accessToken) {
throw new InvalidTokenError('Invalid token: access token is invalid');
}
if (!accessToken) {
throw new InvalidTokenError('Invalid token: access token is invalid');
}
if (!accessToken.user) {
throw new ServerError('Server error: `getAccessToken()` did not return a `user` object');
}
if (!accessToken.user) {
throw new ServerError('Server error: `getAccessToken()` did not return a `user` object');
return accessToken;
}
return accessToken;
};
/**
* Validate access token.
*/
/**
* Validate access token.
*/
validateAccessToken (accessToken) {
if (!(accessToken.accessTokenExpiresAt instanceof Date)) {
throw new ServerError('Server error: `accessTokenExpiresAt` must be a Date instance');
}
AuthenticateHandler.prototype.validateAccessToken = function(accessToken) {
if (!(accessToken.accessTokenExpiresAt instanceof Date)) {
throw new ServerError('Server error: `accessTokenExpiresAt` must be a Date instance');
}
if (accessToken.accessTokenExpiresAt < new Date()) {
throw new InvalidTokenError('Invalid token: access token has expired');
}
if (accessToken.accessTokenExpiresAt < new Date()) {
throw new InvalidTokenError('Invalid token: access token has expired');
return accessToken;
}
return accessToken;
};
/**
* Verify scope.
*/
/**
* Verify scope.
*/
async verifyScope (accessToken) {
const scope = await this.model.verifyScope(accessToken, this.scope);
AuthenticateHandler.prototype.verifyScope = async function(accessToken) {
const scope = await this.model.verifyScope(accessToken, this.scope);
if (!scope) {
throw new InsufficientScopeError('Insufficient scope: authorized scope is insufficient');
}
if (!scope) {
throw new InsufficientScopeError('Insufficient scope: authorized scope is insufficient');
return scope;
}
return scope;
};
/**
* Update response.
*/
/**
* Update response.
*/
updateResponse (response, accessToken) {
if (this.scope && this.addAcceptedScopesHeader) {
response.set('X-Accepted-OAuth-Scopes', this.scope);
}
AuthenticateHandler.prototype.updateResponse = function(response, accessToken) {
if (this.scope && this.addAcceptedScopesHeader) {
response.set('X-Accepted-OAuth-Scopes', this.scope);
if (this.scope && this.addAuthorizedScopesHeader) {
response.set('X-OAuth-Scopes', accessToken.scope);
}
}
if (this.scope && this.addAuthorizedScopesHeader) {
response.set('X-OAuth-Scopes', accessToken.scope);
}
};
}
/**

@@ -257,0 +258,0 @@ * Export constructor.

@@ -37,357 +37,359 @@ 'use strict';

function AuthorizeHandler(options) {
options = options || {};
class AuthorizeHandler {
constructor (options) {
options = options || {};
if (options.authenticateHandler && !options.authenticateHandler.handle) {
throw new InvalidArgumentError('Invalid argument: authenticateHandler does not implement `handle()`');
}
if (options.authenticateHandler && !options.authenticateHandler.handle) {
throw new InvalidArgumentError('Invalid argument: authenticateHandler does not implement `handle()`');
}
if (!options.authorizationCodeLifetime) {
throw new InvalidArgumentError('Missing parameter: `authorizationCodeLifetime`');
}
if (!options.authorizationCodeLifetime) {
throw new InvalidArgumentError('Missing parameter: `authorizationCodeLifetime`');
}
if (!options.model) {
throw new InvalidArgumentError('Missing parameter: `model`');
}
if (!options.model) {
throw new InvalidArgumentError('Missing parameter: `model`');
}
if (!options.model.getClient) {
throw new InvalidArgumentError('Invalid argument: model does not implement `getClient()`');
}
if (!options.model.getClient) {
throw new InvalidArgumentError('Invalid argument: model does not implement `getClient()`');
}
if (!options.model.saveAuthorizationCode) {
throw new InvalidArgumentError('Invalid argument: model does not implement `saveAuthorizationCode()`');
if (!options.model.saveAuthorizationCode) {
throw new InvalidArgumentError('Invalid argument: model does not implement `saveAuthorizationCode()`');
}
this.allowEmptyState = options.allowEmptyState;
this.authenticateHandler = options.authenticateHandler || new AuthenticateHandler(options);
this.authorizationCodeLifetime = options.authorizationCodeLifetime;
this.model = options.model;
}
this.allowEmptyState = options.allowEmptyState;
this.authenticateHandler = options.authenticateHandler || new AuthenticateHandler(options);
this.authorizationCodeLifetime = options.authorizationCodeLifetime;
this.model = options.model;
}
/**
* Authorize Handler.
*/
/**
* Authorize Handler.
*/
async handle (request, response) {
if (!(request instanceof Request)) {
throw new InvalidArgumentError('Invalid argument: `request` must be an instance of Request');
}
AuthorizeHandler.prototype.handle = async function(request, response) {
if (!(request instanceof Request)) {
throw new InvalidArgumentError('Invalid argument: `request` must be an instance of Request');
}
if (!(response instanceof Response)) {
throw new InvalidArgumentError('Invalid argument: `response` must be an instance of Response');
}
if (!(response instanceof Response)) {
throw new InvalidArgumentError('Invalid argument: `response` must be an instance of Response');
}
const expiresAt = await this.getAuthorizationCodeLifetime();
const client = await this.getClient(request);
const user = await this.getUser(request, response);
const expiresAt = await this.getAuthorizationCodeLifetime();
const client = await this.getClient(request);
const user = await this.getUser(request, response);
let uri;
let state;
let uri;
let state;
try {
uri = this.getRedirectUri(request, client);
state = this.getState(request);
try {
uri = this.getRedirectUri(request, client);
state = this.getState(request);
if (request.query.allowed === 'false' || request.body.allowed === 'false') {
throw new AccessDeniedError('Access denied: user denied access to application');
}
if (request.query.allowed === 'false' || request.body.allowed === 'false') {
throw new AccessDeniedError('Access denied: user denied access to application');
}
const requestedScope = await this.getScope(request);
const validScope = await this.validateScope(user, client, requestedScope);
const authorizationCode = await this.generateAuthorizationCode(client, user, validScope);
const requestedScope = this.getScope(request);
const validScope = await this.validateScope(user, client, requestedScope);
const authorizationCode = await this.generateAuthorizationCode(client, user, validScope);
const ResponseType = this.getResponseType(request);
const codeChallenge = this.getCodeChallenge(request);
const codeChallengeMethod = this.getCodeChallengeMethod(request);
const code = await this.saveAuthorizationCode(
authorizationCode,
expiresAt,
validScope,
client,
uri,
user,
codeChallenge,
codeChallengeMethod
);
const ResponseType = this.getResponseType(request);
const codeChallenge = this.getCodeChallenge(request);
const codeChallengeMethod = this.getCodeChallengeMethod(request);
const code = await this.saveAuthorizationCode(
authorizationCode,
expiresAt,
validScope,
client,
uri,
user,
codeChallenge,
codeChallengeMethod
);
const responseTypeInstance = new ResponseType(code.authorizationCode);
const redirectUri = this.buildSuccessRedirectUri(uri, responseTypeInstance);
const responseTypeInstance = new ResponseType(code.authorizationCode);
const redirectUri = this.buildSuccessRedirectUri(uri, responseTypeInstance);
this.updateResponse(response, redirectUri, state);
this.updateResponse(response, redirectUri, state);
return code;
} catch (err) {
let e = err;
return code;
} catch (err) {
let e = err;
if (!(e instanceof OAuthError)) {
e = new ServerError(e);
}
const redirectUri = this.buildErrorRedirectUri(uri, e);
this.updateResponse(response, redirectUri, state);
if (!(e instanceof OAuthError)) {
e = new ServerError(e);
throw e;
}
const redirectUri = this.buildErrorRedirectUri(uri, e);
this.updateResponse(response, redirectUri, state);
throw e;
}
};
/**
* Generate authorization code.
*/
/**
* Generate authorization code.
*/
AuthorizeHandler.prototype.generateAuthorizationCode = async function(client, user, scope) {
if (this.model.generateAuthorizationCode) {
return this.model.generateAuthorizationCode(client, user, scope);
async generateAuthorizationCode (client, user, scope) {
if (this.model.generateAuthorizationCode) {
return this.model.generateAuthorizationCode(client, user, scope);
}
return tokenUtil.generateRandomToken();
}
return tokenUtil.generateRandomToken();
};
/**
* Get authorization code lifetime.
*/
/**
* Get authorization code lifetime.
*/
AuthorizeHandler.prototype.getAuthorizationCodeLifetime = function() {
const expires = new Date();
getAuthorizationCodeLifetime () {
const expires = new Date();
expires.setSeconds(expires.getSeconds() + this.authorizationCodeLifetime);
return expires;
};
expires.setSeconds(expires.getSeconds() + this.authorizationCodeLifetime);
return expires;
}
/**
* Get the client from the model.
*/
/**
* Get the client from the model.
*/
AuthorizeHandler.prototype.getClient = async function(request) {
const self = this;
const clientId = request.body.client_id || request.query.client_id;
async getClient (request) {
const self = this;
const clientId = request.body.client_id || request.query.client_id;
if (!clientId) {
throw new InvalidRequestError('Missing parameter: `client_id`');
}
if (!clientId) {
throw new InvalidRequestError('Missing parameter: `client_id`');
}
if (!isFormat.vschar(clientId)) {
throw new InvalidRequestError('Invalid parameter: `client_id`');
}
if (!isFormat.vschar(clientId)) {
throw new InvalidRequestError('Invalid parameter: `client_id`');
}
const redirectUri = request.body.redirect_uri || request.query.redirect_uri;
const redirectUri = request.body.redirect_uri || request.query.redirect_uri;
if (redirectUri && !isFormat.uri(redirectUri)) {
throw new InvalidRequestError('Invalid request: `redirect_uri` is not a valid URI');
}
if (redirectUri && !isFormat.uri(redirectUri)) {
throw new InvalidRequestError('Invalid request: `redirect_uri` is not a valid URI');
}
const client = await this.model.getClient(clientId, null);
const client = await this.model.getClient(clientId, null);
if (!client) {
throw new InvalidClientError('Invalid client: client credentials are invalid');
}
if (!client) {
throw new InvalidClientError('Invalid client: client credentials are invalid');
}
if (!client.grants) {
throw new InvalidClientError('Invalid client: missing client `grants`');
}
if (!client.grants) {
throw new InvalidClientError('Invalid client: missing client `grants`');
}
if (!Array.isArray(client.grants) || !client.grants.includes('authorization_code')) {
throw new UnauthorizedClientError('Unauthorized client: `grant_type` is invalid');
}
if (!Array.isArray(client.grants) || !client.grants.includes('authorization_code')) {
throw new UnauthorizedClientError('Unauthorized client: `grant_type` is invalid');
}
if (!client.redirectUris || 0 === client.redirectUris.length) {
throw new InvalidClientError('Invalid client: missing client `redirectUri`');
}
if (!client.redirectUris || 0 === client.redirectUris.length) {
throw new InvalidClientError('Invalid client: missing client `redirectUri`');
}
if (redirectUri) {
const valid = await self.validateRedirectUri(redirectUri, client);
if (redirectUri) {
const valid = await self.validateRedirectUri(redirectUri, client);
if (!valid) {
throw new InvalidClientError('Invalid client: `redirect_uri` does not match client value');
if (!valid) {
throw new InvalidClientError('Invalid client: `redirect_uri` does not match client value');
}
}
return client;
}
return client;
};
/**
* Validate requested scope.
*/
async validateScope (user, client, scope) {
if (this.model.validateScope) {
const validatedScope = await this.model.validateScope(user, client, scope);
/**
* Validate requested scope.
*/
AuthorizeHandler.prototype.validateScope = async function(user, client, scope) {
if (this.model.validateScope) {
const validatedScope = await this.model.validateScope(user, client, scope);
if (!validatedScope) {
throw new InvalidScopeError('Invalid scope: Requested scope is invalid');
}
if (!validatedScope) {
throw new InvalidScopeError('Invalid scope: Requested scope is invalid');
return validatedScope;
}
return validatedScope;
return scope;
}
return scope;
};
/**
* Get scope from the request.
*/
/**
* Get scope from the request.
*/
getScope (request) {
const scope = request.body.scope || request.query.scope;
AuthorizeHandler.prototype.getScope = function(request) {
const scope = request.body.scope || request.query.scope;
if (!isFormat.nqschar(scope)) {
throw new InvalidScopeError('Invalid parameter: `scope`');
}
if (!isFormat.nqschar(scope)) {
throw new InvalidScopeError('Invalid parameter: `scope`');
return scope;
}
return scope;
};
/**
* Get state from the request.
*/
/**
* Get state from the request.
*/
getState (request) {
const state = request.body.state || request.query.state;
const stateExists = state && state.length > 0;
const stateIsValid = stateExists
? isFormat.vschar(state)
: this.allowEmptyState;
AuthorizeHandler.prototype.getState = function(request) {
const state = request.body.state || request.query.state;
const stateExists = state && state.length > 0;
const stateIsValid = stateExists
? isFormat.vschar(state)
: this.allowEmptyState;
if (!stateIsValid) {
const message = (!stateExists) ? 'Missing' : 'Invalid';
throw new InvalidRequestError(`${message} parameter: \`state\``);
}
if (!stateIsValid) {
const message = (!stateExists) ? 'Missing' : 'Invalid';
throw new InvalidRequestError(`${message} parameter: \`state\``);
return state;
}
return state;
};
/**
* Get user by calling the authenticate middleware.
*/
/**
* Get user by calling the authenticate middleware.
*/
async getUser (request, response) {
if (this.authenticateHandler instanceof AuthenticateHandler) {
const handled = await this.authenticateHandler.handle(request, response);
return handled
? handled.user
: undefined;
}
AuthorizeHandler.prototype.getUser = async function(request, response) {
if (this.authenticateHandler instanceof AuthenticateHandler) {
const handled = await this.authenticateHandler.handle(request, response);
return handled
? handled.user
: undefined;
}
const user = await this.authenticateHandler.handle(request, response);
const user = await this.authenticateHandler.handle(request, response);
if (!user) {
throw new ServerError('Server error: `handle()` did not return a `user` object');
}
if (!user) {
throw new ServerError('Server error: `handle()` did not return a `user` object');
return user;
}
return user;
};
/**
* Get redirect URI.
*/
/**
* Get redirect URI.
*/
getRedirectUri (request, client) {
return request.body.redirect_uri || request.query.redirect_uri || client.redirectUris[0];
}
AuthorizeHandler.prototype.getRedirectUri = function(request, client) {
return request.body.redirect_uri || request.query.redirect_uri || client.redirectUris[0];
};
/**
* Save authorization code.
*/
/**
* Save authorization code.
*/
async saveAuthorizationCode (authorizationCode, expiresAt, scope, client, redirectUri, user, codeChallenge, codeChallengeMethod) {
let code = {
authorizationCode: authorizationCode,
expiresAt: expiresAt,
redirectUri: redirectUri,
scope: scope
};
AuthorizeHandler.prototype.saveAuthorizationCode = async function(authorizationCode, expiresAt, scope, client, redirectUri, user, codeChallenge, codeChallengeMethod) {
let code = {
authorizationCode: authorizationCode,
expiresAt: expiresAt,
redirectUri: redirectUri,
scope: scope
};
if(codeChallenge && codeChallengeMethod){
code = Object.assign({
codeChallenge: codeChallenge,
codeChallengeMethod: codeChallengeMethod
}, code);
}
if(codeChallenge && codeChallengeMethod){
code = Object.assign({
codeChallenge: codeChallenge,
codeChallengeMethod: codeChallengeMethod
}, code);
return this.model.saveAuthorizationCode(code, client, user);
}
return this.model.saveAuthorizationCode(code, client, user);
};
async validateRedirectUri (redirectUri, client) {
if (this.model.validateRedirectUri) {
return this.model.validateRedirectUri(redirectUri, client);
}
AuthorizeHandler.prototype.validateRedirectUri = async function(redirectUri, client) {
if (this.model.validateRedirectUri) {
return this.model.validateRedirectUri(redirectUri, client);
return client.redirectUris.includes(redirectUri);
}
/**
* Get response type.
*/
return client.redirectUris.includes(redirectUri);
};
/**
* Get response type.
*/
getResponseType (request) {
const responseType = request.body.response_type || request.query.response_type;
AuthorizeHandler.prototype.getResponseType = function(request) {
const responseType = request.body.response_type || request.query.response_type;
if (!responseType) {
throw new InvalidRequestError('Missing parameter: `response_type`');
}
if (!responseType) {
throw new InvalidRequestError('Missing parameter: `response_type`');
}
if (!Object.prototype.hasOwnProperty.call(responseTypes, responseType)) {
throw new UnsupportedResponseTypeError('Unsupported response type: `response_type` is not supported');
}
if (!Object.prototype.hasOwnProperty.call(responseTypes, responseType)) {
throw new UnsupportedResponseTypeError('Unsupported response type: `response_type` is not supported');
return responseTypes[responseType];
}
return responseTypes[responseType];
};
/**
* Build a successful response that redirects the user-agent to the client-provided url.
*/
/**
* Build a successful response that redirects the user-agent to the client-provided url.
*/
buildSuccessRedirectUri (redirectUri, responseType) {
return responseType.buildRedirectUri(redirectUri);
}
AuthorizeHandler.prototype.buildSuccessRedirectUri = function(redirectUri, responseType) {
return responseType.buildRedirectUri(redirectUri);
};
/**
* Build an error response that redirects the user-agent to the client-provided url.
*/
/**
* Build an error response that redirects the user-agent to the client-provided url.
*/
buildErrorRedirectUri (redirectUri, error) {
const uri = url.parse(redirectUri);
AuthorizeHandler.prototype.buildErrorRedirectUri = function(redirectUri, error) {
const uri = url.parse(redirectUri);
uri.query = {
error: error.name
};
uri.query = {
error: error.name
};
if (error.message) {
uri.query.error_description = error.message;
}
if (error.message) {
uri.query.error_description = error.message;
return uri;
}
return uri;
};
/**
* Update response with the redirect uri and the state parameter, if available.
*/
/**
* Update response with the redirect uri and the state parameter, if available.
*/
updateResponse (response, redirectUri, state) {
redirectUri.query = redirectUri.query || {};
AuthorizeHandler.prototype.updateResponse = function(response, redirectUri, state) {
redirectUri.query = redirectUri.query || {};
if (state) {
redirectUri.query.state = state;
}
if (state) {
redirectUri.query.state = state;
response.redirect(url.format(redirectUri));
}
response.redirect(url.format(redirectUri));
};
getCodeChallenge (request) {
return request.body.code_challenge;
}
AuthorizeHandler.prototype.getCodeChallenge = function(request) {
return request.body.code_challenge || request.query.code_challenge;
};
/**
* Get code challenge method from request or defaults to plain.
* https://www.rfc-editor.org/rfc/rfc7636#section-4.3
*
* @throws {InvalidRequestError} if request contains unsupported code_challenge_method
* (see https://www.rfc-editor.org/rfc/rfc7636#section-4.4)
*/
getCodeChallengeMethod (request) {
const algorithm = request.body.code_challenge_method;
/**
* Get code challenge method from request or defaults to plain.
* https://www.rfc-editor.org/rfc/rfc7636#section-4.3
*
* @throws {InvalidRequestError} if request contains unsupported code_challenge_method
* (see https://www.rfc-editor.org/rfc/rfc7636#section-4.4)
*/
AuthorizeHandler.prototype.getCodeChallengeMethod = function(request) {
const algorithm = request.body.code_challenge_method || request.query.code_challenge_method;
if (algorithm && !pkce.isValidMethod(algorithm)) {
throw new InvalidRequestError(`Invalid request: transform algorithm '${algorithm}' not supported`);
}
if (algorithm && !pkce.isValidMethod(algorithm)) {
throw new InvalidRequestError(`Invalid request: transform algorithm '${algorithm}' not supported`);
return algorithm || 'plain';
}
}
return algorithm || 'plain';
};
/**

@@ -394,0 +396,0 @@ * Export constructor.

@@ -37,261 +37,263 @@ 'use strict';

function TokenHandler(options) {
options = options || {};
class TokenHandler {
constructor (options) {
options = options || {};
if (!options.accessTokenLifetime) {
throw new InvalidArgumentError('Missing parameter: `accessTokenLifetime`');
}
if (!options.accessTokenLifetime) {
throw new InvalidArgumentError('Missing parameter: `accessTokenLifetime`');
}
if (!options.model) {
throw new InvalidArgumentError('Missing parameter: `model`');
}
if (!options.model) {
throw new InvalidArgumentError('Missing parameter: `model`');
}
if (!options.refreshTokenLifetime) {
throw new InvalidArgumentError('Missing parameter: `refreshTokenLifetime`');
}
if (!options.refreshTokenLifetime) {
throw new InvalidArgumentError('Missing parameter: `refreshTokenLifetime`');
}
if (!options.model.getClient) {
throw new InvalidArgumentError('Invalid argument: model does not implement `getClient()`');
}
if (!options.model.getClient) {
throw new InvalidArgumentError('Invalid argument: model does not implement `getClient()`');
}
this.accessTokenLifetime = options.accessTokenLifetime;
this.grantTypes = Object.assign({}, grantTypes, options.extendedGrantTypes);
this.model = options.model;
this.refreshTokenLifetime = options.refreshTokenLifetime;
this.allowExtendedTokenAttributes = options.allowExtendedTokenAttributes;
this.requireClientAuthentication = options.requireClientAuthentication || {};
this.alwaysIssueNewRefreshToken = options.alwaysIssueNewRefreshToken !== false;
}
/**
* Token Handler.
*/
TokenHandler.prototype.handle = async function(request, response) {
if (!(request instanceof Request)) {
throw new InvalidArgumentError('Invalid argument: `request` must be an instance of Request');
this.accessTokenLifetime = options.accessTokenLifetime;
this.grantTypes = Object.assign({}, grantTypes, options.extendedGrantTypes);
this.model = options.model;
this.refreshTokenLifetime = options.refreshTokenLifetime;
this.allowExtendedTokenAttributes = options.allowExtendedTokenAttributes;
this.requireClientAuthentication = options.requireClientAuthentication || {};
this.alwaysIssueNewRefreshToken = options.alwaysIssueNewRefreshToken !== false;
}
if (!(response instanceof Response)) {
throw new InvalidArgumentError('Invalid argument: `response` must be an instance of Response');
}
/**
* Token Handler.
*/
if (request.method !== 'POST') {
throw new InvalidRequestError('Invalid request: method must be POST');
}
async handle (request, response) {
if (!(request instanceof Request)) {
throw new InvalidArgumentError('Invalid argument: `request` must be an instance of Request');
}
if (!request.is('application/x-www-form-urlencoded')) {
throw new InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded');
}
if (!(response instanceof Response)) {
throw new InvalidArgumentError('Invalid argument: `response` must be an instance of Response');
}
try {
const client = await this.getClient(request, response);
const data = await this.handleGrantType(request, client);
const model = new TokenModel(data, { allowExtendedTokenAttributes: this.allowExtendedTokenAttributes });
const tokenType = this.getTokenType(model);
if (request.method !== 'POST') {
throw new InvalidRequestError('Invalid request: method must be POST');
}
this.updateSuccessResponse(response, tokenType);
return data;
} catch (err) {
let e = err;
if (!(e instanceof OAuthError)) {
e = new ServerError(e);
if (!request.is('application/x-www-form-urlencoded')) {
throw new InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded');
}
this.updateErrorResponse(response, e);
throw e;
}
};
try {
const client = await this.getClient(request, response);
const data = await this.handleGrantType(request, client);
const model = new TokenModel(data, { allowExtendedTokenAttributes: this.allowExtendedTokenAttributes });
const tokenType = this.getTokenType(model);
/**
* Get the client from the model.
*/
this.updateSuccessResponse(response, tokenType);
TokenHandler.prototype.getClient = async function(request, response) {
const credentials = await this.getClientCredentials(request);
const grantType = request.body.grant_type;
const codeVerifier = request.body.code_verifier;
const isPkce = pkce.isPKCERequest({ grantType, codeVerifier });
return data;
} catch (err) {
let e = err;
if (!credentials.clientId) {
throw new InvalidRequestError('Missing parameter: `client_id`');
}
if (!(e instanceof OAuthError)) {
e = new ServerError(e);
}
if (this.isClientAuthenticationRequired(grantType) && !credentials.clientSecret && !isPkce) {
throw new InvalidRequestError('Missing parameter: `client_secret`');
this.updateErrorResponse(response, e);
throw e;
}
}
if (!isFormat.vschar(credentials.clientId)) {
throw new InvalidRequestError('Invalid parameter: `client_id`');
}
/**
* Get the client from the model.
*/
if (credentials.clientSecret && !isFormat.vschar(credentials.clientSecret)) {
throw new InvalidRequestError('Invalid parameter: `client_secret`');
}
async getClient (request, response) {
const credentials = await this.getClientCredentials(request);
const grantType = request.body.grant_type;
const codeVerifier = request.body.code_verifier;
const isPkce = pkce.isPKCERequest({ grantType, codeVerifier });
try {
const client = await this.model.getClient(credentials.clientId, credentials.clientSecret);
if (!client) {
throw new InvalidClientError('Invalid client: client is invalid');
if (!credentials.clientId) {
throw new InvalidRequestError('Missing parameter: `client_id`');
}
if (!client.grants) {
throw new ServerError('Server error: missing client `grants`');
if (this.isClientAuthenticationRequired(grantType) && !credentials.clientSecret && !isPkce) {
throw new InvalidRequestError('Missing parameter: `client_secret`');
}
if (!(client.grants instanceof Array)) {
throw new ServerError('Server error: `grants` must be an array');
if (!isFormat.vschar(credentials.clientId)) {
throw new InvalidRequestError('Invalid parameter: `client_id`');
}
return client;
} catch (e) {
// Include the "WWW-Authenticate" response header field if the client
// attempted to authenticate via the "Authorization" request header.
//
// @see https://tools.ietf.org/html/rfc6749#section-5.2.
if ((e instanceof InvalidClientError) && request.get('authorization')) {
response.set('WWW-Authenticate', 'Basic realm="Service"');
throw new InvalidClientError(e, { code: 401 });
if (credentials.clientSecret && !isFormat.vschar(credentials.clientSecret)) {
throw new InvalidRequestError('Invalid parameter: `client_secret`');
}
throw e;
}
};
try {
const client = await this.model.getClient(credentials.clientId, credentials.clientSecret);
/**
* Get client credentials.
*
* The client credentials may be sent using the HTTP Basic authentication scheme or, alternatively,
* the `client_id` and `client_secret` can be embedded in the body.
*
* @see https://tools.ietf.org/html/rfc6749#section-2.3.1
*/
if (!client) {
throw new InvalidClientError('Invalid client: client is invalid');
}
TokenHandler.prototype.getClientCredentials = function(request) {
const credentials = auth(request);
const grantType = request.body.grant_type;
const codeVerifier = request.body.code_verifier;
if (!client.grants) {
throw new ServerError('Server error: missing client `grants`');
}
if (credentials) {
return { clientId: credentials.name, clientSecret: credentials.pass };
}
if (!(client.grants instanceof Array)) {
throw new ServerError('Server error: `grants` must be an array');
}
if (request.body.client_id && request.body.client_secret) {
return { clientId: request.body.client_id, clientSecret: request.body.client_secret };
}
return client;
} catch (e) {
// Include the "WWW-Authenticate" response header field if the client
// attempted to authenticate via the "Authorization" request header.
//
// @see https://tools.ietf.org/html/rfc6749#section-5.2.
if ((e instanceof InvalidClientError) && request.get('authorization')) {
response.set('WWW-Authenticate', 'Basic realm="Service"');
throw new InvalidClientError(e, { code: 401 });
}
if (pkce.isPKCERequest({ grantType, codeVerifier })) {
if(request.body.client_id) {
return { clientId: request.body.client_id };
throw e;
}
}
if (!this.isClientAuthenticationRequired(grantType)) {
if(request.body.client_id) {
return { clientId: request.body.client_id };
/**
* Get client credentials.
*
* The client credentials may be sent using the HTTP Basic authentication scheme or, alternatively,
* the `client_id` and `client_secret` can be embedded in the body.
*
* @see https://tools.ietf.org/html/rfc6749#section-2.3.1
*/
getClientCredentials (request) {
const credentials = auth(request);
const grantType = request.body.grant_type;
const codeVerifier = request.body.code_verifier;
if (credentials) {
return { clientId: credentials.name, clientSecret: credentials.pass };
}
}
throw new InvalidClientError('Invalid client: cannot retrieve client credentials');
};
if (request.body.client_id && request.body.client_secret) {
return { clientId: request.body.client_id, clientSecret: request.body.client_secret };
}
/**
* Handle grant type.
*/
if (pkce.isPKCERequest({ grantType, codeVerifier })) {
if(request.body.client_id) {
return { clientId: request.body.client_id };
}
}
TokenHandler.prototype.handleGrantType = async function(request, client) {
const grantType = request.body.grant_type;
if (!this.isClientAuthenticationRequired(grantType)) {
if(request.body.client_id) {
return { clientId: request.body.client_id };
}
}
if (!grantType) {
throw new InvalidRequestError('Missing parameter: `grant_type`');
throw new InvalidClientError('Invalid client: cannot retrieve client credentials');
}
if (!isFormat.nchar(grantType) && !isFormat.uri(grantType)) {
throw new InvalidRequestError('Invalid parameter: `grant_type`');
}
/**
* Handle grant type.
*/
if (!Object.prototype.hasOwnProperty.call(this.grantTypes, grantType)) {
throw new UnsupportedGrantTypeError('Unsupported grant type: `grant_type` is invalid');
}
async handleGrantType (request, client) {
const grantType = request.body.grant_type;
if (!Array.isArray(client.grants) || !client.grants.includes(grantType)) {
throw new UnauthorizedClientError('Unauthorized client: `grant_type` is invalid');
}
if (!grantType) {
throw new InvalidRequestError('Missing parameter: `grant_type`');
}
const accessTokenLifetime = this.getAccessTokenLifetime(client);
const refreshTokenLifetime = this.getRefreshTokenLifetime(client);
const Type = this.grantTypes[grantType];
if (!isFormat.nchar(grantType) && !isFormat.uri(grantType)) {
throw new InvalidRequestError('Invalid parameter: `grant_type`');
}
const options = {
accessTokenLifetime: accessTokenLifetime,
model: this.model,
refreshTokenLifetime: refreshTokenLifetime,
alwaysIssueNewRefreshToken: this.alwaysIssueNewRefreshToken
};
if (!Object.prototype.hasOwnProperty.call(this.grantTypes, grantType)) {
throw new UnsupportedGrantTypeError('Unsupported grant type: `grant_type` is invalid');
}
return new Type(options).handle(request, client);
};
if (!Array.isArray(client.grants) || !client.grants.includes(grantType)) {
throw new UnauthorizedClientError('Unauthorized client: `grant_type` is invalid');
}
/**
* Get access token lifetime.
*/
const accessTokenLifetime = this.getAccessTokenLifetime(client);
const refreshTokenLifetime = this.getRefreshTokenLifetime(client);
const Type = this.grantTypes[grantType];
TokenHandler.prototype.getAccessTokenLifetime = function(client) {
return client.accessTokenLifetime || this.accessTokenLifetime;
};
const options = {
accessTokenLifetime: accessTokenLifetime,
model: this.model,
refreshTokenLifetime: refreshTokenLifetime,
alwaysIssueNewRefreshToken: this.alwaysIssueNewRefreshToken
};
/**
* Get refresh token lifetime.
*/
return new Type(options).handle(request, client);
}
TokenHandler.prototype.getRefreshTokenLifetime = function(client) {
return client.refreshTokenLifetime || this.refreshTokenLifetime;
};
/**
* Get access token lifetime.
*/
/**
* Get token type.
*/
getAccessTokenLifetime (client) {
return client.accessTokenLifetime || this.accessTokenLifetime;
}
TokenHandler.prototype.getTokenType = function(model) {
return new BearerTokenType(model.accessToken, model.accessTokenLifetime, model.refreshToken, model.scope, model.customAttributes);
};
/**
* Get refresh token lifetime.
*/
/**
* Update response when a token is generated.
*/
getRefreshTokenLifetime (client) {
return client.refreshTokenLifetime || this.refreshTokenLifetime;
}
TokenHandler.prototype.updateSuccessResponse = function(response, tokenType) {
response.body = tokenType.valueOf();
/**
* Get token type.
*/
response.set('Cache-Control', 'no-store');
response.set('Pragma', 'no-cache');
};
getTokenType (model) {
return new BearerTokenType(model.accessToken, model.accessTokenLifetime, model.refreshToken, model.scope, model.customAttributes);
}
/**
* Update response when an error is thrown.
*/
/**
* Update response when a token is generated.
*/
TokenHandler.prototype.updateErrorResponse = function(response, error) {
response.body = {
error: error.name,
error_description: error.message
};
updateSuccessResponse (response, tokenType) {
response.body = tokenType.valueOf();
response.status = error.code;
};
response.set('Cache-Control', 'no-store');
response.set('Pragma', 'no-cache');
}
/**
* Given a grant type, check if client authentication is required
*/
TokenHandler.prototype.isClientAuthenticationRequired = function(grantType) {
if (Object.keys(this.requireClientAuthentication).length > 0) {
return (typeof this.requireClientAuthentication[grantType] !== 'undefined') ? this.requireClientAuthentication[grantType] : true;
} else {
return true;
/**
* Update response when an error is thrown.
*/
updateErrorResponse (response, error) {
response.body = {
error: error.name,
error_description: error.message
};
response.status = error.code;
}
};
/**
* Given a grant type, check if client authentication is required
*/
isClientAuthenticationRequired (grantType) {
if (Object.keys(this.requireClientAuthentication).length > 0) {
return (typeof this.requireClientAuthentication[grantType] !== 'undefined') ? this.requireClientAuthentication[grantType] : true;
} else {
return true;
}
}
}
/**

@@ -298,0 +300,0 @@ * Export constructor.

@@ -6,61 +6,76 @@ 'use strict';

*/
const InvalidArgumentError = require('../errors/invalid-argument-error');
const { getLifetimeFromExpiresAt } = require('../utils/date-util');
/**
* Constructor.
* The core model attributes allowed when allowExtendedTokenAttributes is false.
*/
const modelAttributes = new Set([
'accessToken',
'accessTokenExpiresAt',
'refreshToken',
'refreshTokenExpiresAt',
'scope',
'client',
'user'
]);
const modelAttributes = ['accessToken', 'accessTokenExpiresAt', 'refreshToken', 'refreshTokenExpiresAt', 'scope', 'client', 'user'];
class TokenModel {
constructor(data = {}, options = {}) {
const {
accessToken,
accessTokenExpiresAt,
refreshToken,
refreshTokenExpiresAt,
scope,
client,
user,
} = data;
function TokenModel(data, options) {
data = data || {};
if (!accessToken) {
throw new InvalidArgumentError('Missing parameter: `accessToken`');
}
if (!data.accessToken) {
throw new InvalidArgumentError('Missing parameter: `accessToken`');
}
if (!client) {
throw new InvalidArgumentError('Missing parameter: `client`');
}
if (!data.client) {
throw new InvalidArgumentError('Missing parameter: `client`');
}
if (!user) {
throw new InvalidArgumentError('Missing parameter: `user`');
}
if (!data.user) {
throw new InvalidArgumentError('Missing parameter: `user`');
}
if (accessTokenExpiresAt && !(accessTokenExpiresAt instanceof Date)) {
throw new InvalidArgumentError('Invalid parameter: `accessTokenExpiresAt`');
}
if (data.accessTokenExpiresAt && !(data.accessTokenExpiresAt instanceof Date)) {
throw new InvalidArgumentError('Invalid parameter: `accessTokenExpiresAt`');
}
if (refreshTokenExpiresAt && !(refreshTokenExpiresAt instanceof Date)) {
throw new InvalidArgumentError('Invalid parameter: `refreshTokenExpiresAt`');
}
if (data.refreshTokenExpiresAt && !(data.refreshTokenExpiresAt instanceof Date)) {
throw new InvalidArgumentError('Invalid parameter: `refreshTokenExpiresAt`');
}
this.accessToken = accessToken;
this.accessTokenExpiresAt = accessTokenExpiresAt;
this.client = client;
this.refreshToken = refreshToken;
this.refreshTokenExpiresAt = refreshTokenExpiresAt;
this.scope = scope;
this.user = user;
this.accessToken = data.accessToken;
this.accessTokenExpiresAt = data.accessTokenExpiresAt;
this.client = data.client;
this.refreshToken = data.refreshToken;
this.refreshTokenExpiresAt = data.refreshTokenExpiresAt;
this.scope = data.scope;
this.user = data.user;
if (accessTokenExpiresAt) {
this.accessTokenLifetime = getLifetimeFromExpiresAt(accessTokenExpiresAt);
}
if (options && options.allowExtendedTokenAttributes) {
this.customAttributes = {};
const { allowExtendedTokenAttributes } = options;
for (const key in data) {
if ( Object.prototype.hasOwnProperty.call(data, key) && (modelAttributes.indexOf(key) < 0)) {
this.customAttributes[key] = data[key];
}
if (allowExtendedTokenAttributes) {
this.customAttributes = {};
Object.keys(data).forEach(key => {
if (!modelAttributes.has(key)) {
this.customAttributes[key] = data[key];
}
});
}
}
if(this.accessTokenExpiresAt) {
this.accessTokenLifetime = Math.floor((this.accessTokenExpiresAt - new Date()) / 1000);
}
}
/**
* Export constructor.
*/
module.exports = TokenModel;

@@ -10,65 +10,51 @@ 'use strict';

/**
* Constructor.
*/
class Request {
constructor({ headers, method, query, body, ...otherOptions } = {}) {
if (!headers) {
throw new InvalidArgumentError('Missing parameter: `headers`');
}
function Request(options) {
options = options || {};
if (!method) {
throw new InvalidArgumentError('Missing parameter: `method`');
}
if (!options.headers) {
throw new InvalidArgumentError('Missing parameter: `headers`');
}
if (!query) {
throw new InvalidArgumentError('Missing parameter: `query`');
}
if (!options.method) {
throw new InvalidArgumentError('Missing parameter: `method`');
}
this.body = body || {};
this.headers = {};
this.method = method;
this.query = query;
if (!options.query) {
throw new InvalidArgumentError('Missing parameter: `query`');
// Store the headers in lower case.
Object.entries(headers).forEach(([header, value]) => {
this.headers[header.toLowerCase()] = value;
});
// Store additional properties of the request object passed in
Object.entries(otherOptions)
.filter(([property]) => !this[property])
.forEach(([property, value]) => {
this[property] = value;
});
}
this.body = options.body || {};
this.headers = {};
this.method = options.method;
this.query = options.query;
// Store the headers in lower case.
for (const field in options.headers) {
if (Object.prototype.hasOwnProperty.call(options.headers, field)) {
this.headers[field.toLowerCase()] = options.headers[field];
}
/**
* Get a request header.
* @param {String} field
*/
get(field) {
return this.headers[field.toLowerCase()];
}
// Store additional properties of the request object passed in
for (const property in options) {
if (Object.prototype.hasOwnProperty.call(options, property) && !this[property]) {
this[property] = options[property];
}
/**
* Check if the content-type matches any of the given mime types.
* @param {...String|Array} types
*/
is(...types) {
return typeis(this, types.flat()) || false;
}
}
/**
* Get a request header.
*/
Request.prototype.get = function(field) {
return this.headers[field.toLowerCase()];
};
/**
* Check if the content-type matches any of the given mime type.
*/
Request.prototype.is = function(types) {
if (!Array.isArray(types)) {
types = [].slice.call(arguments);
}
return typeis(this, types) || false;
};
/**
* Export constructor.
*/
module.exports = Request;

@@ -10,35 +10,25 @@ 'use strict';

/**
* Constructor.
*/
class CodeResponseType {
constructor(code) {
if (!code) {
throw new InvalidArgumentError('Missing parameter: `code`');
}
function CodeResponseType(code) {
if (!code) {
throw new InvalidArgumentError('Missing parameter: `code`');
this.code = code;
}
this.code = code;
}
buildRedirectUri(redirectUri) {
if (!redirectUri) {
throw new InvalidArgumentError('Missing parameter: `redirectUri`');
}
/**
* Build redirect uri.
*/
const uri = url.parse(redirectUri, true);
CodeResponseType.prototype.buildRedirectUri = function(redirectUri) {
if (!redirectUri) {
throw new InvalidArgumentError('Missing parameter: `redirectUri`');
uri.query.code = this.code;
uri.search = null;
return uri;
}
}
const uri = url.parse(redirectUri, true);
uri.query.code = this.code;
uri.search = null;
return uri;
};
/**
* Export constructor.
*/
module.exports = CodeResponseType;

@@ -9,14 +9,8 @@ 'use strict';

/**
* Constructor.
*/
function TokenResponseType() {
throw new ServerError('Not implemented.');
class TokenResponseType {
constructor() {
throw new ServerError('Not implemented.');
}
}
/**
* Export constructor.
*/
module.exports = TokenResponseType;
'use strict';
/**
* Constructor.
*/
class Response {
constructor({ headers = {}, body = {}, ...otherOptions } = {}) {
this.status = 200;
this.body = body;
this.headers = {};
function Response(options) {
options = options || {};
// Store the headers in lower case.
Object.entries(headers).forEach(([header, value]) => {
this.headers[header.toLowerCase()] = value;
});
this.body = options.body || {};
this.headers = {};
this.status = 200;
// Store additional properties of the response object passed in
Object.entries(otherOptions)
.filter(([property]) => !this[property])
.forEach(([property, value]) => {
this[property] = value;
});
}
// Store the headers in lower case.
for (const field in options.headers) {
if (Object.prototype.hasOwnProperty.call(options.headers, field)) {
this.headers[field.toLowerCase()] = options.headers[field];
}
/**
* Get a response header.
*/
get(field) {
return this.headers[field.toLowerCase()];
}
// Store additional properties of the response object passed in
for (const property in options) {
if (Object.prototype.hasOwnProperty.call(options, property) && !this[property]) {
this[property] = options[property];
}
/**
* Redirect response.
*/
redirect(url) {
this.set('Location', url);
this.status = 302;
}
/**
* Set a response header.
*/
set(field, value) {
this.headers[field.toLowerCase()] = value;
}
}
/**
* Get a response header.
*/
Response.prototype.get = function(field) {
return this.headers[field.toLowerCase()];
};
/**
* Redirect response.
*/
Response.prototype.redirect = function(url) {
this.set('Location', url);
this.status = 302;
};
/**
* Set a response header.
*/
Response.prototype.set = function(field, value) {
this.headers[field.toLowerCase()] = value;
};
/**
* Export constructor.
*/
module.exports = Response;

@@ -16,13 +16,14 @@ 'use strict';

function OAuth2Server(options) {
options = options || {};
class OAuth2Server {
constructor (options) {
options = options || {};
if (!options.model) {
throw new InvalidArgumentError('Missing parameter: `model`');
if (!options.model) {
throw new InvalidArgumentError('Missing parameter: `model`');
}
this.options = options;
}
this.options = options;
}
/**
/**
* Authenticate a token.

@@ -32,43 +33,44 @@ * Note, that callback will soon be deprecated!

OAuth2Server.prototype.authenticate = function(request, response, options) {
if (typeof options === 'string') {
options = {scope: options};
}
authenticate (request, response, options) {
if (typeof options === 'string') {
options = {scope: options};
}
options = Object.assign({
addAcceptedScopesHeader: true,
addAuthorizedScopesHeader: true,
allowBearerTokensInQueryString: false
}, this.options, options);
options = Object.assign({
addAcceptedScopesHeader: true,
addAuthorizedScopesHeader: true,
allowBearerTokensInQueryString: false
}, this.options, options);
return new AuthenticateHandler(options).handle(request, response);
};
return new AuthenticateHandler(options).handle(request, response);
}
/**
/**
* Authorize a request.
*/
OAuth2Server.prototype.authorize = function(request, response, options) {
options = Object.assign({
allowEmptyState: false,
authorizationCodeLifetime: 5 * 60 // 5 minutes.
}, this.options, options);
authorize (request, response, options) {
options = Object.assign({
allowEmptyState: false,
authorizationCodeLifetime: 5 * 60 // 5 minutes.
}, this.options, options);
return new AuthorizeHandler(options).handle(request, response);
};
return new AuthorizeHandler(options).handle(request, response);
}
/**
/**
* Create a token.
*/
OAuth2Server.prototype.token = function(request, response, options) {
options = Object.assign({
accessTokenLifetime: 60 * 60, // 1 hour.
refreshTokenLifetime: 60 * 60 * 24 * 14, // 2 weeks.
allowExtendedTokenAttributes: false,
requireClientAuthentication: {} // defaults to true for all grant types
}, this.options, options);
token (request, response, options) {
options = Object.assign({
accessTokenLifetime: 60 * 60, // 1 hour.
refreshTokenLifetime: 60 * 60 * 24 * 14, // 2 weeks.
allowExtendedTokenAttributes: false,
requireClientAuthentication: {} // defaults to true for all grant types
}, this.options, options);
return new TokenHandler(options).handle(request, response);
};
return new TokenHandler(options).handle(request, response);
}
}

@@ -75,0 +77,0 @@ /**

@@ -13,46 +13,48 @@ 'use strict';

function BearerTokenType(accessToken, accessTokenLifetime, refreshToken, scope, customAttributes) {
if (!accessToken) {
throw new InvalidArgumentError('Missing parameter: `accessToken`');
}
class BearerTokenType {
constructor(accessToken, accessTokenLifetime, refreshToken, scope, customAttributes) {
if (!accessToken) {
throw new InvalidArgumentError('Missing parameter: `accessToken`');
}
this.accessToken = accessToken;
this.accessTokenLifetime = accessTokenLifetime;
this.refreshToken = refreshToken;
this.scope = scope;
this.accessToken = accessToken;
this.accessTokenLifetime = accessTokenLifetime;
this.refreshToken = refreshToken;
this.scope = scope;
if (customAttributes) {
this.customAttributes = customAttributes;
if (customAttributes) {
this.customAttributes = customAttributes;
}
}
}
/**
/**
* Retrieve the value representation.
*/
BearerTokenType.prototype.valueOf = function() {
const object = {
access_token: this.accessToken,
token_type: 'Bearer'
};
valueOf () {
const object = {
access_token: this.accessToken,
token_type: 'Bearer'
};
if (this.accessTokenLifetime) {
object.expires_in = this.accessTokenLifetime;
}
if (this.accessTokenLifetime) {
object.expires_in = this.accessTokenLifetime;
}
if (this.refreshToken) {
object.refresh_token = this.refreshToken;
}
if (this.refreshToken) {
object.refresh_token = this.refreshToken;
}
if (this.scope) {
object.scope = this.scope;
}
if (this.scope) {
object.scope = this.scope;
}
for (const key in this.customAttributes) {
if ( Object.prototype.hasOwnProperty.call(this.customAttributes, key) ) {
object[key] = this.customAttributes[key];
for (const key in this.customAttributes) {
if ( Object.prototype.hasOwnProperty.call(this.customAttributes, key) ) {
object[key] = this.customAttributes[key];
}
}
return object;
}
return object;
};
}

@@ -59,0 +61,0 @@ /**

@@ -13,4 +13,6 @@ 'use strict';

function MacTokenType() {
throw new ServerError('Not implemented.');
class MacTokenType {
constructor() {
throw new ServerError('Not implemented.');
}
}

@@ -17,0 +19,0 @@

{
"name": "@node-oauth/oauth2-server",
"description": "Complete, framework-agnostic, compliant and well tested module for implementing an OAuth2 Server in node.js",
"version": "5.0.0-rc.2",
"version": "5.0.0-rc.3",
"keywords": [

@@ -34,6 +34,6 @@ "oauth",

"chai": "4.3.7",
"eslint": "8.42.0",
"eslint": "8.46.0",
"mocha": "10.2.0",
"nyc": "15.1.0",
"sinon": "15.1.0"
"sinon": "15.2.0"
},

@@ -40,0 +40,0 @@ "license": "MIT",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc