리소스 가시성(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>
279 lines
7.1 KiB
TypeScript
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
|
|
}
|