@keystonejs/access-control
Advanced tools
Comparing version 6.3.1 to 6.3.2
# @keystonejs/access-control | ||
## 6.3.2 | ||
### Patch Changes | ||
- [`619ef5051`](https://github.com/keystonejs/keystone/commit/619ef50512c09d7cf988dc3c877eed868eba68a6) [#4730](https://github.com/keystonejs/keystone/pull/4730) Thanks [@timleslie](https://github.com/timleslie)! - Refactored access parsing to separate parsing from validation. | ||
* [`86b597d41`](https://github.com/keystonejs/keystone/commit/86b597d410c907ed54a4948da438de48e313302f) [#4724](https://github.com/keystonejs/keystone/pull/4724) Thanks [@timleslie](https://github.com/timleslie)! - Rearranged code to have an explicit exports group. | ||
- [`c1257ca83`](https://github.com/keystonejs/keystone/commit/c1257ca834ccf5a0407debe6e7d27b45ed32a26a) [#4727](https://github.com/keystonejs/keystone/pull/4727) Thanks [@timleslie](https://github.com/timleslie)! - Refactored out `parseAccess` and added `checkSchemaNames`. | ||
* [`5e22cc765`](https://github.com/keystonejs/keystone/commit/5e22cc765a8f18c467457fd2ba738cd90273c8c5) [#4725](https://github.com/keystonejs/keystone/pull/4725) Thanks [@timleslie](https://github.com/timleslie)! - Refactored calls to `validateGranularConfigTypes` to be more explicit. | ||
- [`b9ec7fff9`](https://github.com/keystonejs/keystone/commit/b9ec7fff9d96ac56e2836543d698cf0b62b5dc8f) [#4723](https://github.com/keystonejs/keystone/pull/4723) Thanks [@timleslie](https://github.com/timleslie)! - Replaced usage of `getType()` with `typeof`. | ||
* [`5ad7c12e8`](https://github.com/keystonejs/keystone/commit/5ad7c12e86573e73e85368076bdc1296f3f69db3) [#4726](https://github.com/keystonejs/keystone/pull/4726) Thanks [@timleslie](https://github.com/timleslie)! - Refactored `parseAccess` and inlined the code from `validateGranularConfigTypes` and `parseAccessCore`. | ||
* Updated dependencies [[`94c8d349d`](https://github.com/keystonejs/keystone/commit/94c8d349d3795cd9abec407f78752417623ee56f)]: | ||
- @keystonejs/utils@6.0.1 | ||
## 6.3.1 | ||
@@ -4,0 +23,0 @@ |
@@ -1,38 +0,4 @@ | ||
const { getType, pick, defaultObj, intersection } = require('@keystonejs/utils'); | ||
const { pick, defaultObj, intersection } = require('@keystonejs/utils'); | ||
const validateGranularConfigTypes = (longHandAccess, validationError) => { | ||
const errors = Object.entries(longHandAccess) | ||
.map(([accessType, accessConfig]) => validationError(getType(accessConfig), accessType)) | ||
.filter(error => error); | ||
if (errors.length) { | ||
throw new Error(errors.join('\n')); | ||
} | ||
return longHandAccess; | ||
}; | ||
const parseAccessCore = ({ accessTypes, access, defaultAccess, onGranularParseError }) => { | ||
const type = getType(access); | ||
switch (type) { | ||
case 'Boolean': | ||
case 'Function': | ||
return defaultObj(accessTypes, access); | ||
case 'Object': | ||
// An object was supplied, but it has the wrong keys (it's probably a | ||
// declarative access control config being used as a shorthand, which | ||
// isn't possible [due to `create` not supporting declarative config]) | ||
if (Object.keys(pick(access, accessTypes)).length === 0) { | ||
onGranularParseError(); | ||
} | ||
return { ...defaultObj(accessTypes, defaultAccess), ...pick(access, accessTypes) }; | ||
default: | ||
throw new Error( | ||
`Shorthand access must be specified as either a boolean or a function, received ${type}.` | ||
); | ||
} | ||
}; | ||
const parseAccess = ({ schemaNames, accessTypes, access, defaultAccess, parseAndValidate }) => { | ||
const checkSchemaNames = ({ schemaNames, accessTypes, access }) => { | ||
if (schemaNames.includes('internal')) { | ||
@@ -43,265 +9,328 @@ throw new Error(`"internal" is a reserved word and cannot be used as a schema name.`); | ||
// Check that none of the schemaNames match the accessTypes | ||
if (intersection(schemaNames, accessTypes).length > 0) { | ||
const matchingNames = intersection(schemaNames, accessTypes); | ||
if (matchingNames.length > 0) { | ||
throw new Error( | ||
`${JSON.stringify( | ||
intersection(schemaNames, accessTypes) | ||
)} are reserved words and cannot be used as schema names.` | ||
`${JSON.stringify(matchingNames)} are reserved words and cannot be used as schema names.` | ||
); | ||
} | ||
const providedNameCount = intersection(Object.keys(access), schemaNames).length; | ||
const type = getType(access); | ||
if ( | ||
type === 'Object' && | ||
providedNameCount > 0 && | ||
providedNameCount < Object.keys(access).length | ||
) { | ||
// If some are in, and some are out, throw an error! | ||
throw new Error( | ||
`Invalid schema names: ${JSON.stringify( | ||
Object.keys(access).filter(k => !schemaNames.includes(k)) | ||
)}` | ||
); | ||
if (typeof access === 'object') { | ||
const accessKeys = Object.keys(access); | ||
const providedNameCount = intersection(accessKeys, schemaNames).length; | ||
if (providedNameCount > 0 && providedNameCount < accessKeys.length) { | ||
// If some are in, and some are out, throw an error! | ||
const ks = accessKeys.filter(k => !schemaNames.includes(k)); | ||
throw new Error(`Invalid schema names: ${JSON.stringify(ks)}`); | ||
} | ||
} | ||
const namesProvided = type === 'Object' && providedNameCount === Object.keys(access).length; | ||
return schemaNames.reduce( | ||
(acc, schemaName) => ({ | ||
...acc, | ||
[schemaName]: parseAndValidate( | ||
namesProvided | ||
? access.hasOwnProperty(schemaName) // If all the keys are in schemaNames, parse each on their own | ||
? access[schemaName] | ||
: defaultAccess | ||
: access | ||
), | ||
internal: accessTypes.length ? defaultObj(accessTypes, true) : true, | ||
}), | ||
{} | ||
); | ||
const keyedBySchemaName = | ||
typeof access === 'object' && | ||
intersection(Object.keys(access), schemaNames).length === Object.keys(access).length; | ||
return keyedBySchemaName; | ||
}; | ||
module.exports = { | ||
parseCustomAccess({ defaultAccess, access = defaultAccess, schemaNames }) { | ||
const accessTypes = []; | ||
const parseAndValidate = access => { | ||
const type = getType(access); | ||
if (!['Boolean', 'AsyncFunction', 'Function', 'Object'].includes(type)) { | ||
throw new Error( | ||
`Expected a Boolean, Object, or Function for custom access, but got ${type}` | ||
); | ||
} | ||
return access; | ||
}; | ||
return parseAccess({ schemaNames, accessTypes, access, defaultAccess, parseAndValidate }); | ||
}, | ||
function parseCustomAccess({ defaultAccess, access = defaultAccess, schemaNames }) { | ||
const accessTypes = []; | ||
parseListAccess({ listKey, defaultAccess, access = defaultAccess, schemaNames }) { | ||
const accessTypes = ['create', 'read', 'update', 'delete', 'auth']; | ||
const parseAndValidate = access => | ||
validateGranularConfigTypes( | ||
parseAccessCore({ | ||
accessTypes, | ||
access, | ||
defaultAccess, | ||
onGranularParseError: () => { | ||
throw new Error( | ||
`Must specify one of ${JSON.stringify( | ||
accessTypes | ||
)} access configs, but got ${JSON.stringify( | ||
Object.keys(access) | ||
)}. (Did you mean to specify a declarative access control config? This can be done on a granular basis only)` | ||
); | ||
}, | ||
}), | ||
(type, accessType) => { | ||
if (accessType === 'create') { | ||
if (!['Boolean', 'AsyncFunction', 'Function'].includes(type)) { | ||
return `Expected a Boolean, or Function for ${listKey}.access.${accessType}, but got ${type}. (NOTE: 'create' cannot have a Declarative access control config)`; | ||
} | ||
} else { | ||
if (!['Object', 'Boolean', 'AsyncFunction', 'Function'].includes(type)) { | ||
return `Expected a Boolean, Object, or Function for ${listKey}.access.${accessType}, but got ${type}`; | ||
} | ||
} | ||
} | ||
); | ||
return parseAccess({ schemaNames, accessTypes, access, defaultAccess, parseAndValidate }); | ||
}, | ||
const keyedBySchemaName = checkSchemaNames({ schemaNames, accessTypes, access }); | ||
parseFieldAccess({ listKey, fieldKey, defaultAccess, access = defaultAccess, schemaNames }) { | ||
const accessTypes = ['create', 'read', 'update']; | ||
const parseAndValidate = access => | ||
validateGranularConfigTypes( | ||
parseAccessCore({ | ||
accessTypes, | ||
access, | ||
defaultAccess, | ||
onGranularParseError: () => { | ||
throw new Error( | ||
`Must specify one of ${JSON.stringify( | ||
accessTypes | ||
)} access configs, but got ${JSON.stringify( | ||
Object.keys(access) | ||
)}. (Did you mean to specify a declarative access control config? This can be done on lists only)` | ||
); | ||
}, | ||
}), | ||
(type, accessType) => { | ||
if (!['Boolean', 'AsyncFunction', 'Function'].includes(type)) { | ||
return `Expected a Boolean or Function for ${listKey}.fields.${fieldKey}.access.${accessType}, but got ${type}. (NOTE: Fields cannot have declarative access control config)`; | ||
} | ||
} | ||
); | ||
return parseAccess({ schemaNames, accessTypes, access, defaultAccess, parseAndValidate }); | ||
}, | ||
const fullAccess = keyedBySchemaName | ||
? { ...defaultObj(schemaNames, defaultAccess), ...access } // Access keyed by schemaName | ||
: defaultObj(schemaNames, access); // Access not keyed by schemaName | ||
async validateCustomAccessControl({ | ||
item, | ||
args, | ||
context, | ||
info, | ||
access, | ||
authentication = {}, | ||
gqlName, | ||
}) { | ||
// Either a boolean or an object describing a where clause | ||
let result; | ||
if (typeof access !== 'function') { | ||
result = access; | ||
} else { | ||
result = await access({ | ||
item, | ||
args, | ||
context, | ||
info, | ||
authentication: authentication.item ? authentication : {}, | ||
gqlName, | ||
}); | ||
} | ||
const type = getType(result); | ||
const fullParsedAccess = { ...fullAccess, internal: true }; | ||
if (!['Object', 'Boolean'].includes(type)) { | ||
Object.values(fullParsedAccess).forEach(access => { | ||
if (!['boolean', 'function', 'object'].includes(typeof access)) { | ||
throw new Error( | ||
`Must return an Object or Boolean from Imperative or Declarative access control function. Got ${type}` | ||
`Expected a Boolean, Object, or Function for custom access, but got ${typeof access}` | ||
); | ||
} | ||
return result; | ||
}, | ||
}); | ||
async validateListAccessControl({ | ||
access, | ||
listKey, | ||
operation, | ||
authentication = {}, | ||
originalInput, | ||
gqlName, | ||
itemId, | ||
itemIds, | ||
context, | ||
}) { | ||
// Either a boolean or an object describing a where clause | ||
let result; | ||
if (typeof access[operation] !== 'function') { | ||
result = access[operation]; | ||
} else { | ||
result = await access[operation]({ | ||
authentication: authentication.item ? authentication : {}, | ||
listKey, | ||
operation, | ||
originalInput, | ||
gqlName, | ||
itemId, | ||
itemIds, | ||
context, | ||
}); | ||
} | ||
return fullParsedAccess; | ||
} | ||
const type = getType(result); | ||
function parseListAccess({ listKey, defaultAccess, access = defaultAccess, schemaNames }) { | ||
const accessTypes = ['create', 'read', 'update', 'delete', 'auth']; | ||
if (!['Object', 'Boolean'].includes(type)) { | ||
throw new Error( | ||
`Must return an Object or Boolean from Imperative or Declarative access control function. Got ${type}` | ||
); | ||
} | ||
const keyedBySchemaName = checkSchemaNames({ schemaNames, accessTypes, access }); | ||
// Special case for 'create' permission | ||
if (operation === 'create' && type === 'Object') { | ||
throw new Error( | ||
`Expected a Boolean for ${listKey}.access.create(), but got Object. (NOTE: 'create' cannot have a Declarative access control config)` | ||
); | ||
const fullAccess = keyedBySchemaName | ||
? { ...defaultObj(schemaNames, defaultAccess), ...access } // Access keyed by schemaName | ||
: defaultObj(schemaNames, access); // Access not keyed by schemaName | ||
const parseAndValidate = access => { | ||
switch (typeof access) { | ||
case 'boolean': | ||
case 'function': | ||
return defaultObj(accessTypes, access); | ||
case 'object': | ||
// An object was supplied, but it has the wrong keys (it's probably a | ||
// declarative access control config being used as a shorthand, which | ||
// isn't possible [due to `create` not supporting declarative config]) | ||
if (Object.keys(pick(access, accessTypes)).length === 0) { | ||
const at = JSON.stringify(accessTypes); | ||
const aks = JSON.stringify(Object.keys(access)); | ||
throw new Error( | ||
`Must specify one of ${at} access configs, but got ${aks}. (Did you mean to specify a declarative access control config? This can be done on a granular basis only)` | ||
); | ||
} | ||
return { ...defaultObj(accessTypes, defaultAccess), ...pick(access, accessTypes) }; | ||
default: | ||
throw new Error( | ||
`Shorthand access must be specified as either a boolean or a function, received ${typeof access}.` | ||
); | ||
} | ||
}; | ||
const fullParsedAccess = { | ||
...schemaNames.reduce( | ||
(acc, schemaName) => ({ ...acc, [schemaName]: parseAndValidate(fullAccess[schemaName]) }), | ||
{} | ||
), | ||
internal: defaultObj(accessTypes, true), | ||
}; | ||
return result; | ||
}, | ||
Object.values(fullParsedAccess).forEach(parsedAccess => { | ||
const errors = Object.entries(parsedAccess) | ||
.map(([accessType, access]) => { | ||
if (accessType === 'create') { | ||
if (!['boolean', 'function'].includes(typeof access)) { | ||
return `Expected a Boolean, or Function for ${listKey}.access.${accessType}, but got ${typeof access}. (NOTE: 'create' cannot have a Declarative access control config)`; | ||
} | ||
} else { | ||
if (!['object', 'boolean', 'function'].includes(typeof access)) { | ||
return `Expected a Boolean, Object, or Function for ${listKey}.access.${accessType}, but got ${typeof access}`; | ||
} | ||
} | ||
}) | ||
.filter(error => error); | ||
async validateFieldAccessControl({ | ||
access, | ||
listKey, | ||
fieldKey, | ||
originalInput, | ||
existingItem, | ||
operation, | ||
authentication = {}, | ||
gqlName, | ||
itemId, | ||
itemIds, | ||
context, | ||
}) { | ||
let result; | ||
if (typeof access[operation] !== 'function') { | ||
result = access[operation]; | ||
} else { | ||
result = await access[operation]({ | ||
authentication: authentication.item ? authentication : {}, | ||
listKey, | ||
fieldKey, | ||
originalInput, | ||
existingItem, | ||
operation, | ||
gqlName, | ||
itemId, | ||
itemIds, | ||
context, | ||
}); | ||
if (errors.length) { | ||
throw new Error(errors.join('\n')); | ||
} | ||
}); | ||
const type = getType(result); | ||
return fullParsedAccess; | ||
} | ||
if (type !== 'Boolean') { | ||
throw new Error( | ||
`Must return a Boolean from ${listKey}.fields.${fieldKey}.access.${operation}(). Got ${type}` | ||
); | ||
} | ||
function parseFieldAccess({ | ||
listKey, | ||
fieldKey, | ||
defaultAccess, | ||
access = defaultAccess, | ||
schemaNames, | ||
}) { | ||
const accessTypes = ['create', 'read', 'update']; | ||
return result; | ||
}, | ||
const keyedBySchemaName = checkSchemaNames({ schemaNames, accessTypes, access }); | ||
async validateAuthAccessControl({ access, listKey, authentication = {}, gqlName, context }) { | ||
const operation = 'auth'; | ||
// Either a boolean or an object describing a where clause | ||
let result; | ||
if (typeof access[operation] !== 'function') { | ||
result = access[operation]; | ||
} else { | ||
result = await access[operation]({ | ||
authentication: authentication.item ? authentication : {}, | ||
listKey, | ||
operation, | ||
gqlName, | ||
context, | ||
}); | ||
const fullAccess = keyedBySchemaName | ||
? { ...defaultObj(schemaNames, defaultAccess), ...access } // Access keyed by schemaName | ||
: defaultObj(schemaNames, access); // Access not keyed by schemaName | ||
const parseAndValidate = access => { | ||
switch (typeof access) { | ||
case 'boolean': | ||
case 'function': | ||
return defaultObj(accessTypes, access); | ||
case 'object': | ||
// An object was supplied, but it has the wrong keys (it's probably a | ||
// declarative access control config being used as a shorthand, which | ||
// isn't possible [due to `create` not supporting declarative config]) | ||
if (Object.keys(pick(access, accessTypes)).length === 0) { | ||
const at = JSON.stringify(accessTypes); | ||
const aks = JSON.stringify(Object.keys(access)); | ||
throw new Error( | ||
`Must specify one of ${at} access configs, but got ${aks}. (Did you mean to specify a declarative access control config? This can be done on lists only)` | ||
); | ||
} | ||
return { ...defaultObj(accessTypes, defaultAccess), ...pick(access, accessTypes) }; | ||
default: | ||
throw new Error( | ||
`Shorthand access must be specified as either a boolean or a function, received ${typeof access}.` | ||
); | ||
} | ||
}; | ||
const fullParsedAccess = { | ||
...schemaNames.reduce( | ||
(acc, schemaName) => ({ ...acc, [schemaName]: parseAndValidate(fullAccess[schemaName]) }), | ||
{} | ||
), | ||
internal: defaultObj(accessTypes, true), | ||
}; | ||
const type = getType(result); | ||
Object.values(fullParsedAccess).forEach(parsedAccess => { | ||
const errors = Object.entries(parsedAccess) | ||
.map(([accessType, access]) => { | ||
if (!['boolean', 'function'].includes(typeof access)) { | ||
return `Expected a Boolean or Function for ${listKey}.fields.${fieldKey}.access.${accessType}, but got ${typeof access}. (NOTE: Fields cannot have declarative access control config)`; | ||
} | ||
}) | ||
.filter(error => error); | ||
if (!['Object', 'Boolean'].includes(type)) { | ||
throw new Error( | ||
`Must return an Object or Boolean from Imperative or Declarative access control function. Got ${type}` | ||
); | ||
if (errors.length) { | ||
throw new Error(errors.join('\n')); | ||
} | ||
}); | ||
return result; | ||
}, | ||
return fullParsedAccess; | ||
} | ||
async function validateCustomAccessControl({ | ||
item, | ||
args, | ||
context, | ||
info, | ||
access, | ||
authentication = {}, | ||
gqlName, | ||
}) { | ||
// Either a boolean or an object describing a where clause | ||
let result; | ||
if (typeof access !== 'function') { | ||
result = access; | ||
} else { | ||
result = await access({ | ||
item, | ||
args, | ||
context, | ||
info, | ||
authentication: authentication.item ? authentication : {}, | ||
gqlName, | ||
}); | ||
} | ||
if (!['object', 'boolean'].includes(typeof result)) { | ||
throw new Error( | ||
`Must return an Object or Boolean from Imperative or Declarative access control function. Got ${typeof result}` | ||
); | ||
} | ||
return result; | ||
} | ||
async function validateListAccessControl({ | ||
access, | ||
listKey, | ||
operation, | ||
authentication = {}, | ||
originalInput, | ||
gqlName, | ||
itemId, | ||
itemIds, | ||
context, | ||
}) { | ||
// Either a boolean or an object describing a where clause | ||
let result; | ||
if (typeof access[operation] !== 'function') { | ||
result = access[operation]; | ||
} else { | ||
result = await access[operation]({ | ||
authentication: authentication.item ? authentication : {}, | ||
listKey, | ||
operation, | ||
originalInput, | ||
gqlName, | ||
itemId, | ||
itemIds, | ||
context, | ||
}); | ||
} | ||
if (!['object', 'boolean'].includes(typeof result)) { | ||
throw new Error( | ||
`Must return an Object or Boolean from Imperative or Declarative access control function. Got ${typeof result}` | ||
); | ||
} | ||
// Special case for 'create' permission | ||
if (operation === 'create' && typeof result === 'object') { | ||
throw new Error( | ||
`Expected a Boolean for ${listKey}.access.create(), but got Object. (NOTE: 'create' cannot have a Declarative access control config)` | ||
); | ||
} | ||
return result; | ||
} | ||
async function validateFieldAccessControl({ | ||
access, | ||
listKey, | ||
fieldKey, | ||
originalInput, | ||
existingItem, | ||
operation, | ||
authentication = {}, | ||
gqlName, | ||
itemId, | ||
itemIds, | ||
context, | ||
}) { | ||
let result; | ||
if (typeof access[operation] !== 'function') { | ||
result = access[operation]; | ||
} else { | ||
result = await access[operation]({ | ||
authentication: authentication.item ? authentication : {}, | ||
listKey, | ||
fieldKey, | ||
originalInput, | ||
existingItem, | ||
operation, | ||
gqlName, | ||
itemId, | ||
itemIds, | ||
context, | ||
}); | ||
} | ||
if (typeof result !== 'boolean') { | ||
throw new Error( | ||
`Must return a Boolean from ${listKey}.fields.${fieldKey}.access.${operation}(). Got ${typeof result}` | ||
); | ||
} | ||
return result; | ||
} | ||
async function validateAuthAccessControl({ | ||
access, | ||
listKey, | ||
authentication = {}, | ||
gqlName, | ||
context, | ||
}) { | ||
const operation = 'auth'; | ||
// Either a boolean or an object describing a where clause | ||
let result; | ||
if (typeof access[operation] !== 'function') { | ||
result = access[operation]; | ||
} else { | ||
result = await access[operation]({ | ||
authentication: authentication.item ? authentication : {}, | ||
listKey, | ||
operation, | ||
gqlName, | ||
context, | ||
}); | ||
} | ||
if (!['object', 'boolean'].includes(typeof result)) { | ||
throw new Error( | ||
`Must return an Object or Boolean from Imperative or Declarative access control function. Got ${typeof result}` | ||
); | ||
} | ||
return result; | ||
} | ||
module.exports = { | ||
parseCustomAccess, | ||
parseListAccess, | ||
parseFieldAccess, | ||
validateCustomAccessControl, | ||
validateListAccessControl, | ||
validateFieldAccessControl, | ||
validateAuthAccessControl, | ||
}; |
{ | ||
"name": "@keystonejs/access-control", | ||
"description": "KeystoneJS Access Control parsing and validating utilities.", | ||
"version": "6.3.1", | ||
"version": "6.3.2", | ||
"author": "The KeystoneJS Development Team", | ||
@@ -11,5 +11,5 @@ "license": "MIT", | ||
"dependencies": { | ||
"@keystonejs/utils": "^6.0.0" | ||
"@keystonejs/utils": "^6.0.1" | ||
}, | ||
"repository": "https://github.com/keystonejs/keystone/tree/master/packages/access-control" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
22636
316
Updated@keystonejs/utils@^6.0.1