feat(auth): add admin-managed bot API keys with expiry and revocation
This commit is contained in:
@@ -323,6 +323,7 @@ Response 201:
|
||||
"token_prefix": "abcd1234",
|
||||
"permission": "read",
|
||||
"token_type": "client",
|
||||
"bot_principal_id": null,
|
||||
"expires_at": "2026-05-03T00:00:00Z",
|
||||
"last_used_at": null,
|
||||
"revoked_at": null,
|
||||
@@ -353,6 +354,128 @@ Response 204:
|
||||
|
||||
- No content.
|
||||
|
||||
### 6.5 POST /auth/bots
|
||||
|
||||
Request:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/auth/bots" \
|
||||
-H "Authorization: Bearer <ADMIN_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"owner_user_id": "user-1",
|
||||
"name": "market-bot",
|
||||
"description": "External trading bot"
|
||||
}'
|
||||
```
|
||||
|
||||
Response 201:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "bot-1",
|
||||
"owner_user_id": "user-1",
|
||||
"name": "market-bot",
|
||||
"description": "External trading bot",
|
||||
"is_active": true,
|
||||
"created_at": "2026-04-03T00:00:00Z",
|
||||
"updated_at": "2026-04-03T00:00:00Z",
|
||||
"deactivated_at": null
|
||||
}
|
||||
```
|
||||
|
||||
### 6.6 GET /auth/bots
|
||||
|
||||
Request:
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/auth/bots?is_active=true" \
|
||||
-H "Authorization: Bearer <ADMIN_TOKEN>"
|
||||
```
|
||||
|
||||
### 6.7 POST /auth/bots/{bot_id}/tokens
|
||||
|
||||
Request:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/auth/bots/bot-1/tokens" \
|
||||
-H "Authorization: Bearer <ADMIN_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "prod-key",
|
||||
"permission": "read",
|
||||
"expires_in_days": 30
|
||||
}'
|
||||
```
|
||||
|
||||
Response 201:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "plain-bot-token",
|
||||
"token_type": "bearer",
|
||||
"auth_token": {
|
||||
"id": "token-2",
|
||||
"user_id": "user-1",
|
||||
"name": "prod-key",
|
||||
"token_prefix": "efgh5678",
|
||||
"permission": "read",
|
||||
"token_type": "bot",
|
||||
"bot_principal_id": "bot-1",
|
||||
"expires_at": "2026-05-03T00:00:00Z",
|
||||
"last_used_at": null,
|
||||
"revoked_at": null,
|
||||
"created_at": "2026-04-03T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.8 GET /auth/bots/{bot_id}/tokens
|
||||
|
||||
Request:
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8000/auth/bots/bot-1/tokens" \
|
||||
-H "Authorization: Bearer <ADMIN_TOKEN>"
|
||||
```
|
||||
|
||||
### 6.9 DELETE /auth/bots/{bot_id}/tokens/{token_id}
|
||||
|
||||
Request:
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:8000/auth/bots/bot-1/tokens/token-2" \
|
||||
-H "Authorization: Bearer <ADMIN_TOKEN>"
|
||||
```
|
||||
|
||||
Response 204:
|
||||
|
||||
- No content.
|
||||
|
||||
### 6.10 DELETE /auth/bots/{bot_id}
|
||||
|
||||
Request:
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:8000/auth/bots/bot-1" \
|
||||
-H "Authorization: Bearer <ADMIN_TOKEN>"
|
||||
```
|
||||
|
||||
Response 200:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "bot-1",
|
||||
"owner_user_id": "user-1",
|
||||
"name": "market-bot",
|
||||
"description": "External trading bot",
|
||||
"is_active": false,
|
||||
"created_at": "2026-04-03T00:00:00Z",
|
||||
"updated_at": "2026-04-03T01:00:00Z",
|
||||
"deactivated_at": "2026-04-03T01:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
## 7. Users
|
||||
|
||||
All user management endpoints require an admin token.
|
||||
|
||||
+46
-4
@@ -5,13 +5,14 @@
|
||||
This document defines the database schema in an AI-agent-friendly format.
|
||||
|
||||
- Source of truth: Alembic revisions in `migrations/versions`
|
||||
- Current baseline revision: `20260403_0005`
|
||||
- Current baseline revision: `20260403_0006`
|
||||
- Baseline files:
|
||||
- `migrations/versions/20260403_0001_initial_portfolio_schema.py`
|
||||
- `migrations/versions/20260403_0002_strategy_set_columns.py`
|
||||
- `migrations/versions/20260403_0003_portfolio_strategy_refs.py`
|
||||
- `migrations/versions/20260403_0004_users_table.py`
|
||||
- `migrations/versions/20260403_0005_auth_user_fields_and_tokens.py`
|
||||
- `migrations/versions/20260403_0006_bot_principals_and_bot_tokens.py`
|
||||
|
||||
## 2. Scope
|
||||
|
||||
@@ -20,6 +21,7 @@ Schema objects covered by baseline revision:
|
||||
- `strategy_sets`
|
||||
- `users`
|
||||
- `auth_tokens`
|
||||
- `bot_principals`
|
||||
- `portfolios`
|
||||
- `portfolio_ledger`
|
||||
- secondary indexes
|
||||
@@ -76,7 +78,8 @@ Columns:
|
||||
| token_prefix | Text | NO | - | Display prefix for issued token |
|
||||
| token_hash | Text | NO | - | Unique SHA-256 token hash |
|
||||
| permission | Text | NO | - | Effective token permission |
|
||||
| token_type | Text | NO | - | `session` or `client` |
|
||||
| token_type | Text | NO | - | `session`, `client`, or `bot` |
|
||||
| bot_principal_id | Text | YES | - | Logical reference to `bot_principals.id` |
|
||||
| expires_at | DateTime(timezone=True) | YES | - | Expiration time |
|
||||
| last_used_at | DateTime(timezone=True) | YES | - | Last successful use |
|
||||
| revoked_at | DateTime(timezone=True) | YES | - | Revocation time |
|
||||
@@ -87,6 +90,30 @@ Constraints:
|
||||
- Primary key: `id`
|
||||
- Unique: `uq_auth_tokens_token_hash` on (`token_hash`)
|
||||
|
||||
### 4.3 bot_principals
|
||||
|
||||
Purpose:
|
||||
|
||||
- Stores external bot identities for admin-managed API key issuance and lifecycle control.
|
||||
|
||||
Columns:
|
||||
|
||||
| Column | Type | Null | Default | Constraints / Notes |
|
||||
|---|---|---|---|---|
|
||||
| id | Text | NO | - | Primary Key |
|
||||
| owner_user_id | Text | NO | - | Logical owner reference to users.id |
|
||||
| name | Text | NO | - | Unique bot name |
|
||||
| description | Text | YES | - | Optional operator description |
|
||||
| is_active | Boolean | NO | TRUE | When false, bot tokens are rejected |
|
||||
| created_at | DateTime(timezone=True) | NO | NOW() | Creation timestamp |
|
||||
| updated_at | DateTime(timezone=True) | NO | NOW() | Last update timestamp |
|
||||
| deactivated_at | DateTime(timezone=True) | YES | - | Deactivation time |
|
||||
|
||||
Constraints:
|
||||
|
||||
- Primary key: `id`
|
||||
- Unique: `uq_bot_principals_name` on (`name`)
|
||||
|
||||
### 4.3 strategy_sets
|
||||
|
||||
Purpose:
|
||||
@@ -199,14 +226,28 @@ Constraints:
|
||||
- Unique: no
|
||||
- Intent: resolve bearer tokens efficiently.
|
||||
|
||||
### 5.4 idx_portfolio_ledger_portfolio_time
|
||||
### 5.4 idx_auth_tokens_bot_created
|
||||
|
||||
- Table: `auth_tokens`
|
||||
- Columns/expressions: (`bot_principal_id`, `created_at`)
|
||||
- Unique: no
|
||||
- Intent: fetch bot-issued token lists efficiently.
|
||||
|
||||
### 5.5 idx_bot_principals_active_created
|
||||
|
||||
- Table: `bot_principals`
|
||||
- Columns/expressions: (`is_active`, `created_at`)
|
||||
- Unique: no
|
||||
- Intent: speed operational filtering of active/inactive bots.
|
||||
|
||||
### 5.6 idx_portfolio_ledger_portfolio_time
|
||||
|
||||
- Table: `portfolio_ledger`
|
||||
- Columns/expressions: (`portfolio_id`, `recorded_at DESC`)
|
||||
- Unique: no
|
||||
- Intent: fetch recent ledger entries per portfolio efficiently.
|
||||
|
||||
### 5.5 idx_portfolios_broker_status_updated
|
||||
### 5.7 idx_portfolios_broker_status_updated
|
||||
|
||||
- Table: `portfolios`
|
||||
- Columns/expressions: (`broker`, `status`, `updated_at DESC`)
|
||||
@@ -250,3 +291,4 @@ When schema changes:
|
||||
| 20260403_0003 | migrations/versions/20260403_0003_portfolio_strategy_refs.py | Remove embedded portfolio strategy fields, add `active_strategy_id` and `avabile_strategies` |
|
||||
| 20260403_0004 | migrations/versions/20260403_0004_users_table.py | Add `users` table with unique username/email and created_at index |
|
||||
| 20260403_0005 | migrations/versions/20260403_0005_auth_user_fields_and_tokens.py | Add password/permission/login fields to `users` and create `auth_tokens` table |
|
||||
| 20260403_0006 | migrations/versions/20260403_0006_bot_principals_and_bot_tokens.py | Add `bot_principals` table and `auth_tokens.bot_principal_id` for bot API key management |
|
||||
|
||||
@@ -81,6 +81,7 @@ Core settings:
|
||||
- `PORTFOLIOHUB_DB_ENABLED`: enables DB repository selection when `true`
|
||||
- `PORTFOLIOHUB_SESSION_TOKEN_TTL_HOURS`: login session token TTL in hours
|
||||
- `PORTFOLIOHUB_CLIENT_TOKEN_DEFAULT_TTL_DAYS`: default client token TTL in days
|
||||
- `PORTFOLIOHUB_BOT_TOKEN_DEFAULT_TTL_DAYS`: default bot token TTL in days
|
||||
|
||||
Database settings (active source):
|
||||
|
||||
@@ -284,6 +285,54 @@ Note: Since strategy sets are identified by (name, config) uniqueness, updating
|
||||
- Response `204`: no content
|
||||
- Response `404`: token not found
|
||||
|
||||
#### Create Bot Principal
|
||||
|
||||
- `POST /auth/bots`
|
||||
- Auth: `admin`
|
||||
- Body: `CreateBotPrincipalRequest`
|
||||
- Response `201`: `BotPrincipalResponse`
|
||||
|
||||
#### List Bot Principals
|
||||
|
||||
- `GET /auth/bots`
|
||||
- Auth: `admin`
|
||||
- Query params:
|
||||
- `is_active` (optional bool)
|
||||
- Response `200`: array of `BotPrincipalResponse`
|
||||
|
||||
#### Get Bot Principal
|
||||
|
||||
- `GET /auth/bots/{bot_id}`
|
||||
- Auth: `admin`
|
||||
- Response `200`: `BotPrincipalResponse`
|
||||
- Response `404`: bot not found
|
||||
|
||||
#### Deactivate Bot Principal
|
||||
|
||||
- `DELETE /auth/bots/{bot_id}`
|
||||
- Auth: `admin`
|
||||
- Response `200`: `BotPrincipalResponse`
|
||||
- Behavior: deactivates bot and revokes all active tokens linked to the bot
|
||||
|
||||
#### Issue Bot Token
|
||||
|
||||
- `POST /auth/bots/{bot_id}/tokens`
|
||||
- Auth: `admin`
|
||||
- Body: `CreateBotTokenRequest`
|
||||
- Response `201`: `IssuedBotTokenResponse`
|
||||
|
||||
#### List Bot Tokens
|
||||
|
||||
- `GET /auth/bots/{bot_id}/tokens`
|
||||
- Auth: `admin`
|
||||
- Response `200`: array of `AuthTokenResponse`
|
||||
|
||||
#### Revoke Bot Token
|
||||
|
||||
- `DELETE /auth/bots/{bot_id}/tokens/{token_id}`
|
||||
- Auth: `admin`
|
||||
- Response `204`: no content
|
||||
|
||||
### 7.6 User Endpoints
|
||||
|
||||
All user management endpoints are admin-only.
|
||||
@@ -386,6 +435,18 @@ Bootstrap rule:
|
||||
|
||||
`CreateClientTokenRequest`
|
||||
|
||||
- `name: str`
|
||||
- `permission: "read" | "trade-write" | "admin" | null`
|
||||
- `expires_in_days: int | null`
|
||||
|
||||
`CreateBotPrincipalRequest`
|
||||
|
||||
- `owner_user_id: str`
|
||||
- `name: str`
|
||||
- `description: str | null`
|
||||
|
||||
`CreateBotTokenRequest`
|
||||
|
||||
- `name: str`
|
||||
- `permission: "read" | "trade-write" | "admin" | null`
|
||||
- `expires_in_days: int | null`
|
||||
@@ -465,6 +526,17 @@ Bootstrap rule:
|
||||
|
||||
- `detail: str`
|
||||
|
||||
`BotPrincipalResponse`
|
||||
|
||||
- `id: str`
|
||||
- `owner_user_id: str`
|
||||
- `name: str`
|
||||
- `description: str | null`
|
||||
- `is_active: bool`
|
||||
- `created_at: datetime | null`
|
||||
- `updated_at: datetime | null`
|
||||
- `deactivated_at: datetime | null`
|
||||
|
||||
## 9. CLI Specification
|
||||
|
||||
Global option:
|
||||
|
||||
Reference in New Issue
Block a user