summaryrefslogtreecommitdiff
path: root/lib/nonsap/nonsap-auth-plan.md
diff options
context:
space:
mode:
Diffstat (limited to 'lib/nonsap/nonsap-auth-plan.md')
-rw-r--r--lib/nonsap/nonsap-auth-plan.md181
1 files changed, 181 insertions, 0 deletions
diff --git a/lib/nonsap/nonsap-auth-plan.md b/lib/nonsap/nonsap-auth-plan.md
new file mode 100644
index 00000000..fab5e7da
--- /dev/null
+++ b/lib/nonsap/nonsap-auth-plan.md
@@ -0,0 +1,181 @@
+# 외부 시스템(NONSAP) 연동 권한 관리 구현 계획 (v2)
+
+본 문서는 외부 오라클 시스템(NONSAP)의 뷰 테이블을 연동하여 프로젝트의 권한 관리를 처리하기 위한 구현 계획입니다.
+
+## 1. 개요
+
+- **목표**: 외부 시스템에서 관리되는 화면 및 권한 정보를 조회하여, 웹 애플리케이션의 페이지 접근 제어 및 기능별 권한 제어를 수행합니다.
+- **환경**: Node.js Runtime (Not Edge/FaaS).
+- **DB 연결**: `lib/oracle-db/db.ts`의 `oracleKnex`를 사용하여 오라클 DB에 접속합니다.
+
+## 2. 데이터 구조 및 관계
+
+제공된 4개의 뷰 테이블을 사용하여 권한을 판단합니다.
+
+### 2.1 테이블 정의
+
+1. **`CMCVW_SCR_EVCP` (화면 목록)**
+ * **역할**: 시스템의 모든 화면과 해당 URL을 정의합니다.
+ * **주요 컬럼**: `SCR_ID` (PK), `SCR_URL`, `SCRT_CHK_YN`, `DEL_YN`
+
+2. **`CMCVW_SCR_AUTH_EVCP` (화면 권한)**
+ * **역할**: 각 화면(`SCR_ID`)에 대해 접근 가능한 대상(`ACSR_ID`)과 권한 상세(`AUTH_CD`)를 정의합니다.
+ * **주요 컬럼**: `SCR_ID` (FK), `ACSR_GB_CD` (접근자 구분), `ACSR_ID`, `AUTH_CD_*` (권한 플래그)
+ * **ACSR_GB_CD 유형**:
+ * `'U'`: 사용자 (User)
+ * `'R'`: 역할 (Role)
+ * `'D'`: 부서 (Department - 하위 미포함)
+ * `'E'`: 부서 (Department - 하위 포함)
+
+3. **`CMCVW_ROLE_EVCP` (역할 목록)**
+ * **역할**: 시스템에 존재하는 역할(Role)을 정의합니다.
+ * **주요 컬럼**: `ROLE_ID` (PK), `ROLE_NM`
+
+4. **`CMCVW_ROLE_REL_EVCP` (역할-사용자 매핑)**
+ * **역할**: 각 역할(`ROLE_ID`)에 소속된 사용자(`EMPNO`)를 정의합니다.
+ * **주요 컬럼**: `ROLE_ID` (FK), `EMPNO`
+
+## 3. 구현 상세
+
+### 3.1 데이터 페칭 및 캐싱 전략
+
+* **DB Client**: `lib/oracle-db/db.ts`의 `oracleKnex` 사용.
+* **캐싱**: `unstable_cache` (Next.js)를 사용하여 DB 부하를 줄이고 응답 속도를 확보합니다. (TTL: 60초)
+
+### 3.2 권한 검증 함수 설계
+
+URL 인자를 선택적으로 받을 수 있도록 설계하여, 호출 편의성을 높입니다.
+
+```typescript
+// lib/nonsap/auth-service.ts
+
+export type AuthAction = 'SEARCH' | 'ADD' | 'DEL' | 'SAVE' | 'PRINT' | 'DOWN' | 'UP' | 'APPROVAL' | 'PREV' | 'NEXT';
+
+/**
+ * 위 Action의 의미는 아래와 같다.
+ * - 조회
+ * - 추가
+ * - 삭제
+ * - 저장
+ * - 출력
+ * - 파일받기
+ * - 파일올림
+ * - 결재상신
+ * - PrevPage
+ * - NextPage
+ */
+
+interface AuthCheckResult {
+ authorized: boolean;
+ message?: string;
+}
+
+/**
+ * NONSAP 권한 검증 함수
+ * @param empNo 사용자 사번
+ * @param requiredActions 필요한 권한 목록 (예: ['SEARCH'])
+ * @param url (Optional) 검증할 URL. 생략 시 현재 요청의 URL을 자동으로 감지 시도.
+ */
+export async function verifyNonsapPermission(
+ empNo: string,
+ requiredActions: AuthAction[],
+ url?: string,
+ options?: { logic?: 'AND' | 'OR' } // default: 'AND'
+): Promise<AuthCheckResult> {
+ // 1. URL 결정
+ const targetUrl = url || await getCurrentUrlFromHeaders();
+ if (!targetUrl) {
+ throw new Error("URL을 확인할 수 없습니다. URL 인자를 명시해주세요.");
+ }
+
+ // 2. 권한 검증 로직 수행 (oracleKnex 사용)
+ // ...
+}
+
+// Helper: 헤더에서 URL 추출 (Middleware 사전 작업 필요)
+import { headers } from 'next/headers';
+async function getCurrentUrlFromHeaders(): Promise<string | null> {
+ const headerList = headers();
+ // Middleware에서 'x-pathname' 헤더를 심어주어야 함
+ return headerList.get('x-pathname') || null;
+}
+```
+
+### 3.3 권한 검증 로직 (Core Logic)
+
+1. **화면 식별**:
+ * `CMCVW_SCR_EVCP`에서 `SCR_URL`이 `targetUrl`과 일치하는 행 조회 (`oracleKnex` 사용).
+ * `DEL_YN` == 'N', `SCRT_CHK_YN` == 'Y' 확인.
+ * 일치 항목 없으면: 관리되지 않는 페이지로 간주 -> **Pass**.
+
+2. **권한 확인 (ACSR_GB_CD 유형별 처리)**:
+ * `CMCVW_SCR_AUTH_EVCP` 조회 (`SCR_ID` 기준). 조회된 각 권한 레코드(`authRecord`)에 대해 다음 로직 수행:
+
+ * **Case U (User)**: `ACSR_GB_CD == 'U'`
+ * `authRecord.ACSR_ID`가 대상 사용자의 `nonsap_user_id` (Postgres `users` 테이블)와 일치하는지 확인.
+
+ * **Case R (Role)**: `ACSR_GB_CD == 'R'`
+ * `authRecord.ACSR_ID` (Role ID)가 `CMCVW_ROLE_REL_EVCP` 테이블에서 해당 사용자의 사번(`EMPNO`)과 매핑되어 있는지 확인.
+
+ * **Case D (Department - Exact)**: `ACSR_GB_CD == 'D'`
+ * `authRecord.ACSR_ID` (Dept Code)가 대상 사용자의 `deptCode` (Postgres `users` 테이블)와 정확히 일치하는지 확인.
+
+ * **Case E (Department - Recursive)**: `ACSR_GB_CD == 'E'`
+ * 사용자의 부서(`user.deptCode`)부터 시작하여 상위 부서로 거슬러 올라가며 확인.
+ * **데이터 소스**: `db/schema/knox/organization.ts`의 `organization` 테이블 (Knox 조직도).
+ * **Logic**:
+ 1. `currentDeptCode = user.deptCode`
+ 2. Loop:
+ * `currentDeptCode == authRecord.ACSR_ID` 이면 **Match (권한 있음)**.
+ * `organization` 테이블에서 `departmentCode == currentDeptCode`인 행 조회.
+ * 상위 부서 코드(`uprDepartmentCode`) 확인.
+ * **종료 조건 (권한 없음)**:
+ * `uprDepartmentCode` is NULL
+ * `uprDepartmentCode == currentDeptCode` (자기 참조)
+ * `uprDepartmentCode == 'TOP'`
+ * `currentDeptCode = uprDepartmentCode` 로 갱신하고 반복.
+
+3. **Action 체크**:
+ * **Step 3-1. 권한 통합 (Merging)**:
+ * 위 단계에서 **Match**된 모든 권한 레코드들의 `AUTH_CD_*` 컬럼 값을 **OR 연산(Union)**하여 사용자의 '최종 보유 권한'을 산출합니다.
+ * 예: Role A(Search=Y, Save=N) + Role B(Search=N, Save=Y) => 최종(Search=Y, Save=Y).
+ * **Step 3-2. 요구사항 검증 (Checking)**:
+ * `requiredActions` 목록과 '최종 보유 권한'을 비교합니다.
+ * **AND 모드 (Default)**: `requiredActions`의 **모든** 항목에 대해 권한이 있어야 통과 (`every`). (보안상 안전)
+ * **OR 모드**: `requiredActions` 중 **하나라도** 권한이 있으면 통과 (`some`). (메뉴 노출 등 UI 제어용)
+
+## 4. 단계별 실행 계획
+
+### Step 1: Middleware 설정 (URL 감지용)
+* `middleware.ts`에서 요청 URL의 pathname을 `x-pathname` 헤더에 담아 넘겨주는 로직 추가.
+* 이 헤더는 Server Component/Action에서 `headers()`를 통해 접근 가능해짐.
+
+### Step 2: 데이터 접근 레이어 구현 (`lib/nonsap/db.ts`)
+* `oracleKnex`를 사용하여 4개 테이블을 조회하는 함수 구현.
+* `unstable_cache` 적용.
+
+### Step 3: 권한 서비스 구현 (`lib/nonsap/auth-service.ts`)
+* `verifyNonsapPermission` 함수 구현.
+* URL 자동 감지 로직 포함.
+* 부서 계층 구조 조회 로직(Case E) 구현 (Knox Organization 테이블 연동).
+
+### Step 4: 적용 (Layout/Page/Action)
+* **페이지 접근 제어**: `app/[lng]/layout.tsx` 또는 각 Page 컴포넌트 상단에서 `verifyNonsapPermission(..., ['SEARCH'])` 호출.
+* **기능 제어**: Server Action 내부에서 `verifyNonsapPermission(..., ['SAVE'])` 등 호출.
+
+## 5. 이슈 및 고려사항
+
+1. **URL 매핑 정규화**:
+ * DB의 `SCR_URL`은 `/partners/dashboard` 형태이나, 실제 요청은 `/[lng]/partners/dashboard` 일 수 있음.
+ * Middleware 또는 Helper 함수에서 `lng` 파트(예: `/en`, `/ko`)를 제거하고 매칭하는 로직 필요.
+
+2. **Oracle DB 의존성**:
+ * `oracledb` 라이브러리는 Node.js 환경에서만 동작하므로, 권한 체크 로직은 반드시 **Server Component** 또는 **Server Action** 레벨에서 수행되어야 함. (Middleware에서 직접 DB 조회 불가)
+
+3. **부서 계층 정보**:
+ * Knox Organization 테이블(`db/schema/knox/organization.ts`)을 사용하여 상위 부서 정보를 조회합니다.
+
+## 구현 한 부분
+
+evcp 경로의 layout.tsx에서 SEARCH 권한 있는지 authority check 함
+없으면 toast로 없다고 경고만 해줌 (nonsap측에 모든 설정 다 한건 아닐 테니) \ No newline at end of file