대표 화면으로 바꾸면 금액이 보인다. 그렇다면 선택 상자를 직원으로 돌리면 금액을 안전하게 숨긴 것일까. 나는 공개 체험의 화면 구성과 실제 권한을 같은 기능으로 설명하지 않기로 했다.
이번 글은 그 차이에서 출발한다. 현재 체험은 합성 자료의 메뉴를 바꾸어 보여 주고, 새 독립 모듈은 회사·담당·행동을 함께 판단하는 규칙을 코드로 읽게 한다. 실제 회사의 계정과 권한 정책을 옮겨 놓은 것은 아니다.
숨긴 메뉴가 자료의 접근을 막지는 않는다
공개 체험은 직원·담당자·대표를 선택하면 필요한 메뉴와 항목의 설명을 바꾼다. 방문자가 회사에 필요한 구성을 생각해 보도록 만든 기능이다. 선택 자체로 로그인 사용자가 바뀌거나 서버의 조회 권한이 생기지 않는다. 화면에도 실제 권한 제어는 로그인과 API 검사로 별도 구현한다고 적었다. 민감한 자료가 없는 공개 예시이므로 가능한 구성이다.
실제 서비스에서는 화면을 숨겨도 요청을 직접 만들 수 있다는 전제에서 설계해야 한다. 누가 요청했는지 서버에서 확인한 뒤 그 사람이 어느 회사의 어떤 자료에 어떤 행동을 할 수 있는지 검사한다. 나는 읽기 편한 화면을 만드는 단계와 실행을 허용하는 단계를 분리해 설명한다. 어느 쪽도 다른 쪽의 구현을 대신하지 않는다.
대표라는 이름에 모든 행동을 묶지 않았다
회사의 현황을 보는 대표가 주문 수량을 바꿀 필요까지 있는지는 다른 질문이다. 이번 합성 정책에서는 직원은 자기 담당 초안을 읽고 수량을 고칠 수 있고, 담당자는 같은 회사의 초안을 수정·확인할 수 있다. 대표는 같은 회사의 자료를 읽고 내보낼 수 있지만 수정과 확인은 허용하지 않았다. 이는 한 가지 설명용 선택이며 실제 회사의 결재 체계를 일반화한 규칙이 아니다.
복구는 어느 역할에도 기본 허용하지 않았다. 백업을 읽는 것과 현재 자료를 바꾸는 것은 영향 범위가 다르기 때문이다. 실제 복구 담당자나 승인 절차가 필요하면 별도의 권한으로 설계해야 한다. 역할을 높은 순서로 줄 세우기보다 업무별로 허용할 행동을 적으면 대표도 쓰기 편하고 실무자의 책임도 분명한 구성을 논의할 수 있다.
역할보다 먼저 넘지 않아야 할 회사 범위
대표 역할이라도 다른 회사의 자료는 읽을 수 없어야 한다. 새 모듈은 역할별 행동을 보기 전에 회사 범위를 비교하고, 직원에게는 담당 일치 여부도 확인한다. 비활성 사용자나 알 수 없는 역할·행동은 기본 거절한다. 거절 결과에 다른 회사의 자료 내용이나 자세한 존재 여부를 붙이지 않고 같은 거절 형태를 돌려준다.
이 규칙의 입력인 사용자는 신뢰할 수 있는 서버 문맥에서 와야 한다. 브라우저가 보낸 role 값을 그대로 넣으면 누구든 대표라고 주장할 수 있다. 데모의 역할 선택은 이 조건을 비교해 보려는 합성 실험이다. 그 화면이나 순수 함수를 그대로 보안 장벽으로 배포해서는 안 되며 실제 로그인 확인과 서버 연결을 별도로 구현해야 한다.
허용한 행에서도 보낼 필드는 따로 정했다
같은 주문을 읽더라도 직원에게 원가나 내부 메모를 함께 내려보낼 필요는 없을 수 있다. 공유 모듈은 읽기를 허용한 다음에도 반환할 필드를 명시한다. 기본은 식별자·상태·수량이고 대표 예시에만 금액을 더한다. 입력 객체에 내부 메모나 나중에 추가된 필드가 있어도 통째로 복사해 응답하지 않는다. 필드를 화면에서만 감추는 대신 반환 자료의 범위를 좁힌 것이다.
수정할 수 있는 필드도 수량으로만 제시한다. 실무 서버에서는 이 목록을 실제 요청 본문의 허용 목록으로 검사하고 값도 검증해야 한다. 이 모듈은 수정 요청을 저장하지 않는다. 내보내기 역시 한 행이 허용됐다고 전체 자료를 내보내서는 안 된다. 목록 조회·상세·수정·일괄 내보내기가 모두 같은 회사와 자료 경계를 지켜야 한다.
코드와 구현 살펴보기
독립 모듈의 반환 필드 선택 · decideAccess에 의존
export function projectOrder(principal,record) {
if(!decideAccess(principal,'read',record).allowed)return null;
if(!Number.isSafeInteger(record.quantity)||record.quantity<1||record.quantity>99)return null;
const result={id:record.id,status:record.status,quantity:record.quantity};
if(principal.role==='owner'){
if(!Number.isSafeInteger(record.amount)||record.amount<0)return null;
result.amount=record.amount;
}
return result;
}화면의 비활성과 서버의 거절을 함께 설계한다
직원에게 허용하지 않는 버튼을 보여 주지 않는 것은 화면을 단순하게 만든다. 하지만 버튼을 숨기는 것은 편의이고 권한의 최종 판단은 서버에 있어야 한다. 문서가 이미 발송된 상태라면 예전 화면에서 수정 버튼이 남아 있어도 현재 상태를 다시 확인해야 한다. 취소·확인·수정은 그 판단과 실제 쓰기가 어긋나지 않도록 연결해야 한다.
현재 사이트의 관리자 API에는 별도의 인증 경계가 있지만, 그것이 공개 ERP 체험에 다중 역할 서버를 구현했다는 뜻은 아니다. 나는 기존 관리자 인증 구현과 이번 합성 정책을 섞어 완성된 ERP 권한 체계라고 부르지 않는다. 새 모듈에서는 허용·거절과 필드 선택만 독립적으로 시험해 역할을 바꾸었을 때의 규칙을 읽을 수 있게 했다.
금지되어야 할 경우부터 검증했다
테스트는 정상 사용자 한 명의 성공으로 끝내지 않았다. 다른 회사, 다른 담당, 비활성 사용자, 모르는 역할과 행동, 발송된 문서를 조합했다. 세 역할·다섯 행동·두 상태·회사 일치 여부·담당 일치 여부의 120개 조합을 기대 규칙과 대조했다. 대표의 금액 표시와 직원의 제한된 반환 필드, 누구에게도 허용되지 않은 복구도 별도로 확인했다.
이 결과는 합성 정책 함수의 검증이다. 실제 인증 서버나 계정 변경, 권한 회수, 모든 API 경로와 운영 보안 검수를 완료한 것은 아니다. 회사를 위한 설계에서는 누가 무엇을 왜 할 수 있어야 하는지부터 함께 적어야 한다. 그 결정과 테스트를 공유 모듈에서 출발점으로 읽고 자신의 업무에 맞춰 바꿀 수 있도록 남겼다.