Skip to content

Commit

Permalink
feat: add rbac to documents, see #20
Browse files Browse the repository at this point in the history
  • Loading branch information
Arcath committed Aug 14, 2024
1 parent 7c3ab5c commit 6de5c28
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 6 deletions.
96 changes: 95 additions & 1 deletion app/lib/rbac.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,101 @@ export const {can} = canCant<'guest' | 'reader' | 'writer' | 'admin'>({
false
)

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

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

const result = document.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: 'document:write',
when: async ({
user,
documentId
}: {
user: SessionUser
documentId: string
}) => {
const prisma = getPrisma()

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

const result = document.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: 'document:delete',
when: async ({
user,
documentId
}: {
user: SessionUser
documentId: string
}) => {
const prisma = getPrisma()

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

const result = document.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 All @@ -276,7 +371,6 @@ export const {can} = canCant<'guest' | 'reader' | 'writer' | 'admin'>({
'dashboard',
'search',
'logout',
'document:*',
'user:*',
'dashboard:*',
'process:*',
Expand Down
8 changes: 5 additions & 3 deletions app/routes/app.documents.$document.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, 'document:edit', {
const user = await ensureUser(request, 'document:write', {
documentId: params.document
})

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

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

Expand All @@ -39,9 +39,11 @@ export const action = async ({request, params}: ActionFunctionArgs) => {

const title = formData.get('title') as string | undefined
const body = formData.get('body') as string | undefined
const acl = formData.get('acl') as string | undefined

invariant(title)
invariant(body)
invariant(acl)

const document = await prisma.document.findFirstOrThrow({
where: {id: params.document}
Expand All @@ -58,7 +60,7 @@ export const action = async ({request, params}: ActionFunctionArgs) => {

const updatedDocument = await prisma.document.update({
where: {id: params.document},
data: {title, body}
data: {title, body, aclId: acl}
})

return redirect(`/app/documents/${updatedDocument.id}`)
Expand Down
18 changes: 17 additions & 1 deletion app/routes/app.documents._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,23 @@ export const loader = async ({request}: LoaderFunctionArgs) => {

const prisma = getPrisma()

const documents = await prisma.document.findMany({orderBy: {title: 'asc'}})
//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})
)
)
ORDER BY
Document.title ASC`

return json({user, documents})
}
Expand Down
6 changes: 5 additions & 1 deletion app/routes/app.documents.add.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ export const action = async ({request}: ActionFunctionArgs) => {

const title = formData.get('title') as string | undefined
const body = formData.get('body') as string | undefined
const acl = formData.get('acl') as string | undefined

invariant(title)
invariant(body)
invariant(acl)

const document = await prisma.document.create({data: {title, body}})
const document = await prisma.document.create({
data: {title, body, aclId: acl}
})

return redirect(`/app/documents/${document.id}`)
}
Expand Down
17 changes: 17 additions & 0 deletions prisma/migrations/20240814125708_add_acl_to_document/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Document" (
"id" TEXT NOT NULL PRIMARY KEY,
"body" TEXT NOT NULL,
"title" TEXT NOT NULL,
"aclId" TEXT NOT NULL DEFAULT '',
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Document_aclId_fkey" FOREIGN KEY ("aclId") REFERENCES "ACL" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Document" ("body", "createdAt", "id", "title", "updatedAt") SELECT "body", "createdAt", "id", "title", "updatedAt" FROM "Document";
DROP TABLE "Document";
ALTER TABLE "new_Document" RENAME TO "Document";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
4 changes: 4 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ model Document {
history DocumentHistory[]
acl ACL @relation(fields: [aclId], references: [id])
aclId String @default("")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Expand Down Expand Up @@ -227,6 +230,7 @@ model ACL {
entries ACLEntry[]
passwords Password[]
documents Document[]
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 @@ -80,6 +80,22 @@ const main = async () => {
data: {aclId: defaultAcl.id}
})
}

const documentsWithNoACLCount = await prisma.document.count({
where: {aclId: ''}
})

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

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

main()
Expand Down
Loading

0 comments on commit 6de5c28

Please sign in to comment.