콘텐츠로 이동

Identity Schema

identity schema 는 Umbra User 와 Session 을 저장한다. Discord OAuth2 인증의 source of truth.

Schema purpose

  • PostgreSQL schemaidentity
  • Corresponding domainengine/identity/
  • sqlc packagedb/queries/identity/

User 는 결제(Billing), 길드 관리(Guild), 감사 로그(Audit) 등 여러 도메인이 참조하는 상류 entity. Session 은 대시보드 인증 상태 추적.

Tables

identity.users

Umbra 사용자 계정.

CREATE TABLE identity.users (
    id                UUID PRIMARY KEY,
    discord_user_id   TEXT NOT NULL,
    username          TEXT NOT NULL,
    avatar_hash       TEXT,
    locale            TEXT NOT NULL DEFAULT 'ko',
    created_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at        TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    deleted_at        TIMESTAMPTZ,
    CONSTRAINT users_discord_user_id_unique UNIQUE (discord_user_id)
);

Columns

Column Type Constraints Description
id UUID v7 PK Umbra 내부 User ID
discord_user_id TEXT UNIQUE, NOT NULL Discord snowflake (string)
username TEXT NOT NULL 최근 Discord username (캐시)
avatar_hash TEXT Discord avatar hash (캐시, NULL 가능)
locale TEXT NOT NULL UI 언어 (ko, en 등)
created_at TIMESTAMPTZ NOT NULL 계정 생성 시각
updated_at TIMESTAMPTZ NOT NULL 마지막 업데이트
deleted_at TIMESTAMPTZ Soft delete 시각, NULL 이면 활성

Indexes

Index Columns Purpose
users_pkey (id) PK
users_discord_user_id_unique (discord_user_id) Discord ID 조회
users_deleted_at_idx (deleted_at) WHERE deleted_at IS NULL 활성 사용자 조회

Invariants

  • Discord User 와 1:1 매핑
  • Soft delete 원칙 — DELETE 대신 deleted_at 설정

identity.sessions

OAuth2 세션 그림자 레코드. source of truth 는 Redis.

CREATE TABLE identity.sessions (
    id                      TEXT PRIMARY KEY,
    user_id                 UUID NOT NULL REFERENCES identity.users(id),
    encrypted_access_token  BYTEA NOT NULL,
    encrypted_refresh_token BYTEA NOT NULL,
    access_expires_at       TIMESTAMPTZ NOT NULL,
    user_agent              TEXT,
    ip_address              INET,
    created_at              TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at              TIMESTAMPTZ NOT NULL,
    revoked_at              TIMESTAMPTZ
);

Columns

Column Type Constraints Description
id TEXT PK 랜덤 session ID (32 bytes base64)
user_id UUID FK, NOT NULL users.id
encrypted_access_token BYTEA NOT NULL AES-256-GCM 암호화
encrypted_refresh_token BYTEA NOT NULL AES-256-GCM 암호화
access_expires_at TIMESTAMPTZ NOT NULL Discord access token 만료
user_agent TEXT 브라우저 정보 (감사용)
ip_address INET 로그인 IP (단기 보관)
created_at TIMESTAMPTZ NOT NULL 세션 생성
expires_at TIMESTAMPTZ NOT NULL 세션 만료
revoked_at TIMESTAMPTZ 명시적 폐기 시각

Indexes

Index Columns Purpose
sessions_pkey (id) PK
sessions_user_id_idx (user_id) User 별 세션 조회
sessions_expires_at_idx (expires_at) 만료 세션 정리 cron

Foreign keys

Column References On delete
user_id identity.users(id) RESTRICT (soft delete 원칙)

Invariants

  • expires_at 은 항상 created_at 보다 늦음
  • revoked_at IS NOT NULL 이면 세션 무효
  • IP 주소는 GDPR 고려해 30일 후 NULL 로 초기화 (cron)

Relationships

erDiagram
    USERS ||--o{ SESSIONS : has
    USERS ||--o{ BILLING_KEYS : owns
    USERS ||--o{ SUBSCRIPTIONS : pays
    USERS ||--o{ GUILD_OWNERS : owns

Cross-schema references

이 schema 의 User 를 참조하는 다른 schema:

From To Semantics
billing.billing_keys.user_id identity.users.id User 가 빌링키 소유
billing.subscriptions.payer_user_id identity.users.id 결제 주체
guild.guilds.owner_user_id identity.users.id 길드 owner (nullable)
audit.events.actor_user_id identity.users.id 이벤트 발생 주체

Query patterns

주요 쿼리 (db/queries/identity/*.sql):

  • GetUserByID — User PK 조회
  • GetUserByDiscordID — Discord ID 로 조회 (로그인 경로)
  • UpsertUser — 로그인 시 username/avatar 캐시 갱신
  • SoftDeleteUser — 탈퇴 처리
  • CreateSession — 세션 그림자 레코드 생성
  • RevokeSession — 로그아웃
  • CleanupExpiredSessions — cron 으로 만료 세션 삭제

Data retention

  • Users — 영구 보관 (결제/감사 참조). soft delete 후에도 삭제 안 함.
  • Sessionsexpires_at + 7일 경과 시 물리 삭제 (cron)
  • IP 주소 — 30일 후 NULL (GDPR)
  • Token (암호화) — 세션 종료 시 즉시 삭제

Migration history

Date Change Rationale
2026-04-xx 초기 스키마 MVP 구현

See also

  • domain/identity.md — Identity 도메인
  • data/schema-overview.md — 전체 스키마 관계
  • adr/0021-pk-uuid-v7.md — UUID v7 PK
  • adr/0020-postgres-schema-per-domain.md — schema 분리 원칙