콘텐츠로 이동

Identity

Umbra 에서 사용자를 식별하고 인증하는 도메인. Discord 사용자와 1:1 매핑된 내부 User 계정을 관리하며, Discord OAuth2 로 인증하고 세션을 유지한다.

Bounded context

  • Type — Supporting
  • Sibling contexts — Guild, Member, Billing (결제 주체 = User)
  • Location in codebaseengine/identity/

Why this domain exists

Discord User ID 를 그대로 외부에 노출하거나 도메인 키로 사용하면 두 가지 문제가 발생한다.

첫째, 사용자 탈퇴/재가입 처리의 모호성. Discord 는 같은 사용자가 계정을 삭제하고 재가입하면 새 ID 를 발급한다. 내부 User 엔티티가 Discord ID 와 직결되면 탈퇴 이력 관리가 어렵다.

둘째, 결제 주체의 독립성. Hybrid 모델(ADR-0011)에서 결제는 User 단위이며 빌링키를 소유한다. Discord ID 가 아니라 Umbra 내부 User ID (UUID v7) 를 사용해야 Toss customerKey 같은 외부 참조를 안정적으로 유지할 수 있다.

따라서 Identity 는 "Discord 세계" 와 "Umbra 세계" 를 연결하는 경계 역할을 한다.

Domain model

User

Umbra 의 사용자 계정. Discord User 와 1:1 매핑된다.

User
├─ id               UUID v7       PK
├─ discord_user_id  TEXT          Discord snowflake, UNIQUE
├─ username         TEXT          최근 Discord username (캐시)
├─ avatar_hash      TEXT          Discord avatar hash (캐시)
├─ locale           TEXT          사용자 언어 설정 (ko, en 등)
├─ created_at       TIMESTAMPTZ
├─ updated_at       TIMESTAMPTZ
└─ deleted_at       TIMESTAMPTZ   soft delete (탈퇴 이력)
  • usernameavatar_hash 는 표시 목적 캐시. source of truth 는 Discord.
  • deleted_at 은 soft delete — 탈퇴 후에도 과거 결제/감사 기록 참조 유지.

Session

OAuth2 로그인 후 생성되는 웹 세션.

Session
├─ id               TEXT          Session ID (cryptographic random)
├─ user_id          UUID v7       → users.id
├─ discord_access_token   TEXT    암호화
├─ discord_refresh_token  TEXT    암호화
├─ access_expires_at      TIMESTAMPTZ
├─ user_agent       TEXT
├─ ip_address       INET          (GDPR 고려 — 단기 보관만)
├─ created_at       TIMESTAMPTZ
└─ expires_at       TIMESTAMPTZ   TTL (7일 기본)

Session 은 Redis 에 저장되며 (session:{id}), DB 는 감사 목적의 그림자 레코드.

Aggregates

  • User aggregate — User + 자신의 Session 목록
  • Billing 에서 사용하는 BillingKey 는 여기 aggregate 가 아님 (Billing 도메인 소유)

Invariants

  • Discord User 1:1UNIQUE(discord_user_id) DB 제약
  • Active session ≤ N per user — 과도한 세션 발급 방지 (기본 10개, 초과 시 가장 오래된 세션 폐기)
  • Soft delete — User 삭제 시 deleted_at 만 설정, row 유지
  • Token encryptiondiscord_access_token, discord_refresh_token 은 항상 암호화 저장

State machine

User 의 상태:

stateDiagram-v2
    [*] --> Active : OAuth2 first login
    Active --> Deleted : User requests deletion
    Deleted --> Active : Re-login with same Discord ID

Session 의 상태:

stateDiagram-v2
    [*] --> Active : Login
    Active --> Expired : TTL reached
    Active --> Revoked : Logout or admin action
    Expired --> [*]
    Revoked --> [*]

Domain events

Published

Event Trigger Payload Subscribers
UserRegistered 신규 User 생성 user_id, discord_user_id Audit
UserSessionCreated 새 세션 생성 user_id, session_id (IP 마스킹) Audit
UserDeleted User soft delete user_id Billing (빌링키 처리), Audit

Consumed

Identity 는 다른 도메인의 이벤트를 구독하지 않는다. 가장 상류 도메인.

Ports

Identity 는 Supporting Context 이므로 단순 구조다. 공식 Port 분리는 최소화.

Inbound

// engine/identity/service.go
type Service interface {
    AuthenticateWithDiscord(ctx, authCode string) (*User, *Session, error)
    GetUser(ctx, userID) (*User, error)
    GetUserByDiscordID(ctx, discordID string) (*User, error)
    CreateSession(ctx, userID) (*Session, error)
    RevokeSession(ctx, sessionID) error
    DeleteUser(ctx, userID) error
}

Outbound

  • DiscordOAuth2Client — Discord OAuth2 API 호출 (engine/identity/adapter/discord/)
  • UserRepository — sqlc 기반 (engine/identity/adapter/persistence/)
  • SessionStore — Redis (engine/identity/adapter/session/)

Adapters

  • Persistenceengine/identity/adapter/persistence/ — sqlc 래퍼, identity schema
  • Discordengine/identity/adapter/discord/ — Discord OAuth2 token exchange, user fetch
  • Sessionengine/identity/adapter/session/ — Redis set/get/del

Permission model

Identity 는 다른 도메인의 권한 기반. 자체 권한은 단순:

  • User 자신의 데이터 조회/수정만 허용
  • 관리자 기능은 없음 (Umbra 운영자는 별도 시스템 경유)

다른 도메인이 "이 User 가 이 Guild 의 Manage Server 권한을 가진가" 를 확인하려면 Discord API 를 경유 (Identity 가 알지 못함).

Failure modes

  • Discord OAuth2 장애 — 로그인 불가. 기존 세션은 유효하므로 서비스 가용성은 부분 유지.
  • Access token 만료 — Refresh token 으로 자동 갱신. Refresh 도 실패하면 재로그인 요구.
  • Redis 장애 — 신규 세션 생성 불가. 기존 DB 에 그림자 레코드로 세션 복구 가능성 (Phase 2 고려).
  • 빌링키 연결된 User 삭제UserDeleted 이벤트 → Billing 이 빌링키 삭제 + Subscription suspend.

See also

  • data/identity-schema.md — DB 스키마 상세
  • flows/web-join.md — OAuth2 로그인 흐름
  • domain/billing.md — 결제 주체로서 User 사용
  • adr/0011-hybrid-license-model.md — User 의 역할