feat(auth): owner-bound bot tokens and self-service bot management

This commit is contained in:
2026-04-14 00:34:44 +09:00
parent 9787cf3159
commit 2dab17cbb1
16 changed files with 576 additions and 35 deletions
+46 -4
View File
@@ -32,10 +32,16 @@
- `DELETE /auth/tokens/{token_id}`
- `POST /auth/bots`
- `GET /auth/bots`
- `GET /auth/bots/{bot_id}`
- `POST /auth/bots/{bot_id}/tokens`
- `GET /auth/bots/{bot_id}/tokens`
- `DELETE /auth/bots/{bot_id}/tokens/{token_id}`
- `DELETE /auth/bots/{bot_id}`
- `POST /auth/me/bots`
- `GET /auth/me/bots`
- `POST /auth/me/bots/{bot_id}/tokens`
- `GET /auth/me/bots/{bot_id}/tokens`
- `DELETE /auth/me/bots/{bot_id}/tokens/{token_id}`
### 포트폴리오
- `GET /portfolios`
@@ -427,18 +433,46 @@ curl -X POST "http://localhost:8000/auth/tokens" \
- `GET /auth/bots/{bot_id}/tokens`: admin only, `AuthTokenResponse[]`
- `DELETE /auth/bots/{bot_id}/tokens/{token_id}`: admin only, `204`
소유권/권한 제약:
- bot principal은 `owner_user_id`를 필수로 가진다.
- bot permission은 `read` 또는 `trade-write`만 허용한다.
- bot 토큰 인증 컨텍스트의 실효 `user_id`는 bot owner(`owner_user_id`)로 해석된다.
#### 사용자 전용(Self-service) 봇 관리
- `POST /auth/me/bots`: DB-issued `read` 이상 사용자 토큰, `CreateSelfBotPrincipalRequest` -> `BotPrincipalResponse`
- `GET /auth/me/bots`: DB-issued `read` 이상 사용자 토큰, 본인 소유 bot 목록
- `POST /auth/me/bots/{bot_id}/tokens`: DB-issued `read` 이상 사용자 토큰, `CreateBotTokenRequest` -> `IssuedBotTokenResponse`
- `GET /auth/me/bots/{bot_id}/tokens`: DB-issued `read` 이상 사용자 토큰, `AuthTokenResponse[]`
- `DELETE /auth/me/bots/{bot_id}/tokens/{token_id}`: DB-issued `read` 이상 사용자 토큰, `204`
제약:
- `/auth/me/bots*` 경로는 요청자 본인 소유 bot(`owner_user_id == auth_context.user_id`)에만 허용된다.
- 타 사용자 소유 bot 접근 시 `403`.
대표 요청:
```bash
curl -X POST "http://localhost:8000/auth/bots" \
-H "Authorization: Bearer <ADMIN_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"name": "rebalance-bot", "permission": "trade-write", "description": "nightly jobs"}'
-d '{"name": "rebalance-bot", "owner_user_id": "user-1", "permission": "trade-write", "description": "nightly jobs"}'
curl -X POST "http://localhost:8000/auth/bots/bot-1/tokens" \
-H "Authorization: Bearer <ADMIN_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"name": "nightly", "permission": "trade-write", "expires_in_days": 90}'
curl -X POST "http://localhost:8000/auth/me/bots" \
-H "Authorization: Bearer <USER_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"name": "my-bot", "permission": "read", "description": "personal automation"}'
curl -X POST "http://localhost:8000/auth/me/bots/bot-1/tokens" \
-H "Authorization: Bearer <USER_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"name": "personal-key", "permission": "read", "expires_in_days": 30}'
```
### 사용자
@@ -543,13 +577,20 @@ curl -X POST "http://localhost:8000/users" \
`CreateBotPrincipalRequest`
- `name: str`
- `permission: "read" | "trade-write" | "admin"`
- `owner_user_id: str`
- `permission: "read" | "trade-write"`
- `description: str | null`
`CreateSelfBotPrincipalRequest`
- `name: str`
- `permission: "read" | "trade-write"`
- `description: str | null`
`CreateBotTokenRequest`
- `name: str`
- `permission: "read" | "trade-write" | "admin" | null`
- `permission: "read" | "trade-write" | null`
- `expires_in_days: int | null`
`CreateUserRequest`
@@ -628,7 +669,8 @@ curl -X POST "http://localhost:8000/users" \
- `id: str`
- `name: str`
- `permission: "read" | "trade-write" | "admin"`
- `owner_user_id: str`
- `permission: "read" | "trade-write"`
- `description: str | null`
- `is_active: bool`
- `created_at: datetime | null`
@@ -33,6 +33,31 @@
- 환경변수 기반 정적 토큰: `PORTFOLIOHUB_*_TOKENS`
- DB 저장 토큰: 로그인 세션/클라이언트 토큰/봇 토큰
봇 토큰 사용자 컨텍스트 정책:
- 봇 principal은 `owner_user_id`를 가진다.
- 봇 토큰 인증 시 실효 사용자 식별자는 `bot_principals.owner_user_id`로 해석한다.
- 따라서 소유권이 필요한 포트폴리오 API에서 봇 토큰은 소유 사용자 경계 내에서만 동작한다.
## 봇 관리 API 정책
- Admin 경로:
- `POST /auth/bots`
- `GET /auth/bots`
- `GET /auth/bots/{bot_id}`
- `DELETE /auth/bots/{bot_id}`
- `POST/GET/DELETE /auth/bots/{bot_id}/tokens*`
- Self-service 경로(사용자 본인 전용):
- `POST /auth/me/bots`
- `GET /auth/me/bots`
- `POST/GET/DELETE /auth/me/bots/{bot_id}/tokens*`
제약:
- self-service 경로는 DB 사용자 컨텍스트가 있는 사용자 토큰에서만 허용한다.
- self-service 경로는 요청자의 소유 봇(`owner_user_id == auth_context.user_id`)에만 허용한다.
- 봇 권한은 `read` 또는 `trade-write`만 허용한다.
## 토큰 라이프사이클
1. 사용자 로그인(`POST /auth/login`) 또는 토큰 발급 API 호출
2. 토큰 해시 저장 및 prefix 관리
@@ -75,6 +75,7 @@ FOREIGN KEY (owner_user_id) REFERENCES users(id);
- 2단계: admin이 아니면 ownership 검사 수행
- ownership 검사 기준: `portfolio.owner_user_id == auth_context.user_id`
- `auth_context.user_id`가 없는 요청은 ownership 필요한 엔드포인트에서 `403`
- 봇 토큰(`token_type=bot`)의 `auth_context.user_id`는 `bot_principals.owner_user_id`로 해석
## Endpoint Policy Matrix
@@ -92,6 +93,16 @@ FOREIGN KEY (owner_user_id) REFERENCES users(id);
| `DELETE /portfolios/{portfolio_id}` | admin | admin only | 기존 정책 유지 |
| `POST/GET/PATCH/DELETE /portfolios/{id}/comments*` | read or trade-write | 적용 | 상위 포트폴리오 ownership을 재사용 |
## Bot Principal Ownership 정책
- `bot_principals.owner_user_id`는 필수이며 `users.id` FK를 가진다.
- 봇 principal 생성 정책:
- Admin: 임의 사용자 owner로 생성 가능
- Self-service 사용자: 본인 owner로만 생성 가능
- 봇 토큰 관리 정책:
- Admin 경로는 전체 봇 대상으로 관리 가능
- Self-service 경로는 본인 소유 봇 대상으로만 관리 가능
## 서비스/리포지토리 반영 포인트
- Domain model:
- `Portfolio`에 `owner_user_id` 추가
@@ -116,13 +127,16 @@ FOREIGN KEY (owner_user_id) REFERENCES users(id);
1. 기존 portfolios row가 존재할 때 owner_user_id 백필 후 null row가 0건인지 검증
2. users가 없는 상태에서 migration 실행 시 실패 동작 검증
3. downgrade 시 인덱스/제약 해제가 순서대로 수행되는지 검증
4. 기존 bot_principals row가 존재할 때 owner_user_id 백필 후 null row가 0건인지 검증
5. bot_principals.owner_user_id FK(`users.id`) 제약 생성/해제 검증
## API Authorization
1. 사용자 A 소유 portfolio를 사용자 B가 조회하면 403
2. 사용자 A 소유 portfolio를 사용자 B가 수정/매매/리셋하면 403
3. admin이 타 사용자 portfolio 조회/수정 가능
4. 일반 사용자의 `GET /portfolios`는 본인 소유만 반환
5. user_id 없는 bot principal 요청이 ownership 필요 API 호출 시 403
5. 사용자 A에 귀속된 bot 토큰은 A 소유 portfolio만 접근 가능
6. 사용자 B가 A 소유 bot의 self-service 토큰 발급/조회/폐기를 시도하면 403
## API Creation
1. `POST /portfolios`에서 owner_user_id가 인증 사용자로 저장되는지 검증