'use server'; import { redirect } from 'next/navigation'; import { revalidatePath } from 'next/cache'; import { requestAdminLogin, type DuplicateLoginNotice, } from '@/lib/data/repositories/auth-repository'; import { createSession } from '@/lib/auth/session'; import { FORCE_LOGIN_FIELD } from '@/lib/constants/login'; export type LoginFormState = { error?: string; /** 다른 곳에 살아 있는 세션이 있어 확인이 필요하다. 화면이 안내 후 강제 로그인으로 재요청한다. */ duplicate?: DuplicateLoginNotice; }; const GENERIC_LOGIN_ERROR = '아이디 또는 비밀번호가 올바르지 않습니다.'; const MAX_LOGIN_ID_LENGTH = 100; const MAX_PASSWORD_LENGTH = 200; function readTrimmedField(formData: FormData, key: string): string | null { const value = formData.get(key); if (typeof value !== 'string') { return null; } const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; } /** * 로그인 Server Action — 인증의 진입점이라 사전 인증 확인은 성립하지 않는다(예외로 명시). */ export async function login( _prevState: LoginFormState, formData: FormData ): Promise { const loginId = readTrimmedField(formData, 'loginId'); const password = readTrimmedField(formData, 'password'); if ( !loginId || !password || loginId.length > MAX_LOGIN_ID_LENGTH || password.length > MAX_PASSWORD_LENGTH ) { return { error: GENERIC_LOGIN_ERROR }; } const outcome = await requestAdminLogin({ loginId, password, forceLogin: formData.get(FORCE_LOGIN_FIELD) === '1', }); if (outcome.status === 'failure') { return { error: outcome.message }; } if (outcome.status === 'duplicate') { return { duplicate: outcome.notice }; } await createSession({ userId: outcome.admin.id, loginId: outcome.admin.loginId, userNm: outcome.admin.name, roleId: outcome.admin.roleCode, accessToken: outcome.accessToken, refreshToken: outcome.refreshToken, exp: outcome.accessTokenExpiresAt, }); revalidatePath('/', 'layout'); redirect('/'); }