feat: add basic api acl

pull/715/head
Travis Fischer 2025-04-28 07:20:47 +07:00
rodzic 30d909c6f1
commit d02e70ea74
3 zmienionych plików z 46 dodań i 3 usunięć

Wyświetl plik

@ -2,6 +2,7 @@ import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema, userIdSchema } from '@/db'
import { acl } from '@/lib/acl'
import { assert, parseZodSchema } from '@/lib/utils'
const ParamsSchema = z.object({
@ -43,7 +44,8 @@ export function registerV1UsersGetUser(app: OpenAPIHono<AuthenticatedEnv>) {
const user = await db.query.users.findFirst({
where: eq(schema.users.id, userId)
})
assert(user, 404, `User not found: ${userId}`)
assert(user, 404, `User not found "${userId}"`)
acl(c, user, { label: 'User', userField: 'id' })
return c.json(parseZodSchema(schema.userSelectSchema, user))
})

Wyświetl plik

@ -2,6 +2,7 @@ import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types'
import { db, eq, schema, userIdSchema } from '@/db'
import { acl } from '@/lib/acl'
import { assert, parseZodSchema } from '@/lib/utils'
const ParamsSchema = z.object({
@ -49,12 +50,13 @@ export function registerV1UsersUpdateUser(app: OpenAPIHono<AuthenticatedEnv>) {
const { userId } = c.req.valid('param')
const body = c.req.valid('json')
const user = await db
const [user] = await db
.update(schema.users)
.set(body)
.where(eq(schema.users.id, userId))
.returning()
assert(user, 404, `User not found: ${userId}`)
assert(user, 404, `User not found "${userId}"`)
acl(c, user, { label: 'User', userField: 'id' })
return c.json(parseZodSchema(schema.userSelectSchema, user))
})

Wyświetl plik

@ -0,0 +1,39 @@
import type { AuthenticatedContext } from './types'
import { assert } from './utils'
export function acl<
TModel extends Record<string, unknown> & { id: string },
TUserField extends keyof TModel = 'user',
TTeamField extends keyof TModel = 'team'
>(
ctx: AuthenticatedContext,
model: TModel,
{
label,
userField = 'user' as TUserField,
teamField = 'team' as TTeamField
}: {
label: string
userField?: TUserField
teamField?: TTeamField
}
) {
const user = ctx.get('user')
assert(user, 401, 'Authentication required')
const teamMember = ctx.get('teamMember')
const userFieldValue = model[userField]
const teamFieldValue = model[teamField]
const isAuthUserOwner = userFieldValue && userFieldValue === user.id
const isAuthUserAdmin = user.role === 'admin'
const hasTeamAccess =
teamMember && teamFieldValue && teamFieldValue === teamMember.teamId
assert(
isAuthUserOwner || isAuthUserAdmin || hasTeamAccess,
403,
`User does not have access to ${label} "${model.id}"`
)
}