Role simulator
SandboxSimulated role
Client-only — your real session is untouched and this value never leaves the browser.
Аутентификация, авторизация и управление пользователями — в песочнице, на фикстурах, без реальных учётных данных.
Once we know who you are, what may you do? Roles, guards, and the deliberate 404-not-403 pattern.
Client-only — your real session is untouched and this value never leaves the browser.
| 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 |
Notice there is no 403 column — an unauthorized request to an admin surface
returns the same 404 as a route that doesn't exist.
The real guard contract, verbatim except the isAdmin body (elided so this public
page never prints the admin-gate mechanism).
// 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 };
}Думаете, этот паттерн можно сделать лучше? Расскажите как.
Оставить отзыв