chore: init lms adm fe
This commit is contained in:
+167
@@ -0,0 +1,167 @@
|
||||
import type { AuthAppRole, AuthSession } from '../types/auth';
|
||||
|
||||
const AUTH_BASE_URL = (import.meta.env.VITE_ATNMY_AUTH_URL ?? import.meta.env.VITE_ADS_IDE_AUTH_URL ?? 'http://localhost:4100').replace(/\/+$/, '');
|
||||
const ADMIN_SESSION_STORAGE_KEY = 'atnmy.lms.admin.auth.session';
|
||||
const INSTRUCTOR_ROLES = new Set(['INSTRUCTOR', 'LMS_ADMIN']);
|
||||
|
||||
export const isInstructorSession = (session: AuthSession): boolean => {
|
||||
const roles = new Set(getSessionRoles(session));
|
||||
return [...INSTRUCTOR_ROLES].some((role) => roles.has(role));
|
||||
};
|
||||
|
||||
const getSessionRoles = (session: AuthSession): string[] => {
|
||||
return [
|
||||
session.user.role,
|
||||
...(session.user.roles ?? []),
|
||||
...(session.roles ?? []),
|
||||
...getAppRoleCodes(session.user.appRoles),
|
||||
...getAppRoleCodes(session.user.app_roles),
|
||||
...getAppRoleCodes(session.appRoles),
|
||||
...getAppRoleCodes(session.app_roles),
|
||||
]
|
||||
.filter((role): role is string => Boolean(role))
|
||||
.map((role) => role.toUpperCase());
|
||||
};
|
||||
|
||||
const getAppRoleCodes = (roles: AuthAppRole[] | undefined): string[] => {
|
||||
if (!Array.isArray(roles)) return [];
|
||||
|
||||
return roles
|
||||
.filter((role) => {
|
||||
const appKey = role.appKey ?? role.app_key;
|
||||
return !appKey || appKey.toUpperCase() === 'LMS';
|
||||
})
|
||||
.map((role) => role.roleCode ?? role.role_code ?? role.role ?? role.code)
|
||||
.filter((role): role is string => Boolean(role));
|
||||
};
|
||||
|
||||
export const loadStoredSession = (): AuthSession | null => {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(ADMIN_SESSION_STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
|
||||
const session = JSON.parse(raw) as AuthSession;
|
||||
if (!isInstructorSession(session) || isStoredSessionExpired(session)) {
|
||||
clearStoredSession();
|
||||
return null;
|
||||
}
|
||||
|
||||
return session;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const storeSession = (session: AuthSession): void => {
|
||||
window.localStorage.setItem(ADMIN_SESSION_STORAGE_KEY, JSON.stringify({
|
||||
...session,
|
||||
obtainedAt: session.obtainedAt ?? Date.now(),
|
||||
}));
|
||||
};
|
||||
|
||||
export const clearStoredSession = (): void => {
|
||||
window.localStorage.removeItem(ADMIN_SESSION_STORAGE_KEY);
|
||||
};
|
||||
|
||||
export const isStoredSessionExpired = (session: AuthSession): boolean => {
|
||||
const obtainedAt = session.obtainedAt ?? 0;
|
||||
if (!obtainedAt) return false;
|
||||
|
||||
if (session.refreshToken && session.refreshTokenExpiresIn) {
|
||||
return Date.now() >= obtainedAt + session.refreshTokenExpiresIn * 1000;
|
||||
}
|
||||
|
||||
return Date.now() >= obtainedAt + (session.accessTokenExpiresIn ?? 0) * 1000;
|
||||
};
|
||||
|
||||
export const loginInstructor = async (email: string, password: string): Promise<AuthSession> => {
|
||||
const response = await fetch(`${AUTH_BASE_URL}/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(response.status === 429 ? '로그인 시도가 너무 많습니다. 잠시 후 다시 시도해주세요.' : '이메일 또는 비밀번호를 확인해주세요.');
|
||||
}
|
||||
|
||||
const session = {
|
||||
...(await response.json()) as AuthSession,
|
||||
obtainedAt: Date.now(),
|
||||
};
|
||||
if (!isInstructorSession(session)) {
|
||||
clearStoredSession();
|
||||
throw new Error('강사 계정만 로그인할 수 있습니다.');
|
||||
}
|
||||
|
||||
storeSession(session);
|
||||
return session;
|
||||
};
|
||||
|
||||
export const refreshInstructorSession = async (session: AuthSession): Promise<AuthSession> => {
|
||||
if (!session.refreshToken) {
|
||||
throw new Error('리프레시 토큰이 없습니다.');
|
||||
}
|
||||
|
||||
const response = await fetch(`${AUTH_BASE_URL}/auth/refresh`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ refreshToken: session.refreshToken })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('세션을 갱신하지 못했습니다.');
|
||||
}
|
||||
|
||||
const nextSession = {
|
||||
...(await response.json()) as AuthSession,
|
||||
obtainedAt: Date.now(),
|
||||
};
|
||||
|
||||
if (!isInstructorSession(nextSession)) {
|
||||
clearStoredSession();
|
||||
throw new Error('강사 계정만 로그인할 수 있습니다.');
|
||||
}
|
||||
|
||||
storeSession(nextSession);
|
||||
return nextSession;
|
||||
};
|
||||
|
||||
export const refreshCurrentUser = async (session: AuthSession): Promise<AuthSession> => {
|
||||
const response = await fetch(`${AUTH_BASE_URL}/auth/me`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('사용자 정보를 다시 가져오지 못했습니다.');
|
||||
}
|
||||
|
||||
const payload = (await response.json()) as { user?: AuthSession['user'] };
|
||||
const nextSession = {
|
||||
...session,
|
||||
user: payload.user ?? session.user
|
||||
};
|
||||
|
||||
if (!isInstructorSession(nextSession)) {
|
||||
clearStoredSession();
|
||||
throw new Error('강사 계정만 로그인할 수 있습니다.');
|
||||
}
|
||||
|
||||
storeSession(nextSession);
|
||||
return nextSession;
|
||||
};
|
||||
|
||||
export const logoutInstructor = async (): Promise<void> => {
|
||||
try {
|
||||
await fetch(`${AUTH_BASE_URL}/auth/logout`, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
} finally {
|
||||
clearStoredSession();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user