Skip to content

Commit 3ca5915

Browse files
MajorTalclaude
andcommitted
feat(auth): SSR auth guards on admin and profile pages
Adds `export const prerender = false` + `await auth.requireUser()` to admin.astro, admin-members.astro, admin-settings.astro, profile.astro. The platform turns AuthRequiredError into a 303 → /auth/sign-in?return_to=… automatically — no try/catch (catching the error is the silent-null bug the brief calls out). For admin pages, requireUser is followed by an adminDb() lookup against members.role. Using auth.requireRole('admin') wouldn't work — the Actor envelope carries platform identity only; Kychon's "admin" is a project- defined member role. Annotated the user_id filter with `run402-allow-user-filter:` since adminDb() bypasses RLS by design. Non-admin signed-in visitors now get a 403 from the page itself instead of a fully-rendered admin shell with a client-side redirect — the admin HTML and JS bundles never reach unauthorized browsers. /join stays prerendered because it IS the sign-in surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 348b987 commit 3ca5915

4 files changed

Lines changed: 40 additions & 0 deletions

File tree

src/pages/admin-members.astro

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
---
2+
export const prerender = false;
3+
import { auth, adminDb } from '@run402/functions';
24
import AdminMembersApp from '../components/kychon/AdminMembersApp';
35
import Portal from '../layouts/Portal.astro';
6+
7+
const user = await auth.requireUser();
8+
// run402-allow-user-filter: adminDb() bypasses RLS for the admin role lookup
9+
const rows = await adminDb().from('members').select('role,status').eq('user_id', user.id).limit(1);
10+
const member = rows?.[0];
11+
if (member?.role !== 'admin' || member?.status !== 'active') {
12+
return new Response('Admin access required', { status: 403 });
13+
}
414
---
515
<Portal title="Manage Members">
616
<AdminMembersApp client:load />

src/pages/admin-settings.astro

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
---
2+
export const prerender = false;
3+
import { auth, adminDb } from '@run402/functions';
24
import AdminSettingsApp from '../components/kychon/AdminSettingsApp';
35
import Portal from '../layouts/Portal.astro';
6+
7+
const user = await auth.requireUser();
8+
// run402-allow-user-filter: adminDb() bypasses RLS for the admin role lookup
9+
const rows = await adminDb().from('members').select('role,status').eq('user_id', user.id).limit(1);
10+
const member = rows?.[0];
11+
if (member?.role !== 'admin' || member?.status !== 'active') {
12+
return new Response('Admin access required', { status: 403 });
13+
}
414
---
515
<Portal title="Site Settings">
616
<AdminSettingsApp client:load />

src/pages/admin.astro

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
---
2+
// SSR-guarded: auth.requireUser() throws AuthRequiredError on anon (platform
3+
// 303-redirects to /auth/sign-in?return_to=/admin). For Kychon-admin we
4+
// manually consult members.role — the platform's actor envelope doesn't carry
5+
// project-defined roles, so requireRole('admin') wouldn't match.
6+
export const prerender = false;
7+
import { auth, adminDb } from '@run402/functions';
28
import AdminDashboardApp from '../components/kychon/AdminDashboardApp';
39
import Portal from '../layouts/Portal.astro';
10+
11+
const user = await auth.requireUser();
12+
// run402-allow-user-filter: adminDb() bypasses RLS for the admin role lookup
13+
const rows = await adminDb().from('members').select('role,status').eq('user_id', user.id).limit(1);
14+
const member = rows?.[0];
15+
if (member?.role !== 'admin' || member?.status !== 'active') {
16+
return new Response('Admin access required', { status: 403 });
17+
}
418
---
519
<Portal title="Admin Dashboard">
620
<AdminDashboardApp client:load />

src/pages/profile.astro

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
---
2+
// SSR-guarded: anonymous visitors get a 303 to sign-in via auth.requireUser().
3+
// Any signed-in member can view their own profile — no role check needed.
4+
export const prerender = false;
5+
import { auth } from '@run402/functions';
26
import ProfilePageApp from '../components/kychon/ProfilePageApp';
37
import Portal from '../layouts/Portal.astro';
8+
9+
await auth.requireUser();
410
---
511
<Portal title="Profile">
612
<div class="mx-auto w-full max-w-xl px-4 py-8 sm:px-6 lg:px-8">

0 commit comments

Comments
 (0)