Files
frontend/apps/cs/src/entities/auth/api/auth-service.ts
T

133 lines
3.4 KiB
TypeScript

import {
ACCESS_TOKEN,
AUTH_LOGIN_REDIRECT_URL,
AUTH_USER_ID,
CLAIM_NAME,
ENDPOINT,
} from "@/shared/config";
import {
type AuthPayload,
type AuthService,
getRemainingMs,
} from "@common/auth";
import {
clearRefreshTimer,
removeToken,
setToken,
setupRefreshTimer,
tokenManager,
} from "../lib/token-manager";
function resolveLoginError(message: string | undefined): string {
switch (message) {
case "Invalid Credentials":
return "아이디와 비밀번호가 일치하지 않습니다.";
case "User not found":
return "존재하지 않는 아이디입니다.";
case "User is not active":
return "활성화되지 않은 계정입니다.";
case "User is not verified":
return "이메일 인증이 필요합니다.";
default:
return message ?? "로그인에 실패했습니다.";
}
}
async function login(payload: AuthPayload): Promise<void> {
const response = await fetch(`/api/proxy${ENDPOINT.USER_SERVICE.LOGIN}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const data = await response.json();
if (!response.ok) {
throw new Error(resolveLoginError(data?.message));
}
const accessToken = data[ACCESS_TOKEN] as string | undefined;
const tokenId = data[AUTH_USER_ID] as string | undefined;
if (!accessToken || !tokenId) {
throw new Error("인증 토큰을 받지 못했습니다.");
}
setToken(CLAIM_NAME, accessToken);
setToken(AUTH_USER_ID, tokenId);
const remainingMs = getRemainingMs(accessToken);
if (remainingMs > 0) {
setupRefreshTimer(silentRefresh, remainingMs);
}
}
async function silentRefresh(): Promise<void> {
console.log(`[silentRefresh] Called at ${new Date().toISOString()}`);
try {
const response = await fetch(
`/api/proxy${ENDPOINT.USER_SERVICE.SILENT_REFRESH}`,
{ method: "PUT" },
);
const data = await response.json();
if (!response.ok) {
throw new Error(data?.message ?? "토큰 갱신에 실패했습니다.");
}
const accessToken = data[ACCESS_TOKEN] as string | undefined;
const tokenId = data[AUTH_USER_ID] as string | undefined;
if (!accessToken || !tokenId) {
throw new Error("갱신된 인증 토큰을 받지 못했습니다.");
}
setToken(CLAIM_NAME, accessToken);
setToken(AUTH_USER_ID, tokenId);
const remainingMs = getRemainingMs(accessToken);
if (remainingMs > 0) {
setupRefreshTimer(silentRefresh, remainingMs);
}
} catch (error) {
console.error(
`[silentRefresh] Failed at ${new Date().toISOString()}`,
error,
);
tokenManager.clearAuthToken();
if (typeof window !== "undefined") {
window.dispatchEvent(new CustomEvent("auth:logout"));
}
// re-throw하지 않음: setupRefreshTimer 콜백의 catch와 이중 처리 방지
}
}
async function logout(): Promise<void> {
try {
await fetch("/api/auth/logout", {
method: "POST",
credentials: "include",
});
} catch (error) {
console.error("Logout API failed:", error);
}
tokenManager.clearAuthToken();
removeToken("site-id-key");
removeToken("site-theme-key");
if (typeof window !== "undefined") {
window.dispatchEvent(new CustomEvent("auth:logout"));
window.location.href = AUTH_LOGIN_REDIRECT_URL;
}
}
export const authService: AuthService = {
login,
silentRefresh,
declineSessionExtend: clearRefreshTimer,
logout,
};