refactor: cleans up authentication duplication between page and api routes
This commit is contained in:
parent
384ca71f89
commit
5cc122bf39
5 changed files with 33 additions and 54 deletions
|
|
@ -1,5 +1,3 @@
|
||||||
import { isAdmin } from './auth';
|
|
||||||
|
|
||||||
export function jsonResponse(data: unknown, status = 200): Response {
|
export function jsonResponse(data: unknown, status = 200): Response {
|
||||||
return new Response(JSON.stringify(data), {
|
return new Response(JSON.stringify(data), {
|
||||||
status,
|
status,
|
||||||
|
|
@ -10,10 +8,3 @@ export function jsonResponse(data: unknown, status = 200): Response {
|
||||||
export function errorResponse(message: string, status: number): Response {
|
export function errorResponse(message: string, status: number): Response {
|
||||||
return jsonResponse({ error: message }, status);
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,26 @@
|
||||||
import { getSession } from 'auth-astro/server';
|
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 {
|
export type AuthResult =
|
||||||
return userId === import.meta.env.ADMIN_GITHUB_ID;
|
| { status: 'admin'; session: Session }
|
||||||
}
|
| { status: 'unauthenticated' }
|
||||||
|
| { status: 'forbidden' }
|
||||||
|
| { status: 'error' };
|
||||||
|
|
||||||
export async function requireAdminSession(request: Request): Promise<
|
export async function getAdminSession(request: Request): Promise<AuthResult> {
|
||||||
| { session: Session; error: null }
|
|
||||||
| { session: null; error: Response | null }
|
|
||||||
> {
|
|
||||||
let session: Session | null;
|
let session: Session | null;
|
||||||
try {
|
try {
|
||||||
session = await getSession(request);
|
session = await getSession(request);
|
||||||
} catch {
|
} catch {
|
||||||
return { session: null, error: new Response('Auth not configured', { status: 500 }) };
|
return { status: 'error' };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!session) {
|
if (!session) return { status: 'unauthenticated' };
|
||||||
return { session: null, error: null };
|
|
||||||
|
if (session.user?.id !== import.meta.env.ADMIN_GITHUB_ID) {
|
||||||
|
return { status: 'forbidden' };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isAdmin(session.user?.id)) {
|
return { status: 'admin', session };
|
||||||
return { session: null, error: new Response('Forbidden', { status: 403 }) };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { session, error: null };
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,15 @@
|
||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
|
|
||||||
import { getPendingEntries, type GuestbookEntry } from '../lib/db';
|
import { getPendingEntries, type GuestbookEntry } from '../lib/db';
|
||||||
import { requireAdminSession } from '../lib/auth';
|
import { getAdminSession } from '../lib/auth';
|
||||||
import Layout from '../layouts/Layout.astro';
|
import Layout from '../layouts/Layout.astro';
|
||||||
import { formatDate } from '../lib/format';
|
import { formatDate } from '../lib/format';
|
||||||
|
|
||||||
const { session, error } = await requireAdminSession(Astro.request);
|
const auth = await getAdminSession(Astro.request);
|
||||||
if (error) return error;
|
if (auth.status === 'error') return new Response('Auth not configured', { status: 500 });
|
||||||
if (!session) return Astro.redirect('/api/auth/signin');
|
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[] = [];
|
let entries: GuestbookEntry[] = [];
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,18 @@
|
||||||
import type { APIRoute } from 'astro';
|
import type { APIRoute } from 'astro';
|
||||||
import { getSession } from 'auth-astro/server';
|
import { jsonResponse, errorResponse } from '../../lib/api';
|
||||||
import { jsonResponse, errorResponse, requireAdmin } from '../../lib/api';
|
import { getAdminSession } from '../../lib/auth';
|
||||||
|
|
||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
|
|
||||||
export const POST: APIRoute = async ({ request }) => {
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
const session = await getSession(request);
|
const auth = await getAdminSession(request);
|
||||||
const authError = requireAdmin(session);
|
if (auth.status !== 'admin') return errorResponse('Unauthorized', 403);
|
||||||
if (authError) return authError;
|
|
||||||
|
|
||||||
const hookUrl = import.meta.env.VERCEL_DEPLOY_HOOK;
|
const hookUrl = import.meta.env.VERCEL_DEPLOY_HOOK;
|
||||||
if (!hookUrl) {
|
if (!hookUrl) return errorResponse('Deploy hook not configured', 500);
|
||||||
return errorResponse('Deploy hook not configured', 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch(hookUrl, { method: 'POST' });
|
const res = await fetch(hookUrl, { method: 'POST' });
|
||||||
if (!res.ok) {
|
if (!res.ok) return errorResponse('Failed to trigger deploy', 502);
|
||||||
return errorResponse('Failed to trigger deploy', 502);
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonResponse({ success: true });
|
return jsonResponse({ success: true });
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,27 @@
|
||||||
import type { APIRoute } from 'astro';
|
import type { APIRoute } from 'astro';
|
||||||
import { getSession } from 'auth-astro/server';
|
|
||||||
import { approveEntry, deleteEntry } from '../../../lib/db';
|
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 prerender = false;
|
||||||
|
|
||||||
export const PATCH: APIRoute = async ({ params, request }) => {
|
export const PATCH: APIRoute = async ({ params, request }) => {
|
||||||
const session = await getSession(request);
|
const auth = await getAdminSession(request);
|
||||||
const authError = requireAdmin(session);
|
if (auth.status !== 'admin') return errorResponse('Unauthorized', 403);
|
||||||
if (authError) return authError;
|
|
||||||
|
|
||||||
const id = parseInt(params.id!, 10);
|
const id = parseInt(params.id!, 10);
|
||||||
if (isNaN(id)) {
|
if (isNaN(id)) return errorResponse('Invalid ID', 400);
|
||||||
return errorResponse('Invalid ID', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
await approveEntry(id);
|
await approveEntry(id);
|
||||||
return jsonResponse({ success: true });
|
return jsonResponse({ success: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DELETE: APIRoute = async ({ params, request }) => {
|
export const DELETE: APIRoute = async ({ params, request }) => {
|
||||||
const session = await getSession(request);
|
const auth = await getAdminSession(request);
|
||||||
const authError = requireAdmin(session);
|
if (auth.status !== 'admin') return errorResponse('Unauthorized', 403);
|
||||||
if (authError) return authError;
|
|
||||||
|
|
||||||
const id = parseInt(params.id!, 10);
|
const id = parseInt(params.id!, 10);
|
||||||
if (isNaN(id)) {
|
if (isNaN(id)) return errorResponse('Invalid ID', 400);
|
||||||
return errorResponse('Invalid ID', 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
await deleteEntry(id);
|
await deleteEntry(id);
|
||||||
return jsonResponse({ success: true });
|
return jsonResponse({ success: true });
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue