Skip to content

Commit

Permalink
feat: add rbac for process, closes #20
Browse files Browse the repository at this point in the history
  • Loading branch information
Arcath committed Sep 30, 2024
1 parent 71cd90e commit 705a124
Show file tree
Hide file tree
Showing 11 changed files with 177 additions and 24 deletions.
95 changes: 95 additions & 0 deletions app/lib/rbac.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,101 @@ export const {can} = canCant<'guest' | 'reader' | 'writer' | 'admin'>({
false
)

return result
}
},
'process:list',
'process:add',
{
name: 'process:view',
when: async ({
user,
processId
}: {
user: SessionUser
processId: string
}) => {
const prisma = getPrisma()

const process = await prisma.process.findFirstOrThrow({
where: {id: processId},
include: {acl: {include: {entries: true}}}
})

const result = process.acl.entries.reduce(
(r, {target, type, read}) => {
if (r) return true

if (type === 'user' && target !== user.id) return false
if (type === 'role' && target !== user.role) return false

return read
},
false
)

return result
}
},
{
name: 'process:write',
when: async ({
user,
processId
}: {
user: SessionUser
processId: string
}) => {
const prisma = getPrisma()

const process = await prisma.process.findFirstOrThrow({
where: {id: processId},
include: {acl: {include: {entries: true}}}
})

const result = process.acl.entries.reduce(
(r, {target, type, write}) => {
if (r) return true

if (type === 'user' && target !== user.id) return false
if (type === 'role' && target !== user.role) return false

return write
},
false
)

return result
}
},
{
name: 'process:delete',
when: async ({
user,
processId
}: {
user: SessionUser
processId: string
}) => {
const prisma = getPrisma()

const process = await prisma.process.findFirstOrThrow({
where: {id: processId},
include: {acl: {include: {entries: true}}}
})

const result = process.acl.entries.reduce(
(r, {target, type, delete: del}) => {
if (r) return true

if (type === 'user' && target !== user.id) return false
if (type === 'role' && target !== user.role) return false

return del
},
false
)

return result
}
}
Expand Down
19 changes: 3 additions & 16 deletions app/routes/app.documents._index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {type LoaderFunctionArgs, type MetaFunction, json} from '@remix-run/node'
import {useLoaderData, Link} from '@remix-run/react'
import {getDocuments} from '@prisma/client/sql'

import {ensureUser} from '~/lib/utils/ensure-user'
import {getPrisma} from '~/lib/prisma.server'
Expand All @@ -11,23 +12,9 @@ export const loader = async ({request}: LoaderFunctionArgs) => {

const prisma = getPrisma()

//const documents = await prisma.document.findMany({orderBy: {title: 'asc'}})
const documents = await prisma.$queryRaw<
Array<{id: string; title: string; updatedAt: string}>
>`SELECT
Document.id, Document.title, Document.updatedAt
FROM
Document
WHERE
aclId IN (SELECT aclId FROM ACLEntry
WHERE read = true AND (
(type = "role" AND target = ${user.role})
OR
(type = "user" AND target = ${user.id})
)
const documents = await prisma.$queryRawTyped(
getDocuments(user.role, user.id)
)
ORDER BY
Document.title ASC`

return json({user, documents})
}
Expand Down
2 changes: 1 addition & 1 deletion app/routes/app.process.$process.complete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {ensureUser} from '~/lib/utils/ensure-user'
import {getPrisma} from '~/lib/prisma.server'

export const loader = async ({request, params}: LoaderFunctionArgs) => {
await ensureUser(request, 'process:view', {
await ensureUser(request, 'process:write', {
processId: params.process
})

Expand Down
4 changes: 2 additions & 2 deletions app/routes/app.process.$process.edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {Label, Input, HelperText, TextArea} from '~/lib/components/input'
import {pageTitle} from '~/lib/utils/page-title'

export const loader = async ({request, params}: LoaderFunctionArgs) => {
const user = await ensureUser(request, 'process:edit', {
const user = await ensureUser(request, 'process:write', {
processId: params.process
})

Expand All @@ -29,7 +29,7 @@ export const loader = async ({request, params}: LoaderFunctionArgs) => {
}

export const action = async ({request, params}: ActionFunctionArgs) => {
await ensureUser(request, 'process:edit', {
await ensureUser(request, 'process:write', {
processId: params.process
})

Expand Down
7 changes: 4 additions & 3 deletions app/routes/app.process._index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {type LoaderFunctionArgs, type MetaFunction, json} from '@remix-run/node'
import {useLoaderData, Link} from '@remix-run/react'
import {getProcesses} from '@prisma/client/sql'

import {ensureUser} from '~/lib/utils/ensure-user'
import {getPrisma} from '~/lib/prisma.server'
Expand All @@ -11,9 +12,9 @@ export const loader = async ({request}: LoaderFunctionArgs) => {

const prisma = getPrisma()

const processes = await prisma.process.findMany({
orderBy: [{complete: 'asc'}, {title: 'asc'}]
})
const processes = await prisma.$queryRawTyped(
getProcesses(user.role, user.id)
)

return json({user, processes})
}
Expand Down
2 changes: 1 addition & 1 deletion app/routes/app.process.add.$document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {ensureUser} from '~/lib/utils/ensure-user'
import {getPrisma} from '~/lib/prisma.server'

export const loader = async ({request, params}: LoaderFunctionArgs) => {
await ensureUser(request, 'process:create', {})
await ensureUser(request, 'process:add', {})

const prisma = getPrisma()

Expand Down
18 changes: 18 additions & 0 deletions prisma/migrations/20240930165111_add_acl_to_process/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Process" (
"id" TEXT NOT NULL PRIMARY KEY,
"body" TEXT NOT NULL,
"title" TEXT NOT NULL,
"complete" BOOLEAN NOT NULL DEFAULT false,
"aclId" TEXT NOT NULL DEFAULT '',
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Process_aclId_fkey" FOREIGN KEY ("aclId") REFERENCES "ACL" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Process" ("body", "complete", "createdAt", "id", "title", "updatedAt") SELECT "body", "complete", "createdAt", "id", "title", "updatedAt" FROM "Process";
DROP TABLE "Process";
ALTER TABLE "new_Process" RENAME TO "Process";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
6 changes: 5 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

generator client {
provider = "prisma-client-js"
provider = "prisma-client-js"
previewFeatures = ["typedSql"]
}

Expand Down Expand Up @@ -221,6 +221,9 @@ model Process {
title String
complete Boolean @default(false)
acl ACL @relation(fields: [aclId], references: [id])
aclId String @default("")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Expand All @@ -232,6 +235,7 @@ model ACL {
entries ACLEntry[]
passwords Password[]
documents Document[]
processes Process[]
assets Asset[]
assetEntries Entry[]
Expand Down
16 changes: 16 additions & 0 deletions prisma/seed.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,22 @@ const main = async () => {
data: {aclId: defaultAcl.id}
})
}

const processesWithNoACLCount = await prisma.process.count({
where: {aclId: ''}
})

if (processesWithNoACLCount !== 0) {
console.log('Processes need ACLs')
const defaultAcl = await prisma.aCL.findFirstOrThrow({
where: {name: 'Default'}
})

await prisma.process.updateMany({
where: {aclId: ''},
data: {aclId: defaultAcl.id}
})
}
}

main()
Expand Down
16 changes: 16 additions & 0 deletions prisma/sql/getDocuments.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- @param {String} $1:userRole The role of the current user
-- @param {String} $2:userId The ID of the current user
SELECT
Document.id, Document.title, Document.updatedAt
FROM
Document
WHERE
aclId IN (SELECT aclId FROM ACLEntry
WHERE read = true AND (
(type = "role" AND target = $1)
OR
(type = "user" AND target = $2)
)
)
ORDER BY
Document.title ASC
16 changes: 16 additions & 0 deletions prisma/sql/getProcesses.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- @param {String} $1:userRole The role of the current user
-- @param {String} $2:userId The ID of the current user
SELECT
Process.id, Process.title, Process.updatedAt, Process.complete
FROM
Process
WHERE
aclId IN (SELECT aclId FROM ACLEntry
WHERE read = true AND (
(type = "role" AND target = $1)
OR
(type = "user" AND target = $2)
)
)
ORDER BY
Process.title ASC

0 comments on commit 705a124

Please sign in to comment.