Column Level Security can be accomplished via the entity hooks provided by Graphweaver. In this example we will implement column level security by preventing users with a specific role from viewing a specific column.
In the entity with the column we wish to secure, for each of the CRUD operations we wish to control, we must filter the params’s entities returned by the hook.
For this example, we will prevent users on the light side from seeing the priority
field on the Task
entity.
Prevent the server from providing the secure column
In the afterRead hook of the task entity, we change the data that’s provided by the hook and returned to the client.
@Hook(HookRegister.AFTER_READ)
async afterRead(params: ReadHook) {
// filter the returned entities's for Task.priority
const filteredEntities = preventLightSideAccess(params, params.fields['Task'], 'priority');
return { ...params, entities: filteredEntities };
}
The preventLightSideAccess
function accepts the params provided by hooks, the fields being requested by the client, and the column name that is being restricted. If the user has the role of Light Side, and the request includes the restricted column, then the entities are filtered to remove the priority column from the task entity.
const preventLightSideAccess = (
params: CreateOrUpdateHook | ReadHook,
requestedFields: ResolveTree | { [str: string]: ResolveTree },
preventedColumn: TaskField
) => {
if (
params.context.user?.roles.includes(Roles.LIGHT_SIDE) &&
Object.keys(requestedFields).includes(preventedColumn)
) {
// filter out the prevented column from the returned entities
const filteredEntities = params.entities?.map((entity) => {
const { [preventedColumn]: _, ...rest } = entity;
return rest;
});
return filteredEntities;
}
return params.entities;
};
This prevents the server from providing the restricted column’s data to the client. The user will not see the restricted data in the admin UI, but will still see the empty column.
Hiding the restricted column in the UI
To prevent the empty column from showing up in the Admin UI dashboard an additional step is required. In the Graphweaver instance, the adminMetadata can define beforeRead
and afterRead
hooks.
const graphweaver = new Graphweaver<AuthorizationContext>({
resolvers,
apolloServerOptions: {
plugins: [
localAuthApolloPlugin(addUserToContext),
],
},
adminMetadata: {
enabled: true,
hooks: {
beforeRead,
afterRead,
},
},
});
Our afterRead hook must similarly check if the user is on the light side and filter out the priority field on the Task entity. Note that the params here are of a different type than those in the backend schema for Task.
export const afterRead = async (params: MetadataHookParams<AuthorizationContext>) => {
// Ensure only logged in users can access the admin ui metadata
if (!params.context.token) throw new ForbiddenError('Forbidden');
// Filter out the priority column from the Task entity if the user is on the light side
const requestedFieldNames = params.metadata?.entities
.find((entity) => entity.name === 'Task')
?.fields.map((field) => field.name);
const entityName = 'Task';
const preventedColumn = 'priority';
if (
params.context.user?.roles.includes(Roles.LIGHT_SIDE) &&
requestedFieldNames.includes(preventedColumn)
) {
// Filter out the prevented column from within the specificed entity
const filteredEntities = params.metadata?.entities?.map((entity) => {
if (entity.name === entityName) {
const filteredFields = entity.fields.filter((field) => field.name !== preventedColumn);
return {
...entity,
fields: filteredFields,
};
}
return entity;
});
return {
...params,
metadata: {
...params.metadata,
entities: filteredEntities,
},
};
}
return params;
};
With this implemented, users on the light side will not see the empty priority column in the Admin UI dashboard and will not be served the priority data. This completes our implementation of column level security for the Task
entity.