WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(appview): update checkPermission and getUserRole to use role_permissions table

Malpercio 729390d0 8eab406e

+31 -25
+13 -15
apps/appview/src/middleware/__tests__/permissions.test.ts
··· 1 1 import { describe, it, expect, beforeEach, afterEach } from "vitest"; 2 2 import { createTestContext, type TestContext } from "../../lib/__tests__/test-context.js"; 3 - import { roles, memberships, users } from "@atbb/db"; 3 + import { roles, rolePermissions, memberships, users } from "@atbb/db"; 4 4 import { 5 5 checkPermission, 6 6 checkMinRole, ··· 21 21 describe("checkPermission", () => { 22 22 it("returns true when user has required permission", async () => { 23 23 // Create a test role with createTopics permission 24 - await ctx.db.insert(roles).values({ 24 + const [memberRole] = await ctx.db.insert(roles).values({ 25 25 did: ctx.config.forumDid, 26 26 rkey: "test-role-123", 27 27 cid: "test-cid", 28 28 name: "Member", 29 29 description: "Test member role", 30 - permissions: ["space.atbb.permission.createTopics"], 31 30 priority: 30, 32 31 createdAt: new Date(), 33 32 indexedAt: new Date(), 34 - }); 33 + }).returning({ id: roles.id }); 34 + 35 + await ctx.db.insert(rolePermissions).values([ 36 + { roleId: memberRole.id, permission: "space.atbb.permission.createTopics" }, 37 + ]); 35 38 36 39 // Create a test user 37 40 await ctx.db.insert(users).values({ ··· 62 65 63 66 it("returns true for Owner role with wildcard permission", async () => { 64 67 // Create Owner role with wildcard 65 - await ctx.db.insert(roles).values({ 68 + const [ownerRole] = await ctx.db.insert(roles).values({ 66 69 did: ctx.config.forumDid, 67 70 rkey: "owner-role", 68 71 cid: "test-cid", 69 72 name: "Owner", 70 73 description: "Forum owner", 71 - permissions: ["*"], // Wildcard 72 74 priority: 0, 73 75 createdAt: new Date(), 74 76 indexedAt: new Date(), 75 - }); 77 + }).returning({ id: roles.id }); 78 + 79 + await ctx.db.insert(rolePermissions).values([ 80 + { roleId: ownerRole.id, permission: "*" }, 81 + ]); 76 82 77 83 await ctx.db.insert(users).values({ 78 84 did: "did:plc:test-owner", ··· 180 186 rkey: "admin-role", 181 187 cid: "test-cid", 182 188 name: "Admin", 183 - permissions: [], 184 189 priority: 10, 185 190 createdAt: new Date(), 186 191 indexedAt: new Date(), ··· 214 219 rkey: "owner-role-2", 215 220 cid: "test-cid", 216 221 name: "Owner", 217 - permissions: ["*"], 218 222 priority: 0, 219 223 createdAt: new Date(), 220 224 indexedAt: new Date(), ··· 248 252 rkey: "mod-role", 249 253 cid: "test-cid", 250 254 name: "Moderator", 251 - permissions: [], 252 255 priority: 20, 253 256 createdAt: new Date(), 254 257 indexedAt: new Date(), ··· 294 297 rkey: "admin-role-2", 295 298 cid: "test-cid", 296 299 name: "Admin", 297 - permissions: [], 298 300 priority: 10, 299 301 createdAt: new Date(), 300 302 indexedAt: new Date(), ··· 306 308 rkey: "mod-role-2", 307 309 cid: "test-cid", 308 310 name: "Moderator", 309 - permissions: [], 310 311 priority: 20, 311 312 createdAt: new Date(), 312 313 indexedAt: new Date(), ··· 358 359 rkey: "admin-role-3", 359 360 cid: "test-cid", 360 361 name: "Admin", 361 - permissions: [], 362 362 priority: 10, 363 363 createdAt: new Date(), 364 364 indexedAt: new Date(), ··· 410 410 rkey: "admin-role-4", 411 411 cid: "test-cid", 412 412 name: "Admin", 413 - permissions: [], 414 413 priority: 10, 415 414 createdAt: new Date(), 416 415 indexedAt: new Date(), ··· 422 421 rkey: "mod-role-4", 423 422 cid: "test-cid", 424 423 name: "Moderator", 425 - permissions: [], 426 424 priority: 20, 427 425 createdAt: new Date(), 428 426 indexedAt: new Date(),
+18 -10
apps/appview/src/middleware/permissions.ts
··· 1 1 import type { AppContext } from "../lib/app-context.js"; 2 2 import type { Context, Next } from "hono"; 3 3 import type { Variables } from "../types.js"; 4 - import { memberships, roles } from "@atbb/db"; 5 - import { eq, and } from "drizzle-orm"; 4 + import { memberships, roles, rolePermissions } from "@atbb/db"; 5 + import { eq, and, or } from "drizzle-orm"; 6 6 7 7 /** 8 8 * Check if a user has a specific permission. ··· 53 53 return false; // Role not found = treat as Guest (fail closed) 54 54 } 55 55 56 - // 4. Check for wildcard (Owner role) 57 - if (role.permissions.includes("*")) { 58 - return true; 59 - } 56 + // 4. Check if user has the permission (wildcard or specific) 57 + const [match] = await ctx.db 58 + .select() 59 + .from(rolePermissions) 60 + .where( 61 + and( 62 + eq(rolePermissions.roleId, role.id), 63 + or( 64 + eq(rolePermissions.permission, permission), 65 + eq(rolePermissions.permission, "*") 66 + ) 67 + ) 68 + ) 69 + .limit(1); 60 70 61 - // 5. Check if specific permission is in role's permissions array 62 - return role.permissions.includes(permission); 71 + return !!match; 63 72 } catch (error) { 64 73 // Re-throw programming errors (typos, undefined variables, etc.) 65 74 // These should crash during development, not silently deny access ··· 88 97 async function getUserRole( 89 98 ctx: AppContext, 90 99 did: string 91 - ): Promise<{ id: bigint; name: string; priority: number; permissions: string[] } | null> { 100 + ): Promise<{ id: bigint; name: string; priority: number } | null> { 92 101 try { 93 102 const [membership] = await ctx.db 94 103 .select() ··· 110 119 id: roles.id, 111 120 name: roles.name, 112 121 priority: roles.priority, 113 - permissions: roles.permissions, 114 122 }) 115 123 .from(roles) 116 124 .where(