IAM 도메인 모델
데이터 저장 규칙
- IAM 도메인 관련 데이터는
iamPostgreSQL 스키마에 저장되어야 합니다. (business-rules.md 1.1) - 사용자 유형에 따라 User 정보는 다른 스키마에 저장됩니다:
- 고객(환자) 사용자: User 도메인(
private스키마) - 내부 운영자, 의료진, 서비스 계정 등: User 도메인(
operation스키마)
- 고객(환자) 사용자: User 도메인(
- 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 참조
주요 규칙 카테고리는 다음과 같습니다:
- 일반 규칙: 스키마(
iam), 고유성(ID, 이름), 감사 로깅 - 권한 규칙: 명명 규칙(
{domain}:{resource}:{action}), ID 변경 불가 - 역할 규칙: 권한 집합, 상태 관리, 순환 참조 금지
- 플랜 규칙: 역할 집합, 상태 관리, (현재)주로 환자 대상
- 그룹 규칙: 단일 플랜 필수 연결
- 사용자 할당 규칙: 최소 1개 그룹 소속 (User 도메인 규칙 연계)
- 접근 제어 결정 규칙: 권한 파생 경로 (
User -> Group -> Plan -> Role -> Permission) - 보안 규칙: 인증/인가, RBAC, 최소 권한, 암호화, 입력 검증, Rate Limiting
- 성능 규칙: 응답 시간 목표, 처리량 목표, 캐싱 전략
- 데이터 제약 규칙: 고유성, 관계 제약 (
onDelete) - API 호출 제한 규칙: 사용자 유형별 제한
- 서비스 간 인증 규칙: 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.0 | 2025-04-20 | bok@weltcorp.com | 최초 문서 생성 |
| 0.2.0 | 2025-01-27 | bok@weltcorp.com | Group-Plan 독립성 반영: ERD 수정, 권한 계산 경로 분리 |
| 0.2.0 | 2025-05-07 | bok@weltcorp.com | 사용자 유형 분리에 따른 스키마 저장 요구사항 추가: 환자는 'private' 스키마, 내부 운영자는 'operation' 스키마에 저장 |
| 0.3.0 | 2025-06-13 | bok@weltcorp.com | 서비스 간 인증 관련 도메인 서비스, 이벤트, 엔티티 추가 (ServiceAccountAuthenticationService, ServiceAccountManagementService, ServiceAccountApiKey, ServiceAccountToken) |
| 0.4.0 | 2025-06-14 | bok@weltcorp.com | User 도메인으로부터 Service Account 관련 내용 이관 완료, SERVICE_ACCOUNT 유형 및 관련 서비스 통합 관리 |
| 0.5.0 | 2025-07-15 | bok@weltcorp.com | Plan 및 Group 관련 내용을 각각의 도메인으로 분리, IAM 도메인은 Permission/Role/Authorization에 집중 |
| 0.6.0 | 2025-11-26 | bok@weltcorp.com | MAO 라우팅을 위한 tier/consent/allowed_domains/data_persistence 클레임과 Agent/Tool requiredTier 메타데이터, 캐시 무효화 근거 필드 추가 |