feat(REQ-012): implement service token rotation CLI

- Add service token rotation CLI command with force flag support
- Implement token rotation in auth service with expiration handling
- Add portfolio repository helper for transaction/batch operations
- Add cascade delete support in postgres repository
- Add comprehensive CLI tests for service token operations
- Add REQ-012 specification for service token rotation
- Create BAT-002 delivery batch for token rotation implementation
- Update SRS and batch indexes
This commit is contained in:
2026-05-02 12:43:43 +00:00
parent 8370d998d2
commit b4d964253b
12 changed files with 492 additions and 3 deletions
@@ -0,0 +1,59 @@
# BAT-002 Planning
- Status: confirmed
- Batch: BAT-002
- Profile: batch-lite
## Included REQ
- REQ-012: service-token rotate CLI 명령 구현
## Delivery Plan
### Out of Scope
- `expires_at` 변경 옵션 (이번 rotate는 secret 교체만)
- HTTP API rotate 엔드포인트 (CLI 전용)
### 구현 순서
#### 1단계: Repository 레이어
- `app/repositories/portfolio_repository.py` — `rotate_service_token(token_id, token_hash, token_prefix, runtime_id) -> AuthToken` Protocol stub 추가
- `app/repositories/postgres.py` — `rotate_service_token` UPDATE 구현
```sql
UPDATE auth_tokens
SET token_hash = %s, token_prefix = %s, runtime_id = %s
WHERE id = %s AND revoked_at IS NULL
RETURNING id, user_id, name, token_prefix, ...
```
- 대상 행이 없거나 revoked이면 ValueError 반환
#### 2단계: Service 레이어
- `app/services/auth_service.py` — `rotate_service_token(name) -> IssuedServiceToken` 추가
- `get_service_token_by_name(name)` 조회 → 없으면 ValueError
- revoked 상태이면 ValueError
- `expires_at is not None`이면 ValueError ("만료일이 설정된 토큰은 rotate할 수 없습니다")
- `generate_token()` + `hash_token()` + `token_prefix()` + `uuid.uuid4()` 로 새 값 생성
- `repository.rotate_service_token(...)` 호출
- `IssuedServiceToken(token=raw_token, auth_token=updated_token)` 반환
#### 3단계: CLI 명령
- `app/cli/commands/service_tokens.py` — `rotate` 명령 추가
- `--force` 없으면 확인 프롬프트
- issue 명령과 동일한 1회 원문 출력 패턴 적용
#### 4단계: 테스트
- `tests/test_cli.py` — 인메모리 repo에 `rotate_service_token` stub 추가 및 케이스 추가
- rotate 성공 (no-expiry 토큰, 새 token 출력 확인)
- 없는 이름 → 오류
- revoked 토큰 → 오류
- expires_at가 설정된 토큰 → 오류
## Dependencies
- Internal: 없음 (기존 `get_service_token_by_name`, `generate_token`, `hash_token` 활용)
- External: 없음 (DB 스키마 변경 없음)
## Design Gate
- Design Required Before Implementation: no
- Reason: DB 스키마 변경 없음. 기존 패턴(issue/revoke) 동일하게 적용. 결정 사항 없음.