- 분야: BE
- 기술 스택: TypeScript · Next.js API · vinext · Cloudflare Workers
- 관련 기술: 서버 페이지 세션 · forbidden() · SSR 상태 코드 · 계정 전환
문제와 원인
이전 관리자 세션 함수는 학습자에게 status: "forbidden" 객체를 반환했다. 페이지는 이 값을 받아 일반 HTML에 “403”과 접근 거부 문구를 출력해, 권한 거부를 서버 응답 상태로 전달하는 경계가 필요했다.
수정 전 코드
loadAdminPageSession()
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
if (!isAdminEmail(user.email)) {
return {
status: "forbidden",
signOutPath: applicationSignOutPath("/admin"),
};
}
AdminPage()
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
if (session.status === "forbidden") {
return (
<main className="admin-access-page">
<section className="admin-access-card">
<span>403</span>
<h1>관리자 권한이 없습니다.</h1>
{/* 안내와 계정 전환 링크 */}
</section>
</main>
);
}
수정 후 코드
학습자는 forbidden()으로 종료하고 반환 타입을 authorized 세션으로 좁혔다. 관리자 페이지의 거부 상태 렌더 분기와 SKCT 개인 관리 페이지의 별도 검사도 제거했다.
loadAdminPageSession()
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
export async function loadAdminPageSession(
returnTo: string,
): Promise<Extract<AdminPageSession, { status: "authorized" }>> {
const user = await requireSiteUser(returnTo);
const { isAdminEmail } = await import("@backend/common/auth/admin-auth");
if (!isAdminEmail(user.email)) forbidden();
return {
status: "authorized",
displayName: user.displayName,
signOutPath: applicationSignOutPath("/"),
};
}
AdminPage()
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
export default async function AdminPage({
initialSection,
session,
}: {
initialSection: AdminSection;
session: Extract<AdminPageSession, { status: "authorized" }>;
}) {
return (
<AdminApp
key={initialSection}
initialSection={initialSection}
displayName={session.displayName}
signOutPath={session.signOutPath}
/>
);
}
Forbidden()
거부 화면은 관리자 경로의 forbidden.tsx로 옮겼다. 권한 안내와 다른 계정으로 로그인하는 POST 폼을 유지하며, vinext의 NEXT_HTTP_ERROR_FALLBACK;403 오류가 서버 응답 경계를 지정한다.
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
export default function Forbidden() {
return (
<main className="admin-access-page">
<section className="admin-access-card">
<span>403</span>
<h1>관리자 권한이 없습니다.</h1>
<p>이 페이지는 모두의 문제집 운영자 계정으로만 접근할 수 있습니다.</p>
<div>
<Link href="/">학습 사이트로 돌아가기</Link>
<SignOutForm action={applicationSignOutPath("/admin")}>
다른 계정으로 로그인
</SignOutForm>
</div>
</section>
</main>
);
}
검증 결과
Node.js에서 수정 전후 원본 loadAdminPageSession()에 합성 사용자 정보를 연결했다. HTTP 검증은 현재 빌드 Worker·SQLite·합성 서명 세션 조건에서 경로별 1회 실행했다.
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
| 사용자 역할 | 수정 전 세션 함수 | 수정 후 세션 함수 |
|---|---|---|
| 학습자 | forbidden 객체 반환 | 반환 전에 HTTP 403 접근 오류 |
| 관리자 | authorized 반환 | authorized 반환 |
좌우로 이동해 전체 내용을 확인하세요. 키보드: ←·→, Home·End
| 현재 로컬 Worker 요청 | HTTP | 응답 내용 |
|---|---|---|
학습자 /admin | 403 | 거부 안내·계정 전환 POST 폼 |
학습자 /admin/members | 403 | 거부 안내·계정 전환 POST 폼 |
학습자 /admin/skct-personal | 403 | 거부 안내·계정 전환 POST 폼 |
관리자 /admin | 200 | 정상 관리자 응답 |
학습자 3/3건은 HTTP 403, 관리자 1/1건은 HTTP 200을 반환했다. 같은 Worker의 /admin을 메모리 SQLite·합성 학습자 세션으로 1회 요청한 Swagger UI 화면(1,280×720 CSS px·DPR 2)에서도 HTTP 403 응답이 표시됐다.