Перейти к основному содержимому

Once we know who you are, what may you do? Roles, guards, and the deliberate 404-not-403 pattern.

Role simulator

Sandbox
Simulated role

Client-only — your real session is untouched and this value never leaves the browser.

Access decision matrix

Sandbox
Route (placeholder) Requires As guest
/area/public Anyone 200 OK
/area/dashboard Signed-in 404 Not Found
/area/studio Blog-author grant 404 Not Found
/area/console Admin only 404 Not Found

Guard contract (recorded)

Recorded

The real guard contract, verbatim except the isAdmin body (elided so this public page never prints the admin-gate mechanism).

guards.ts (excerpt)
// src/lib/server/http/guards.ts (excerpt)

// Page guards THROW Kit's own error/redirect objects.
export function requireAuth(locals: App.Locals, returnTo?: string) {
  if (!locals.user || !locals.session) {
    const target = returnTo ? `/auth/login?returnTo=${encodeURIComponent(returnTo)}` : '/auth/login';
    redirect(303, localizeHref(target));
  }
  return { user: locals.user, session: locals.session };
}

export function requireAdmin(locals: App.Locals, returnTo?: string) {
  const { user, session } = requireAuth(locals, returnTo);
  // 404, NOT 403 — an admin surface must not confirm it exists.
  if (!isAdmin(user)) error(404, 'Not Found');
  return { user, session };
}

// API guards RETURN a Response instead. SvelteKit does not unwrap a thrown
// Response the way it unwraps HttpError/Redirect, so `throw apiError(...)`
// would answer 500 rather than 401/403 — invisibly, because a Response also
// has a .status. This split is enforced by guard-contract.gate.test.ts.
export function guardApiBlogAuthor(locals: App.Locals): ApiGuardResult {
  if (!locals.user || !locals.session) {
    return { error: apiError(401, 'unauthorized', 'Authentication required') };
  }
  // Grants are populated per-request by sessionPopulate from auth.grant
  // rows where revoked_at IS NULL.
  if (!isAdmin(locals.user) && !hasBlogAuthorGrant(locals)) {
    return { error: apiError(403, 'forbidden_no_grant', 'Insufficient permissions') };
  }
  return { user: locals.user, session: locals.session };
}

Думаете, этот паттерн можно сделать лучше? Расскажите как.

Оставить отзыв