diff --git a/adminfront/src/features/auth/LoginPage.tsx b/adminfront/src/features/auth/LoginPage.tsx index 4367c9bf..233da28c 100644 --- a/adminfront/src/features/auth/LoginPage.tsx +++ b/adminfront/src/features/auth/LoginPage.tsx @@ -1,7 +1,5 @@ -import { useMutation } from "@tanstack/react-query"; -import type { AxiosError } from "axios"; -import { KeyRound, Lock, Mail, ShieldHalf } from "lucide-react"; -import { useState } from "react"; +import { ShieldHalf, LogIn, ExternalLink } from "lucide-react"; +import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { Button } from "../../components/ui/button"; import { @@ -11,32 +9,55 @@ import { CardHeader, CardTitle, } from "../../components/ui/card"; -import { Input } from "../../components/ui/input"; -import { Label } from "../../components/ui/label"; -import { login } from "../../lib/adminApi"; -import { t } from "../../lib/i18n"; function LoginPage() { const navigate = useNavigate(); - const [loginId, setLoginId] = useState(""); - const [password, setPassword] = useState(""); + const [isLoggingIn, setIsLoggingIn] = useState(false); - const loginMutation = useMutation({ - mutationFn: () => login({ loginId, password }), - onSuccess: (data) => { - window.localStorage.setItem("admin_session", data.sessionToken); - navigate("/"); - }, - }); + useEffect(() => { + // Listen for login success message from the popup + const handleMessage = (event: MessageEvent) => { + // Security check: In production, verify event.origin + if (event.data?.type === "LOGIN_SUCCESS" && event.data?.token) { + window.localStorage.setItem("admin_session", event.data.token); + setIsLoggingIn(false); + navigate("/"); + } + }; - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - loginMutation.mutate(); + window.addEventListener("message", handleMessage); + return () => window.removeEventListener("message", handleMessage); + }, [navigate]); + + const handleSSOLogin = () => { + const userfrontUrl = import.meta.env.VITE_USERFRONT_URL || "https://sso.hmac.kr"; + const loginUrl = `${userfrontUrl}/login?source=adminfront`; + + const width = 500; + const height = 700; + const left = window.screen.width / 2 - width / 2; + const top = window.screen.height / 2 - height / 2; + + const popup = window.open( + loginUrl, + "BaronSSOLogin", + `width=${width},height=${height},top=${top},left=${left},status=no,menubar=no,toolbar=no` + ); + + if (popup) { + setIsLoggingIn(true); + // Optional: Polling to detect if popup was closed without login + const timer = setInterval(() => { + if (popup.closed) { + clearInterval(timer); + setIsLoggingIn(false); + } + }, 1000); + } else { + alert("팝업 차단이 설정되어 있습니다. 팝업 허용 후 다시 시도해 주세요."); + } }; - const errorMsg = (loginMutation.error as AxiosError<{ error?: string }>)?.response - ?.data?.error; - return (
+ 관리자 전역 세션은 보안을 위해 15분간 유지됩니다.
+ 민감한 작업 시 재인증을 요구할 수 있습니다.
+
인증 정보가 없거나 로그인이 되지 않는 경우
시스템 관리자에게 문의하세요.
@@ -120,4 +125,4 @@ function LoginPage() {
);
}
-export default LoginPage;
+export default LoginPage;
\ No newline at end of file
diff --git a/adminfront/src/lib/apiClient.ts b/adminfront/src/lib/apiClient.ts
index c93d5766..a3c0f3eb 100644
--- a/adminfront/src/lib/apiClient.ts
+++ b/adminfront/src/lib/apiClient.ts
@@ -29,7 +29,10 @@ apiClient.interceptors.request.use((config) => {
apiClient.interceptors.response.use(
(response) => response,
(error) => {
- // TODO: 401/403 응답 시 로그인/재인증 플로우로 리다이렉션한다.
+ if (error.response?.status === 401) {
+ window.localStorage.removeItem("admin_session");
+ window.location.href = "/login";
+ }
return Promise.reject(error);
},
);