wing-ops/frontend/src/common/services/authApi.ts
htlee 8657190578 feat(auth): RBAC 오퍼레이션 기반 2차원 권한 시스템 구현
리소스 가시성(READ/HIDE) 단일 차원에서 리소스 × 오퍼레이션(RCUD) 2차원
권한 모델로 전환하여 세밀한 CRUD 권한 제어 지원.

- DB: AUTH_PERM에 OPER_CD 컬럼 추가, 마이그레이션 004 작성
- DB: AUTH_PERM_TREE 리소스 트리 테이블 추가 (마이그레이션 003)
- Backend: permResolver 2차원 권한 해석 엔진 (상속 + 오퍼레이션)
- Backend: requirePermission 미들웨어 신규 (리소스×오퍼레이션 검증)
- Backend: authService permissions → Record<string, string[]> 반환
- Backend: roleService/roleRouter OPER_CD 지원 API
- Backend: Helmet CORP 설정 (sendBeacon cross-origin 허용)
- Frontend: authStore.hasPermission(resource, operation?) 하위 호환 확장
- Frontend: PermissionsPanel 역할탭 + RCUD 4열 매트릭스 UI 전면 재작성
- Frontend: sendBeacon API_BASE_URL 절대경로 전환
- Docs: COMMON-GUIDE 권한 체계 + CRUD API 규칙 문서화

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 17:55:06 +09:00

279 lines
7.1 KiB
TypeScript

import { api } from './api'
export interface AuthUser {
id: string
account: string
name: string
rank: string | null
org: { sn: number; name: string; abbr: string } | null
roles: string[]
permissions: Record<string, string[]>
}
interface LoginResponse {
success: boolean
user: AuthUser
pending?: boolean
message?: string
}
export async function loginApi(account: string, password: string): Promise<AuthUser> {
const response = await api.post<LoginResponse>('/auth/login', { account, password })
return response.data.user
}
export class PendingApprovalError extends Error {
constructor(message: string) {
super(message)
this.name = 'PendingApprovalError'
}
}
export async function googleLoginApi(credential: string): Promise<AuthUser> {
const response = await api.post<LoginResponse>('/auth/oauth/google', { credential })
if (response.data.pending) {
throw new PendingApprovalError(response.data.message || '관리자 승인 후 로그인할 수 있습니다.')
}
return response.data.user
}
export async function logoutApi(): Promise<void> {
await api.post('/auth/logout')
}
export async function fetchMe(): Promise<AuthUser> {
const response = await api.get<AuthUser>('/auth/me')
return response.data
}
// 사용자 관리 API (ADMIN 전용)
export interface UserListItem {
id: string
account: string
name: string
rank: string | null
orgSn: number | null
orgName: string | null
orgAbbr: string | null
status: string
failCount: number
lastLogin: string | null
roles: string[]
roleSns: number[]
regDtm: string
oauthProvider: string | null
email: string | null
}
export async function fetchUsers(search?: string, status?: string): Promise<UserListItem[]> {
const params = new URLSearchParams()
if (search) params.set('search', search)
if (status) params.set('status', status)
const response = await api.get<UserListItem[]>(`/users?${params}`)
return response.data
}
export async function fetchUser(id: string): Promise<UserListItem> {
const response = await api.get<UserListItem>(`/users/${id}`)
return response.data
}
export async function createUserApi(data: {
account: string
password: string
name: string
rank?: string
orgSn?: number
roleSns?: number[]
}): Promise<{ id: string }> {
const response = await api.post<{ id: string }>('/users', data)
return response.data
}
export async function updateUserApi(id: string, data: {
name?: string
rank?: string
orgSn?: number | null
status?: string
}): Promise<void> {
await api.put(`/users/${id}`, data)
}
export async function changePasswordApi(id: string, password: string): Promise<void> {
await api.put(`/users/${id}/password`, { password })
}
export async function assignRolesApi(id: string, roleSns: number[]): Promise<void> {
await api.put(`/users/${id}/roles`, { roleSns })
}
// 역할/권한 API (ADMIN 전용)
export interface RoleWithPermissions {
sn: number
code: string
name: string
description: string | null
isDefault: boolean
permissions: Array<{
sn: number
resourceCode: string
operationCode: string
granted: boolean
}>
}
export async function fetchRoles(): Promise<RoleWithPermissions[]> {
const response = await api.get<RoleWithPermissions[]>('/roles')
return response.data
}
// 권한 트리 구조 API
export interface PermTreeNode {
code: string
parentCode: string | null
name: string
description: string | null
icon: string | null
level: number
sortOrder: number
children: PermTreeNode[]
}
export async function fetchPermTree(): Promise<PermTreeNode[]> {
const response = await api.get<PermTreeNode[]>('/roles/perm-tree')
return response.data
}
export async function updatePermissionsApi(
roleSn: number,
permissions: Array<{ resourceCode: string; operationCode: string; granted: boolean }>
): Promise<void> {
await api.put(`/roles/${roleSn}/permissions`, { permissions })
}
export async function updateRoleDefaultApi(roleSn: number, isDefault: boolean): Promise<void> {
await api.put(`/roles/${roleSn}/default`, { isDefault })
}
export async function createRoleApi(data: {
code: string
name: string
description?: string
}): Promise<RoleWithPermissions> {
const response = await api.post<RoleWithPermissions>('/roles', data)
return response.data
}
export async function updateRoleApi(
roleSn: number,
data: { name?: string; description?: string }
): Promise<void> {
await api.put(`/roles/${roleSn}`, data)
}
export async function deleteRoleApi(roleSn: number): Promise<void> {
await api.delete(`/roles/${roleSn}`)
}
// 사용자 승인/거절 API (ADMIN 전용)
export async function approveUserApi(id: string): Promise<void> {
await api.put(`/users/${id}/approve`)
}
export async function rejectUserApi(id: string): Promise<void> {
await api.put(`/users/${id}/reject`)
}
// 시스템 설정 API (ADMIN 전용)
export interface RegistrationSettings {
autoApprove: boolean
defaultRole: boolean
}
export async function fetchRegistrationSettings(): Promise<RegistrationSettings> {
const response = await api.get<RegistrationSettings>('/settings/registration')
return response.data
}
export async function updateRegistrationSettingsApi(
settings: Partial<RegistrationSettings>
): Promise<RegistrationSettings> {
const response = await api.put<RegistrationSettings>('/settings/registration', settings)
return response.data
}
// OAuth 설정 API (ADMIN 전용)
export interface OAuthSettings {
autoApproveDomains: string
}
export async function fetchOAuthSettings(): Promise<OAuthSettings> {
const response = await api.get<OAuthSettings>('/settings/oauth')
return response.data
}
export async function updateOAuthSettingsApi(
settings: Partial<OAuthSettings>
): Promise<OAuthSettings> {
const response = await api.put<OAuthSettings>('/settings/oauth', settings)
return response.data
}
// 메뉴 설정 API
export interface MenuConfigItem {
id: string
label: string
icon: string
enabled: boolean
order: number
}
export async function fetchMenuConfig(): Promise<MenuConfigItem[]> {
const response = await api.get<MenuConfigItem[]>('/menus')
return response.data
}
export async function updateMenuConfigApi(menus: MenuConfigItem[]): Promise<MenuConfigItem[]> {
const response = await api.put<MenuConfigItem[]>('/menus', { menus })
return response.data
}
// 감사 로그 API (ADMIN 전용)
export interface AuditLogItem {
logSn: number
userId: string
userName: string | null
userAccount: string | null
actionCd: string
actionDtl: string | null
httpMethod: string | null
crudType: string | null
reqUrl: string | null
reqDtm: string
resDtm: string | null
resStatus: number | null
resSize: number | null
ipAddr: string | null
userAgent: string | null
extra: Record<string, unknown> | null
}
export interface AuditLogListResult {
items: AuditLogItem[]
total: number
page: number
size: number
}
export async function fetchAuditLogs(params?: {
page?: number
size?: number
userId?: string
actionCd?: string
from?: string
to?: string
}): Promise<AuditLogListResult> {
const response = await api.get<AuditLogListResult>('/audit/logs', { params })
return response.data
}