Enforce roleId checks on all admin page actions
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { prisma } from '$lib/db';
|
||||
import { isAdmin, isStaff } from '$lib/server/roles';
|
||||
import { superValidate } from 'sveltekit-superforms/server';
|
||||
import { zod4 } from 'sveltekit-superforms/adapters';
|
||||
import { z } from 'zod';
|
||||
@@ -45,7 +46,11 @@ export const load: PageServerLoad = async () => {
|
||||
};
|
||||
|
||||
export const actions = {
|
||||
upsert: async ({ request }) => {
|
||||
upsert: async ({ request, locals }) => {
|
||||
if (!isStaff(locals)) {
|
||||
return { success: false, action: 'upsert', error: 'Not authorized' };
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
const form = await superValidate(formData, zod4(ProfileSchema));
|
||||
if (!form.valid) {
|
||||
@@ -112,14 +117,22 @@ export const actions = {
|
||||
}
|
||||
return { success: true, action: 'upsert' };
|
||||
},
|
||||
delete: async ({ request }) => {
|
||||
delete: async ({ request, locals }) => {
|
||||
if (!isAdmin(locals)) {
|
||||
return { success: false, action: 'delete', error: 'Not authorized' };
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
const rawId = formData.get('id')?.toString();
|
||||
if (!rawId) return { success: false, error: 'Missing id' };
|
||||
await prisma.profile.delete({ where: { id: rawId } });
|
||||
return { success: true, action: 'delete' };
|
||||
},
|
||||
upsertAddress: async ({ request }) => {
|
||||
upsertAddress: async ({ request, locals }) => {
|
||||
if (!isStaff(locals)) {
|
||||
return { success: false, action: 'upsertAddress', addressError: 'Not authorized' };
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
const form = await superValidate(formData, zod4(AddressSchema));
|
||||
if (!form.valid) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { createLocals, createFormRequest } from '$lib/test-utils';
|
||||
import { createLocals, createFormRequest, mockUser } from '$lib/test-utils';
|
||||
|
||||
vi.mock('$lib/db', () => ({
|
||||
prisma: {
|
||||
@@ -41,6 +41,9 @@ function makeProfileRequest(overrides: Record<string, string> = {}) {
|
||||
return createFormRequest({ ...validFields, ...overrides });
|
||||
}
|
||||
|
||||
const staff = () => createLocals(mockUser({ roleId: 3 }) as any);
|
||||
const admin = () => createLocals(mockUser({ roleId: 2 }) as any);
|
||||
|
||||
describe('profiles admin page server', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -73,7 +76,10 @@ describe('profiles admin page server', () => {
|
||||
mockUserFindUnique.mockResolvedValue({ id: 'user-1', email: validFields.email });
|
||||
mockProfileCreate.mockResolvedValue({ id: 'p-new' });
|
||||
|
||||
const result = await actions.upsert({ request: makeProfileRequest() } as any);
|
||||
const result = await actions.upsert({
|
||||
request: makeProfileRequest(),
|
||||
locals: staff()
|
||||
} as any);
|
||||
|
||||
expect(result).toEqual({ success: true, action: 'upsert' });
|
||||
expect(mockUserFindUnique).toHaveBeenCalledWith({ where: { email: validFields.email } });
|
||||
@@ -86,7 +92,10 @@ describe('profiles admin page server', () => {
|
||||
mockUserFindUnique.mockResolvedValue(null);
|
||||
mockProfileCreate.mockResolvedValue({ id: 'p-new' });
|
||||
|
||||
const result = await actions.upsert({ request: makeProfileRequest() } as any);
|
||||
const result = await actions.upsert({
|
||||
request: makeProfileRequest(),
|
||||
locals: staff()
|
||||
} as any);
|
||||
|
||||
expect(result).toEqual({ success: true, action: 'upsert' });
|
||||
expect(mockProfileCreate).toHaveBeenCalledWith({ data: validFields });
|
||||
@@ -96,7 +105,10 @@ describe('profiles admin page server', () => {
|
||||
mockUserFindUnique.mockResolvedValue(null);
|
||||
mockProfileCreate.mockRejectedValue({ code: 'P2002' });
|
||||
|
||||
const result = await actions.upsert({ request: makeProfileRequest() } as any);
|
||||
const result = await actions.upsert({
|
||||
request: makeProfileRequest(),
|
||||
locals: staff()
|
||||
} as any);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
@@ -119,7 +131,7 @@ describe('profiles admin page server', () => {
|
||||
mockProfileUpdate.mockResolvedValue({});
|
||||
|
||||
const request = makeProfileRequest({ email: 'new@example.com', id: 'p1' });
|
||||
const result = await actions.upsert({ request } as any);
|
||||
const result = await actions.upsert({ request, locals: staff() } as any);
|
||||
|
||||
expect(result).toEqual({ success: true, action: 'upsert' });
|
||||
expect(mockUserUpdate).toHaveBeenCalledWith({
|
||||
@@ -142,7 +154,7 @@ describe('profiles admin page server', () => {
|
||||
mockProfileUpdate.mockResolvedValue({});
|
||||
|
||||
const request = makeProfileRequest({ id: 'p1' });
|
||||
const result = await actions.upsert({ request } as any);
|
||||
const result = await actions.upsert({ request, locals: staff() } as any);
|
||||
|
||||
expect(result).toEqual({ success: true, action: 'upsert' });
|
||||
expect(mockUserFindFirst).not.toHaveBeenCalled();
|
||||
@@ -159,7 +171,7 @@ describe('profiles admin page server', () => {
|
||||
mockUserFindFirst.mockResolvedValue({ id: 'user-2' });
|
||||
|
||||
const request = makeProfileRequest({ email: 'taken@example.com', id: 'p1' });
|
||||
const result = await actions.upsert({ request } as any);
|
||||
const result = await actions.upsert({ request, locals: staff() } as any);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
@@ -181,7 +193,7 @@ describe('profiles admin page server', () => {
|
||||
mockProfileUpdate.mockResolvedValue({});
|
||||
|
||||
const request = makeProfileRequest({ id: 'p1' });
|
||||
const result = await actions.upsert({ request } as any);
|
||||
const result = await actions.upsert({ request, locals: staff() } as any);
|
||||
|
||||
expect(result).toEqual({ success: true, action: 'upsert' });
|
||||
expect(mockProfileUpdate).toHaveBeenCalledWith({
|
||||
@@ -194,7 +206,7 @@ describe('profiles admin page server', () => {
|
||||
describe('upsert - validation', () => {
|
||||
it('returns field errors for invalid input', async () => {
|
||||
const request = makeProfileRequest({ email: 'not-an-email' });
|
||||
const result = await actions.upsert({ request } as any);
|
||||
const result = await actions.upsert({ request, locals: staff() } as any);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.action).toBe('upsert');
|
||||
@@ -205,7 +217,7 @@ describe('profiles admin page server', () => {
|
||||
mockProfileFindUnique.mockResolvedValue(null);
|
||||
|
||||
const request = makeProfileRequest({ id: 'missing' });
|
||||
const result = await actions.upsert({ request } as any);
|
||||
const result = await actions.upsert({ request, locals: staff() } as any);
|
||||
|
||||
expect(result).toEqual({ success: false, action: 'upsert', error: 'Record not found' });
|
||||
});
|
||||
@@ -215,7 +227,7 @@ describe('profiles admin page server', () => {
|
||||
it('deletes the profile by id', async () => {
|
||||
mockProfileDelete.mockResolvedValue({});
|
||||
|
||||
const locals = createLocals();
|
||||
const locals = admin();
|
||||
const req = createFormRequest({ id: 'p1' });
|
||||
const result = await actions.delete({ request: req, locals } as any);
|
||||
|
||||
@@ -224,7 +236,7 @@ describe('profiles admin page server', () => {
|
||||
});
|
||||
|
||||
it('returns an error when id is missing', async () => {
|
||||
const locals = createLocals();
|
||||
const locals = admin();
|
||||
const result = await actions.delete({ request: createFormRequest({}), locals } as any);
|
||||
|
||||
expect(result).toEqual({ success: false, error: 'Missing id' });
|
||||
|
||||
Reference in New Issue
Block a user