로그인 처리
iipbs_sn / build (push) Waiting to run

This commit is contained in:
yschoi
2025-05-26 10:32:26 +09:00
parent 6156cf6419
commit d975e8fed0
14 changed files with 494 additions and 262 deletions
@@ -0,0 +1,105 @@
import { AxiosError } from "axios";
import { tokenManager } from "../lib/token-manager";
const ENDPOINT = {
USER_SERVICE: {
LOGIN: "/user-service/login",
USERS: "/user-service/api/v1/users",
SILENT_REFRESH: "/user-service/api/v1/users/token/refresh",
AUTHORIZATIONS: "/user-service/api/v1/authorizations",
ROLE_AUTHORIZATIONS: "/user-service/api/v1/role-authorizations",
},
};
const JWT_EXPIRATION_TIME = 1800000;
const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
const ACCESS_TOKEN = process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token";
export interface ILogin {
userId: string;
password: string;
}
export interface LoginService {
login(payload: ILogin): Promise<void>;
silentRefresh(): Promise<void>;
}
export class LoginApiService implements LoginService {
private static instance: LoginApiService;
private constructor() {}
public static getInstance(): LoginApiService {
if (!LoginApiService.instance) {
LoginApiService.instance = new LoginApiService();
}
return LoginApiService.instance;
}
public async login(payload: ILogin): Promise<void> {
try {
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, {
method: "POST",
body: JSON.stringify({
userId: payload.userId,
password: payload.password,
}),
});
const data = await response.json();
const accessToken = data[ACCESS_TOKEN];
const tokenId = data[AUTH_USER_ID];
if (!accessToken || !tokenId) {
throw new Error("Missing required authentication tokens");
}
tokenManager.setToken(CLAIM_NAME, accessToken);
tokenManager.setToken(AUTH_USER_ID, tokenId);
tokenManager.setupRefreshTimer(
() => this.silentRefresh(),
JWT_EXPIRATION_TIME,
);
} catch (error) {
if (error instanceof AxiosError) {
throw new Error(`Login failed: ${error.response?.data?.message}`);
}
throw new Error(`Login failed: ${error}`);
}
}
public async silentRefresh(): Promise<void> {
try {
const response = await fetch(
`/api/proxy${ENDPOINT.USER_SERVICE.SILENT_REFRESH}`,
{
method: "PUT",
},
);
const data = await response.json();
const accessToken = response.headers.get("access-token");
const tokenId = response.headers.get("token-id");
if (!accessToken || !tokenId) {
throw new Error("Missing required authentication tokens");
}
tokenManager.setToken(CLAIM_NAME, accessToken);
tokenManager.setToken(AUTH_USER_ID, tokenId);
} catch (error) {
tokenManager.clearAuthToken();
if (error instanceof AxiosError) {
throw new Error(
`Silent refresh failed: ${error.response?.data?.message}`,
);
}
throw new Error(`Silent refresh failed: ${error}`);
}
}
}
export const LoginApi = LoginApiService.getInstance();
@@ -0,0 +1,2 @@
export { LoginApi } from "./api/login-service";
export { AuthWrapper } from "./lib/auth-wrapper";
@@ -0,0 +1,87 @@
"use client";
import { useEffect } from "react";
import { useRouter, usePathname } from "next/navigation";
import axios from "axios";
import Cookies from "js-cookie";
import { useUserStore, type IUserInfo } from "@store";
import { getCookie } from "@utils";
import { LoginApi } from "../api/login-service";
import { tokenManager } from "../lib/token-manager";
const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
const ENDPOINT = {
USER_SERVICE: {
LOGIN: "/user-service/login",
USERS: "/user-service/api/v1/users",
SILENT_REFRESH: "/user-service/api/v1/users/token/refresh",
AUTHORIZATIONS: "/user-service/api/v1/authorizations",
ROLE_AUTHORIZATIONS: "/user-service/api/v1/role-authorizations",
},
};
const AuthWrapper = ({ children }: { children: React.ReactNode }) => {
const router = useRouter();
const pathname = usePathname(); // 현재 경로
const { userInfo, setUserInfo } = useUserStore((state) => state);
async function authCheck() {
const checkToken = getCookie(AUTH_USER_ID);
if (!checkToken) {
//TODO 나중에 수정해야....
const userId = "admin1gmail";
const password = "test1234!";
await LoginApi.login({ userId, password });
// window.location.href = "http://dev.onasys.co.kr/auth/login";
// throw new Error("Missing required authentication tokens");
}
const accessToken = tokenManager.getToken(CLAIM_NAME);
const tokenId = tokenManager.getToken(AUTH_USER_ID);
const response = await axios.get<IUserInfo>(
`${ENDPOINT.USER_SERVICE.USERS}/info/me`,
{
withCredentials: true,
headers: {
[CLAIM_NAME]: accessToken,
[AUTH_USER_ID]: tokenId ?? "",
},
},
);
if (!userInfo) setUserInfo(response.data);
}
useEffect(() => {
(async () => {
try {
await authCheck();
} catch (error) {
console.error("Auth check failed:", error);
try {
await LoginApi.silentRefresh();
await authCheck();
} catch (refreshError) {
console.error("Token refresh failed:", refreshError);
setUserInfo(null);
// TODO 운영에 반영 할때 수정해야.....
// 현재 경로가 로그인 페이지가 아닐 때만 redirect
// if (!pathname.startsWith("/auth/login")) {
// window.location.href = "http://dev.onasys.co.kr/auth/login";
// }
}
}
})();
}, [pathname]); // pathname도 의존성에 포함
return <>{children}</>;
};
export { AuthWrapper };
@@ -0,0 +1,61 @@
import { getCookie, setCookie, removeCookie } from "@utils";
const AUTH_USER_ID = process.env.NEXT_PUBLIC_AUTH_USER_ID || "token-id";
const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization";
export class TokenManager {
private static instance: TokenManager;
private refreshTimer: NodeJS.Timeout | null = null;
private constructor() {}
public static getInstance(): TokenManager {
if (!TokenManager.instance) {
TokenManager.instance = new TokenManager();
}
return TokenManager.instance;
}
public getToken(key: string): string | undefined {
return getCookie(key);
}
public setToken(key: string, value: string): void {
setCookie(key, value, {
secure: window.location.protocol === "https:",
sameSite: "lax",
});
}
public removeToken(key: string): void {
removeCookie(key);
}
public setupRefreshTimer(
callback: () => Promise<void>,
expirationTime: number,
): void {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
}
this.refreshTimer = setTimeout(() => {
callback().catch((error) => {
console.error("Silent refresh failed:", error);
this.clearAuthToken();
});
}, expirationTime - 60000);
}
public clearAuthToken(): void {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
this.refreshTimer = null;
}
this.removeToken(CLAIM_NAME);
this.removeToken(AUTH_USER_ID);
}
}
export const tokenManager = TokenManager.getInstance();
@@ -0,0 +1,9 @@
export interface ILogin {
userId: string;
password: string;
}
export interface LoginService {
login(payload: ILogin): Promise<void>;
silentRefresh(): Promise<void>;
}