feat: mock 인증 Repository 및 관리자 도메인 타입 추가
Co-Authored-By: Claude Opus 5
@2825e1d309f2319513898d458160ce0064ee6cbb
+++ lib/data/repositories/auth-repository.ts
... | ... | @@ -0,0 +1,56 @@ |
| 1 | +import 'server-only'; | |
| 2 | +import { createHash, timingSafeEqual } from 'node:crypto'; | |
| 3 | +import { getMockAdminCredentials } from '@/lib/env'; | |
| 4 | +import type { AdminUser } from '@/lib/domain/admin-user'; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * mock 인증 Repository. | |
| 8 | + * | |
| 9 | + * 백엔드(edupay-backend)에 관리자 인증 API가 아직 없어 mock으로 구현한다. | |
| 10 | + * 공개 시그니처(도메인 타입만 주고받음)는 백엔드 연동 후에도 유지한다 — 연동 시점에는 | |
| 11 | + * 이 파일의 내부 구현만 실제 API 호출로 교체하고, 각 함수에 캐시 전략(`cache: 'no-store'`)을 | |
| 12 | + * 명시적으로 추가해야 한다. 인증·개인 데이터는 요청 간 캐시 잔존이 금지되기 때문이다. | |
| 13 | + */ | |
| 14 | + | |
| 15 | +const MOCK_ADMIN_ID = 'mock-admin-1'; | |
| 16 | + | |
| 17 | +export type AdminCredentials = { | |
| 18 | + loginId: string; | |
| 19 | + password: string; | |
| 20 | +}; | |
| 21 | + | |
| 22 | +/** SHA-256 다이제스트 후 timingSafeEqual 비교 — 길이·타이밍 정보 누출을 방지한다. */ | |
| 23 | +function digestsMatch(a: string, b: string): boolean { | |
| 24 | + const digestA = createHash('sha256').update(a, 'utf8').digest(); | |
| 25 | + const digestB = createHash('sha256').update(b, 'utf8').digest(); | |
| 26 | + return timingSafeEqual(digestA, digestB); | |
| 27 | +} | |
| 28 | + | |
| 29 | +export async function verifyAdminCredentials( | |
| 30 | + credentials: AdminCredentials | |
| 31 | +): Promise<AdminUser | null> { | |
| 32 | + const seed = getMockAdminCredentials(); | |
| 33 | + if (!seed) { | |
| 34 | + return null; | |
| 35 | + } | |
| 36 | + | |
| 37 | + const loginIdMatches = digestsMatch(credentials.loginId, seed.loginId); | |
| 38 | + const passwordMatches = digestsMatch(credentials.password, seed.password); | |
| 39 | + | |
| 40 | + if (!loginIdMatches || !passwordMatches) { | |
| 41 | + return null; | |
| 42 | + } | |
| 43 | + | |
| 44 | + return { id: MOCK_ADMIN_ID, name: '관리자' }; | |
| 45 | +} | |
| 46 | + | |
| 47 | +export async function fetchAdminById( | |
| 48 | + adminId: string | |
| 49 | +): Promise<AdminUser | null> { | |
| 50 | + const seed = getMockAdminCredentials(); | |
| 51 | + if (!seed || adminId !== MOCK_ADMIN_ID) { | |
| 52 | + return null; | |
| 53 | + } | |
| 54 | + | |
| 55 | + return { id: MOCK_ADMIN_ID, name: '관리자' }; | |
| 56 | +} |
+++ lib/domain/admin-user.ts
... | ... | @@ -0,0 +1,8 @@ |
| 1 | +/** | |
| 2 | + * 관리자 도메인 타입 — 순수 데이터 표현, 외부 의존 없음. | |
| 3 | + * 화면에는 최소 DTO만 노출한다 (비밀번호 등 민감 필드는 여기 포함하지 않는다). | |
| 4 | + */ | |
| 5 | +export type AdminUser = { | |
| 6 | + id: string; | |
| 7 | + name: string; | |
| 8 | +}; |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?