- RLS: profiles 재귀 참조 → app_metadata 기반으로 교체 - auth callback: 쿠키를 response에 직접 설정 (세션 유지) - middleware: Supabase 세션 갱신 추가 - layout: requireAdmin() 사용으로 통합 (중복 제거) - admin-guard: app_metadata.role 기반 (JWT, DB 쿼리 불필요) - debug 라우트 제거 - config.toml: localhost:3000/3001 redirect URL 추가
39 lines
1 KiB
TypeScript
39 lines
1 KiB
TypeScript
// apps/admin/src/lib/admin-guard.ts
|
|
// RSC용 admin 가드 — app_metadata.role='admin' 체크
|
|
|
|
import { redirect } from 'next/navigation'
|
|
import { getSupabaseServerClient } from './supabase-server'
|
|
|
|
export interface AdminUser {
|
|
id: string
|
|
email: string | null
|
|
name: string | null
|
|
}
|
|
|
|
export async function requireAdmin(): Promise<AdminUser> {
|
|
const supabase = await getSupabaseServerClient()
|
|
const { data: { user } } = await supabase.auth.getUser()
|
|
|
|
if (!user) {
|
|
redirect('/login')
|
|
}
|
|
|
|
// app_metadata.role 체크 (JWT에 포함, RLS 재귀 없음)
|
|
const role = (user.app_metadata as Record<string, unknown>)?.role as string | undefined
|
|
if (role !== 'admin') {
|
|
redirect('/unauthorized')
|
|
}
|
|
|
|
// profile 이름 조회 (자기 자신은 기존 RLS로 접근 가능)
|
|
const { data: profile } = await supabase
|
|
.from('profiles')
|
|
.select('name')
|
|
.eq('id', user.id)
|
|
.maybeSingle()
|
|
|
|
return {
|
|
id: user.id,
|
|
email: user.email ?? null,
|
|
name: (profile as { name: string | null } | null)?.name ?? null,
|
|
}
|
|
}
|