From 80e3d0befed487e0447bacffd76ed6539f01e992 Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Mon, 13 Oct 2025 18:24:00 +0900 Subject: (김준회) S-GIPS 로그인시 유저 선택해 sms 전송 처리 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/login/login-form.tsx | 215 ++++++++++++++++++++++++++++++++++------ 1 file changed, 186 insertions(+), 29 deletions(-) (limited to 'components/login') diff --git a/components/login/login-form.tsx b/components/login/login-form.tsx index 8e9509c8..b0a0e574 100644 --- a/components/login/login-form.tsx +++ b/components/login/login-form.tsx @@ -24,6 +24,15 @@ import Loading from "../common/loading/loading"; type LoginMethod = 'username' | 'sgips'; +type OtpUser = { + name: string; + vndrcd: string; + phone: string; + email: string; + nation_cd: string; + userId: number; // 백엔드에서 생성된 로컬 DB 사용자 ID +}; + export function LoginForm() { const params = useParams() || {}; const pathname = usePathname() || ''; @@ -44,7 +53,7 @@ export function LoginForm() { const [showMfaForm, setShowMfaForm] = useState(false); const [mfaToken, setMfaToken] = useState(''); const [tempAuthKey, setTempAuthKey] = useState(''); - const [mfaUserId, setMfaUserId] = useState(null); + const [mfaUserId, setMfaUserId] = useState(null); const [mfaUserEmail, setMfaUserEmail] = useState(''); const [mfaCountdown, setMfaCountdown] = useState(0); @@ -52,10 +61,15 @@ export function LoginForm() { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); - // S-Gips 로그인 폼 데이터 + // S-Gips 로그인 폼 데이터 const [sgipsUsername, setSgipsUsername] = useState(''); const [sgipsPassword, setSgipsPassword] = useState(''); + // OTP 사용자 선택 관련 상태 + const [otpUsers, setOtpUsers] = useState([]); + const [showUserSelectionDialog, setShowUserSelectionDialog] = useState(false); + const [selectedOtpUser, setSelectedOtpUser] = useState(null); + const [isMfaLoading, setIsMfaLoading] = useState(false); const [isSmsLoading, setIsSmsLoading] = useState(false); @@ -189,25 +203,40 @@ export function LoginForm() { } }; - // SMS 토큰 전송 (userId 파라미터 추가) + // SMS 토큰 전송 const handleSendSms = async (userIdParam?: number) => { const targetUserId = userIdParam || mfaUserId; if (!targetUserId || mfaCountdown > 0) return; setIsSmsLoading(true); try { + const requestBody: any = { userId: targetUserId }; + + // S-GIPS 사용자인 경우 추가 정보 포함 + if (selectedOtpUser) { + requestBody.phone = selectedOtpUser.phone; + requestBody.name = selectedOtpUser.name; + } + const response = await fetch('/api/auth/send-sms', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ userId: targetUserId }), + body: JSON.stringify(requestBody), }); if (response.ok) { setMfaCountdown(60); - toast({ - title: t('smsSent'), - description: t('smsCodeSent'), - }); + if (selectedOtpUser) { + toast({ + title: t('smsSent'), + description: t('smsCodeSentTo', { name: selectedOtpUser.name, phone: selectedOtpUser.phone }), + }); + } else { + toast({ + title: t('smsSent'), + description: t('smsCodeSent'), + }); + } } else { const errorData = await response.json(); toast({ @@ -391,30 +420,24 @@ export function LoginForm() { const authResult = await performFirstAuth(sgipsUsername, sgipsPassword, 'sgips'); if (authResult.success) { - toast({ - title: t('sgipsAuthComplete'), - description: t('proceedingSmsAuth'), - }); - - // MFA 화면으로 전환 - setTempAuthKey(authResult.tempAuthKey); - setMfaUserId(authResult.userId); - setMfaUserEmail(authResult.email); - setShowMfaForm(true); - - // 자동으로 SMS 전송 (userId 직접 전달) - setTimeout(() => { - handleSendSms(authResult.userId); - }, 500); + const users = authResult.otpUsers || []; + + if (users.length === 0) { + toast({ + title: t('errorTitle'), + description: t('noUsersFound'), + variant: 'destructive', + }); + return; + } - toast({ - title: t('smsAuthStarted'), - description: t('sendingCodeToSgipsPhone'), - }); + // 사용자 선택 다이얼로그 표시 (항상) + setOtpUsers(users); + setShowUserSelectionDialog(true); } } catch (error: unknown) { console.error('S-Gips login error:', error); - + const errorMessage = getErrorMessage(error as { errorCode?: string; message?: string }, 'sgips'); toast({ @@ -427,6 +450,78 @@ export function LoginForm() { } }; + // 선택된 OTP 사용자와 함께 MFA 진행 + const proceedWithSelectedUser = async (user: OtpUser, tempAuthKey: string) => { + try { + // 사용자 정보를 기반으로 MFA 진행 + setTempAuthKey(tempAuthKey); + setSelectedOtpUser(user); + setMfaUserId(user.userId); // 선택된 사용자의 userId 설정 + setMfaUserEmail(user.email); + setShowMfaForm(true); + + // 선택된 사용자의 정보를 이용해 SMS 전송 준비 + // 실제로는 userId가 필요하므로 API에서 받아와야 함 + // 여기서는 임시로 user 객체를 저장하고 SMS 전송 시 사용 + setTimeout(() => { + // 실제 구현에서는 user 정보를 기반으로 SMS 전송 + // 현재는 기존 로직 유지하되, 선택된 사용자 정보 활용 + handleSendSms(); + }, 500); + + toast({ + title: t('sgipsAuthComplete'), + description: t('sendingCodeToSelectedUser', { name: user.name }), + }); + } catch (error) { + console.error('Proceeding with selected user error:', error); + toast({ + title: t('errorTitle'), + description: t('mfaSetupError'), + variant: 'destructive', + }); + } + }; + + // OTP 사용자 선택 처리 + const handleUserSelection = async (user: OtpUser) => { + setShowUserSelectionDialog(false); + + try { + // 선택된 사용자에 대한 임시 인증 세션 생성 요청 + const response = await fetch('/api/auth/select-sgips-user', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + userId: user.userId, + email: user.email, + name: user.name + }), + }); + + const result = await response.json(); + + if (!response.ok || !result.success) { + toast({ + title: t('errorTitle'), + description: result.error || t('mfaSetupError'), + variant: 'destructive', + }); + return; + } + + // 임시 인증 세션 생성 성공, MFA 진행 + await proceedWithSelectedUser(user, result.tempAuthKey); + } catch (error) { + console.error('User selection error:', error); + toast({ + title: t('errorTitle'), + description: t('mfaSetupError'), + variant: 'destructive', + }); + } + }; + // MFA 화면에서 뒤로 가기 const handleBackToLogin = () => { setShowMfaForm(false); @@ -435,6 +530,9 @@ export function LoginForm() { setMfaUserId(null); setMfaUserEmail(''); setMfaCountdown(0); + setSelectedOtpUser(null); + setShowUserSelectionDialog(false); + setOtpUsers([]); }; // 세션 로딩 중이거나 이미 인증된 상태에서는 로딩 표시 @@ -491,7 +589,10 @@ export function LoginForm() {

{t('smsVerification')}

- {t('firstAuthCompleteFor', { email: mfaUserEmail })} + {selectedOtpUser + ? t('firstAuthCompleteForSgips', { name: selectedOtpUser.name, email: mfaUserEmail }) + : t('firstAuthCompleteFor', { email: mfaUserEmail }) + }

{t('enterSixDigitCodeInstructions')} @@ -738,6 +839,62 @@ export function LoginForm() { )} + {/* OTP 사용자 선택 다이얼로그 */} + {showUserSelectionDialog && !showMfaForm && ( +

+
+
+

{t('selectUser')}

+ +
+
+

+ {t('selectUserDescription')} +

+ {otpUsers.map((user, index) => ( +
handleUserSelection(user)} + > +
+
+
{user.name}
+
{user.email}
+
{user.phone}
+
Vendor: {user.vndrcd}
+
+ +
+
+ ))} +
+
+ +
+
+
+ )} + {/* 비밀번호 재설정 다이얼로그 */} {showForgotPassword && !showMfaForm && (
-- cgit v1.2.3