ERP-node/frontend/hooks/useAuth.ts

452 lines
13 KiB
TypeScript
Raw Normal View History

2025-09-09 14:29:04 +09:00
import { useState, useEffect, useCallback, useRef } from "react";
2025-08-21 09:41:46 +09:00
import { useRouter } from "next/navigation";
import { apiCall } from "@/lib/api/client";
2025-08-21 09:41:46 +09:00
interface UserInfo {
userId: string;
userName: string;
userNameEng?: string;
userNameCn?: string;
deptCode?: string;
deptName?: string;
positionCode?: string;
positionName?: string;
email?: string;
tel?: string;
cellPhone?: string;
userType?: string;
userTypeName?: string;
authName?: string;
partnerCd?: string;
2025-08-28 10:05:06 +09:00
locale?: string;
2025-08-21 09:41:46 +09:00
isAdmin: boolean;
sabun?: string;
photo?: string | null;
companyCode?: string;
company_code?: string;
2025-08-21 09:41:46 +09:00
}
interface AuthStatus {
isLoggedIn: boolean;
isAdmin: boolean;
userId?: string;
deptCode?: string;
}
interface LoginResult {
success: boolean;
message: string;
errorCode?: string;
}
interface ApiResponse<T = any> {
success: boolean;
message: string;
data?: T;
errorCode?: string;
}
// JWT 토큰 관리 유틸리티 (client.ts와 동일한 localStorage 키 사용)
2025-08-21 09:41:46 +09:00
const TokenManager = {
getToken: (): string | null => {
if (typeof window !== "undefined") {
return localStorage.getItem("authToken");
}
return null;
},
setToken: (token: string): void => {
if (typeof window !== "undefined") {
localStorage.setItem("authToken", token);
// 쿠키에도 저장 (미들웨어에서 사용)
document.cookie = `authToken=${token}; path=/; max-age=86400; SameSite=Lax`;
2025-08-21 09:41:46 +09:00
}
},
removeToken: (): void => {
if (typeof window !== "undefined") {
localStorage.removeItem("authToken");
document.cookie = "authToken=; path=/; max-age=0; SameSite=Lax";
2025-08-21 09:41:46 +09:00
}
},
isTokenExpired: (token: string): boolean => {
try {
const payload = JSON.parse(atob(token.split(".")[1]));
return payload.exp * 1000 < Date.now();
} catch {
return true;
}
},
};
/**
*
* - 401 client.ts의
* -
2025-08-21 09:41:46 +09:00
*/
export const useAuth = () => {
const router = useRouter();
const [user, setUser] = useState<UserInfo | null>(null);
const [authStatus, setAuthStatus] = useState<AuthStatus>({
isLoggedIn: false,
isAdmin: false,
});
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
2025-09-09 14:29:04 +09:00
const initializedRef = useRef(false);
2025-08-21 09:41:46 +09:00
/**
*
*/
const fetchCurrentUser = useCallback(async (): Promise<UserInfo | null> => {
try {
const response = await apiCall<UserInfo>("GET", "/auth/me");
if (response.success && response.data) {
// 사용자 로케일 정보 조회
2025-08-25 17:22:20 +09:00
try {
const localeResponse = await apiCall<string>("GET", "/admin/user-locale");
if (localeResponse.success && localeResponse.data) {
const userLocale = localeResponse.data;
(window as any).__GLOBAL_USER_LANG = userLocale;
(window as any).__GLOBAL_USER_LOCALE_LOADED = true;
localStorage.setItem("userLocale", userLocale);
localStorage.setItem("userLocaleLoaded", "true");
}
} catch {
2025-08-25 17:22:20 +09:00
(window as any).__GLOBAL_USER_LANG = "KR";
(window as any).__GLOBAL_USER_LOCALE_LOADED = true;
localStorage.setItem("userLocale", "KR");
localStorage.setItem("userLocaleLoaded", "true");
}
2025-08-21 09:41:46 +09:00
return response.data;
}
return null;
} catch {
2025-08-21 09:41:46 +09:00
return null;
}
}, []);
/**
*
*/
const checkAuthStatus = useCallback(async (): Promise<AuthStatus> => {
try {
const response = await apiCall<AuthStatus>("GET", "/auth/status");
if (response.success && response.data) {
return {
2025-08-21 09:41:46 +09:00
isLoggedIn: (response.data as any).isAuthenticated || response.data.isLoggedIn || false,
isAdmin: response.data.isAdmin || false,
};
}
return { isLoggedIn: false, isAdmin: false };
} catch {
return { isLoggedIn: false, isAdmin: false };
2025-08-21 09:41:46 +09:00
}
}, []);
/**
*
* - API
* -
2025-08-21 09:41:46 +09:00
*/
const refreshUserData = useCallback(async () => {
try {
setLoading(true);
const token = TokenManager.getToken();
if (!token || TokenManager.isTokenExpired(token)) {
2025-08-21 09:41:46 +09:00
setUser(null);
setAuthStatus({ isLoggedIn: false, isAdmin: false });
setLoading(false);
2025-08-21 09:41:46 +09:00
return;
}
// 토큰이 유효하면 우선 인증된 상태로 설정
setAuthStatus({
isLoggedIn: true,
isAdmin: false,
});
try {
const [userInfo, authStatusData] = await Promise.all([fetchCurrentUser(), checkAuthStatus()]);
if (userInfo) {
setUser(userInfo);
const isAdminFromUser = userInfo.userId === "plm_admin" || userInfo.userType === "ADMIN";
const finalAuthStatus = {
isLoggedIn: authStatusData.isLoggedIn,
isAdmin: authStatusData.isAdmin || isAdminFromUser,
};
setAuthStatus(finalAuthStatus);
2025-10-29 11:26:00 +09:00
// API 결과가 비인증이면 상태만 업데이트 (리다이렉트는 client.ts가 처리)
if (!finalAuthStatus.isLoggedIn) {
TokenManager.removeToken();
setUser(null);
setAuthStatus({ isLoggedIn: false, isAdmin: false });
}
} else {
// userInfo 조회 실패 → 토큰에서 최소 정보 추출하여 유지
try {
const payload = JSON.parse(atob(token.split(".")[1]));
const tempUser: UserInfo = {
userId: payload.userId || payload.id || "unknown",
userName: payload.userName || payload.name || "사용자",
companyCode: payload.companyCode || payload.company_code || "",
isAdmin: payload.userId === "plm_admin" || payload.userType === "ADMIN",
};
setUser(tempUser);
setAuthStatus({
isLoggedIn: true,
isAdmin: tempUser.isAdmin,
});
} catch {
// 토큰 파싱도 실패하면 비인증 상태로 전환
TokenManager.removeToken();
setUser(null);
setAuthStatus({ isLoggedIn: false, isAdmin: false });
}
}
} catch {
// API 호출 전체 실패 → 토큰 기반 임시 인증 유지 시도
try {
const payload = JSON.parse(atob(token.split(".")[1]));
const tempUser: UserInfo = {
2025-10-29 11:26:00 +09:00
userId: payload.userId || payload.id || "unknown",
userName: payload.userName || payload.name || "사용자",
companyCode: payload.companyCode || payload.company_code || "",
isAdmin: payload.userId === "plm_admin" || payload.userType === "ADMIN",
};
setUser(tempUser);
setAuthStatus({
isLoggedIn: true,
isAdmin: tempUser.isAdmin,
});
} catch {
TokenManager.removeToken();
setUser(null);
setAuthStatus({ isLoggedIn: false, isAdmin: false });
}
2025-08-21 09:41:46 +09:00
}
} catch {
2025-08-21 09:41:46 +09:00
setError("사용자 정보를 불러오는데 실패했습니다.");
setUser(null);
setAuthStatus({ isLoggedIn: false, isAdmin: false });
} finally {
setLoading(false);
}
}, [fetchCurrentUser, checkAuthStatus]);
2025-08-21 09:41:46 +09:00
/**
*
*/
const login = useCallback(
async (userId: string, password: string): Promise<LoginResult> => {
try {
setLoading(true);
setError(null);
const response = await apiCall<any>("POST", "/auth/login", {
userId,
password,
});
if (response.success && response.data?.token) {
TokenManager.setToken(response.data.token);
await refreshUserData();
return {
success: true,
message: response.message || "로그인에 성공했습니다.",
};
} else {
return {
success: false,
message: response.message || "로그인에 실패했습니다.",
errorCode: response.errorCode,
};
}
} catch (error: any) {
const errorMessage = error.message || "로그인 중 오류가 발생했습니다.";
setError(errorMessage);
return {
success: false,
message: errorMessage,
};
} finally {
setLoading(false);
}
},
[refreshUserData],
2025-08-21 09:41:46 +09:00
);
/**
* (WACE )
*/
const switchCompany = useCallback(
async (companyCode: string): Promise<{ success: boolean; message: string }> => {
try {
setLoading(true);
setError(null);
const response = await apiCall<any>("POST", "/auth/switch-company", {
companyCode,
});
if (response.success && response.data?.token) {
TokenManager.setToken(response.data.token);
return {
success: true,
message: response.message || "회사 전환에 성공했습니다.",
};
} else {
return {
success: false,
message: response.message || "회사 전환에 실패했습니다.",
};
}
} catch (error: any) {
const errorMessage = error.message || "회사 전환 중 오류가 발생했습니다.";
setError(errorMessage);
return {
success: false,
message: errorMessage,
};
} finally {
setLoading(false);
}
},
[],
);
2025-08-21 09:41:46 +09:00
/**
*
*/
const logout = useCallback(async (): Promise<boolean> => {
try {
setLoading(true);
const response = await apiCall("POST", "/auth/logout");
TokenManager.removeToken();
2025-08-25 17:22:20 +09:00
localStorage.removeItem("userLocale");
localStorage.removeItem("userLocaleLoaded");
(window as any).__GLOBAL_USER_LANG = undefined;
(window as any).__GLOBAL_USER_LOCALE_LOADED = undefined;
2025-08-21 09:41:46 +09:00
setUser(null);
setAuthStatus({ isLoggedIn: false, isAdmin: false });
2025-08-21 09:41:46 +09:00
setError(null);
router.push("/login");
return response.success;
} catch {
2025-08-21 09:41:46 +09:00
TokenManager.removeToken();
2025-08-25 17:22:20 +09:00
localStorage.removeItem("userLocale");
localStorage.removeItem("userLocaleLoaded");
(window as any).__GLOBAL_USER_LANG = undefined;
(window as any).__GLOBAL_USER_LOCALE_LOADED = undefined;
2025-08-21 09:41:46 +09:00
setUser(null);
setAuthStatus({ isLoggedIn: false, isAdmin: false });
2025-08-21 09:41:46 +09:00
router.push("/login");
return false;
} finally {
setLoading(false);
}
}, [router]);
2025-08-21 09:41:46 +09:00
/**
*
*/
const checkMenuAuth = useCallback(async (menuUrl: string): Promise<boolean> => {
try {
const response = await apiCall<{ menuUrl: string; hasAuth: boolean }>("GET", "/auth/menu-auth");
if (response.success && response.data) {
return response.data.hasAuth;
}
return false;
} catch {
2025-08-21 09:41:46 +09:00
return false;
}
}, []);
/**
*
*/
useEffect(() => {
if (initializedRef.current) return;
2025-09-09 14:29:04 +09:00
initializedRef.current = true;
if (typeof window === "undefined") return;
2025-08-21 09:41:46 +09:00
// 로그인 페이지에서는 인증 상태 확인하지 않음
if (window.location.pathname === "/login") {
setLoading(false);
2025-08-21 09:41:46 +09:00
return;
}
const token = TokenManager.getToken();
2025-08-21 09:41:46 +09:00
if (token && !TokenManager.isTokenExpired(token)) {
// 유효한 토큰 → 우선 인증 상태로 설정 후 API 확인
setAuthStatus({
isLoggedIn: true,
isAdmin: false,
});
2025-08-21 09:41:46 +09:00
refreshUserData();
} else if (token && TokenManager.isTokenExpired(token)) {
// 만료된 토큰 → 정리 (리다이렉트는 AuthGuard에서 처리)
TokenManager.removeToken();
setAuthStatus({ isLoggedIn: false, isAdmin: false });
setLoading(false);
} else {
// 토큰 없음 → 비인증 상태 (리다이렉트는 AuthGuard에서 처리)
setAuthStatus({ isLoggedIn: false, isAdmin: false });
setLoading(false);
2025-08-21 09:41:46 +09:00
}
}, []);
2025-08-21 09:41:46 +09:00
return {
user,
authStatus,
loading,
error,
isLoggedIn: authStatus.isLoggedIn,
isAdmin: authStatus.isAdmin,
userId: user?.userId,
userName: user?.userName,
companyCode: user?.companyCode || user?.company_code,
2025-08-21 09:41:46 +09:00
login,
logout,
switchCompany,
2025-08-21 09:41:46 +09:00
checkMenuAuth,
refreshUserData,
clearError: () => setError(null),
};
};
export default useAuth;