Fix Super admins being blocked from editing proclamations

This commit is contained in:
NeilClack
2026-09-10 14:50:08 -05:00
parent 9cd5397395
commit 9de0e2e25a
2 changed files with 37 additions and 5 deletions
@@ -13,8 +13,16 @@ const ProclamationSchema = z.object({
content: z.string().optional().default('') content: z.string().optional().default('')
}); });
function isSuper(locals: App.Locals): boolean { async function isSuper(locals: App.Locals): Promise<boolean> {
return ((locals.user as { roleId?: number } | null)?.roleId ?? 4) <= 1; if (!locals.user?.id) return false;
// The Better Auth session user does not include `roleId`, so read the
// current role from the database.
const user = await prisma.user.findUnique({
where: { id: locals.user.id },
select: { roleId: true }
});
return (user?.roleId ?? 4) <= 1;
} }
export const load: PageServerLoad = async ({ locals }) => { export const load: PageServerLoad = async ({ locals }) => {
@@ -40,7 +48,7 @@ export const load: PageServerLoad = async ({ locals }) => {
const records = proclamations as unknown as Record<string, unknown>[]; const records = proclamations as unknown as Record<string, unknown>[];
// Strip content from non-Super users // Strip content from non-Super users
if (!isSuper(locals)) { if (!(await isSuper(locals))) {
for (const record of records) { for (const record of records) {
delete record.content; delete record.content;
} }
@@ -55,7 +63,7 @@ export const actions = {
return { success: false, action: 'upsert', error: 'Unauthorized' }; return { success: false, action: 'upsert', error: 'Unauthorized' };
} }
if (!isSuper(locals)) { if (!(await isSuper(locals))) {
return { return {
success: false, success: false,
action: 'upsert', action: 'upsert',
@@ -98,7 +106,7 @@ export const actions = {
return { success: true, action: 'upsert' }; return { success: true, action: 'upsert' };
}, },
delete: async ({ request, locals }) => { delete: async ({ request, locals }) => {
if (!isSuper(locals)) { if (!(await isSuper(locals))) {
return { return {
success: false, success: false,
action: 'delete', action: 'delete',
@@ -18,6 +18,9 @@ vi.mock('$lib/db', () => ({
}, },
proclamationGroup: { proclamationGroup: {
findMany: vi.fn() findMany: vi.fn()
},
user: {
findUnique: vi.fn()
} }
} }
})); }));
@@ -33,6 +36,7 @@ const mockProclamationDelete = prisma.proclamation.delete as ReturnType<typeof v
const mockFreqFindMany = prisma.solfeggioFrequency.findMany as ReturnType<typeof vi.fn>; const mockFreqFindMany = prisma.solfeggioFrequency.findMany as ReturnType<typeof vi.fn>;
const mockCatFindMany = prisma.proclamationCategory.findMany as ReturnType<typeof vi.fn>; const mockCatFindMany = prisma.proclamationCategory.findMany as ReturnType<typeof vi.fn>;
const mockGroupFindMany = prisma.proclamationGroup.findMany as ReturnType<typeof vi.fn>; const mockGroupFindMany = prisma.proclamationGroup.findMany as ReturnType<typeof vi.fn>;
const mockUserFindUnique = prisma.user.findUnique as ReturnType<typeof vi.fn>;
const frequencies = [{ id: 1, name: 'UT', value: 396 }]; const frequencies = [{ id: 1, name: 'UT', value: 396 }];
const categories = [{ id: 1, name: 'Healing' }]; const categories = [{ id: 1, name: 'Healing' }];
@@ -52,6 +56,8 @@ function validRequest(overrides: Record<string, string> = {}) {
describe('proclamations admin page server', () => { describe('proclamations admin page server', () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
// Default the caller to SUPER (1); override per test as needed.
mockUserFindUnique.mockResolvedValue({ roleId: 1 });
}); });
describe('load', () => { describe('load', () => {
@@ -68,6 +74,7 @@ describe('proclamations admin page server', () => {
}); });
it('strips content for non-Super users', async () => { it('strips content for non-Super users', async () => {
mockUserFindUnique.mockResolvedValue({ roleId: 4 });
const locals = createLocals(mockUser({ roleId: 4 }) as any); const locals = createLocals(mockUser({ roleId: 4 }) as any);
const result = await load({ locals } as any); const result = await load({ locals } as any);
@@ -109,6 +116,7 @@ describe('proclamations admin page server', () => {
}); });
it('rejects non-Super users', async () => { it('rejects non-Super users', async () => {
mockUserFindUnique.mockResolvedValue({ roleId: 4 });
const locals = createLocals(mockUser({ roleId: 4 }) as any); const locals = createLocals(mockUser({ roleId: 4 }) as any);
const req = createFormRequest({ name: 'test' }); const req = createFormRequest({ name: 'test' });
@@ -149,6 +157,21 @@ describe('proclamations admin page server', () => {
}); });
}); });
it('allows a Super user whose session user has no roleId', async () => {
mockProclamationCreate.mockResolvedValue({ id: 'p1' });
// The real Better Auth session user does not carry `roleId`; the
// action must look the role up in the database instead.
const locals = createLocals(mockUser({ roleId: null }) as any);
const result = await actions.upsert({ request: validRequest(), locals } as any);
expect(result).toEqual({ success: true, action: 'upsert' });
expect(mockUserFindUnique).toHaveBeenCalledWith({
where: { id: 'user-1' },
select: { roleId: true }
});
});
it('updates an existing proclamation for a Super user', async () => { it('updates an existing proclamation for a Super user', async () => {
mockProclamationFindUnique.mockResolvedValue(mockProclamation()); mockProclamationFindUnique.mockResolvedValue(mockProclamation());
mockProclamationUpdate.mockResolvedValue({}); mockProclamationUpdate.mockResolvedValue({});
@@ -200,6 +223,7 @@ describe('proclamations admin page server', () => {
describe('delete action', () => { describe('delete action', () => {
it('rejects non-Super users', async () => { it('rejects non-Super users', async () => {
mockUserFindUnique.mockResolvedValue({ roleId: 4 });
const locals = createLocals(mockUser({ roleId: 4 }) as any); const locals = createLocals(mockUser({ roleId: 4 }) as any);
const req = createFormRequest({ id: '1' }); const req = createFormRequest({ id: '1' });