diff --git a/www/src/lib/api.ts b/www/src/lib/api.ts index 1961a01..6e4622e 100644 --- a/www/src/lib/api.ts +++ b/www/src/lib/api.ts @@ -1,5 +1,3 @@ -import { isAdmin } from './auth'; - export function jsonResponse(data: unknown, status = 200): Response { return new Response(JSON.stringify(data), { status, @@ -10,10 +8,3 @@ export function jsonResponse(data: unknown, status = 200): Response { export function errorResponse(message: string, status: number): Response { return jsonResponse({ error: message }, status); } - -export function requireAdmin(session: { user?: { id?: string } } | null): Response | null { - if (!session?.user?.id || !isAdmin(session.user.id)) { - return errorResponse('Unauthorized', 403); - } - return null; -} diff --git a/www/src/lib/auth.ts b/www/src/lib/auth.ts index 28b0d8b..eaac841 100644 --- a/www/src/lib/auth.ts +++ b/www/src/lib/auth.ts @@ -1,29 +1,26 @@ import { getSession } from 'auth-astro/server'; -type Session = { user?: { id?: string; name?: string | null } }; +export type Session = { user?: { id?: string; name?: string | null } }; -export function isAdmin(userId: string | undefined): boolean { - return userId === import.meta.env.ADMIN_GITHUB_ID; -} +export type AuthResult = + | { status: 'admin'; session: Session } + | { status: 'unauthenticated' } + | { status: 'forbidden' } + | { status: 'error' }; -export async function requireAdminSession(request: Request): Promise< - | { session: Session; error: null } - | { session: null; error: Response | null } -> { +export async function getAdminSession(request: Request): Promise { let session: Session | null; try { session = await getSession(request); } catch { - return { session: null, error: new Response('Auth not configured', { status: 500 }) }; + return { status: 'error' }; } - if (!session) { - return { session: null, error: null }; + if (!session) return { status: 'unauthenticated' }; + + if (session.user?.id !== import.meta.env.ADMIN_GITHUB_ID) { + return { status: 'forbidden' }; } - if (!isAdmin(session.user?.id)) { - return { session: null, error: new Response('Forbidden', { status: 403 }) }; - } - - return { session, error: null }; + return { status: 'admin', session }; } diff --git a/www/src/pages/admin.astro b/www/src/pages/admin.astro index f540c6c..66ff49c 100644 --- a/www/src/pages/admin.astro +++ b/www/src/pages/admin.astro @@ -2,13 +2,15 @@ export const prerender = false; import { getPendingEntries, type GuestbookEntry } from '../lib/db'; -import { requireAdminSession } from '../lib/auth'; +import { getAdminSession } from '../lib/auth'; import Layout from '../layouts/Layout.astro'; import { formatDate } from '../lib/format'; -const { session, error } = await requireAdminSession(Astro.request); -if (error) return error; -if (!session) return Astro.redirect('/api/auth/signin'); +const auth = await getAdminSession(Astro.request); +if (auth.status === 'error') return new Response('Auth not configured', { status: 500 }); +if (auth.status === 'unauthenticated') return Astro.redirect('/api/auth/signin'); +if (auth.status !== 'admin') return new Response('Forbidden', { status: 403 }); +const { session } = auth; let entries: GuestbookEntry[] = []; try { diff --git a/www/src/pages/api/deploy.ts b/www/src/pages/api/deploy.ts index 57ad7ec..c6f7ab4 100644 --- a/www/src/pages/api/deploy.ts +++ b/www/src/pages/api/deploy.ts @@ -1,23 +1,18 @@ import type { APIRoute } from 'astro'; -import { getSession } from 'auth-astro/server'; -import { jsonResponse, errorResponse, requireAdmin } from '../../lib/api'; +import { jsonResponse, errorResponse } from '../../lib/api'; +import { getAdminSession } from '../../lib/auth'; export const prerender = false; export const POST: APIRoute = async ({ request }) => { - const session = await getSession(request); - const authError = requireAdmin(session); - if (authError) return authError; + const auth = await getAdminSession(request); + if (auth.status !== 'admin') return errorResponse('Unauthorized', 403); const hookUrl = import.meta.env.VERCEL_DEPLOY_HOOK; - if (!hookUrl) { - return errorResponse('Deploy hook not configured', 500); - } + if (!hookUrl) return errorResponse('Deploy hook not configured', 500); const res = await fetch(hookUrl, { method: 'POST' }); - if (!res.ok) { - return errorResponse('Failed to trigger deploy', 502); - } + if (!res.ok) return errorResponse('Failed to trigger deploy', 502); return jsonResponse({ success: true }); }; diff --git a/www/src/pages/api/guestbook/[id].ts b/www/src/pages/api/guestbook/[id].ts index a8a91a6..32d92d9 100644 --- a/www/src/pages/api/guestbook/[id].ts +++ b/www/src/pages/api/guestbook/[id].ts @@ -1,33 +1,27 @@ import type { APIRoute } from 'astro'; -import { getSession } from 'auth-astro/server'; import { approveEntry, deleteEntry } from '../../../lib/db'; -import { jsonResponse, errorResponse, requireAdmin } from '../../../lib/api'; +import { jsonResponse, errorResponse } from '../../../lib/api'; +import { getAdminSession } from '../../../lib/auth'; export const prerender = false; export const PATCH: APIRoute = async ({ params, request }) => { - const session = await getSession(request); - const authError = requireAdmin(session); - if (authError) return authError; + const auth = await getAdminSession(request); + if (auth.status !== 'admin') return errorResponse('Unauthorized', 403); const id = parseInt(params.id!, 10); - if (isNaN(id)) { - return errorResponse('Invalid ID', 400); - } + if (isNaN(id)) return errorResponse('Invalid ID', 400); await approveEntry(id); return jsonResponse({ success: true }); }; export const DELETE: APIRoute = async ({ params, request }) => { - const session = await getSession(request); - const authError = requireAdmin(session); - if (authError) return authError; + const auth = await getAdminSession(request); + if (auth.status !== 'admin') return errorResponse('Unauthorized', 403); const id = parseInt(params.id!, 10); - if (isNaN(id)) { - return errorResponse('Invalid ID', 400); - } + if (isNaN(id)) return errorResponse('Invalid ID', 400); await deleteEntry(id); return jsonResponse({ success: true });