Skip to main content

Ability file

This file is the main entrypoint for your rules, it will instantiate Guard and assign the specified types.

See: app/guard/ability.ts

import db, { Prisma } from "db"
import { GuardBuilder } from "@blitz-guard/core"
type ExtendedResourceTypes = "comment" | "article" | Prisma.ModelName
type ExtendedAbilityTypes = "send email"
const Guard = GuardBuilder<ExtendedResourceTypes, ExtendedAbilityTypes>(
async (ctx, { can, cannot }) => {
cannot("manage", "all") // See "Best practices" section
can("read", "article")
can("read", "comment")
if (ctx.session.$isAuthorized()) {
can("create", "article")
can("create", "comment")
can("send email", "comment")
can("delete", "comment", async (_args) => {
return (await db.comment.count({ where: { userId: ctx.session.userId } })) === 1
})
}
},
)
export default Guard

Rules#

The ability file is read top to bottom, the bottom rules take more precedence that the ones at the top.

Take the following example:

...
const Guard = GuardBuilder(
cannot('manage', 'all')
can("create", "article")
cannot("create", "article")
)
Guard.can("create", "article",{},{}) // false

Best practices#

Remove all permissions, give them one by one.

Your logic will be more direct and easier to follow, this reduces confusion and potential bugs.

cannot("manage", "all") // This removes all abilities for all resources
can("create", "article")
if (some_condition()) {
can("delete", "article")
}
caution

Blitz Guard will allow everything unless you state otherwise. No rules means that everything is allowed.


if guards look similar, take some code out of them

Guard will execute the guard condition if the rule matches the ability and resource. This means that you should, whenever possible, take as much logic out of the rule's guard.

// Wrong โŒ
can("delete", "article", (ctx) => someHeavyMethod() === true)
can("update", "article", (ctx) => someHeavyMethod() === true)
// Right โœ…
if (someHeavyMethod() === true) {
can("delete", "article")
can("update", "article")
}

Can & Cannot#

These two methods will determine what a user can or cannot do in your application.

can(ability, resource, guard)
cannot(ability, resource, guard)
  • ability
    The action that the user can perform.
    Default: create, read, update, delete, manage
    More information

  • resource
    The subject of the action.
    Default: all
    More information

  • guard (optional):
    It's the condition for the rule to apply, args are passed down from a wrapped mutation or query or manually when calling Guard.can
    async (args) => boolean

Reasons#

With each rule, you can define a reason for it.

The text will be used in replacement of the AuthorizationError message for both the authorizePipe and authorize

While using Guard.can you will receive the result, true/false and the reason.

...
const Guard = GuardBuilder(
cannot('manage', 'all')
can("create", "article")
cannot("create", "article").reason("Because I say so")
)
const { can, reason } = Guard.can("create", "article",{},{})
console.log(can) // false
console.log(reason) // "Because I say so"