From ff3e5b8dfb743a0c41785f7ce8edd196a7fcf44f Mon Sep 17 00:00:00 2001 From: Nicola Marcacci Rossi Date: Wed, 19 Jul 2023 11:40:54 +0200 Subject: [PATCH] break: Improve error logging and output --- src/errors.ts | 4 ++-- src/permissions/check.ts | 25 ++++++++++++++++++------- src/resolvers/resolver.ts | 7 ++++++- src/utils.ts | 1 + 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/errors.ts b/src/errors.ts index b4bdbbc..a44594d 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -26,7 +26,7 @@ export class UserInputError extends GraphQLError { } export class PermissionError extends ForbiddenError { - constructor(action: PermissionAction, what: string) { - super(`You do not have sufficient permissions to ${action.toLowerCase()} ${what}.`); + constructor(action: PermissionAction, what: string, why: string) { + super(`You do not have sufficient permissions to ${action.toLowerCase()} ${what} (${why}).`); } } diff --git a/src/permissions/check.ts b/src/permissions/check.ts index 37c7d6f..11aaae3 100644 --- a/src/permissions/check.ts +++ b/src/permissions/check.ts @@ -45,6 +45,7 @@ export const applyPermissions = ( } if (permissionStack === false) { + console.error(`No applicable permissions exist for ${ctx.user.role} ${type} ${action}.`); // eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here query.where(false); return permissionStack; @@ -93,13 +94,23 @@ export const getEntityToMutate = async ( let entity = await query.clone(); if (!entity) { - throw new NotFoundError('Entity to mutate'); + console.error( + `Not found: ${Object.entries(where) + .map(([key, value]) => `${key}: ${value}`) + .join(', ')}` + ); + throw new NotFoundError(`Entity to ${action.toLowerCase()}`); } applyPermissions(ctx, model.name, model.name, query, action); entity = await query; if (!entity) { - throw new PermissionError(action, `this ${model.name}`); + console.error( + `Permission error: ${Object.entries(where) + .map(([key, value]) => `${key}: ${value}`) + .join(', ')}` + ); + throw new PermissionError(action, `this ${model.name}`, 'no available permissions applied'); } return entity; @@ -120,7 +131,7 @@ export const checkCanWrite = async ( return; } if (permissionStack === false) { - throw new PermissionError(action, getModelPlural(model)); + throw new PermissionError(action, getModelPlural(model), 'no applicable permissions'); } // eslint-disable-next-line @typescript-eslint/no-explicit-any -- using `select(1 as any)` to instantiate an "empty" query builder @@ -138,7 +149,7 @@ export const checkCanWrite = async ( const fieldPermissions = field[action === 'CREATE' ? 'creatableBy' : 'updatableBy']; if (fieldPermissions && !fieldPermissions.includes(ctx.user.role)) { - throw new PermissionError(action, `this ${model.name}'s ${field.name}`); + throw new PermissionError(action, `this ${model.name}'s ${field.name}`, 'field permission not available'); } linked = true; @@ -153,7 +164,7 @@ export const checkCanWrite = async ( } if (fieldPermissionStack === false || !fieldPermissionStack.length) { - throw new PermissionError(action, `this ${model.name}'s ${field.name}`); + throw new PermissionError(action, `this ${model.name}'s ${field.name}`, 'no applicable permissions on data to link'); } // eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here @@ -168,10 +179,10 @@ export const checkCanWrite = async ( if (linked) { const canMutate = await query; if (!canMutate) { - throw new PermissionError(action, `this ${model.name} because there are no entities you can link it to`); + throw new PermissionError(action, `this ${model.name}`, 'no linkable entities'); } } else if (action === 'CREATE') { - throw new PermissionError(action, `this ${model.name} because there are no entity types you can link it to`); + throw new PermissionError(action, `this ${model.name}`, 'no linkable entities'); } }; diff --git a/src/resolvers/resolver.ts b/src/resolvers/resolver.ts index 8fd8f88..4d5b6b8 100644 --- a/src/resolvers/resolver.ts +++ b/src/resolvers/resolver.ts @@ -61,6 +61,7 @@ export const resolve = async (ctx: FullContext, id?: string) => { } if (!res[0]) { + console.error('Entity not found', query.toString()); throw new NotFoundError('Entity not found'); } @@ -119,7 +120,11 @@ const applySelects = (node: ResolverNode, query: Knex.QueryBuilder, joins: Joins } if (field.queriableBy && !field.queriableBy.includes(node.ctx.user.role)) { - throw new PermissionError('READ', `${node.model.name}'s field "${field.name}"`); + throw new PermissionError( + 'READ', + `${node.model.name}'s field "${field.name}"`, + 'field permission not available' + ); } return true; diff --git a/src/utils.ts b/src/utils.ts index f34944a..5cbe3f5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -138,6 +138,7 @@ export const summonByKey = (array: readonly T[] | undefined, key: string, val export const summon = (array: readonly T[] | undefined, cb: Parameters[1], errorMessage?: string) => { if (array === undefined) { + console.trace(); throw new Error('Base array is not defined.'); } const result = array.find(cb);