본문으로 건너뛰기

IAM 도메인 모델

데이터 저장 규칙

  • IAM 도메인 관련 데이터는 iam PostgreSQL 스키마에 저장되어야 합니다. (business-rules.md 1.1)
  • 사용자 유형에 따라 User 정보는 다른 스키마에 저장됩니다:
    • 고객(환자) 사용자: User 도메인(private 스키마)
    • 내부 운영자, 의료진, 서비스 계정 등: User 도메인(operation 스키마)
  • Plan 정보는 Plan 도메인(plan 스키마)을 참조합니다.
  • Group 정보는 Group 도메인(group 스키마)을 참조합니다.
  • 본 문서의 Prisma 스키마 섹션에는 IAM 도메인이 직접 관리하는 엔티티(Permission, Role, ServiceAccount 관련 테이블)만 정의합니다.
  • User, Plan, Group 엔티티의 전체 Prisma 스키마 정의는 각 도메인의 domain-model.md 문서를 참조하세요.

1. 엔티티 관계도 (ERD)

참고: User, Group, Plan 엔티티는 외부 도메인에 속하며, IAM 도메인은 이들을 참조합니다. 실제 관계 테이블(UserGroup, GroupRole, PlanRole)은 각각의 도메인에서 관리됩니다. 위 ERD는 IAM 도메인 관점에서의 핵심 권한 관리 엔티티와 참조 관계를 보여줍니다.

2. 엔티티

2.1 Permission

/**
* 개별 권한 단위를 나타내는 엔티티.
* ID는 '{domain}:{resource}:{action}' 형식을 따릅니다.
*/
interface Permission {
id: PermissionId; // PK, e.g., "user:profile:read"
description?: string;
createdAt: Date;
updatedAt: Date;
}

2.2 Role

/**
* 여러 권한(Permission)의 집합으로, 특정 책임 단위를 나타내는 엔티티.
*/
interface Role {
id: RoleId; // PK
name: string; // Unique
description?: string;
isActive: boolean; // 활성/비활성 상태
createdAt: Date;
updatedAt: Date;

// 관계 필드 (런타임 로드)
permissions?: Permission[];
plans?: Plan[];
}

2.3 Plan (IAM Domain View)

/**
* 플랜 엔티티 (IAM 도메인 관점).
* 실제 데이터는 `plan.plans` 테이블에 저장되며, Plan 도메인이 관리합니다.
* IAM 도메인은 플랜과 역할의 연결 관계를 통해 사용자 권한을 계산합니다.
*/
interface PlanIamView {
id: PlanId;
name: string;
planType: PlanType;
isActive: boolean;
// Plan 도메인에서 관리하는 기타 필드 (description, metadata 등)
}

2.4 User (IAM Domain View)

/**
* 사용자 엔티티 (IAM 도메인 관점).
* 사용자 유형에 따라 실제 데이터는 다른 스키마에 저장됩니다:
* - 고객(환자) 사용자: `private.users` 테이블
* - 내부 운영자 사용자: `operation.users` 테이블
* User 도메인이 사용자 데이터를 관리하며, IAM은 주로 사용자 ID를 참조하여 권한을 계산합니다.
*/
interface UserIamView {
id: UserId;
userType: UserType; // 사용자 유형에 따라 저장 스키마가 결정됨
// User 도메인에서 관리하는 기타 필드 (email, status 등)
}

/**
* 사용자 유형 (User 도메인과 일치)
*/
enum UserType {
PATIENT = 'PATIENT', // 환자(고객) - private 스키마에 저장
OPERATION_USER = 'OPERATION_USER', // 운영자 - operation 스키마에 저장
}

2.5 Group (IAM Domain View)

/**
* 그룹 엔티티 (IAM 도메인 관점).
* 실제 데이터는 `group.groups` 테이블에 저장되며, Group 도메인이 관리합니다.
* IAM은 그룹 멤버십을 통해 사용자 권한을 파생합니다.
*/
interface GroupIamView {
id: GroupId;
planId: PlanId; // 그룹에 할당된 플랜 ID
groupType: GroupType;
// Group 도메인에서 관리하는 기타 필드 (name, description 등)
}

2.6 RolePermission (Association Entity)

/**
* Role과 Permission 간의 다대다 관계를 나타내는 연결 엔티티.
*/
interface RolePermission {
roleId: RoleId; // Composite PK, FK
permissionId: PermissionId; // Composite PK, FK
assignedAt: Date;
}

2.7 ServiceAccountApiKey

/**
* Service Account의 장기 자격증명(API Key)을 관리하는 엔티티.
* Google Service Account의 Private Key와 동일한 역할을 합니다.
*/
interface ServiceAccountApiKey {
id: string; // PK
serviceAccountId: UserId; // FK, operation.users(id) 참조
keyHash: string; // SHA-256 해시된 API 키 (평문 저장 금지)
isActive: boolean;
createdAt: Date;
lastUsedAt?: Date;
expiresAt?: Date; // 선택적: API Key 만료일
}

2.8 ServiceAccountToken

/**
* Service Account JWT Token의 블랙리스트 관리를 위한 엔티티.
* 무효화된 토큰 추적을 위한 블랙리스트 테이블입니다.
*/
interface ServiceAccountToken {
id: string; // PK, JWT Token ID (jti claim)
serviceAccountId: UserId; // FK, operation.users(id) 참조
tokenHash: string; // SHA-256 해시된 토큰
isRevoked: boolean;
issuedAt: Date;
expiresAt: Date;
revokedAt?: Date;
}

3. 값 객체

3.1 PermissionId

type PermissionId = string; // e.g., "user:profile:read"

3.2 RoleId

type RoleId = string; // UUID or other unique identifier

3.3 UserId

type UserId = string; // From User Domain

3.4 PlanId

type PlanId = string; // From Plan Domain

3.5 GroupId

type GroupId = string; // From Group Domain

3.6 PlanType

/**
* 플랜 유형 (Plan 도메인과 일치).
*/
enum PlanType {
THERAPEUTIC = 'THERAPEUTIC', // 치료 플랜
LIMITED_ACCESS = 'LIMITED_ACCESS', // 제한 접근 플랜
SAMPLE = 'SAMPLE', // 샘플/체험 플랜
DEVELOPMENT = 'DEVELOPMENT', // 개발/테스트 플랜
CLINICIAN = 'CLINICIAN' // 의료진 플랜
}

3.7 GroupType

/**
* 그룹 유형 (Group 도메인과 일치).
*/
enum GroupType {
PATIENT = 'PATIENT', // 환자 그룹
OPERATION = 'OPERATION', // 내부 운영자 그룹
TESTER = 'TESTER', // 테스터 그룹
EXTERNAL = 'EXTERNAL' // 외부 협력사 그룹
}

3.8 EffectivePermissions

/**
* 특정 사용자에 대해 최종적으로 계산된 유효 권한 목록.
*/
interface EffectivePermissions {
userId: UserId;
permissions: Set<PermissionId>;
calculatedAt: Date;
}

4. 집계 (Aggregates)

4.1 Permission Aggregate

  • 루트: Permission
  • 값 객체: PermissionId, Description
  • 불변식: Permission ID는 고유하며 변경 불가.

4.2 Role Aggregate

  • 루트: Role
  • 엔티티: RolePermission (관계)
  • 값 객체: RoleId, RoleName (unique), Description, IsActive
  • 참조: Permission
  • 불변식: Role 이름은 고유해야 함. 활성/비활성 상태 관리. 순환 참조 금지.

4.3 ServiceAccount Aggregate

  • 루트: User (Service Account 유형, Operation 스키마)
  • 엔티티: ServiceAccountApiKey, ServiceAccountToken
  • 값 객체: UserId, ApiKeyHash, TokenHash
  • 불변식:
    • Service Account는 여러 API Key를 가질 수 있음
    • API Key는 해시된 형태로만 저장
    • 무효화된 토큰은 블랙리스트에 유지
    • Service Account는 operation 스키마에만 존재

4.4 UserAccess Aggregate (Conceptual)

  • 루트: User (외부 참조)
  • 참조: Group (외부 참조), Plan (외부 참조), Role (Group 및 Plan 통해), Permission (Role 통해)
  • 값 객체: EffectivePermissions
  • 불변식:
    • 사용자의 최종 권한은 두 경로로 파생됨:
      • 조직/기능적 권한: User -> Group -> Role -> Permission
      • 서비스 수준 권한: User -> Plan -> Role -> Permission
    • 최종 권한은 두 경로의 권한을 병합하여 결정 참고: 실제 집계 루트는 아니며, 사용자의 최종 권한 계산 로직을 개념적으로 나타냅니다.

5. 도메인 서비스

5.1 PermissionService

interface PermissionService {
createPermission(id: PermissionId, description?: string): Promise<Permission>;
getPermission(id: PermissionId): Promise<Permission | null>;
getAllPermissions(page?: number, limit?: number): Promise<Permission[]>;
updatePermission(id: PermissionId, description: string): Promise<Permission>;
deletePermission(id: PermissionId): Promise<void>;
validatePermissionIdFormat(id: PermissionId): boolean;
}

5.2 RoleService

interface RoleService {
createRole(name: string, description?: string, permissions?: PermissionId[]): Promise<Role>;
getRole(id: RoleId): Promise<Role | null>;
getRoleByName(name: string): Promise<Role | null>;
getAllRoles(filter?: { isActive?: boolean }): Promise<Role[]>;
updateRole(id: RoleId, data: { name?: string; description?: string; isActive?: boolean }): Promise<Role>;
deleteRole(id: RoleId): Promise<void>; // Soft delete or check dependencies
assignPermissionToRole(roleId: RoleId, permissionId: PermissionId): Promise<void>;
revokePermissionFromRole(roleId: RoleId, permissionId: PermissionId): Promise<void>;
getRolePermissions(roleId: RoleId): Promise<Permission[]>;
}

5.3 AuthorizationService

/**
* 사용자 권한 검증 및 계산을 담당하는 서비스.
* User, Group, Plan 도메인과의 협력이 필요합니다.
*/
interface AuthorizationService {
checkPermission(userId: UserId, permissionId: PermissionId, context?: any): Promise<boolean>;
getEffectivePermissions(userId: UserId): Promise<EffectivePermissions>;
getUserPlan(userId: UserId): Promise<PlanIamView | null>; // 그룹 정보를 통해 플랜 조회
getUserGroups(userId: UserId): Promise<GroupIamView[]>; // 사용자가 속한 그룹 조회
calculatePermissionsFromPlan(planId: PlanId): Promise<Set<PermissionId>>; // 플랜으로부터 권한 계산
}

5.4 ServiceAccountAuthenticationService

/**
* Service Account 간 인증을 담당하는 서비스.
* JWT 토큰 발급, 검증, 갱신, 무효화 기능을 제공합니다.
*/
interface ServiceAccountAuthenticationService {
issueToken(serviceAccountId: UserId, apiKey: string): Promise<{ token: string; expiresAt: Date }>;
verifyToken(token: string): Promise<{ isValid: boolean; serviceAccountId?: UserId; permissions?: PermissionId[] }>;
refreshToken(token: string): Promise<{ token: string; expiresAt: Date }>;
revokeToken(token: string): Promise<void>;
generateApiKey(serviceAccountId: UserId): Promise<string>;
regenerateApiKey(serviceAccountId: UserId): Promise<string>;
validateApiKey(serviceAccountId: UserId, apiKey: string): Promise<boolean>;
}

5.5 ServiceAccountManagementService

/**
* Service Account 생명주기 관리를 담당하는 서비스.
*/
interface ServiceAccountManagementService {
createServiceAccount(name: string, description?: string, permissions?: PermissionId[]): Promise<{ serviceAccount: UserIamView; apiKey: string }>;
getServiceAccount(id: UserId): Promise<UserIamView | null>;
updateServiceAccount(id: UserId, data: { name?: string; description?: string; isActive?: boolean }): Promise<UserIamView>;
deactivateServiceAccount(id: UserId): Promise<void>;
deleteServiceAccount(id: UserId): Promise<void>;
assignPermissionToServiceAccount(serviceAccountId: UserId, permissionId: PermissionId): Promise<void>;
revokePermissionFromServiceAccount(serviceAccountId: UserId, permissionId: PermissionId): Promise<void>;
getServiceAccountPermissions(serviceAccountId: UserId): Promise<Permission[]>;
}

6. 도메인 이벤트

상세 내용은 event-storming.md 참조

6.1 권한 및 역할 관련 이벤트

  • 권한 관련: PermissionCreated, PermissionUpdated, PermissionDeleted, PermissionVerified, PermissionVerificationFailed, PermissionValidationFailed
  • 역할 관련: RoleCreated, RoleUpdated, RoleDeleted, RoleActivated, RoleDeactivated, PermissionsAssignedToRole, PermissionsRevokedFromRole, RoleValidationFailed

6.2 접근 제어 관련 이벤트

  • 권한 계산: UserEffectivePermissionsCalculated, UserEffectivePermissionsRead, UserPermissionsCacheInvalidated
  • 접근 제어 결정: AccessControlDecisionMade, AuthorizationGranted, AuthorizationDenied
  • 감사 로그: AuditLogRecorded

6.3 도메인 간 협력 이벤트

  • Plan 도메인 연계: PlanRoleAssignmentChanged (Plan 도메인에서 발생, IAM에서 수신)
  • Group 도메인 연계: GroupPlanAssignmentChanged, UserGroupMembershipChanged (Group 도메인에서 발생, IAM에서 수신)
  • User 도메인 연계: UserTypeChanged, UserStatusChanged (User 도메인에서 발생, IAM에서 수신)

6.4 Service Account 관련 이벤트

  • 인증: ServiceAccountTokenIssued, ServiceAccountTokenVerified, ServiceAccountTokenVerificationFailed, ServiceAccountTokenRefreshed, ServiceAccountTokenRevoked
  • API Key 관리: ServiceAccountApiKeyGenerated, ServiceAccountApiKeyRegenerated, ServiceAccountApiKeyRevoked
  • 보안: ServiceAccountPermissionsEvaluated, ServiceAccountAnomalousActivityDetected, ServiceAccountRateLimitExceeded

7. 도메인 규칙

상세 내용은 business-rules.md 참조

주요 규칙 카테고리는 다음과 같습니다:

  1. 일반 규칙: 스키마(iam), 고유성(ID, 이름), 감사 로깅
  2. 권한 규칙: 명명 규칙({domain}:{resource}:{action}), ID 변경 불가
  3. 역할 규칙: 권한 집합, 상태 관리, 순환 참조 금지
  4. 플랜 규칙: 역할 집합, 상태 관리, (현재)주로 환자 대상
  5. 그룹 규칙: 단일 플랜 필수 연결
  6. 사용자 할당 규칙: 최소 1개 그룹 소속 (User 도메인 규칙 연계)
  7. 접근 제어 결정 규칙: 권한 파생 경로 (User -> Group -> Plan -> Role -> Permission)
  8. 보안 규칙: 인증/인가, RBAC, 최소 권한, 암호화, 입력 검증, Rate Limiting
  9. 성능 규칙: 응답 시간 목표, 처리량 목표, 캐싱 전략
  10. 데이터 제약 규칙: 고유성, 관계 제약 (onDelete)
  11. API 호출 제한 규칙: 사용자 유형별 제한
  12. 서비스 간 인증 규칙: API 키 기반 인증, JWT 토큰 관리, 보안 알고리즘(SHA-256, RS256/ES256), 성능 목표(토큰 검증 < 30ms, 초당 2000건 처리), Rate Limiting, 캐싱 전략(5분 TTL), 최소 권한 원칙, 이상 패턴 감지, 생명주기 관리, 서비스별 격리

7.7 사용자 데이터 스키마 분리 규칙

  • 사용자 유형 분류에 따라 다른 스키마에 저장됩니다:
    • PATIENT 유형: private 스키마
    • OPERATION_USER, SERVICE_ACCOUNT 유형: operation 스키마
  • IAM 서비스는 사용자 유형에 따라 적절한 스키마에서 사용자 데이터를 조회합니다.
  • 사용자 유형 변경 시 데이터 마이그레이션 프로세스가 필요합니다.
  • 사용자 유형과 무관하게 IAM 권한 체계는 동일하게 적용됩니다.

8. 데이터베이스 스키마 (Prisma)

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
schemas = ["iam", "private", "group", "plan", "operation"]
}

// --- 외부 도메인 모델 (참조용 stub) ---

// User 모델 (private 스키마) - 환자용
// 실제 정의는 user/domain-model.md 참조
model User {
id String @id @map("id")
userType String @map("user_type")
// ... other fields defined in User domain

@@map("users")
@@schema("private")
}

// User 모델 (operation 스키마) - 내부 운영자용
// 실제 정의는 user/domain-model.md 참조
model OperationUser {
id String @id @map("id")
userType String @map("user_type")
// ... other fields defined in User domain
apiKeys ServiceAccountApiKey[] @relation("ServiceAccountApiKeys")
tokens ServiceAccountToken[] @relation("ServiceAccountTokens")

@@map("users")
@@schema("operation")
}

// Group 모델 (group 스키마)
// 실제 정의는 group/domain-model.md 참조
model Group {
id String @id @map("id")
planId String @map("plan_id") // plan.plans(id) 참조
groupType String @map("group_type")
// ... other fields defined in Group domain

@@map("groups")
@@schema("group")
}

// Plan 모델 (plan 스키마)
// 실제 정의는 plan/domain-model.md 참조
model Plan {
id String @id @map("id")
name String @unique
planType String @map("plan_type")
// ... other fields defined in Plan domain

@@map("plans")
@@schema("plan")
}

// --- IAM 도메인 모델 (iam 스키마) ---

// Permission 모델
model Permission {
id String @id @map("id") // '{domain}:{resource}:{action}' 형식
description String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

// 관계
rolePermissions RolePermission[]

@@map("permissions")
@@schema("iam")
}

// Role 모델
model Role {
id String @id @default(uuid()) @map("id")
name String @unique
description String?
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")

// 관계
rolePermissions RolePermission[]

@@map("roles")
@@schema("iam")
}

// Role-Permission 관계 테이블
model RolePermission {
roleId String @map("role_id")
permissionId String @map("permission_id")
assignedAt DateTime @default(now()) @map("assigned_at")

// 관계
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)

@@id([roleId, permissionId])
@@map("role_permissions")
@@schema("iam")
}

// Service Account API Key 모델 (장기 자격증명)
// Google Service Account의 Private Key와 동일한 역할
model ServiceAccountApiKey {
id String @id @default(uuid()) @map("id")
serviceAccountId String @map("service_account_id") // operation.users(id) 참조
keyHash String @map("key_hash") // SHA-256 해시된 API 키 (평문 저장 금지)
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
lastUsedAt DateTime? @map("last_used_at")
expiresAt DateTime? @map("expires_at") // 선택적: API Key 만료일

// 관계
serviceAccount OperationUser @relation("ServiceAccountApiKeys", fields: [serviceAccountId], references: [id], onDelete: Cascade)

@@map("service_account_api_keys")
@@schema("iam")
}

// Service Account JWT Token 모델 (단기 접근 토큰 블랙리스트 관리용)
// 무효화된 토큰 추적을 위한 블랙리스트 테이블
model ServiceAccountToken {
id String @id @map("id") // JWT Token ID (jti claim)
serviceAccountId String @map("service_account_id") // operation.users(id) 참조
tokenHash String @map("token_hash") // SHA-256 해시된 토큰
isRevoked Boolean @default(false) @map("is_revoked")
issuedAt DateTime @map("issued_at")
expiresAt DateTime @map("expires_at")
revokedAt DateTime? @map("revoked_at")

// 관계
serviceAccount OperationUser @relation("ServiceAccountTokens", fields: [serviceAccountId], references: [id], onDelete: Cascade)

@@map("service_account_tokens")
@@schema("iam")
}

참고: Prisma 스키마에서는 환자와 내부 운영자를 위한 별도의 스키마와 관계를 정의했습니다. 실제 구현 시 애플리케이션 레벨에서 사용자 유형에 따라 적절한 스키마의 테이블에 접근하는 로직이 필요합니다.

8. MAO 티어/동의 연계 모델 확장

  • PolicyEvaluationContext/TokenClaims 모델에 tier(anonymous/guest/member), consent_version, allowed_domains, data_persistence 필드를 추가한다.
  • Agent/Tool 리소스 메타데이터에 requiredTier/requiresConsent 속성을 추가해 정책 엔진이 평가하도록 한다.
  • TokenRevocation/PolicyCache 엔티티에 tier/consent_version을 기록해 guest→member 승격 및 consent 업데이트 시 캐시 무효화 근거로 사용한다.

9. 변경 이력

버전날짜작성자변경 내용
0.1.02025-04-20bok@weltcorp.com최초 문서 생성
0.2.02025-01-27bok@weltcorp.comGroup-Plan 독립성 반영: ERD 수정, 권한 계산 경로 분리
0.2.02025-05-07bok@weltcorp.com사용자 유형 분리에 따른 스키마 저장 요구사항 추가: 환자는 'private' 스키마, 내부 운영자는 'operation' 스키마에 저장
0.3.02025-06-13bok@weltcorp.com서비스 간 인증 관련 도메인 서비스, 이벤트, 엔티티 추가 (ServiceAccountAuthenticationService, ServiceAccountManagementService, ServiceAccountApiKey, ServiceAccountToken)
0.4.02025-06-14bok@weltcorp.comUser 도메인으로부터 Service Account 관련 내용 이관 완료, SERVICE_ACCOUNT 유형 및 관련 서비스 통합 관리
0.5.02025-07-15bok@weltcorp.comPlan 및 Group 관련 내용을 각각의 도메인으로 분리, IAM 도메인은 Permission/Role/Authorization에 집중
0.6.02025-11-26bok@weltcorp.comMAO 라우팅을 위한 tier/consent/allowed_domains/data_persistence 클레임과 Agent/Tool requiredTier 메타데이터, 캐시 무효화 근거 필드 추가