refactor(timeline): extract requireWriteAll route guard
Both curator-event loaders repeated the same null-user + hasWriteAll block. hasWriteAll already returns false for an anonymous user, so a single requireWriteAll(locals) helper covers both REQ-002 (null user → 403) and REQ-003 (no WRITE_ALL → 403) without the redundant pre-check. Addresses PR #832 review (#781). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import { error } from '@sveltejs/kit';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server-side permission predicates derived from the authenticated user in `locals`.
|
* Server-side permission predicates derived from the authenticated user in `locals`.
|
||||||
*
|
*
|
||||||
@@ -12,3 +14,13 @@ type PermissionLocals = {
|
|||||||
export function hasWriteAll(locals: PermissionLocals): boolean {
|
export function hasWriteAll(locals: PermissionLocals): boolean {
|
||||||
return locals.user?.groups?.some((group) => group.permissions.includes('WRITE_ALL')) ?? false;
|
return locals.user?.groups?.some((group) => group.permissions.includes('WRITE_ALL')) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws a 403 unless the user holds WRITE_ALL. Anonymous users are rejected too
|
||||||
|
* — `hasWriteAll` returns false for a null user, so a single check covers both
|
||||||
|
* the unauthenticated and the under-privileged case. Server-side gate; the
|
||||||
|
* frontend canWrite flag only hides entry-point buttons.
|
||||||
|
*/
|
||||||
|
export function requireWriteAll(locals: PermissionLocals): void {
|
||||||
|
if (!hasWriteAll(locals)) throw error(403, 'Forbidden');
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { error, fail, redirect } from '@sveltejs/kit';
|
import { error, fail, redirect } from '@sveltejs/kit';
|
||||||
import { createApiClient, extractErrorCode } from '$lib/shared/api.server';
|
import { createApiClient, extractErrorCode } from '$lib/shared/api.server';
|
||||||
import { getErrorMessage } from '$lib/shared/errors';
|
import { getErrorMessage } from '$lib/shared/errors';
|
||||||
import { hasWriteAll } from '$lib/shared/server/permissions';
|
import { requireWriteAll } from '$lib/shared/server/permissions';
|
||||||
import {
|
import {
|
||||||
parseEventForm,
|
parseEventForm,
|
||||||
validateEventForm,
|
validateEventForm,
|
||||||
@@ -20,12 +20,7 @@ export async function load({
|
|||||||
url: URL;
|
url: URL;
|
||||||
fetch: typeof globalThis.fetch;
|
fetch: typeof globalThis.fetch;
|
||||||
}) {
|
}) {
|
||||||
// Null-user guard first — avoids a TypeError on locals.user.groups for an
|
requireWriteAll(locals);
|
||||||
// unauthenticated request that reaches the route.
|
|
||||||
if (!locals.user) throw error(403, 'Forbidden');
|
|
||||||
// WRITE_ALL check mirrors Permission.WRITE_ALL — server-side gate; frontend
|
|
||||||
// canWrite flag is for hiding entry-point buttons only.
|
|
||||||
if (!hasWriteAll(locals)) throw error(403, 'Forbidden');
|
|
||||||
|
|
||||||
const api = createApiClient(fetch);
|
const api = createApiClient(fetch);
|
||||||
const result = await api.GET('/api/timeline/events/{id}', {
|
const result = await api.GET('/api/timeline/events/{id}', {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { error, fail, redirect } from '@sveltejs/kit';
|
import { fail, redirect } from '@sveltejs/kit';
|
||||||
import { createApiClient, extractErrorCode } from '$lib/shared/api.server';
|
import { createApiClient, extractErrorCode } from '$lib/shared/api.server';
|
||||||
import { getErrorMessage } from '$lib/shared/errors';
|
import { getErrorMessage } from '$lib/shared/errors';
|
||||||
import { hasWriteAll } from '$lib/shared/server/permissions';
|
import { requireWriteAll } from '$lib/shared/server/permissions';
|
||||||
import {
|
import {
|
||||||
parseEventForm,
|
parseEventForm,
|
||||||
validateEventForm,
|
validateEventForm,
|
||||||
@@ -20,12 +20,7 @@ export async function load({
|
|||||||
url: URL;
|
url: URL;
|
||||||
fetch: typeof globalThis.fetch;
|
fetch: typeof globalThis.fetch;
|
||||||
}) {
|
}) {
|
||||||
// Null-user guard first — avoids a TypeError on locals.user.groups for an
|
requireWriteAll(locals);
|
||||||
// unauthenticated request that reaches the route.
|
|
||||||
if (!locals.user) throw error(403, 'Forbidden');
|
|
||||||
// WRITE_ALL check mirrors Permission.WRITE_ALL — server-side gate; frontend
|
|
||||||
// canWrite flag is for hiding entry-point buttons only.
|
|
||||||
if (!hasWriteAll(locals)) throw error(403, 'Forbidden');
|
|
||||||
|
|
||||||
const api = createApiClient(fetch);
|
const api = createApiClient(fetch);
|
||||||
const personId = url.searchParams.get('personId');
|
const personId = url.searchParams.get('personId');
|
||||||
|
|||||||
Reference in New Issue
Block a user