Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@keystonejs/access-control

Package Overview
Dependencies
Maintainers
6
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@keystonejs/access-control - npm Package Compare versions

Comparing version 6.3.1 to 6.3.2

19

CHANGELOG.md
# @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 @@

563

lib/access-control.js

@@ -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"
}
SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc