diff --git a/.changeset/cold-plants-look.md b/.changeset/cold-plants-look.md new file mode 100644 index 00000000000..0d046b59fa5 --- /dev/null +++ b/.changeset/cold-plants-look.md @@ -0,0 +1,6 @@ +--- +'@keystonejs/access-control': minor +'@keystonejs/keystone': minor +--- + +Made `context` available to user designed access control functions. diff --git a/docs/api/access-control.md b/docs/api/access-control.md index 0e36ea1222f..001f38c9ad5 100644 --- a/docs/api/access-control.md +++ b/docs/api/access-control.md @@ -101,6 +101,7 @@ the list `User` it would match the input type `UserWhereInput`. | `gqlName` | The name of the query or mutation which triggered the access check. | | `itemId` | The `id` of the item being updated/deleted in singular `update` and `delete` operations. | | `itemIds` | The `ids` of the items being updated/deleted in multiple `update` and `delete` operations. | +| `context` | The `context` of the originating GraphQL operation. | When resolving `StaticAccess`: @@ -281,6 +282,7 @@ interface AccessInput { gqlName?: string; itemId?: string; itemIds?: [string]; + context?: {}; } type StaticAccess = boolean; @@ -314,6 +316,7 @@ type FieldConfig = { | `gqlName` | The name of the query or mutation which triggered the access check. | | `itemId` | The `id` of the item being updated/deleted in singular `update` and `delete` operations. | | `itemIds` | The `ids` of the items being updated/deleted in multiple `update` and `delete` operations. | +| `context` | The `context` of the originating GraphQL operation. | When defining `StaticAccess`: diff --git a/packages/access-control/lib/access-control.js b/packages/access-control/lib/access-control.js index 8100a6f12f4..13632752a85 100644 --- a/packages/access-control/lib/access-control.js +++ b/packages/access-control/lib/access-control.js @@ -193,6 +193,7 @@ module.exports = { gqlName, itemId, itemIds, + context, }) { // Either a boolean or an object describing a where clause let result; @@ -207,6 +208,7 @@ module.exports = { gqlName, itemId, itemIds, + context, }); } @@ -239,6 +241,7 @@ module.exports = { gqlName, itemId, itemIds, + context, }) { let result; if (typeof access[operation] !== 'function') { @@ -254,6 +257,7 @@ module.exports = { gqlName, itemId, itemIds, + context, }); } @@ -268,7 +272,7 @@ module.exports = { return result; }, - async validateAuthAccessControl({ access, listKey, authentication = {}, gqlName }) { + async validateAuthAccessControl({ access, listKey, authentication = {}, gqlName, context }) { const operation = 'auth'; // Either a boolean or an object describing a where clause let result; @@ -280,6 +284,7 @@ module.exports = { listKey, operation, gqlName, + context, }); } diff --git a/packages/keystone/lib/Keystone/index.js b/packages/keystone/lib/Keystone/index.js index 98cca9f378f..eaef4d20efe 100644 --- a/packages/keystone/lib/Keystone/index.js +++ b/packages/keystone/lib/Keystone/index.js @@ -165,7 +165,7 @@ module.exports = class Keystone { ); const getListAccessControlForUser = memoize( - async (listKey, originalInput, operation, { gqlName, itemId, itemIds } = {}) => { + async (listKey, originalInput, operation, { gqlName, itemId, itemIds, context } = {}) => { return validateListAccessControl({ access: this.lists[listKey].access[schemaName], originalInput, @@ -175,6 +175,7 @@ module.exports = class Keystone { gqlName, itemId, itemIds, + context, }); }, { isPromise: true } @@ -187,7 +188,7 @@ module.exports = class Keystone { originalInput, existingItem, operation, - { gqlName, itemId, itemIds } = {} + { gqlName, itemId, itemIds, context } = {} ) => { return validateFieldAccessControl({ access: this.lists[listKey].fieldsByPath[fieldKey].access[schemaName], @@ -200,18 +201,20 @@ module.exports = class Keystone { gqlName, itemId, itemIds, + context, }); }, { isPromise: true } ); const getAuthAccessControlForUser = memoize( - async (listKey, { gqlName } = {}) => { + async (listKey, { gqlName, context } = {}) => { return validateAuthAccessControl({ access: this.lists[listKey].access[schemaName], authentication, listKey, gqlName, + context, }); }, { isPromise: true } diff --git a/packages/keystone/lib/ListTypes/list.js b/packages/keystone/lib/ListTypes/list.js index 16781ecf2c3..eaf1c42e187 100644 --- a/packages/keystone/lib/ListTypes/list.js +++ b/packages/keystone/lib/ListTypes/list.js @@ -404,7 +404,8 @@ module.exports = class List { field.path, undefined, item, - operation + operation, + { context } ); if (!access) { // If the client handles errors correctly, it should be able to @@ -437,7 +438,7 @@ module.exports = class List { data, existingItem, operation, - { gqlName, itemId: id, ...extraInternalData } + { gqlName, itemId: id, context, ...extraInternalData } ); if (!access) { restrictedFields.push(field.path); @@ -454,6 +455,7 @@ module.exports = class List { async checkListAccess(context, originalInput, operation, { gqlName, ...extraInternalData }) { const access = await context.getListAccessControlForUser(this.key, originalInput, operation, { gqlName, + context, ...extraInternalData, }); if (!access) { @@ -1483,11 +1485,15 @@ module.exports = class List { // NOTE: These could return a Boolean or a JSON object (if using the // declarative syntax) getAccess: () => ({ - getCreate: () => context.getListAccessControlForUser(this.key, undefined, 'create'), - getRead: () => context.getListAccessControlForUser(this.key, undefined, 'read'), - getUpdate: () => context.getListAccessControlForUser(this.key, undefined, 'update'), - getDelete: () => context.getListAccessControlForUser(this.key, undefined, 'delete'), - getAuth: () => context.getAuthAccessControlForUser(this.key), + getCreate: () => + context.getListAccessControlForUser(this.key, undefined, 'create', { context }), + getRead: () => + context.getListAccessControlForUser(this.key, undefined, 'read', { context }), + getUpdate: () => + context.getListAccessControlForUser(this.key, undefined, 'update', { context }), + getDelete: () => + context.getListAccessControlForUser(this.key, undefined, 'delete', { context }), + getAuth: () => context.getAuthAccessControlForUser(this.key, { context }), }), getSchema: () => { diff --git a/packages/keystone/lib/providers/listAuth.js b/packages/keystone/lib/providers/listAuth.js index 897858090e9..bfee9af35ff 100644 --- a/packages/keystone/lib/providers/listAuth.js +++ b/packages/keystone/lib/providers/listAuth.js @@ -138,7 +138,7 @@ class ListAuthProvider { async checkAccess(context, type, { gqlName }) { const operation = 'auth'; - const access = await context.getAuthAccessControlForUser(this.list.key, { gqlName }); + const access = await context.getAuthAccessControlForUser(this.list.key, { gqlName, context }); if (!access) { graphqlLogger.debug({ operation, access, gqlName }, 'Access statically or implicitly denied'); graphqlLogger.info({ operation, gqlName }, 'Access Denied');