@node-oauth/oauth2-server
Advanced tools
Comparing version 5.0.0-rc.2 to 5.0.0-rc.3
@@ -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 @@ */ |
@@ -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", |
99651
42
2609