feat: setup and roles

This commit is contained in:
Zoe
2026-10-08 13:08:20 -05:00
parent 4bba529ba8
commit 7b0800882a
13 changed files with 3502 additions and 2 deletions
+47
View File
@@ -1,9 +1,29 @@
import { drizzleAdapter } from "@better-auth/drizzle-adapter/relations-v2";
import { db } from "../server/lib/db";
import { APIError, createAuthMiddleware, getSessionFromCtx } from 'better-auth/api';
import { betterAuth } from 'better-auth/minimal';
import { createAccessControl } from 'better-auth/plugins/access';
import { admin } from 'better-auth/plugins/admin';
import { defaultStatements } from 'better-auth/plugins/admin/access';
import { twoFactor } from 'better-auth/plugins/two-factor';
import * as schema from '../drizzle/schema';
const accessControl = createAccessControl(defaultStatements);
// Admins manage accounts, not content. Signing in as someone else and setting
// another user's password or email are left out on purpose: the first would
// show their chats, the others would lock them out of their encrypted API keys.
const roles = {
admin: accessControl.newRole({
user: ['create', 'list', 'set-role', 'ban', 'delete', 'get', 'update'],
session: ['list', 'revoke', 'delete'],
}),
user: accessControl.newRole({
user: [],
session: [],
}),
};
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
@@ -23,10 +43,37 @@ export const auth = betterAuth({
baseURL: process.env.NUXT_PUBLIC_URL ?? 'http://localhost:3000',
trustedOrigins: [process.env.NUXT_PUBLIC_URL ?? 'http://localhost:3000'],
hooks: {
// The admin endpoints already stop admins from removing or disabling
// themselves. Giving up their own admin role is the one way left to
// end up with an instance nobody can manage, so that is refused too.
before: createAuthMiddleware(async (ctx) => {
if (ctx.path !== '/admin/set-role' && ctx.path !== '/admin/update-user') return;
const role = ctx.path === '/admin/set-role' ? ctx.body?.role : ctx.body?.data?.role;
if (role === undefined) return;
const roleList = (Array.isArray(role) ? role : String(role).split(',')).map((r) => String(r).trim());
if (roleList.includes('admin')) return;
const session = await getSessionFromCtx(ctx);
if (session && String(ctx.body?.userId) === session.user.id) {
throw new APIError('BAD_REQUEST', {
message: 'You cannot remove your own admin role',
code: 'YOU_CANNOT_DEMOTE_YOURSELF',
});
}
}),
},
plugins: [
twoFactor({
issuer: 'Veridian',
}),
admin({
ac: accessControl,
roles,
}),
],
emailAndPassword: {