작업 내용 저장
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
# 0. 문서 상태
|
||||
이 문서의 현재 상태와 식별 정보를 기록한다.
|
||||
|
||||
- 상태: confirmed
|
||||
- 문서 ID: sdlc-002_20260423_service-token-user-based
|
||||
- 이슈명: service-token user-based 소유권
|
||||
- 참조 Design: docs/sdlc/sdlc-002_20260423_service-token-user-based/3_design.md
|
||||
- 작성 시각(KST): 2026-04-23
|
||||
- 마지막 갱신 시각(KST): 2026-04-23 (confirmed)
|
||||
|
||||
# 1. Design 인계 요약
|
||||
Design에서 확정된 핵심 내용을 요약한다. 원본은 3_design.md를 참조하며, 이 섹션은 Implementation 작업의 입력 기준선이 된다.
|
||||
|
||||
## 이슈 목적
|
||||
`bot_principals` / `auth_audit_logs` 테이블을 제거하고, service token을 `auth_tokens` 단일 엔티티로 직접 발급하도록 재구현한다. CLI `pfhub service-token issue <name> --user <username>` 명령으로 사용자 기반 소유권을 명확히 한다.
|
||||
|
||||
## 확정 태스크 및 변경 대상
|
||||
| ID | 태스크 제목 | 변경 대상 파일/함수 | 담당자 |
|
||||
|----|-----------|-------------------|--------|
|
||||
| T-1 | DB 마이그레이션 파일 작성 | `migrations/versions/20260423_0013_remove_bot_principals_service_tokens.py` (신규) | copilot |
|
||||
| T-2 | 도메인/스키마/인터페이스 정리 | `app/domain/models.py`, `app/schemas/auth.py`, `app/repositories/portfolio_repository.py` | copilot |
|
||||
| T-3 | postgres.py 구현체 정리 | `app/repositories/postgres.py` | copilot |
|
||||
| T-4 | AuthContext/security.py 변경 | `app/core/auth.py`, `app/core/security.py` | copilot |
|
||||
| T-5 | AuthService 재구현 | `app/services/auth_service.py` | copilot |
|
||||
| T-6 | API 경로 정리 | `app/api/auth_routes.py` | copilot |
|
||||
| T-7 | CLI 재구현 | `app/cli/commands/bot_tokens.py` (삭제), `app/cli/commands/service_tokens.py` (신규), `app/cli/core/output.py`, `app/cli/main.py` | copilot |
|
||||
| T-8 | 테스트 업데이트 | `tests/test_auth.py`, `tests/test_cli.py`, `tests/test_portfolio_ownership.py`, `tests/test_users.py` | copilot |
|
||||
| T-9 | 기술 문서 업데이트 | `docs/technical/auth-and-token-lifecycle.md`, `docs/technical/api-contracts.md`, `docs/technical/management-cli-operations.md`, `README.md` | copilot |
|
||||
|
||||
## 이월된 오픈 질문 / 리스크
|
||||
없음. Design 단계에서 모든 OQ 해소 완료.
|
||||
|
||||
# 2. 구현 계획
|
||||
태스크(T-N)별 구현 순서와 접근 방법을 기술한다.
|
||||
|
||||
## 구현 순서
|
||||
태스크 간 의존 관계에 따른 순서:
|
||||
|
||||
1. T-1 (DB 마이그레이션 파일 작성) — T-2와 병렬 가능, 독립적
|
||||
2. T-2 (도메인/스키마/인터페이스 정리) — T-1과 병렬 가능
|
||||
3. T-3 (postgres.py 구현체 정리) — T-2 완료 후 (인터페이스 변경 선행 필요)
|
||||
4. T-4 (AuthContext/security.py 변경) — T-2 완료 후 (모델 변경 선행 필요)
|
||||
5. T-5 (AuthService 재구현) — T-3, T-4 완료 후
|
||||
6. T-6 (API 경로 정리) — T-5 완료 후, T-7과 병렬 가능
|
||||
7. T-7 (CLI 재구현) — T-5 완료 후, T-6과 병렬 가능
|
||||
8. T-8 (테스트 업데이트) — T-5, T-6, T-7 완료 후
|
||||
9. T-9 (기술 문서 업데이트) — T-8 완료 후
|
||||
|
||||
## T-1: DB 마이그레이션 파일 작성
|
||||
- 구현 방법: Alembic 마이그레이션 파일을 신규 생성한다. `upgrade()` 내 첫 스텝은 `bot_principals.owner_user_id IS NULL` 검사로, NULL이 있으면 `MigrationError`를 raise하고 중단한다. 이후 user_id 보정 → token_type rename → unique index → bot_principal_id 컬럼 DROP → auth_audit_logs DROP → bot_principals DROP 순으로 실행한다. `downgrade()`는 역방향 구조 복원만 수행한다.
|
||||
- 변경 대상: `migrations/versions/20260423_0013_remove_bot_principals_service_tokens.py` (신규 생성)
|
||||
- 완료 조건: `alembic upgrade head` 실행 시 `bot_principals`, `auth_audit_logs` 테이블이 DROP되고, `auth_tokens.user_id`가 실제 user FK를 참조하며, `token_type = 'service'`로 업데이트됨. `alembic downgrade -1` 실행 시 역방향 복원 가능.
|
||||
- 주의사항: `op.execute()` SQL은 파라미터 바인딩 없이 직접 실행 (Alembic DDL 관례). NULL 검사 쿼리는 `op.get_bind().execute()` 패턴 사용.
|
||||
|
||||
## T-2: 도메인/스키마/인터페이스 정리
|
||||
- 구현 방법: 파일별 삭제/변경을 수행한다. `models.py`에서 `BotPrincipal`, `bot_principal_id` 필드를 삭제한다. `schemas/auth.py`에서 bot 관련 스키마를 삭제하고 `ServiceTokenIssueCliResponse`, `ServiceTokenRevokeCliResponse`를 추가한다. `portfolio_repository.py`에서 bot 관련 메서드 시그니처를 삭제하고 `get_service_token_by_name()`을 추가한다.
|
||||
- 변경 대상: `app/domain/models.py`, `app/schemas/auth.py`, `app/repositories/portfolio_repository.py`
|
||||
- 완료 조건: `python -c "from app.domain.models import AuthToken"` ImportError 없음. `BotPrincipal` 클래스 미존재. `ServiceTokenIssueCliResponse` import 가능.
|
||||
- 주의사항: `AuthTokenResponse.token_type` Literal 변경(`"bot"` → `"service"`)은 API 응답 직렬화에 즉시 영향을 주므로 T-6 완료 전에 수행해도 무방 (기존 bot 토큰 데이터 없음 전제).
|
||||
|
||||
## T-3: postgres.py 구현체 정리
|
||||
- 구현 방법: `ensure_schema()` 내 bot 관련 DDL 블록을 순서대로 제거한다. `get_token_principal()`의 SELECT문을 단순화한다. bot/audit 관련 메서드를 삭제하고 `get_service_token_by_name()`을 추가한다.
|
||||
- 변경 대상: `app/repositories/postgres.py`
|
||||
- 완료 조건: `ensure_schema()` 재실행 후 `bot_principals`, `auth_audit_logs` 테이블 미생성. `get_token_principal()` 반환값에 `bot_principal_id` 없음. `get_service_token_by_name("test")` 호출 시 `None` 또는 `AuthToken` 반환.
|
||||
- 주의사항: `_SELECT_AUTH_TOKEN` 상수에서 `bot_principal_id` 컬럼 제거. `TokenPrincipal` 생성 시 `bot_principal_id` 인자 제거.
|
||||
|
||||
## T-4: AuthContext/security.py 변경
|
||||
- 구현 방법: `AuthContext` 데이터클래스에서 `bot_principal_id` 필드를 제거하고 `token_type: str | None = None`을 추가한다. `_resolve_auth_context()`에서 `token_type=principal.token_type` 전달을 추가하고 `bot_principal_id` 할당을 제거한다. `security.py`에서 `build_audit_event()` 함수를 삭제한다.
|
||||
- 변경 대상: `app/core/auth.py`, `app/core/security.py`
|
||||
- 완료 조건: `AuthContext` 생성 시 `token_type` 파라미터 사용 가능. `bot_principal_id` 파라미터 없음. `from app.core.security import build_audit_event` ImportError 발생.
|
||||
|
||||
## T-5: AuthService 재구현
|
||||
- 구현 방법: bot 관련 메서드와 `_require_db_user_context()`, `_build_bot_expiration()`, `_record_audit_event()` 헬퍼를 삭제한다. `issue_client_token()`, `list_tokens()`, `revoke_token()` 내 `bot_principal_id` 가드를 `token_type == "service"` 가드로 교체한다. `issue_service_token()`, `revoke_service_token()` 메서드를 추가한다. `IssuedBotToken` → `IssuedServiceToken` 교체.
|
||||
- 변경 대상: `app/services/auth_service.py`
|
||||
- 완료 조건: `AuthService().issue_service_token(name="test", username="user")` 시그니처 존재. `issue_client_token()`에서 service token AuthContext로 호출 시 ValueError 발생. bot 관련 메서드 미존재.
|
||||
- 주의사항: `issue_service_token()` 내 unique constraint 위반 시 `psycopg.errors.UniqueViolation` 예외를 `ValueError`로 래핑해야 한다.
|
||||
|
||||
## T-6: API 경로 정리
|
||||
- 구현 방법: `auth_routes.py`에서 `/auth/bots`, `/auth/me/bots` 관련 라우트 핸들러와 헬퍼 함수를 삭제한다. 관련 import를 제거한다. `_to_auth_token_response()`에서 `bot_principal_id` 인자를 제거한다.
|
||||
- 변경 대상: `app/api/auth_routes.py`
|
||||
- 완료 조건: FastAPI 앱 기동 후 `/openapi.json`에 `/auth/bots`, `/auth/me/bots` 경로 미노출.
|
||||
|
||||
## T-7: CLI 재구현
|
||||
- 구현 방법: `app/cli/commands/service_tokens.py` 신규 생성 (Design `# 3 T-7` 시그니처 기준). `bot_tokens.py` 파일 삭제. `app/cli/main.py` import 경로 변경. `output.py`에서 `render_bot_result()` → `render_service_token_result()` rename.
|
||||
- 변경 대상: `app/cli/commands/service_tokens.py` (신규), `app/cli/commands/bot_tokens.py` (삭제), `app/cli/core/output.py`, `app/cli/main.py`
|
||||
- 완료 조건: `pfhub service-token issue --help` 실행 시 `NAME`, `--user` 인수 노출. help 텍스트에 "bot" 문자열 없음.
|
||||
- 주의사항: `service_tokens.py`는 `token_app = typer.Typer(...)` 인스턴스를 export해야 한다 (`app/cli/main.py`에서 `token_app`으로 import).
|
||||
|
||||
## T-8: 테스트 업데이트
|
||||
- 구현 방법: bot_principal 관련 픽스처와 테스트 함수를 제거하고 service token 시나리오 테스트를 추가한다. `AuthContext` mock에서 `bot_principal_id` → `token_type` 교체.
|
||||
- 변경 대상: `tests/test_auth.py`, `tests/test_cli.py`, `tests/test_portfolio_ownership.py`, `tests/test_users.py`
|
||||
- 완료 조건: `pytest` 실행 결과 0 failures, 0 errors. service token 발급/폐기 시나리오 테스트 포함.
|
||||
|
||||
## T-9: 기술 문서 업데이트
|
||||
- 구현 방법: 4개 문서에서 "bot_principal", "bot token" 참조를 제거하고 service token 기반 설명으로 갱신한다.
|
||||
- 변경 대상: `docs/technical/auth-and-token-lifecycle.md`, `docs/technical/api-contracts.md`, `docs/technical/management-cli-operations.md`, `README.md`
|
||||
- 완료 조건: 4개 문서에서 "bot_principal", "bot token" 잔류 참조 없음. `pfhub service-token` 명령 예제 포함.
|
||||
|
||||
# 3. 변경 파일 목록
|
||||
이번 Implementation에서 생성/수정/삭제되는 파일을 미리 나열한다.
|
||||
|
||||
| 파일 경로 | 변경 유형 | 관련 태스크 | 비고 |
|
||||
|---------|---------|-----------|------|
|
||||
| `migrations/versions/20260423_0013_remove_bot_principals_service_tokens.py` | 생성 | T-1 | Alembic 마이그레이션 신규 |
|
||||
| `app/domain/models.py` | 수정 | T-2 | BotPrincipal 삭제, bot_principal_id 필드 삭제 |
|
||||
| `app/schemas/auth.py` | 수정 | T-2 | bot 스키마 삭제, ServiceToken* 스키마 추가 |
|
||||
| `app/repositories/portfolio_repository.py` | 수정 | T-2 | bot 메서드 삭제, get_service_token_by_name 추가 |
|
||||
| `app/repositories/postgres.py` | 수정 | T-3 | ensure_schema DDL 제거, get_token_principal 단순화 |
|
||||
| `app/core/auth.py` | 수정 | T-4 | AuthContext.token_type 추가, bot_principal_id 삭제 |
|
||||
| `app/core/security.py` | 수정 | T-4 | build_audit_event 함수 삭제 |
|
||||
| `app/services/auth_service.py` | 수정 | T-5 | bot 메서드 삭제, issue/revoke_service_token 추가 |
|
||||
| `app/api/auth_routes.py` | 수정 | T-6 | /auth/bots, /auth/me/bots 라우트 삭제 |
|
||||
| `app/cli/commands/bot_tokens.py` | 삭제 | T-7 | service_tokens.py로 대체 |
|
||||
| `app/cli/commands/service_tokens.py` | 생성 | T-7 | 신규 CLI 명령 모듈 |
|
||||
| `app/cli/core/output.py` | 수정 | T-7 | render_bot_result → render_service_token_result |
|
||||
| `app/cli/main.py` | 수정 | T-7 | import 경로 변경 |
|
||||
| `tests/test_auth.py` | 수정 | T-8 | bot 테스트 삭제, service token 테스트 추가 |
|
||||
| `tests/test_cli.py` | 수정 | T-8 | service-token CLI 테스트 업데이트 |
|
||||
| `tests/test_portfolio_ownership.py` | 수정 | T-8 | token_type 기반 mock으로 교체 |
|
||||
| `tests/test_users.py` | 수정 | T-8 | bot_principal 참조 제거 (있는 경우) |
|
||||
| `docs/technical/auth-and-token-lifecycle.md` | 수정 | T-9 | bot → service 전반 업데이트 |
|
||||
| `docs/technical/api-contracts.md` | 수정 | T-9 | /auth/bots 경로 삭제 |
|
||||
| `docs/technical/management-cli-operations.md` | 수정 | T-9 | service-token 명령 예제 교체 |
|
||||
| `README.md` | 수정 | T-9 | CLI 섹션 service-token 예제 추가 |
|
||||
|
||||
# 4. 리스크 및 제약
|
||||
Implementation 단계에서 새로 식별된 리스크와 제약사항을 기록한다.
|
||||
|
||||
## 리스크
|
||||
- R-1 (High, 이월): 마이그레이션 upgrade 전 `bot_principals.owner_user_id IS NULL` 행 존재 시 FK 위반 — upgrade() 사전 검사로 차단.
|
||||
- R-2 (Medium, 이월): 15개 파일 rename 중 누락 시 런타임 AttributeError — T-8 완료 후 `grep` 잔류 검사.
|
||||
- R-3 (Medium, 이월): `revoke_service_token(name)` 호출 시 이미 폐기된 토큰 → "찾을 수 없음" 오류 — 설계상 의도된 동작.
|
||||
- R-4 (Low, 이월): `ensure_schema()` DDL 제거 후 테스트 환경 스키마 캐시로 인한 bot_principals 잔류 — 재실행 검증으로 확인.
|
||||
- R-5 (Medium): `psycopg.errors.UniqueViolation` 예외를 `ValueError`로 래핑하지 않으면 FastAPI/CLI에서 500 응답 발생.
|
||||
- 완화 방안: T-5 구현 시 `try/except UniqueViolation → raise ValueError(...)` 처리 포함.
|
||||
|
||||
## 가정
|
||||
- IA-1: `get_user_by_username()` 메서드가 `app/repositories/postgres.py`에 이미 구현되어 있다 (auth_service.login()에서 사용 중으로 확인됨).
|
||||
- IA-2: `revoke_auth_token(user_id, token_id)` 메서드가 `app/repositories/postgres.py`에 구현되어 있다.
|
||||
- IA-3: `app/cli/main.py`의 `name="service-token"` 등록이 완료되어 있어 T-7에서 import 경로만 변경하면 된다.
|
||||
- IA-4: 현재 테스트 환경에서 Docker Compose DB 접속이 가능하다.
|
||||
|
||||
## 오픈 질문
|
||||
없음.
|
||||
|
||||
# 5. Verification으로 넘기기 전 확인 체크
|
||||
|
||||
- [x] 모든 T-N 태스크 구현 완료
|
||||
- 기준: T-1~T-9 각 태스크의 완료 조건(DoD)을 충족해야 한다 — **T-1~T-9 모두 완료**
|
||||
- [x] 변경 파일 목록과 실제 변경 일치
|
||||
- 기준: 3_design.md의 변경 대상과 실제 변경된 파일이 일치해야 한다 — **일치 확인**
|
||||
- [x] 단위 테스트 또는 로컬 검증 완료
|
||||
- 기준: `pytest` 실행 결과 0 failures, 0 errors — **73 passed**
|
||||
- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인
|
||||
- 기준: OQ 목록에 `Open` 상태 항목이 0개 — 확인 완료
|
||||
- [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정
|
||||
- 기준: 담당자 Justin 지정 완료 — 문서 서명 대기
|
||||
|
||||
# 6. 파일 처리 결과
|
||||
|
||||
- 처리 결과: 생성
|
||||
- 생성 경로: docs/sdlc/sdlc-002_20260423_service-token-user-based/4_implementation.md
|
||||
- 참조 문서:
|
||||
- docs/sdlc/sdlc-002_20260423_service-token-user-based/3_design.md
|
||||
- docs/sdlc/sdlc-002_20260423_service-token-user-based/2_planning.md
|
||||
|
||||
# 7. 사용자 결정 필요 항목 요약
|
||||
|
||||
## DECIDE
|
||||
없음. 모든 구현 결정이 Design에서 완료되었다.
|
||||
|
||||
## CONFIRM
|
||||
- Freeze 확정 담당자(`CONFIRMED_BY`): Justin — confirm-implementation 단계에서 처리
|
||||
|
||||
## DATA
|
||||
- 마이그레이션 실행 전 운영 DB `bot_principals` 행 수 및 `owner_user_id NULL` 행 수 확인 권장 (R-1 완화)
|
||||
|
||||
# 8. Implementation Freeze
|
||||
|
||||
## 섹션 참조
|
||||
- 구현 계획: [# 2. 구현 계획](#2-구현-계획)
|
||||
- 변경 파일 목록: [# 3. 변경 파일 목록](#3-변경-파일-목록)
|
||||
- 리스크/오픈 질문: [# 4. 리스크 및 제약](#4-리스크-및-제약)
|
||||
|
||||
## 확정 플래그
|
||||
- Handoff Decision: 승인
|
||||
- Handoff Rationale: T-1~T-9 전체 태스크 완료, 73개 단위 테스트 통과, 변경 파일 목록 일치 확인
|
||||
- Ready for Verification: true
|
||||
- Confirmed By: Justin
|
||||
- Confirmed At (KST): 2026-04-23
|
||||
Reference in New Issue
Block a user