Files
MH-DashBoard-organization/docs/AUTH_DB_DESIGN.md

9.1 KiB

Auth DB Design

Goal

현재 조직도 업무 데이터와 로그인 데이터를 분리한다.

분리 원칙:

  • 업무 데이터는 기존 public.members, seat_maps, seat_positions 중심으로 유지
  • 인증/권한 데이터는 별도 auth 스키마로 분리
  • 로그인 사용자는 필요할 때만 members.id 와 연결

이 방식이면 비밀번호, 세션, 감사로그를 업무 데이터와 분리해서 관리할 수 있고, 엑셀 임포트로 members 가 갱신돼도 인증 체계가 직접 흔들리지 않는다.

Scope

이번 설계의 대상:

  • 사용자 계정
  • 비밀번호 해시
  • 세션
  • 역할과 권한
  • 로그인 감사로그
  • 사용자와 조직 구성원 연결

이번 설계에서 제외:

  • SSO 연동
  • OAuth/OpenID Connect
  • MFA
  • 비밀번호 재설정 메일 발송

권장 구조는 "같은 PostgreSQL, 다른 스키마" 이다.

  • 업무 스키마: public
  • 인증 스키마: auth

초기 운영에서는 DB 인스턴스를 분리하지 않아도 된다. 대신 아래 원칙은 바로 적용한다.

  • 애플리케이션 계정도 가능하면 읽기/쓰기 범위를 분리
  • 인증 관련 쿼리는 auth.* 만 접근
  • 업무 API 는 public.* 중심으로 접근

Core Tables

auth.users

로그인 가능한 계정의 기준 테이블.

주요 컬럼:

  • id BIGSERIAL PRIMARY KEY
  • username TEXT NOT NULL UNIQUE
  • password_hash TEXT NOT NULL
  • display_name TEXT NOT NULL
  • email TEXT
  • status TEXT NOT NULL DEFAULT 'active'
  • member_id INTEGER NULL REFERENCES public.members(id) ON DELETE SET NULL
  • last_login_at TIMESTAMPTZ
  • password_changed_at TIMESTAMPTZ
  • created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
  • updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

상태값 권장:

  • active
  • locked
  • disabled

원칙:

  • username 는 로그인 식별자
  • member_id 는 선택 연결
  • 구성원이 퇴사하거나 엑셀에서 빠져도 계정 자체는 바로 삭제하지 않음

auth.roles

권한 묶음 정의.

주요 컬럼:

  • id BIGSERIAL PRIMARY KEY
  • code TEXT NOT NULL UNIQUE
  • name TEXT NOT NULL
  • description TEXT

초기 권장 역할:

  • super_admin
  • org_admin
  • viewer

auth.user_roles

사용자와 역할의 다대다 연결.

주요 컬럼:

  • user_id BIGINT NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE
  • role_id BIGINT NOT NULL REFERENCES auth.roles(id) ON DELETE CASCADE
  • created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
  • PRIMARY KEY (user_id, role_id)

auth.permissions

세분화된 권한 코드 정의.

주요 컬럼:

  • id BIGSERIAL PRIMARY KEY
  • code TEXT NOT NULL UNIQUE
  • name TEXT NOT NULL
  • description TEXT

초기 권장 권한:

  • member.read
  • member.write
  • member.import
  • seatmap.read
  • seatmap.write
  • photo.upload
  • admin.user.manage

auth.role_permissions

역할과 권한의 다대다 연결.

주요 컬럼:

  • role_id BIGINT NOT NULL REFERENCES auth.roles(id) ON DELETE CASCADE
  • permission_id BIGINT NOT NULL REFERENCES auth.permissions(id) ON DELETE CASCADE
  • created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
  • PRIMARY KEY (role_id, permission_id)

auth.sessions

서버 세션 저장 테이블.

주요 컬럼:

  • id UUID PRIMARY KEY
  • user_id BIGINT NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE
  • refresh_token_hash TEXT
  • issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
  • expires_at TIMESTAMPTZ NOT NULL
  • revoked_at TIMESTAMPTZ
  • ip_address INET
  • user_agent TEXT
  • created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

원칙:

  • 브라우저 쿠키에는 세션 식별자만 저장
  • 토큰 자체를 평문으로 DB에 저장하지 않음
  • 만료와 강제 로그아웃을 DB에서 통제 가능하게 함

auth.login_audit_logs

로그인 시도와 결과 기록.

주요 컬럼:

  • id BIGSERIAL PRIMARY KEY
  • username TEXT NOT NULL
  • user_id BIGINT NULL REFERENCES auth.users(id) ON DELETE SET NULL
  • success BOOLEAN NOT NULL
  • failure_reason TEXT
  • ip_address INET
  • user_agent TEXT
  • created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

용도:

  • 로그인 실패 추적
  • 계정 잠금 기준 판단
  • 보안 감사 대응

Relationship To Current members

핵심은 auth.users.member_id -> public.members.id 연결이다.

의미:

  • 로그인 계정과 조직도 인원을 분리한다
  • 로그인하지 않는 구성원은 members 에만 있어도 된다
  • 외부 관리자 계정은 member_id 없이 운영할 수 있다

권장 규칙:

  • 일반 사내 사용자는 employee_id 기준으로 계정-구성원 연결
  • 엑셀 동기화 시 members.id 유지가 중요하므로 이미 반영한 비교 기반 동기화 방식을 유지
  • member_id 연결이 끊긴 계정은 자동 삭제하지 말고 관리자 검토 대상으로 둔다

Login Flow

1. 로그인 요청

입력:

  • username
  • password

처리:

  • auth.users 에서 username 조회
  • status != active 이면 거부
  • password_hash 검증
  • 성공 시 auth.sessions 생성
  • auth.login_audit_logs 기록

응답 권장:

  • 사용자 기본 정보
  • 역할 목록
  • 권한 목록
  • 세션 만료 시각

2. 인증 확인

각 보호 API 요청 시:

  • 세션 쿠키 또는 Bearer 토큰 확인
  • auth.sessions 조회
  • 만료/폐기 여부 확인
  • 사용자 상태와 역할 재검증

3. 로그아웃

처리:

  • 현재 세션의 revoked_at 업데이트
  • 클라이언트 쿠키 제거

Authorization Model

초기에는 RBAC 기반으로 충분하다.

권장 역할별 범위:

super_admin

  • 사용자 관리
  • 권한 관리
  • 조직도/사진/자리배치 전체 수정

org_admin

  • 조직도 조회/수정
  • 엑셀 임포트
  • 사진 업로드
  • 자리배치 수정

viewer

  • 조직도 조회
  • 자리배치 조회

API 보호 예시:

  • GET /api/members: member.read
  • POST /api/members/import: member.import
  • POST /api/uploads/profile-photo: photo.upload
  • PUT /api/seat-maps/{seat_map_id}/layout: seatmap.write
  • 사용자 관리 API: admin.user.manage

Password Policy

비밀번호는 평문 저장 금지.

권장:

  • Argon2id 우선
  • 대안으로 bcrypt

추가 원칙:

  • 첫 구현부터 해시 알고리즘 버전 정보 포함
  • 비밀번호 변경 시 password_changed_at 갱신
  • 실패 횟수 기반 잠금은 앱 로직 또는 별도 컬럼으로 확장 가능

Migration Plan

Phase 1

인증 스키마와 기본 테이블만 추가.

작업:

  • CREATE SCHEMA IF NOT EXISTS auth
  • auth.users
  • auth.roles
  • auth.user_roles
  • auth.permissions
  • auth.role_permissions
  • auth.sessions
  • auth.login_audit_logs

이 단계에서는 기존 /api/mock-login 유지 가능.

Phase 2

관리자 1명 이상을 수동 생성하고 실제 로그인 API 추가.

권장 추가 API:

  • POST /api/auth/login
  • POST /api/auth/logout
  • GET /api/auth/me

Phase 3

기존 프론트엔드의 mock 로그인 제거.

변경 대상:

Phase 4

권한 기반으로 API 보호 적용.

우선순위:

  1. 쓰기 API 보호
  2. 업로드 API 보호
  3. 읽기 API 권한 정리
CREATE SCHEMA IF NOT EXISTS auth;

CREATE TABLE IF NOT EXISTS auth.users (
    id BIGSERIAL PRIMARY KEY,
    username TEXT NOT NULL UNIQUE,
    password_hash TEXT NOT NULL,
    display_name TEXT NOT NULL,
    email TEXT,
    status TEXT NOT NULL DEFAULT 'active',
    member_id INTEGER NULL REFERENCES public.members(id) ON DELETE SET NULL,
    last_login_at TIMESTAMPTZ,
    password_changed_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS auth.roles (
    id BIGSERIAL PRIMARY KEY,
    code TEXT NOT NULL UNIQUE,
    name TEXT NOT NULL,
    description TEXT
);

CREATE TABLE IF NOT EXISTS auth.user_roles (
    user_id BIGINT NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    role_id BIGINT NOT NULL REFERENCES auth.roles(id) ON DELETE CASCADE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (user_id, role_id)
);

CREATE TABLE IF NOT EXISTS auth.sessions (
    id UUID PRIMARY KEY,
    user_id BIGINT NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    refresh_token_hash TEXT,
    issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at TIMESTAMPTZ NOT NULL,
    revoked_at TIMESTAMPTZ,
    ip_address INET,
    user_agent TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Operational Notes

  • 엑셀 임포트는 계속 public.members 기준으로 처리
  • 로그인 계정 생성은 엑셀 업로드와 분리
  • 프로필 사진은 현재처럼 파일 저장 + members.photo_url 참조 유지 가능
  • 감사로그와 세션은 삭제보다 보존 기간 정책으로 관리

Decision

현재 프로젝트의 권장안은 아래 한 줄로 정리된다.

"로그인 DB는 auth 스키마로 분리하고, 업무 DB는 public 에 유지하며, 두 영역은 auth.users.member_id 로만 연결한다."