graphile / graphile/crystal

A solution to add authorization on root fields via the makeWrapPlansPlugin in V5

Open
#2,209 0 comments 1 reaction 0 assignees View on GitHub
📄 add-to-docs
Dominant language
TypeScript
Stars
12.9k
Forks
625
Avg merge
5h 23m
Merged PRs (30d)
24

Description

After considering multiple solutions (that were known to me) including a [schema-first approach with directives](https://blog.atulr.com/graphql-auth/)
I've started discussing with @benjie where he pointed me to some great alternatives for achieving authorization on root fields.

His proposal:
```ts
makeWrapPlansPlugin({
Mutation: {
updateCustomer(plan, _, { $customerId }) {
const $hasWriteAccess = currentUserHasWriteAccessToCustomerId($customerId);
sideEffect($hasWriteAccess, (hwa) => {
if (!hwa) throw new Error("No write access!")
});
return plan();
}
}
})
```
With `currentUserHasWriteAccessToCustomerId` being:
```ts
function currentUserHasWriteAccessToCustomerId($customerId) {
const $currentUserId = context().get('currentUserId');
const $access = customerPrivileges.find();
$access.where(sql`${$access}.user_id = ${$access.placeholder($currentUserId, TYPES.int)}`);
const $customerIds = applyTransforms(each($access, $row => $row.get('customer_id')));
return lambda([$customerIds, $customerId], ([customerIds, customerId]) => customerIds.includes(customerId));
```

I settled on an implementation that worked for my use case, which is as follows:

```ts
import { context, lambda, sideEffect } from "postgraphile/grafast";
import { makeWrapPlansPlugin } from "postgraphile/utils";
import { GraphQLError } from "postgraphile/graphql";

const USER_ROLE_ADMIN = "ADMIN";

const getUserRoleFromContext = () => {
return context().get("pgSettings").get("myapp.user.userRole");
};

const userHasAdminRole = (userRole: string) => {
if (userRole !== USER_ROLE_ADMIN) {
throw new GraphQLError(
"User does not have the correct role to access this resource.",
);
}
};

// I've added :any for the plans here, since the proper type: SmartFieldPlanResolver is not exported and TS doesn't infer it correctly.
export const AuthorizationPlugin = makeWrapPlansPlugin({
Mutation: {
createCustomer(plan: any) {
sideEffect(getUserRoleFromContext(), userHasAdminRole);
return plan();
},
editCustomerById(plan: any) {
sideEffect(getUserRoleFromContext(), userHasAdminRole);
return plan();
},
},
});
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.