Blitz: Add advanced RBAC roles + permissions based authorization option

Created on 16 Jul 2020  路  4Comments  路  Source: blitz-js/blitz

What do you want and why?

Not sure on exactly what is best, but here's one idea.

The idea is that roles are groups of permissions. And you enforce permissions, not roles.

So you centralize the definition of roles with something like this:

const roles = {
  admin: ['product:read', 'product:create', 'product:update', 'product:delete'],
  manager: ['product:update'],
}

And then in your code you enforce a permission.

export default async function updateProduct(input, ctx) {
  ctx.session.authorize('product:update')
}

This allows you to add/remove/refactor your roles without also having to change a ton of other files.

This will be added as an experimental authorization implementation.


Another idea is use something like https://github.com/onury/accesscontrol

kinfeature-change statuassigned

Most helpful comment

@flybayer Really love the idea but have a different API idea, something closer to prisma itself. Also I think we should see if Prisma Team has any ideas about this @janpio @nikolasburk

// anonymous users can only select certain columns
const anonymousGrant = new Grant({
  roles: null,
  project: {
    read: {
      published: { eq: true },
      select: {
        id: true,
        title: true,
        createdAt: true,
      },
    },
  },
})

const userGrant = new Grant({
  roles: ['user'],
  project: {
    manage: (ctx) => ({ owner: { id: ctx.userId } }),
    read: (ctx) => ({ creator: { id: ctx.userId } }),
  },
  task: {
    create: (ctx) => ({ project: { connect: { organization: { id: ctx.orgId } } } }),
  },
})

// Merge admin and superadmin
const superAdminGrant = new Grant({
  roles: ['admin', 'superadmin'],
  project: { manage: true },
  task: { manage: true },
  organization: { manage: (ctx) => ctx.role === 'superadmin' }
})

const prismaClient = new PrismaClient()
const secureDB = new SecureDB(prismaClient, [anonymousGrant, userGrant, superAdminGrant])

export { secureDB, prismaClient as db }

All 4 comments

Your statement about roles being permission groups is spot on. Similarly, permissions for an entity are just a group of permissions that you need per property.

Field or Column level permissions are often needed, in any application. Hasura does it well, where they track that information per field in its metadata tables.

Here's some noodling from today on ways to integrate authorization at the prisma level.

Have some builder pattern to build up authn rules. The second argument is a prisma Where statement or a function that takes context object and returns a Where statement. The given Where statement will be automatically added to the query at runtime.

const secureDb = new SecuredPrisma(prismaClient)

secureDb.grant('anonymous')
    .read('post', {published: {equals: true}})
  .grant('user')
    .manage('project', ctx => ({owner: {id: ctx.userId}}))
    .read('project', ctx => ({creator: {id: ctx.userId}}))
    .create('task', ctx => ({project: {connect: {organization: {id: ctx.orgId}}}}))
  .grant('superadmin')
    .manageAll('project')
    .manageAll('task')

export secureDb

And then use that like this. It will automatically throw AuthenticationError or AuthorizationError based on who the current user is and what they are trying to access. No need to manually call session.authorize()

import secureDb from "db";

export default async function getProject(args, { session }) {
  const project = await secureDb
    .ctx(session)
    .project.findMany({ where: { id: args.id } }).one()

  if (!project) throw new NotFoundError();

  return project;
}

The other way to do this is add authorization to Prisma Middleware instead of wrapping prisma client as shown here.

We also need a way to restrict which fields can be read and modified by different roles.

@flybayer Really love the idea but have a different API idea, something closer to prisma itself. Also I think we should see if Prisma Team has any ideas about this @janpio @nikolasburk

// anonymous users can only select certain columns
const anonymousGrant = new Grant({
  roles: null,
  project: {
    read: {
      published: { eq: true },
      select: {
        id: true,
        title: true,
        createdAt: true,
      },
    },
  },
})

const userGrant = new Grant({
  roles: ['user'],
  project: {
    manage: (ctx) => ({ owner: { id: ctx.userId } }),
    read: (ctx) => ({ creator: { id: ctx.userId } }),
  },
  task: {
    create: (ctx) => ({ project: { connect: { organization: { id: ctx.orgId } } } }),
  },
})

// Merge admin and superadmin
const superAdminGrant = new Grant({
  roles: ['admin', 'superadmin'],
  project: { manage: true },
  task: { manage: true },
  organization: { manage: (ctx) => ctx.role === 'superadmin' }
})

const prismaClient = new PrismaClient()
const secureDB = new SecureDB(prismaClient, [anonymousGrant, userGrant, superAdminGrant])

export { secureDB, prismaClient as db }

What's the status on this?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

simonedelmann picture simonedelmann  路  3Comments

ganeshmani picture ganeshmani  路  4Comments

markhaehnel picture markhaehnel  路  3Comments

timsuchanek picture timsuchanek  路  5Comments

LoriKarikari picture LoriKarikari  路  4Comments