-
Notifications
You must be signed in to change notification settings - Fork 141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merge seperate config schemas #57
Merged
joshcanhelp
merged 8 commits into
auth0:master
from
joshcanhelp:merge-seperate-config-schemas
Jan 27, 2020
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
268a907
Merge appSessionCookie schema into main config schema
joshcanhelp 5b274f4
Merge authorizationParams schema into main config schema
joshcanhelp 44894ce
Cleanup for postLogoutRedirectUri, authorizationParams
joshcanhelp 7deb960
Allow configured response_type to fuzzy match issuer response_type
joshcanhelp 323a67d
Clean up test names and docs
joshcanhelp c88a5d4
Move loadEnvs to Joi default functions
joshcanhelp a709b2d
Move clientSecret checks into Joi schema
joshcanhelp 44cc859
Update test/config.tests.js
joshcanhelp File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,138 +1,92 @@ | ||
const Joi = require('@hapi/joi'); | ||
const clone = require('clone'); | ||
const loadEnvs = require('./loadEnvs'); | ||
const getUser = require('./hooks/getUser'); | ||
const handleCallback = require('./hooks/handleCallback'); | ||
|
||
const defaultAuthorizeParams = { | ||
response_mode: 'form_post', | ||
response_type: 'id_token', | ||
scope: 'openid profile email' | ||
}; | ||
|
||
const authorizationParamsSchema = Joi.object().keys({ | ||
response_mode: [Joi.string().optional(), Joi.allow(null).optional()], | ||
response_type: Joi.string().required(), | ||
scope: Joi.string().required() | ||
}).unknown(true); | ||
|
||
const defaultAppSessionCookie = { | ||
httpOnly: true, | ||
sameSite: 'Lax', | ||
ephemeral: false | ||
}; | ||
|
||
const appSessionCookieSchema = Joi.object().keys({ | ||
domain: Joi.string().optional(), | ||
ephemeral: Joi.boolean().optional(), | ||
httpOnly: Joi.boolean().optional(), | ||
path: Joi.string().optional(), | ||
sameSite: Joi.string().valid('Lax', 'Strict', 'None').optional(), | ||
secure: Joi.boolean().optional() | ||
}).unknown(false); | ||
|
||
const paramsSchema = Joi.object().keys({ | ||
appSessionCookie: Joi.object().optional(), | ||
const paramsSchema = Joi.object({ | ||
appSessionCookie: Joi.object({ | ||
domain: Joi.string().optional(), | ||
ephemeral: Joi.boolean().optional().default(false), | ||
httpOnly: Joi.boolean().optional().default(true), | ||
path: Joi.string().optional(), | ||
sameSite: Joi.string().valid('Lax', 'Strict', 'None').optional().default('Lax'), | ||
secure: Joi.boolean().optional() | ||
}).optional().unknown(false).default(), | ||
appSessionDuration: Joi.number().integer().optional().default(7 * 24 * 60 * 60), | ||
appSessionName: Joi.string().token().optional().default('identity'), | ||
appSessionSecret: Joi.alternatives([ | ||
// Single string key. | ||
Joi.string(), | ||
// Array of keys to allow for rotation. | ||
Joi.array().items(Joi.string()), | ||
// False to stop client session from being created. | ||
Joi.boolean().valid(false) | ||
]).required(), | ||
auth0Logout: Joi.boolean().optional().default(false), | ||
authorizationParams: Joi.object().optional(), | ||
authorizationParams: Joi.object({ | ||
response_type: Joi.string().optional().default('id_token'), | ||
scope: Joi.string().optional().default('openid profile email'), | ||
response_mode: Joi.alternatives([ | ||
Joi.string().optional(), | ||
Joi.allow(null).optional() | ||
]).default(function(parent) { | ||
const responseType = parent.response_type.split(' '); | ||
const responseIncludesTokens = responseType.includes('id_token') || responseType.includes('token'); | ||
return responseIncludesTokens ? 'form_post' : undefined; | ||
}), | ||
}).optional().unknown(true).default(), | ||
baseURL: Joi.string().uri().required(), | ||
clientID: Joi.string().required(), | ||
clientSecret: Joi.string().optional(), | ||
clientSecret: Joi.string().when( | ||
Joi.ref('authorizationParams.response_type', {adjust: (value) => value && value.split(' ').includes('code')}), | ||
{ | ||
is: true, | ||
then: Joi.string().required().messages({ | ||
'any.required': '"clientSecret" is required for response_type code' | ||
}), | ||
otherwise: Joi.when( | ||
Joi.ref('idTokenAlg', {adjust: (value) => value && 'HS' === value.substring(0,2)}), | ||
{ | ||
is: true, | ||
then: Joi.string().required().messages({ | ||
'any.required': '"clientSecret" is required for ID tokens with HS algorithms' | ||
}) | ||
} | ||
) | ||
} | ||
), | ||
clockTolerance: Joi.number().optional().default(60), | ||
errorOnRequiredAuth: Joi.boolean().optional().default(false), | ||
getUser: Joi.function().optional().default(() => getUser), | ||
handleCallback: Joi.function().optional().default(() => handleCallback), | ||
httpOptions: Joi.object().optional(), | ||
identityClaimFilter: Joi.array().optional().default(['aud', 'iss', 'iat', 'exp', 'nonce', 'azp', 'auth_time']), | ||
idpLogout: Joi.boolean().optional().default(false).when( | ||
'auth0Logout', { is: true, then: Joi.boolean().optional().default(true) } | ||
), | ||
idpLogout: Joi.boolean().optional().default((parent) => parent.auth0Logout || false), | ||
idTokenAlg: Joi.string().not('none').optional().default('RS256'), | ||
issuerBaseURL: Joi.alternatives([ Joi.string().uri(), Joi.string().hostname() ]).required(), | ||
issuerBaseURL: Joi.alternatives([ | ||
Joi.string().uri(), | ||
Joi.string().hostname() | ||
]).required(), | ||
legacySameSiteCookie: Joi.boolean().optional().default(true), | ||
loginPath: Joi.string().uri({relativeOnly: true}).optional().default('/login'), | ||
logoutPath: Joi.string().uri({relativeOnly: true}).optional().default('/logout'), | ||
postLogoutRedirectUri: Joi.string().uri({allowRelative: true}).optional().default(''), | ||
redirectUriPath: Joi.string().uri({relativeOnly: true}).optional().default('/callback'), | ||
required: Joi.alternatives([ Joi.function(), Joi.boolean()]).optional().default(true), | ||
routes: Joi.boolean().optional().default(true), | ||
postLogoutRedirectUri: Joi.string().uri({allowRelative: true}).optional().default('/') | ||
}); | ||
|
||
function buildAuthorizeParams(authorizationParams) { | ||
/* | ||
If the user does not provide authorizationParams we default to "defaultAuthorizeParams" (id_token/form_post). | ||
|
||
If the user provides authorizationParams then | ||
- the default response_mode is DEFAULT (undefined), | ||
- the default scope is defaultAuthorizeParams.scope | ||
- response type is required | ||
*/ | ||
|
||
authorizationParams = authorizationParams && Object.keys(authorizationParams).length > 0 ? | ||
authorizationParams : | ||
clone(defaultAuthorizeParams); | ||
|
||
if (!authorizationParams.scope) { | ||
authorizationParams.scope = defaultAuthorizeParams.scope; | ||
} | ||
|
||
const authParamsValidation = authorizationParamsSchema.validate(authorizationParams); | ||
|
||
if(authParamsValidation.error) { | ||
throw new Error(authParamsValidation.error.details[0].message); | ||
} | ||
|
||
return authorizationParams; | ||
} | ||
|
||
function buildAppSessionCookieConfig(cookieConfig) { | ||
|
||
cookieConfig = cookieConfig && Object.keys(cookieConfig).length ? cookieConfig : {}; | ||
cookieConfig = Object.assign({}, defaultAppSessionCookie, cookieConfig); | ||
const cookieConfigValidation = appSessionCookieSchema.validate(cookieConfig); | ||
|
||
if(cookieConfigValidation.error) { | ||
throw new Error(cookieConfigValidation.error.details[0].message); | ||
} | ||
|
||
return cookieConfig; | ||
} | ||
|
||
module.exports.get = function(params) { | ||
let config = typeof params == 'object' ? clone(params) : {}; | ||
|
||
loadEnvs(config); | ||
let config = (typeof params == 'object' ? clone(params) : {}); | ||
config = Object.assign({ | ||
issuerBaseURL: process.env.ISSUER_BASE_URL, | ||
baseURL: process.env.BASE_URL, | ||
clientID: process.env.CLIENT_ID, | ||
clientSecret: process.env.CLIENT_SECRET, | ||
appSessionSecret: process.env.APP_SESSION_SECRET, | ||
}, config); | ||
|
||
const paramsValidation = paramsSchema.validate(config); | ||
|
||
if(paramsValidation.error) { | ||
if (paramsValidation.error) { | ||
throw new Error(paramsValidation.error.details[0].message); | ||
} | ||
|
||
config = paramsValidation.value; | ||
config.authorizationParams = buildAuthorizeParams(config.authorizationParams); | ||
config.appSessionCookie = buildAppSessionCookieConfig(config.appSessionCookie); | ||
|
||
// Code grant requires a client secret to exchange the code for tokens | ||
const responseTypeHasCode = config.authorizationParams.response_type.split(' ').includes('code'); | ||
if (responseTypeHasCode && !config.clientSecret) { | ||
throw new Error('"clientSecret" is required for response_type code'); | ||
} | ||
|
||
// HS256 ID tokens require a client secret to validate the signature. | ||
if ('HS' === config.idTokenAlg.substring(0,2) && !config.clientSecret) { | ||
throw new Error('"clientSecret" is required for ID tokens with HS algorithms'); | ||
} | ||
|
||
return config; | ||
return paramsValidation.value; | ||
}; |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,16 +20,16 @@ describe('config', function() { | |
assert.equal(config.authorizationParams.response_mode, 'form_post'); | ||
}); | ||
|
||
it('should default to scope=openid profile email ', function() { | ||
it('should default to scope=openid profile email', function() { | ||
assert.equal(config.authorizationParams.scope, 'openid profile email'); | ||
}); | ||
|
||
it('should default to required true ', function() { | ||
it('should default to required true', function() { | ||
assert.ok(config.required); | ||
}); | ||
}); | ||
|
||
describe('when authorizationParams is response_type=x', function() { | ||
describe('when authorizationParams is response_type=code', function() { | ||
const customConfig = Object.assign({}, defaultConfig, { | ||
clientSecret: '__test_client_secret__', | ||
authorizationParams: { | ||
|
@@ -38,19 +38,33 @@ describe('config', function() { | |
}); | ||
const config = getConfig(customConfig); | ||
|
||
it('should default to response_type=id_token', function() { | ||
it('should set new response_type', function() { | ||
assert.equal(config.authorizationParams.response_type, 'code'); | ||
}); | ||
|
||
it('should default to response_mode=form_post', function() { | ||
it('should allow undefined response_mode', function() { | ||
assert.equal(config.authorizationParams.response_mode, undefined); | ||
}); | ||
|
||
it('should default to scope=openid profile email ', function() { | ||
it('should keep default scope', function() { | ||
assert.equal(config.authorizationParams.scope, 'openid profile email'); | ||
}); | ||
}); | ||
|
||
describe('when authorizationParams response_type fuzzy matches issuer', function() { | ||
const customConfig = Object.assign({}, defaultConfig, { | ||
clientSecret: '__test_client_secret__', | ||
authorizationParams: { | ||
response_type: 'token id_token code' | ||
} | ||
}); | ||
const config = getConfig(customConfig); | ||
|
||
it('should keep token code', function() { | ||
assert.equal(config.authorizationParams.response_type, 'token id_token code'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does not exactly match metadata fixture so this would have thrown before. |
||
}); | ||
}); | ||
|
||
describe('with auth0Logout', function() { | ||
const config = getConfig(Object.assign({}, defaultConfig, {auth0Logout: true})); | ||
|
||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was a remnant of
fragment
support.id_token
with an undefinedresponse_mode
defaults tofragment
which is not supported in this SDK.