@quarks/quarks-iam
Version:
A modern authorization server built to authenticate your users and protect your APIs
223 lines (186 loc) • 6.52 kB
JavaScript
/**
* Module dependencies
*/
var settings = require('../boot/settings')
var AuthorizationError = require('../errors/AuthorizationError')
/**
* Valid response types
*/
var validResponseTypes = [
'code',
'token',
'id_token',
'none'
]
/**
* Supported response modes
*/
var validResponseModes = [
'query',
'fragment'
]
/**
* Validate Authorization Parameters
*
* Ensures that `response_type`, `redirect_uri`, and `client_id` are included in
* request parameters, and that `response_type` is supported.
*/
function validateAuthorizationParams (req, res, next) {
var params = req.connectParams
// missing response type
if (!params.response_type || !params.response_type.trim()) {
return next(new AuthorizationError({
error: 'invalid_request',
error_description: 'Missing response type',
redirect_uri: params.redirect_uri,
statusCode: 302
}))
}
var responseTypes = params.response_type.trim().split(' ')
// Check that
// - All response_types are valid
// - If `none` response_type is given, that it is the only response_type
var isValidResponseType = responseTypes.indexOf('none') !== -1
? responseTypes.length === 1
: responseTypes.every(
function (responseType) {
return validResponseTypes.indexOf(responseType) !== -1
}
)
// invalid response type
if (!isValidResponseType) {
return next(new AuthorizationError({
error: 'unsupported_response_type',
error_description: 'Unsupported response type',
redirect_uri: params.redirect_uri,
statusCode: 302
}))
}
// Check that there is at least one defined set of response_types that are
// supported together in both the settings and on the client.
//
// For example, if the settings allow 'code', 'id_token token', and
// 'code id_token token', then:
//
// - 'code' will pass
// - 'id_token token' will pass
// - 'code token id_token' will pass
// - 'token' will NOT pass
// - 'id_token code' will NOT pass
// Get the registered response_type sets off the client
var registeredResponseTypeSets =
// If the client exists and has response_types defined
(req.client && Array.isArray(req.client.response_types))
// Return an array of response_type sets
? req.client.response_types.map(function (responseTypeString) {
return responseTypeString.trim().split(' ')
})
// Otherwise, return an array with the default set, [ 'code' ]
: [ [ 'code' ] ]
// In the array of response_types in the settings...
var isSupportedResponseType = settings.response_types_supported
// check each set until we find one that satisfies the following:
.some(function (responseTypeString) {
var responseTypeSet = responseTypeString.split(' ')
// if there is at least one response_type set registered on the client...
if (registeredResponseTypeSets.some(function (regResTypeSet) {
var indices = []
// that has the same number of response_types in it...
return regResTypeSet.length === responseTypeSet.length &&
regResTypeSet.every(function (responseType) {
var index = responseTypeSet.indexOf(responseType)
// of which there are no duplicates...
if (indices.indexOf(index) !== -1) {
return false
}
indices.push(index)
// and where every response_type value exists in the response_type
// set from the settings...
return responseTypeSet.indexOf(responseType) !== -1
})
// then we have found a response_type set that is both permitted by the
// server settings and registered for the current client
})) {
var indices = []
// then we check to see that the response_type set received in the auth
// request also has the same number of response_types...
return responseTypes.length === responseTypeSet.length &&
responseTypes.every(function (responseType) {
var index = responseTypeSet.indexOf(responseType)
// that there are no duplicates...
if (indices.indexOf(index) !== -1) {
return false
}
indices.push(index)
// and that every response_type value exists in the response_type
// set that we verified is both registered by the client and
// permitted by the settings
// if this is true, then we found a match, and the response_type
// requested is valid, permitted, and registered
return responseTypeSet.indexOf(responseType) !== -1
})
} else {
// otherwise, continue on to the next response_type in the settings
return false
}
})
// unsupported response type
if (!isSupportedResponseType) {
return next(new AuthorizationError({
error: 'unsupported_response_type',
error_description: 'Unsupported response type',
redirect_uri: params.redirect_uri,
statusCode: 302
}))
}
// unsupported response mode
if (params.response_mode && params.response_mode.trim() &&
validResponseModes.indexOf(params.response_mode.trim()) === -1) {
return next(new AuthorizationError({
error: 'unsupported_response_mode',
error_description: 'Unsupported response mode',
redirect_uri: params.redirect_uri,
statusCode: 302
}))
}
// missing scope
if (!params.scope) {
return next(new AuthorizationError({
error: 'invalid_scope',
error_description: 'Missing scope',
redirect_uri: params.redirect_uri,
statusCode: 302
}))
}
// missing openid scope
if (params.scope.indexOf('openid') === -1) {
return next(new AuthorizationError({
error: 'invalid_scope',
error_description: 'Missing openid scope',
redirect_uri: params.redirect_uri,
statusCode: 302
}))
}
// missing nonce
if (!params.nonce && requiresNonce(responseTypes)) {
return next(new AuthorizationError({
error: 'invalid_request',
error_description: 'Missing nonce',
redirect_uri: params.redirect_uri,
statusCode: 302
}))
}
next()
}
/**
* Check if a nonce is required
*/
function requiresNonce (responseTypes) {
return responseTypes.some(function (responseType) {
return responseType === 'id_token' || responseType === 'token'
})
}
/**
* Exports
*/
module.exports = validateAuthorizationParams