init commit
This commit is contained in:
635
.history/docs/ARCHITECTURE_20251210210601.md
Normal file
635
.history/docs/ARCHITECTURE_20251210210601.md
Normal file
@@ -0,0 +1,635 @@
|
||||
# 🏗️ API-First Zero-Trust Architecture - Complete Guide
|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
1. [Architecture Overview](#architecture-overview)
|
||||
2. [Security Model](#security-model)
|
||||
3. [Authentication Flows](#authentication-flows)
|
||||
4. [RBAC & Permissions](#rbac--permissions)
|
||||
5. [API Endpoints](#api-endpoints)
|
||||
6. [Telegram Bot Integration](#telegram-bot-integration)
|
||||
7. [Testing Strategy](#testing-strategy)
|
||||
8. [Deployment](#deployment)
|
||||
9. [Production Checklist](#production-checklist)
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Architecture Overview
|
||||
|
||||
### System Components
|
||||
|
||||
```
|
||||
EXTERNAL CLIENTS (Web, Mobile, Bot)
|
||||
↓
|
||||
API GATEWAY (FastAPI)
|
||||
↓
|
||||
MIDDLEWARE STACK
|
||||
├─ Security Headers
|
||||
├─ Rate Limiting
|
||||
├─ HMAC Verification
|
||||
├─ JWT Authentication
|
||||
├─ RBAC Authorization
|
||||
└─ Request Logging
|
||||
↓
|
||||
DOMAIN SERVICES
|
||||
├─ AuthService
|
||||
├─ TransactionService
|
||||
├─ WalletService
|
||||
├─ BudgetService
|
||||
└─ NotificationService
|
||||
↓
|
||||
REPOSITORY LAYER (SQLAlchemy)
|
||||
↓
|
||||
DATABASE + REDIS + MESSAGE QUEUE
|
||||
```
|
||||
|
||||
### Key Principles
|
||||
|
||||
| Principle | Implementation |
|
||||
|-----------|-----------------|
|
||||
| **Zero-Trust** | Every request requires JWT + HMAC verification |
|
||||
| **Immutability** | No direct record deletion; use reversals + audit logs |
|
||||
| **Isolation** | Family-level data isolation at service layer |
|
||||
| **Observability** | Every action logged to event_log + access_log |
|
||||
| **Stateless** | API calls don't depend on session state |
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security Model
|
||||
|
||||
### Token Types
|
||||
|
||||
#### 1. **Access Token (JWT)**
|
||||
- **Purpose:** Authenticate API requests
|
||||
- **Lifetime:** 15 minutes
|
||||
- **Scope:** Contains family_ids user can access
|
||||
- **Usage:**
|
||||
```
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
#### 2. **Refresh Token**
|
||||
- **Purpose:** Issue new access tokens without re-login
|
||||
- **Lifetime:** 30 days
|
||||
- **Usage:**
|
||||
```
|
||||
POST /api/v1/auth/refresh
|
||||
{ "refresh_token": "..." }
|
||||
```
|
||||
|
||||
#### 3. **Service Token**
|
||||
- **Purpose:** Telegram bot → API communication
|
||||
- **Lifetime:** 1 year
|
||||
- **Scope:** "telegram_bot" service
|
||||
- **Note:** Different from user tokens; issued separately
|
||||
|
||||
### HMAC Signature Verification
|
||||
|
||||
**Base String Format:**
|
||||
```
|
||||
METHOD:ENDPOINT:TIMESTAMP:BODY_HASH
|
||||
POST:/api/v1/transactions:1702237800:a3f5d8c2e1b9...
|
||||
```
|
||||
|
||||
**Headers Required:**
|
||||
```
|
||||
X-Signature: HMAC_SHA256(base_string, client_secret)
|
||||
X-Timestamp: 1702237800
|
||||
X-Client-Id: telegram_bot|web_frontend|ios_app
|
||||
```
|
||||
|
||||
**Verification Steps:**
|
||||
1. Check timestamp freshness (±30 seconds)
|
||||
2. Reconstruct base_string
|
||||
3. Compute HMAC with client secret
|
||||
4. Compare with X-Signature
|
||||
5. Check signature nonce (prevent replay)
|
||||
|
||||
**MVP Default:** HMAC disabled (`require_hmac_verification=false`)
|
||||
|
||||
### Encryption Strategy
|
||||
|
||||
| Data | Encryption | Notes |
|
||||
|------|-----------|-------|
|
||||
| Password Hash | bcrypt | Never store plain passwords |
|
||||
| Phone Number | AES-256 | At rest, logged as masked |
|
||||
| Notes/Descriptions | None (MVP) | Can add AES-256 for sensitive notes |
|
||||
| Transit | HTTPS TLS 1.2+ | Enforced in production |
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Authentication Flows
|
||||
|
||||
### User Login Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 1. User submits credentials │
|
||||
│ POST /api/v1/auth/login │
|
||||
│ { "email": "user@example.com", "password": "..." } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 2. Server verifies password hash with bcrypt │
|
||||
│ Load user → Load family_ids → Create tokens │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 3. Response │
|
||||
│ { │
|
||||
│ "access_token": "eyJhbGc...", │
|
||||
│ "refresh_token": "eyJhbGc...", │
|
||||
│ "user_id": 123, │
|
||||
│ "expires_in": 900 │
|
||||
│ } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Telegram Binding Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ STEP 1: User sends /start │
|
||||
│ Bot chat_id: 12345 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ STEP 2: Bot generates binding code │
|
||||
│ POST /api/v1/auth/telegram/start │
|
||||
│ Response: { "code": "ABC123XYZ...", "expires_in": 600 } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ STEP 3: Bot sends link to user │
|
||||
│ "Click: https://app.com/auth/telegram?code=ABC123&... │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ STEP 4: User clicks link │
|
||||
│ (User must be logged in or create account) │
|
||||
│ POST /api/v1/auth/telegram/confirm │
|
||||
│ { "code": "ABC123", "chat_id": 12345 } │
|
||||
│ Headers: Authorization: Bearer <user_jwt> │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ STEP 5: Server creates TelegramIdentity record │
|
||||
│ TelegramIdentity { │
|
||||
│ user_id: 123, │
|
||||
│ chat_id: 12345, │
|
||||
│ verified_at: now │
|
||||
│ } │
|
||||
│ │
|
||||
│ Generate JWT for bot usage │
|
||||
│ Response: { "jwt_token": "..." } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ STEP 6: Bot stores JWT in Redis │
|
||||
│ Redis key: chat_id:12345:jwt │
|
||||
│ Redis key: chat_id:12345:jwt:exp │
|
||||
│ TTL: 30 days │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ STEP 7: Bot can now make API calls │
|
||||
│ POST /api/v1/transactions │
|
||||
│ Authorization: Bearer <jwt_from_redis> │
|
||||
│ X-Client-Id: telegram_bot │
|
||||
│ X-Signature: HMAC_SHA256(...) │
|
||||
│ X-Timestamp: unixtime │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 👥 RBAC & Permissions
|
||||
|
||||
### Role Hierarchy
|
||||
|
||||
```
|
||||
OWNER (Full Control)
|
||||
└─ Can manage everything
|
||||
└─ Can approve/reject large transactions
|
||||
└─ Can manage members
|
||||
└─ Can delete family
|
||||
|
||||
ADULT
|
||||
└─ Can create/edit transactions
|
||||
└─ Can approve transactions (with owner)
|
||||
└─ Can create budgets/goals
|
||||
└─ Can invite members
|
||||
|
||||
MEMBER
|
||||
└─ Can create/view own transactions
|
||||
└─ Can view budgets/goals
|
||||
└─ Can view shared reports
|
||||
|
||||
CHILD
|
||||
└─ Can create/view limited transactions
|
||||
└─ Can view their allowance
|
||||
└─ Very restricted permissions
|
||||
|
||||
READ_ONLY
|
||||
└─ Can view reports only
|
||||
└─ Audit/observer role
|
||||
```
|
||||
|
||||
### Permission Examples
|
||||
|
||||
| Action | Owner | Adult | Member | Child | Read-Only |
|
||||
|--------|-------|-------|--------|-------|-----------|
|
||||
| Create Transaction | ✓ | ✓ | ✓ | ✓ (limited) | ✗ |
|
||||
| Edit Own Transaction | ✓ | ✓ | ✓ | ✗ | ✗ |
|
||||
| Edit Any Transaction | ✓ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Delete Transaction | ✓ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Approve Transaction | ✓ | ✓ | ✗ | ✗ | ✗ |
|
||||
| Create Budget | ✓ | ✓ | ✗ | ✗ | ✗ |
|
||||
| Manage Members | ✓ | ✗ | ✗ | ✗ | ✗ |
|
||||
| View Audit Log | ✓ | ✓ | ✓ | ✗ | ✓ |
|
||||
| Delete Family | ✓ | ✗ | ✗ | ✗ | ✗ |
|
||||
|
||||
### RBAC Implementation
|
||||
|
||||
**In Code:**
|
||||
```python
|
||||
# Check permission
|
||||
RBACEngine.check_permission(user_context, Permission.CREATE_TRANSACTION)
|
||||
|
||||
# Check family access
|
||||
RBACEngine.check_family_access(user_context, family_id=1)
|
||||
|
||||
# Check resource ownership
|
||||
RBACEngine.check_resource_ownership(user_context, owner_id=123)
|
||||
```
|
||||
|
||||
**In Endpoint:**
|
||||
```python
|
||||
@router.post("/transactions")
|
||||
async def create_transaction(
|
||||
request: TransactionCreateRequest,
|
||||
user_context: UserContext = Depends(get_user_context),
|
||||
):
|
||||
# Middleware already verified JWT
|
||||
# RBAC middleware already checked family access
|
||||
# Now just check specific permission
|
||||
|
||||
RBACEngine.check_permission(
|
||||
user_context,
|
||||
Permission.CREATE_TRANSACTION
|
||||
)
|
||||
|
||||
# Proceed with business logic
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📡 API Endpoints
|
||||
|
||||
### Authentication
|
||||
|
||||
| Method | Endpoint | Purpose |
|
||||
|--------|----------|---------|
|
||||
| POST | `/api/v1/auth/login` | User login |
|
||||
| POST | `/api/v1/auth/refresh` | Refresh access token |
|
||||
| POST | `/api/v1/auth/logout` | Revoke session |
|
||||
| POST | `/api/v1/auth/telegram/start` | Generate binding code |
|
||||
| POST | `/api/v1/auth/telegram/confirm` | Confirm Telegram binding |
|
||||
|
||||
### Transactions
|
||||
|
||||
| Method | Endpoint | Purpose | Auth |
|
||||
|--------|----------|---------|------|
|
||||
| POST | `/api/v1/transactions` | Create transaction | JWT + HMAC |
|
||||
| GET | `/api/v1/transactions` | List transactions | JWT |
|
||||
| GET | `/api/v1/transactions/{id}` | Get transaction | JWT |
|
||||
| POST | `/api/v1/transactions/{id}/confirm` | Approve pending | JWT + HMAC |
|
||||
| DELETE | `/api/v1/transactions/{id}` | Reverse transaction | JWT + HMAC |
|
||||
|
||||
### Wallets
|
||||
|
||||
| Method | Endpoint | Purpose |
|
||||
|--------|----------|---------|
|
||||
| POST | `/api/v1/wallets` | Create wallet |
|
||||
| GET | `/api/v1/wallets` | List wallets |
|
||||
| GET | `/api/v1/wallets/summary` | Balance summary |
|
||||
| PUT | `/api/v1/wallets/{id}` | Update wallet |
|
||||
|
||||
### Budgets & Goals
|
||||
|
||||
| Method | Endpoint | Purpose |
|
||||
|--------|----------|---------|
|
||||
| POST | `/api/v1/budgets` | Create budget |
|
||||
| GET | `/api/v1/budgets` | List budgets |
|
||||
| POST | `/api/v1/goals` | Create goal |
|
||||
| GET | `/api/v1/goals` | List goals |
|
||||
|
||||
### Events & Webhooks
|
||||
|
||||
| Method | Endpoint | Purpose |
|
||||
|--------|----------|---------|
|
||||
| GET | `/api/v1/events/stream/{family_id}` | WebSocket event stream |
|
||||
| POST | `/api/v1/events/webhook/telegram-notification` | Send Telegram notification |
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Telegram Bot Integration
|
||||
|
||||
### Bot Commands
|
||||
|
||||
```
|
||||
/start - Begin account binding
|
||||
/help - Show available commands
|
||||
/balance - View wallet balances
|
||||
/add - Add new transaction
|
||||
/reports - View financial reports
|
||||
```
|
||||
|
||||
### Bot API Communication Pattern
|
||||
|
||||
```python
|
||||
# Get user JWT from Redis
|
||||
jwt_token = redis.get(f"chat_id:{chat_id}:jwt")
|
||||
|
||||
# Make API request
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
"https://api.example.com/api/v1/transactions",
|
||||
headers={
|
||||
"Authorization": f"Bearer {jwt_token}",
|
||||
"X-Client-Id": "telegram_bot",
|
||||
"X-Signature": hmac_signature,
|
||||
"X-Timestamp": str(timestamp),
|
||||
},
|
||||
json=payload,
|
||||
) as response:
|
||||
result = await response.json()
|
||||
```
|
||||
|
||||
### Event Handling
|
||||
|
||||
```
|
||||
Bot listens to Redis Streams:
|
||||
- transaction.created
|
||||
- budget.alert
|
||||
- goal.completed
|
||||
- member.joined
|
||||
|
||||
Bot processes events → Sends Telegram messages
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- JWT token generation/verification
|
||||
- HMAC signature creation/verification
|
||||
- RBAC permission checks
|
||||
- Service business logic
|
||||
|
||||
### Integration Tests
|
||||
- Full request → response cycles
|
||||
- Authentication flows
|
||||
- RBAC in middleware
|
||||
- Database transactions
|
||||
|
||||
### Security Tests
|
||||
- Invalid token rejection
|
||||
- HMAC signature verification
|
||||
- Timestamp freshness
|
||||
- Signature replay prevention
|
||||
- Family isolation
|
||||
|
||||
### Load Testing Example (k6)
|
||||
|
||||
```javascript
|
||||
import http from 'k6/http';
|
||||
import { check } from 'k6';
|
||||
|
||||
export let options = {
|
||||
vus: 10,
|
||||
duration: '30s',
|
||||
};
|
||||
|
||||
export default function() {
|
||||
let url = 'http://localhost:8000/api/v1/wallets';
|
||||
let params = {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${__ENV.JWT_TOKEN}`,
|
||||
'X-Client-Id': 'k6_test',
|
||||
},
|
||||
};
|
||||
|
||||
let res = http.get(url, params);
|
||||
check(res, {
|
||||
'status is 200': (r) => r.status === 200,
|
||||
'response time < 500ms': (r) => r.timings.duration < 500,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment
|
||||
|
||||
### Docker Compose (MVP)
|
||||
|
||||
```yaml
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
api:
|
||||
image: finance-bot:latest
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://...
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- JWT_SECRET_KEY=...
|
||||
- HMAC_SECRET_KEY=...
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
networks:
|
||||
- finance
|
||||
|
||||
bot:
|
||||
image: finance-bot-bot:latest
|
||||
environment:
|
||||
- BOT_TOKEN=...
|
||||
- API_BASE_URL=http://api:8000
|
||||
- REDIS_URL=redis://redis:6379
|
||||
depends_on:
|
||||
- api
|
||||
- redis
|
||||
networks:
|
||||
- finance
|
||||
|
||||
worker:
|
||||
image: finance-bot-worker:latest
|
||||
environment:
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- DATABASE_URL=postgresql://...
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
networks:
|
||||
- finance
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_DB: finance_db
|
||||
POSTGRES_USER: trevor
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
networks:
|
||||
- finance
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
networks:
|
||||
- finance
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
|
||||
networks:
|
||||
finance:
|
||||
```
|
||||
|
||||
### Kubernetes Deployment (Future)
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: api-config
|
||||
data:
|
||||
JWT_SECRET_KEY: <secret>
|
||||
HMAC_SECRET_KEY: <secret>
|
||||
DATABASE_URL: postgresql://postgres/finance_db
|
||||
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: api-server
|
||||
spec:
|
||||
replicas: 3
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: api
|
||||
image: finance-bot:latest
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: api-config
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Production Checklist
|
||||
|
||||
### Security
|
||||
- [ ] Change JWT_SECRET_KEY from default
|
||||
- [ ] Change HMAC_SECRET_KEY from default
|
||||
- [ ] Enable HTTPS/TLS (Nginx reverse proxy)
|
||||
- [ ] Enable CORS only for approved origins
|
||||
- [ ] Set require_hmac_verification=true
|
||||
- [ ] Implement password hashing (bcrypt)
|
||||
- [ ] Implement token blacklisting
|
||||
- [ ] Add rate limiting (current: 100 req/min)
|
||||
- [ ] Enable audit logging (EventLog)
|
||||
- [ ] Encrypt sensitive data at rest
|
||||
|
||||
### Deployment
|
||||
- [ ] Run migrations in production
|
||||
- [ ] Set app_env="production"
|
||||
- [ ] Disable debug mode
|
||||
- [ ] Configure proper logging
|
||||
- [ ] Set up monitoring/alerts
|
||||
- [ ] Configure backup strategy
|
||||
- [ ] Test failover procedures
|
||||
- [ ] Document runbooks
|
||||
|
||||
### Testing
|
||||
- [ ] Unit tests coverage > 80%
|
||||
- [ ] Integration tests for critical flows
|
||||
- [ ] Security testing (OWASP Top 10)
|
||||
- [ ] Load testing (identify bottlenecks)
|
||||
- [ ] Penetration testing
|
||||
- [ ] API contract testing
|
||||
|
||||
### Operations
|
||||
- [ ] Set up CI/CD pipeline
|
||||
- [ ] Configure health check endpoints
|
||||
- [ ] Set up application monitoring
|
||||
- [ ] Configure database backups
|
||||
- [ ] Document API in OpenAPI/Swagger
|
||||
- [ ] Set up error tracking (Sentry)
|
||||
- [ ] Implement graceful shutdown
|
||||
|
||||
---
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
### OpenAPI Specification
|
||||
```bash
|
||||
# Auto-generated from FastAPI
|
||||
GET /docs # Swagger UI
|
||||
GET /redoc # ReDoc documentation
|
||||
GET /openapi.json # OpenAPI spec
|
||||
```
|
||||
|
||||
### Architecture Decision Records (ADR)
|
||||
- ADR-001: JWT + HMAC for authentication (not just JWT)
|
||||
- ADR-002: Redis Streams for event bus (vs RabbitMQ)
|
||||
- ADR-003: Compensation transactions for reversals
|
||||
- ADR-004: Family-level isolation in all queries
|
||||
|
||||
### Performance Targets
|
||||
- API response time: < 200ms (p95)
|
||||
- Transaction creation: < 100ms
|
||||
- List queries: < 500ms (for 1000 items)
|
||||
- HMAC verification: < 5ms
|
||||
- JWT verification: < 2ms
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Roadmap (Post-MVP)
|
||||
|
||||
### Phase 2: Enhanced Features
|
||||
- [ ] Web Frontend (React/Vue)
|
||||
- [ ] Mobile App (React Native/Flutter)
|
||||
- [ ] Advanced reporting (PDF export, charts)
|
||||
- [ ] Recurring transactions
|
||||
- [ ] Currency conversion
|
||||
- [ ] Multi-family support
|
||||
- [ ] User notifications preferences
|
||||
|
||||
### Phase 3: Enterprise Features
|
||||
- [ ] Kubernetes deployment
|
||||
- [ ] Multi-region failover
|
||||
- [ ] Advanced RBAC (custom roles)
|
||||
- [ ] Audit webhook integrations
|
||||
- [ ] API rate limiting (per-user)
|
||||
- [ ] Data export (GDPR compliance)
|
||||
- [ ] SSO integration (OAuth2)
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** 2025-12-10
|
||||
**Status:** MVP Complete
|
||||
635
.history/docs/ARCHITECTURE_20251210210906.md
Normal file
635
.history/docs/ARCHITECTURE_20251210210906.md
Normal file
@@ -0,0 +1,635 @@
|
||||
# 🏗️ API-First Zero-Trust Architecture - Complete Guide
|
||||
|
||||
## 📋 Table of Contents
|
||||
|
||||
1. [Architecture Overview](#architecture-overview)
|
||||
2. [Security Model](#security-model)
|
||||
3. [Authentication Flows](#authentication-flows)
|
||||
4. [RBAC & Permissions](#rbac--permissions)
|
||||
5. [API Endpoints](#api-endpoints)
|
||||
6. [Telegram Bot Integration](#telegram-bot-integration)
|
||||
7. [Testing Strategy](#testing-strategy)
|
||||
8. [Deployment](#deployment)
|
||||
9. [Production Checklist](#production-checklist)
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Architecture Overview
|
||||
|
||||
### System Components
|
||||
|
||||
```
|
||||
EXTERNAL CLIENTS (Web, Mobile, Bot)
|
||||
↓
|
||||
API GATEWAY (FastAPI)
|
||||
↓
|
||||
MIDDLEWARE STACK
|
||||
├─ Security Headers
|
||||
├─ Rate Limiting
|
||||
├─ HMAC Verification
|
||||
├─ JWT Authentication
|
||||
├─ RBAC Authorization
|
||||
└─ Request Logging
|
||||
↓
|
||||
DOMAIN SERVICES
|
||||
├─ AuthService
|
||||
├─ TransactionService
|
||||
├─ WalletService
|
||||
├─ BudgetService
|
||||
└─ NotificationService
|
||||
↓
|
||||
REPOSITORY LAYER (SQLAlchemy)
|
||||
↓
|
||||
DATABASE + REDIS + MESSAGE QUEUE
|
||||
```
|
||||
|
||||
### Key Principles
|
||||
|
||||
| Principle | Implementation |
|
||||
|-----------|-----------------|
|
||||
| **Zero-Trust** | Every request requires JWT + HMAC verification |
|
||||
| **Immutability** | No direct record deletion; use reversals + audit logs |
|
||||
| **Isolation** | Family-level data isolation at service layer |
|
||||
| **Observability** | Every action logged to event_log + access_log |
|
||||
| **Stateless** | API calls don't depend on session state |
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security Model
|
||||
|
||||
### Token Types
|
||||
|
||||
#### 1. **Access Token (JWT)**
|
||||
- **Purpose:** Authenticate API requests
|
||||
- **Lifetime:** 15 minutes
|
||||
- **Scope:** Contains family_ids user can access
|
||||
- **Usage:**
|
||||
```
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
```
|
||||
|
||||
#### 2. **Refresh Token**
|
||||
- **Purpose:** Issue new access tokens without re-login
|
||||
- **Lifetime:** 30 days
|
||||
- **Usage:**
|
||||
```
|
||||
POST /api/v1/auth/refresh
|
||||
{ "refresh_token": "..." }
|
||||
```
|
||||
|
||||
#### 3. **Service Token**
|
||||
- **Purpose:** Telegram bot → API communication
|
||||
- **Lifetime:** 1 year
|
||||
- **Scope:** "telegram_bot" service
|
||||
- **Note:** Different from user tokens; issued separately
|
||||
|
||||
### HMAC Signature Verification
|
||||
|
||||
**Base String Format:**
|
||||
```
|
||||
METHOD:ENDPOINT:TIMESTAMP:BODY_HASH
|
||||
POST:/api/v1/transactions:1702237800:a3f5d8c2e1b9...
|
||||
```
|
||||
|
||||
**Headers Required:**
|
||||
```
|
||||
X-Signature: HMAC_SHA256(base_string, client_secret)
|
||||
X-Timestamp: 1702237800
|
||||
X-Client-Id: telegram_bot|web_frontend|ios_app
|
||||
```
|
||||
|
||||
**Verification Steps:**
|
||||
1. Check timestamp freshness (±30 seconds)
|
||||
2. Reconstruct base_string
|
||||
3. Compute HMAC with client secret
|
||||
4. Compare with X-Signature
|
||||
5. Check signature nonce (prevent replay)
|
||||
|
||||
**MVP Default:** HMAC disabled (`require_hmac_verification=false`)
|
||||
|
||||
### Encryption Strategy
|
||||
|
||||
| Data | Encryption | Notes |
|
||||
|------|-----------|-------|
|
||||
| Password Hash | bcrypt | Never store plain passwords |
|
||||
| Phone Number | AES-256 | At rest, logged as masked |
|
||||
| Notes/Descriptions | None (MVP) | Can add AES-256 for sensitive notes |
|
||||
| Transit | HTTPS TLS 1.2+ | Enforced in production |
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Authentication Flows
|
||||
|
||||
### User Login Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 1. User submits credentials │
|
||||
│ POST /api/v1/auth/login │
|
||||
│ { "email": "user@example.com", "password": "..." } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 2. Server verifies password hash with bcrypt │
|
||||
│ Load user → Load family_ids → Create tokens │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 3. Response │
|
||||
│ { │
|
||||
│ "access_token": "eyJhbGc...", │
|
||||
│ "refresh_token": "eyJhbGc...", │
|
||||
│ "user_id": 123, │
|
||||
│ "expires_in": 900 │
|
||||
│ } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Telegram Binding Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ STEP 1: User sends /start │
|
||||
│ Bot chat_id: 12345 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ STEP 2: Bot generates binding code │
|
||||
│ POST /api/v1/auth/telegram/start │
|
||||
│ Response: { "code": "ABC123XYZ...", "expires_in": 600 } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ STEP 3: Bot sends link to user │
|
||||
│ "Click: https://app.com/auth/telegram?code=ABC123&... │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ STEP 4: User clicks link │
|
||||
│ (User must be logged in or create account) │
|
||||
│ POST /api/v1/auth/telegram/confirm │
|
||||
│ { "code": "ABC123", "chat_id": 12345 } │
|
||||
│ Headers: Authorization: Bearer <user_jwt> │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ STEP 5: Server creates TelegramIdentity record │
|
||||
│ TelegramIdentity { │
|
||||
│ user_id: 123, │
|
||||
│ chat_id: 12345, │
|
||||
│ verified_at: now │
|
||||
│ } │
|
||||
│ │
|
||||
│ Generate JWT for bot usage │
|
||||
│ Response: { "jwt_token": "..." } │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ STEP 6: Bot stores JWT in Redis │
|
||||
│ Redis key: chat_id:12345:jwt │
|
||||
│ Redis key: chat_id:12345:jwt:exp │
|
||||
│ TTL: 30 days │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ STEP 7: Bot can now make API calls │
|
||||
│ POST /api/v1/transactions │
|
||||
│ Authorization: Bearer <jwt_from_redis> │
|
||||
│ X-Client-Id: telegram_bot │
|
||||
│ X-Signature: HMAC_SHA256(...) │
|
||||
│ X-Timestamp: unixtime │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 👥 RBAC & Permissions
|
||||
|
||||
### Role Hierarchy
|
||||
|
||||
```
|
||||
OWNER (Full Control)
|
||||
└─ Can manage everything
|
||||
└─ Can approve/reject large transactions
|
||||
└─ Can manage members
|
||||
└─ Can delete family
|
||||
|
||||
ADULT
|
||||
└─ Can create/edit transactions
|
||||
└─ Can approve transactions (with owner)
|
||||
└─ Can create budgets/goals
|
||||
└─ Can invite members
|
||||
|
||||
MEMBER
|
||||
└─ Can create/view own transactions
|
||||
└─ Can view budgets/goals
|
||||
└─ Can view shared reports
|
||||
|
||||
CHILD
|
||||
└─ Can create/view limited transactions
|
||||
└─ Can view their allowance
|
||||
└─ Very restricted permissions
|
||||
|
||||
READ_ONLY
|
||||
└─ Can view reports only
|
||||
└─ Audit/observer role
|
||||
```
|
||||
|
||||
### Permission Examples
|
||||
|
||||
| Action | Owner | Adult | Member | Child | Read-Only |
|
||||
|--------|-------|-------|--------|-------|-----------|
|
||||
| Create Transaction | ✓ | ✓ | ✓ | ✓ (limited) | ✗ |
|
||||
| Edit Own Transaction | ✓ | ✓ | ✓ | ✗ | ✗ |
|
||||
| Edit Any Transaction | ✓ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Delete Transaction | ✓ | ✗ | ✗ | ✗ | ✗ |
|
||||
| Approve Transaction | ✓ | ✓ | ✗ | ✗ | ✗ |
|
||||
| Create Budget | ✓ | ✓ | ✗ | ✗ | ✗ |
|
||||
| Manage Members | ✓ | ✗ | ✗ | ✗ | ✗ |
|
||||
| View Audit Log | ✓ | ✓ | ✓ | ✗ | ✓ |
|
||||
| Delete Family | ✓ | ✗ | ✗ | ✗ | ✗ |
|
||||
|
||||
### RBAC Implementation
|
||||
|
||||
**In Code:**
|
||||
```python
|
||||
# Check permission
|
||||
RBACEngine.check_permission(user_context, Permission.CREATE_TRANSACTION)
|
||||
|
||||
# Check family access
|
||||
RBACEngine.check_family_access(user_context, family_id=1)
|
||||
|
||||
# Check resource ownership
|
||||
RBACEngine.check_resource_ownership(user_context, owner_id=123)
|
||||
```
|
||||
|
||||
**In Endpoint:**
|
||||
```python
|
||||
@router.post("/transactions")
|
||||
async def create_transaction(
|
||||
request: TransactionCreateRequest,
|
||||
user_context: UserContext = Depends(get_user_context),
|
||||
):
|
||||
# Middleware already verified JWT
|
||||
# RBAC middleware already checked family access
|
||||
# Now just check specific permission
|
||||
|
||||
RBACEngine.check_permission(
|
||||
user_context,
|
||||
Permission.CREATE_TRANSACTION
|
||||
)
|
||||
|
||||
# Proceed with business logic
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📡 API Endpoints
|
||||
|
||||
### Authentication
|
||||
|
||||
| Method | Endpoint | Purpose |
|
||||
|--------|----------|---------|
|
||||
| POST | `/api/v1/auth/login` | User login |
|
||||
| POST | `/api/v1/auth/refresh` | Refresh access token |
|
||||
| POST | `/api/v1/auth/logout` | Revoke session |
|
||||
| POST | `/api/v1/auth/telegram/start` | Generate binding code |
|
||||
| POST | `/api/v1/auth/telegram/confirm` | Confirm Telegram binding |
|
||||
|
||||
### Transactions
|
||||
|
||||
| Method | Endpoint | Purpose | Auth |
|
||||
|--------|----------|---------|------|
|
||||
| POST | `/api/v1/transactions` | Create transaction | JWT + HMAC |
|
||||
| GET | `/api/v1/transactions` | List transactions | JWT |
|
||||
| GET | `/api/v1/transactions/{id}` | Get transaction | JWT |
|
||||
| POST | `/api/v1/transactions/{id}/confirm` | Approve pending | JWT + HMAC |
|
||||
| DELETE | `/api/v1/transactions/{id}` | Reverse transaction | JWT + HMAC |
|
||||
|
||||
### Wallets
|
||||
|
||||
| Method | Endpoint | Purpose |
|
||||
|--------|----------|---------|
|
||||
| POST | `/api/v1/wallets` | Create wallet |
|
||||
| GET | `/api/v1/wallets` | List wallets |
|
||||
| GET | `/api/v1/wallets/summary` | Balance summary |
|
||||
| PUT | `/api/v1/wallets/{id}` | Update wallet |
|
||||
|
||||
### Budgets & Goals
|
||||
|
||||
| Method | Endpoint | Purpose |
|
||||
|--------|----------|---------|
|
||||
| POST | `/api/v1/budgets` | Create budget |
|
||||
| GET | `/api/v1/budgets` | List budgets |
|
||||
| POST | `/api/v1/goals` | Create goal |
|
||||
| GET | `/api/v1/goals` | List goals |
|
||||
|
||||
### Events & Webhooks
|
||||
|
||||
| Method | Endpoint | Purpose |
|
||||
|--------|----------|---------|
|
||||
| GET | `/api/v1/events/stream/{family_id}` | WebSocket event stream |
|
||||
| POST | `/api/v1/events/webhook/telegram-notification` | Send Telegram notification |
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Telegram Bot Integration
|
||||
|
||||
### Bot Commands
|
||||
|
||||
```
|
||||
/start - Begin account binding
|
||||
/help - Show available commands
|
||||
/balance - View wallet balances
|
||||
/add - Add new transaction
|
||||
/reports - View financial reports
|
||||
```
|
||||
|
||||
### Bot API Communication Pattern
|
||||
|
||||
```python
|
||||
# Get user JWT from Redis
|
||||
jwt_token = redis.get(f"chat_id:{chat_id}:jwt")
|
||||
|
||||
# Make API request
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
"https://api.example.com/api/v1/transactions",
|
||||
headers={
|
||||
"Authorization": f"Bearer {jwt_token}",
|
||||
"X-Client-Id": "telegram_bot",
|
||||
"X-Signature": hmac_signature,
|
||||
"X-Timestamp": str(timestamp),
|
||||
},
|
||||
json=payload,
|
||||
) as response:
|
||||
result = await response.json()
|
||||
```
|
||||
|
||||
### Event Handling
|
||||
|
||||
```
|
||||
Bot listens to Redis Streams:
|
||||
- transaction.created
|
||||
- budget.alert
|
||||
- goal.completed
|
||||
- member.joined
|
||||
|
||||
Bot processes events → Sends Telegram messages
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
- JWT token generation/verification
|
||||
- HMAC signature creation/verification
|
||||
- RBAC permission checks
|
||||
- Service business logic
|
||||
|
||||
### Integration Tests
|
||||
- Full request → response cycles
|
||||
- Authentication flows
|
||||
- RBAC in middleware
|
||||
- Database transactions
|
||||
|
||||
### Security Tests
|
||||
- Invalid token rejection
|
||||
- HMAC signature verification
|
||||
- Timestamp freshness
|
||||
- Signature replay prevention
|
||||
- Family isolation
|
||||
|
||||
### Load Testing Example (k6)
|
||||
|
||||
```javascript
|
||||
import http from 'k6/http';
|
||||
import { check } from 'k6';
|
||||
|
||||
export let options = {
|
||||
vus: 10,
|
||||
duration: '30s',
|
||||
};
|
||||
|
||||
export default function() {
|
||||
let url = 'http://localhost:8000/api/v1/wallets';
|
||||
let params = {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${__ENV.JWT_TOKEN}`,
|
||||
'X-Client-Id': 'k6_test',
|
||||
},
|
||||
};
|
||||
|
||||
let res = http.get(url, params);
|
||||
check(res, {
|
||||
'status is 200': (r) => r.status === 200,
|
||||
'response time < 500ms': (r) => r.timings.duration < 500,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment
|
||||
|
||||
### Docker Compose (MVP)
|
||||
|
||||
```yaml
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
api:
|
||||
image: finance-bot:latest
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://...
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- JWT_SECRET_KEY=...
|
||||
- HMAC_SECRET_KEY=...
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
networks:
|
||||
- finance
|
||||
|
||||
bot:
|
||||
image: finance-bot-bot:latest
|
||||
environment:
|
||||
- BOT_TOKEN=...
|
||||
- API_BASE_URL=http://api:8000
|
||||
- REDIS_URL=redis://redis:6379
|
||||
depends_on:
|
||||
- api
|
||||
- redis
|
||||
networks:
|
||||
- finance
|
||||
|
||||
worker:
|
||||
image: finance-bot-worker:latest
|
||||
environment:
|
||||
- REDIS_URL=redis://redis:6379
|
||||
- DATABASE_URL=postgresql://...
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
networks:
|
||||
- finance
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_DB: finance_db
|
||||
POSTGRES_USER: trevor
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
networks:
|
||||
- finance
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
networks:
|
||||
- finance
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
|
||||
networks:
|
||||
finance:
|
||||
```
|
||||
|
||||
### Kubernetes Deployment (Future)
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: api-config
|
||||
data:
|
||||
JWT_SECRET_KEY: <secret>
|
||||
HMAC_SECRET_KEY: <secret>
|
||||
DATABASE_URL: postgresql://postgres/finance_db
|
||||
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: api-server
|
||||
spec:
|
||||
replicas: 3
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: api
|
||||
image: finance-bot:latest
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: api-config
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Production Checklist
|
||||
|
||||
### Security
|
||||
- [ ] Change JWT_SECRET_KEY from default
|
||||
- [ ] Change HMAC_SECRET_KEY from default
|
||||
- [ ] Enable HTTPS/TLS (Nginx reverse proxy)
|
||||
- [ ] Enable CORS only for approved origins
|
||||
- [ ] Set require_hmac_verification=true
|
||||
- [ ] Implement password hashing (bcrypt)
|
||||
- [ ] Implement token blacklisting
|
||||
- [ ] Add rate limiting (current: 100 req/min)
|
||||
- [ ] Enable audit logging (EventLog)
|
||||
- [ ] Encrypt sensitive data at rest
|
||||
|
||||
### Deployment
|
||||
- [ ] Run migrations in production
|
||||
- [ ] Set app_env="production"
|
||||
- [ ] Disable debug mode
|
||||
- [ ] Configure proper logging
|
||||
- [ ] Set up monitoring/alerts
|
||||
- [ ] Configure backup strategy
|
||||
- [ ] Test failover procedures
|
||||
- [ ] Document runbooks
|
||||
|
||||
### Testing
|
||||
- [ ] Unit tests coverage > 80%
|
||||
- [ ] Integration tests for critical flows
|
||||
- [ ] Security testing (OWASP Top 10)
|
||||
- [ ] Load testing (identify bottlenecks)
|
||||
- [ ] Penetration testing
|
||||
- [ ] API contract testing
|
||||
|
||||
### Operations
|
||||
- [ ] Set up CI/CD pipeline
|
||||
- [ ] Configure health check endpoints
|
||||
- [ ] Set up application monitoring
|
||||
- [ ] Configure database backups
|
||||
- [ ] Document API in OpenAPI/Swagger
|
||||
- [ ] Set up error tracking (Sentry)
|
||||
- [ ] Implement graceful shutdown
|
||||
|
||||
---
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
### OpenAPI Specification
|
||||
```bash
|
||||
# Auto-generated from FastAPI
|
||||
GET /docs # Swagger UI
|
||||
GET /redoc # ReDoc documentation
|
||||
GET /openapi.json # OpenAPI spec
|
||||
```
|
||||
|
||||
### Architecture Decision Records (ADR)
|
||||
- ADR-001: JWT + HMAC for authentication (not just JWT)
|
||||
- ADR-002: Redis Streams for event bus (vs RabbitMQ)
|
||||
- ADR-003: Compensation transactions for reversals
|
||||
- ADR-004: Family-level isolation in all queries
|
||||
|
||||
### Performance Targets
|
||||
- API response time: < 200ms (p95)
|
||||
- Transaction creation: < 100ms
|
||||
- List queries: < 500ms (for 1000 items)
|
||||
- HMAC verification: < 5ms
|
||||
- JWT verification: < 2ms
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Roadmap (Post-MVP)
|
||||
|
||||
### Phase 2: Enhanced Features
|
||||
- [ ] Web Frontend (React/Vue)
|
||||
- [ ] Mobile App (React Native/Flutter)
|
||||
- [ ] Advanced reporting (PDF export, charts)
|
||||
- [ ] Recurring transactions
|
||||
- [ ] Currency conversion
|
||||
- [ ] Multi-family support
|
||||
- [ ] User notifications preferences
|
||||
|
||||
### Phase 3: Enterprise Features
|
||||
- [ ] Kubernetes deployment
|
||||
- [ ] Multi-region failover
|
||||
- [ ] Advanced RBAC (custom roles)
|
||||
- [ ] Audit webhook integrations
|
||||
- [ ] API rate limiting (per-user)
|
||||
- [ ] Data export (GDPR compliance)
|
||||
- [ ] SSO integration (OAuth2)
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** 2025-12-10
|
||||
**Status:** MVP Complete
|
||||
523
.history/docs/MVP_QUICK_START_20251210210643.md
Normal file
523
.history/docs/MVP_QUICK_START_20251210210643.md
Normal file
@@ -0,0 +1,523 @@
|
||||
# 🚀 MVP Implementation Quick Start
|
||||
|
||||
## Phase-by-Phase Implementation Guide
|
||||
|
||||
### ✅ Phase 1: Complete (Existing)
|
||||
- Database schema with 10 tables
|
||||
- Environment variable management
|
||||
- Docker Compose setup
|
||||
- API health endpoint
|
||||
|
||||
### 🔄 Phase 2: Security Foundation (THIS DELIVERABLE)
|
||||
|
||||
#### 2.1 Database Migrations
|
||||
```bash
|
||||
# Run the new migration
|
||||
cd /home/data/finance_bot
|
||||
source .venv/bin/activate
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
**What it creates:**
|
||||
- `sessions` table (for refresh token tracking)
|
||||
- `telegram_identities` table (Telegram user binding)
|
||||
- `event_log` table (audit trail)
|
||||
- `access_log` table (request logging)
|
||||
- Enhanced `transactions` (with approval workflow)
|
||||
- Enhanced `family_members` (RBAC)
|
||||
|
||||
#### 2.2 Install Dependencies
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
**Key additions:**
|
||||
```
|
||||
PyJWT==2.8.1 # JWT token management
|
||||
aiohttp==3.9.1 # Async HTTP client
|
||||
python-multipart==0.0.6 # Form data parsing
|
||||
redis==5.0.1 # Redis client
|
||||
```
|
||||
|
||||
#### 2.3 Update Configuration
|
||||
```bash
|
||||
# Add to .env
|
||||
JWT_SECRET_KEY=your-super-secret-key-min-32-chars-here-please
|
||||
HMAC_SECRET_KEY=your-hmac-secret-key-min-32-chars-please
|
||||
REQUIRE_HMAC_VERIFICATION=false # Disabled in MVP
|
||||
```
|
||||
|
||||
#### 2.4 Verify API Starts
|
||||
```bash
|
||||
# Start FastAPI server
|
||||
python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
||||
|
||||
# In another terminal, test
|
||||
curl http://localhost:8000/health
|
||||
# Response: {"status":"ok","environment":"development","version":"1.0.0"}
|
||||
```
|
||||
|
||||
### 📋 Phase 3: API Endpoints (EXAMPLES)
|
||||
|
||||
#### 3.1 Authentication Endpoints
|
||||
|
||||
**Login:**
|
||||
```bash
|
||||
POST /api/v1/auth/login
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "password123"
|
||||
}
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"user_id": 1,
|
||||
"expires_in": 900
|
||||
}
|
||||
```
|
||||
|
||||
**Telegram Binding (Start):**
|
||||
```bash
|
||||
POST /api/v1/auth/telegram/start
|
||||
{
|
||||
"chat_id": 12345
|
||||
}
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"code": "ABC123XYZ...",
|
||||
"expires_in": 600
|
||||
}
|
||||
```
|
||||
|
||||
**Telegram Binding (Confirm):**
|
||||
```bash
|
||||
POST /api/v1/auth/telegram/confirm
|
||||
Authorization: Bearer <user_access_token>
|
||||
{
|
||||
"code": "ABC123XYZ...",
|
||||
"chat_id": 12345,
|
||||
"username": "john_doe",
|
||||
"first_name": "John"
|
||||
}
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"success": true,
|
||||
"user_id": 1,
|
||||
"jwt_token": "eyJhbGc...",
|
||||
"expires_at": "2024-01-09T12:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 Transaction Endpoints
|
||||
|
||||
**Create Transaction (Small Amount - Auto-executed):**
|
||||
```bash
|
||||
POST /api/v1/transactions
|
||||
Authorization: Bearer <jwt_token>
|
||||
X-Client-Id: telegram_bot
|
||||
X-Timestamp: 1702237800
|
||||
X-Signature: <hmac_sha256>
|
||||
|
||||
{
|
||||
"family_id": 1,
|
||||
"from_wallet_id": 10,
|
||||
"to_wallet_id": 11,
|
||||
"amount": 50.00,
|
||||
"category_id": 5,
|
||||
"description": "Groceries"
|
||||
}
|
||||
|
||||
Response 201:
|
||||
{
|
||||
"id": 100,
|
||||
"status": "executed",
|
||||
"amount": "50.00",
|
||||
"confirmation_required": false,
|
||||
"created_at": "2023-12-10T12:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Create Transaction (Large Amount - Requires Approval):**
|
||||
```bash
|
||||
POST /api/v1/transactions
|
||||
...
|
||||
{
|
||||
...
|
||||
"amount": 600.00, # > threshold
|
||||
}
|
||||
|
||||
Response 201:
|
||||
{
|
||||
"id": 101,
|
||||
"status": "pending_approval",
|
||||
"amount": "600.00",
|
||||
"confirmation_required": true,
|
||||
"created_at": "2023-12-10T12:30:00Z"
|
||||
}
|
||||
|
||||
# Bot notifies owner in Telegram
|
||||
```
|
||||
|
||||
**Approve Transaction:**
|
||||
```bash
|
||||
POST /api/v1/transactions/101/confirm
|
||||
Authorization: Bearer <owner_jwt>
|
||||
{
|
||||
"confirmation_token": null
|
||||
}
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"id": 101,
|
||||
"status": "executed",
|
||||
"executed_at": "2023-12-10T12:35:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Reverse Transaction:**
|
||||
```bash
|
||||
DELETE /api/v1/transactions/100
|
||||
Authorization: Bearer <jwt>
|
||||
{
|
||||
"reason": "User requested refund"
|
||||
}
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"original_transaction_id": 100,
|
||||
"reversal_transaction_id": 102,
|
||||
"reversed_at": "2023-12-10T12:40:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3 Wallet Endpoints
|
||||
|
||||
**List Wallets:**
|
||||
```bash
|
||||
GET /api/v1/wallets?family_id=1
|
||||
Authorization: Bearer <jwt>
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"wallets": [
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Cash",
|
||||
"balance": "150.00",
|
||||
"type": "cash"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "Bank Account",
|
||||
"balance": "1250.00",
|
||||
"type": "bank"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing the MVP
|
||||
|
||||
### 1. Unit Tests
|
||||
```bash
|
||||
# Run security tests
|
||||
pytest tests/test_security.py -v
|
||||
|
||||
# Run specific test
|
||||
pytest tests/test_security.py::TestJWTManager::test_create_access_token -v
|
||||
```
|
||||
|
||||
### 2. Integration Tests
|
||||
```bash
|
||||
# Start API server in background
|
||||
python -m uvicorn app.main:app &
|
||||
|
||||
# Run full test suite
|
||||
pytest tests/ -v
|
||||
|
||||
# Run with coverage
|
||||
pytest tests/ --cov=app --cov-report=html
|
||||
```
|
||||
|
||||
### 3. Manual API Testing
|
||||
|
||||
**Using curl:**
|
||||
```bash
|
||||
# Get health
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# Create transaction (need valid JWT)
|
||||
JWT_TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"user@example.com","password":"pass"}' | jq -r '.access_token')
|
||||
|
||||
curl -X POST http://localhost:8000/api/v1/transactions \
|
||||
-H "Authorization: Bearer $JWT_TOKEN" \
|
||||
-H "X-Client-Id: manual_test" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"family_id": 1,
|
||||
"from_wallet_id": 10,
|
||||
"to_wallet_id": 11,
|
||||
"amount": 50.00,
|
||||
"category_id": 5,
|
||||
"description": "Test transaction"
|
||||
}'
|
||||
```
|
||||
|
||||
**Using Swagger UI:**
|
||||
```
|
||||
http://localhost:8000/docs
|
||||
```
|
||||
- All endpoints documented with interactive testing
|
||||
- Try endpoints directly from browser
|
||||
|
||||
**Using Postman:**
|
||||
1. Open Postman
|
||||
2. Create new request
|
||||
3. Set URL: `http://localhost:8000/api/v1/transactions`
|
||||
4. Set Method: `POST`
|
||||
5. Add Headers:
|
||||
- `Authorization: Bearer <your_jwt>`
|
||||
- `X-Client-Id: postman`
|
||||
6. Set Body (JSON):
|
||||
```json
|
||||
{
|
||||
"family_id": 1,
|
||||
"from_wallet_id": 10,
|
||||
"to_wallet_id": 11,
|
||||
"amount": 50.00,
|
||||
"category_id": 5,
|
||||
"description": "Postman test"
|
||||
}
|
||||
```
|
||||
7. Send!
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Telegram Bot Testing
|
||||
|
||||
### 1. Local Bot Testing
|
||||
|
||||
**Create test bot:**
|
||||
1. Open Telegram
|
||||
2. Chat with @BotFather
|
||||
3. `/newbot`
|
||||
4. Follow instructions
|
||||
5. Get BOT_TOKEN
|
||||
|
||||
**Update .env:**
|
||||
```
|
||||
BOT_TOKEN=<your_test_bot_token>
|
||||
```
|
||||
|
||||
**Run bot:**
|
||||
```bash
|
||||
# Terminal 1: API server
|
||||
python -m uvicorn app.main:app --reload
|
||||
|
||||
# Terminal 2: Bot client (polling)
|
||||
# TODO: Implement bot polling in app/bot/worker.py
|
||||
```
|
||||
|
||||
**Test flow:**
|
||||
```
|
||||
Your Telegram → /start
|
||||
Bot → "Click link to bind: https://..."
|
||||
You → Click link (authenticate)
|
||||
API → Create TelegramIdentity
|
||||
You → Bot says "Connected!"
|
||||
You → /balance
|
||||
Bot → Shows wallets via API call
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 RBAC Testing
|
||||
|
||||
### Test Permission Checking
|
||||
|
||||
```python
|
||||
# python -i
|
||||
from app.security.rbac import RBACEngine, MemberRole, Permission, UserContext
|
||||
|
||||
# Create contexts for different roles
|
||||
owner = UserContext(
|
||||
user_id=1,
|
||||
family_id=1,
|
||||
role=MemberRole.OWNER,
|
||||
permissions=RBACEngine.get_permissions(MemberRole.OWNER),
|
||||
family_ids=[1],
|
||||
)
|
||||
|
||||
member = UserContext(
|
||||
user_id=2,
|
||||
family_id=1,
|
||||
role=MemberRole.MEMBER,
|
||||
permissions=RBACEngine.get_permissions(MemberRole.MEMBER),
|
||||
family_ids=[1],
|
||||
)
|
||||
|
||||
# Test owner permissions
|
||||
RBACEngine.has_permission(owner, Permission.DELETE_FAMILY) # True
|
||||
RBACEngine.has_permission(member, Permission.DELETE_FAMILY) # False
|
||||
|
||||
# Test family access
|
||||
RBACEngine.check_family_access(owner, 1) # OK
|
||||
RBACEngine.check_family_access(member, 2, raise_exception=False) # False
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment Steps
|
||||
|
||||
### Docker Compose Deployment
|
||||
|
||||
```bash
|
||||
# Navigate to project
|
||||
cd /home/data/finance_bot
|
||||
|
||||
# Build images
|
||||
docker-compose build
|
||||
|
||||
# Start services
|
||||
docker-compose up -d
|
||||
|
||||
# Run migrations
|
||||
docker-compose exec migrations python -m alembic upgrade head
|
||||
|
||||
# Verify
|
||||
docker-compose ps
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# Check logs
|
||||
docker-compose logs -f api
|
||||
docker-compose logs -f bot
|
||||
```
|
||||
|
||||
### Docker-Free Deployment
|
||||
|
||||
```bash
|
||||
# Setup environment
|
||||
source .venv/bin/activate
|
||||
|
||||
# Update .env
|
||||
export $(cat .env | grep -v '#' | xargs)
|
||||
|
||||
# Start services (in separate terminals)
|
||||
# Terminal 1: API
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
|
||||
# Terminal 2: Bot (polling)
|
||||
# TODO: Implement in app/bot/worker.py
|
||||
python -m app.bot.worker
|
||||
|
||||
# Terminal 3: Worker (event processing)
|
||||
# TODO: Implement in app/workers/event_processor.py
|
||||
python -m app.workers.event_processor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Monitoring & Debugging
|
||||
|
||||
### Enable Debug Logging
|
||||
|
||||
```python
|
||||
# In app/core/config.py
|
||||
log_level: str = "DEBUG"
|
||||
|
||||
# In .env
|
||||
LOG_LEVEL=DEBUG
|
||||
```
|
||||
|
||||
### View Event Log
|
||||
|
||||
```python
|
||||
# python -i
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.db.models import EventLog
|
||||
|
||||
engine = create_engine("postgresql://...")
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
|
||||
# Query recent events
|
||||
events = session.query(EventLog).order_by(EventLog.created_at.desc()).limit(10)
|
||||
for e in events:
|
||||
print(f"{e.created_at} | {e.action} | {e.entity_type} #{e.entity_id} | Actor: {e.actor_id}")
|
||||
```
|
||||
|
||||
### View Access Log
|
||||
|
||||
```python
|
||||
from app.db.models import AccessLog
|
||||
|
||||
access = session.query(AccessLog).order_by(AccessLog.created_at.desc()).limit(10)
|
||||
for a in access:
|
||||
print(f"{a.created_at} | {a.method} {a.endpoint} | {a.status_code} | {a.user_id} | {a.ip_address}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ❌ Troubleshooting
|
||||
|
||||
### Issue: "type already exists"
|
||||
**Solution:**
|
||||
```bash
|
||||
# Drop conflicting type in PostgreSQL
|
||||
docker exec finance_bot_postgres psql -U trevor -d finance_db -c \
|
||||
"DROP TYPE IF EXISTS family_role CASCADE;"
|
||||
|
||||
# Re-run migration
|
||||
docker-compose exec migrations python -m alembic downgrade -1
|
||||
docker-compose exec migrations python -m alembic upgrade head
|
||||
```
|
||||
|
||||
### Issue: JWT token verification fails
|
||||
**Solution:**
|
||||
```python
|
||||
# Check token expiration
|
||||
from app.security.jwt_manager import jwt_manager
|
||||
token_payload = jwt_manager.decode_token(token) # Ignore signature
|
||||
print(f"Expires at: {token_payload.get('exp')}")
|
||||
print(f"Current time: {datetime.utcnow().timestamp()}")
|
||||
```
|
||||
|
||||
### Issue: HMAC signature mismatch
|
||||
**Solution:**
|
||||
1. Verify base_string format: `METHOD:ENDPOINT:TIMESTAMP:BODY_HASH`
|
||||
2. Verify client_secret matches on both sides
|
||||
3. Check timestamp isn't too old (±30 seconds)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Next Steps
|
||||
|
||||
### To Complete MVP:
|
||||
1. ✅ Security foundation created
|
||||
2. ⏳ Add remaining endpoints (Wallets, Budgets, Goals, Reports)
|
||||
3. ⏳ Implement worker process (event consumer)
|
||||
4. ⏳ Implement Telegram bot webhook (instead of polling)
|
||||
5. ⏳ Add comprehensive tests
|
||||
6. ⏳ Generate API documentation
|
||||
|
||||
### To Extend MVP:
|
||||
1. Web frontend (React)
|
||||
2. Mobile app (React Native)
|
||||
3. Advanced reporting
|
||||
4. Kubernetes deployment
|
||||
5. Multi-region setup
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** 2025-12-10
|
||||
**Next Review:** 2025-12-24
|
||||
523
.history/docs/MVP_QUICK_START_20251210210906.md
Normal file
523
.history/docs/MVP_QUICK_START_20251210210906.md
Normal file
@@ -0,0 +1,523 @@
|
||||
# 🚀 MVP Implementation Quick Start
|
||||
|
||||
## Phase-by-Phase Implementation Guide
|
||||
|
||||
### ✅ Phase 1: Complete (Existing)
|
||||
- Database schema with 10 tables
|
||||
- Environment variable management
|
||||
- Docker Compose setup
|
||||
- API health endpoint
|
||||
|
||||
### 🔄 Phase 2: Security Foundation (THIS DELIVERABLE)
|
||||
|
||||
#### 2.1 Database Migrations
|
||||
```bash
|
||||
# Run the new migration
|
||||
cd /home/data/finance_bot
|
||||
source .venv/bin/activate
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
**What it creates:**
|
||||
- `sessions` table (for refresh token tracking)
|
||||
- `telegram_identities` table (Telegram user binding)
|
||||
- `event_log` table (audit trail)
|
||||
- `access_log` table (request logging)
|
||||
- Enhanced `transactions` (with approval workflow)
|
||||
- Enhanced `family_members` (RBAC)
|
||||
|
||||
#### 2.2 Install Dependencies
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
**Key additions:**
|
||||
```
|
||||
PyJWT==2.8.1 # JWT token management
|
||||
aiohttp==3.9.1 # Async HTTP client
|
||||
python-multipart==0.0.6 # Form data parsing
|
||||
redis==5.0.1 # Redis client
|
||||
```
|
||||
|
||||
#### 2.3 Update Configuration
|
||||
```bash
|
||||
# Add to .env
|
||||
JWT_SECRET_KEY=your-super-secret-key-min-32-chars-here-please
|
||||
HMAC_SECRET_KEY=your-hmac-secret-key-min-32-chars-please
|
||||
REQUIRE_HMAC_VERIFICATION=false # Disabled in MVP
|
||||
```
|
||||
|
||||
#### 2.4 Verify API Starts
|
||||
```bash
|
||||
# Start FastAPI server
|
||||
python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
||||
|
||||
# In another terminal, test
|
||||
curl http://localhost:8000/health
|
||||
# Response: {"status":"ok","environment":"development","version":"1.0.0"}
|
||||
```
|
||||
|
||||
### 📋 Phase 3: API Endpoints (EXAMPLES)
|
||||
|
||||
#### 3.1 Authentication Endpoints
|
||||
|
||||
**Login:**
|
||||
```bash
|
||||
POST /api/v1/auth/login
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"password": "password123"
|
||||
}
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"user_id": 1,
|
||||
"expires_in": 900
|
||||
}
|
||||
```
|
||||
|
||||
**Telegram Binding (Start):**
|
||||
```bash
|
||||
POST /api/v1/auth/telegram/start
|
||||
{
|
||||
"chat_id": 12345
|
||||
}
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"code": "ABC123XYZ...",
|
||||
"expires_in": 600
|
||||
}
|
||||
```
|
||||
|
||||
**Telegram Binding (Confirm):**
|
||||
```bash
|
||||
POST /api/v1/auth/telegram/confirm
|
||||
Authorization: Bearer <user_access_token>
|
||||
{
|
||||
"code": "ABC123XYZ...",
|
||||
"chat_id": 12345,
|
||||
"username": "john_doe",
|
||||
"first_name": "John"
|
||||
}
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"success": true,
|
||||
"user_id": 1,
|
||||
"jwt_token": "eyJhbGc...",
|
||||
"expires_at": "2024-01-09T12:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 Transaction Endpoints
|
||||
|
||||
**Create Transaction (Small Amount - Auto-executed):**
|
||||
```bash
|
||||
POST /api/v1/transactions
|
||||
Authorization: Bearer <jwt_token>
|
||||
X-Client-Id: telegram_bot
|
||||
X-Timestamp: 1702237800
|
||||
X-Signature: <hmac_sha256>
|
||||
|
||||
{
|
||||
"family_id": 1,
|
||||
"from_wallet_id": 10,
|
||||
"to_wallet_id": 11,
|
||||
"amount": 50.00,
|
||||
"category_id": 5,
|
||||
"description": "Groceries"
|
||||
}
|
||||
|
||||
Response 201:
|
||||
{
|
||||
"id": 100,
|
||||
"status": "executed",
|
||||
"amount": "50.00",
|
||||
"confirmation_required": false,
|
||||
"created_at": "2023-12-10T12:30:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Create Transaction (Large Amount - Requires Approval):**
|
||||
```bash
|
||||
POST /api/v1/transactions
|
||||
...
|
||||
{
|
||||
...
|
||||
"amount": 600.00, # > threshold
|
||||
}
|
||||
|
||||
Response 201:
|
||||
{
|
||||
"id": 101,
|
||||
"status": "pending_approval",
|
||||
"amount": "600.00",
|
||||
"confirmation_required": true,
|
||||
"created_at": "2023-12-10T12:30:00Z"
|
||||
}
|
||||
|
||||
# Bot notifies owner in Telegram
|
||||
```
|
||||
|
||||
**Approve Transaction:**
|
||||
```bash
|
||||
POST /api/v1/transactions/101/confirm
|
||||
Authorization: Bearer <owner_jwt>
|
||||
{
|
||||
"confirmation_token": null
|
||||
}
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"id": 101,
|
||||
"status": "executed",
|
||||
"executed_at": "2023-12-10T12:35:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Reverse Transaction:**
|
||||
```bash
|
||||
DELETE /api/v1/transactions/100
|
||||
Authorization: Bearer <jwt>
|
||||
{
|
||||
"reason": "User requested refund"
|
||||
}
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"original_transaction_id": 100,
|
||||
"reversal_transaction_id": 102,
|
||||
"reversed_at": "2023-12-10T12:40:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3 Wallet Endpoints
|
||||
|
||||
**List Wallets:**
|
||||
```bash
|
||||
GET /api/v1/wallets?family_id=1
|
||||
Authorization: Bearer <jwt>
|
||||
|
||||
Response 200:
|
||||
{
|
||||
"wallets": [
|
||||
{
|
||||
"id": 10,
|
||||
"name": "Cash",
|
||||
"balance": "150.00",
|
||||
"type": "cash"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "Bank Account",
|
||||
"balance": "1250.00",
|
||||
"type": "bank"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing the MVP
|
||||
|
||||
### 1. Unit Tests
|
||||
```bash
|
||||
# Run security tests
|
||||
pytest tests/test_security.py -v
|
||||
|
||||
# Run specific test
|
||||
pytest tests/test_security.py::TestJWTManager::test_create_access_token -v
|
||||
```
|
||||
|
||||
### 2. Integration Tests
|
||||
```bash
|
||||
# Start API server in background
|
||||
python -m uvicorn app.main:app &
|
||||
|
||||
# Run full test suite
|
||||
pytest tests/ -v
|
||||
|
||||
# Run with coverage
|
||||
pytest tests/ --cov=app --cov-report=html
|
||||
```
|
||||
|
||||
### 3. Manual API Testing
|
||||
|
||||
**Using curl:**
|
||||
```bash
|
||||
# Get health
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# Create transaction (need valid JWT)
|
||||
JWT_TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email":"user@example.com","password":"pass"}' | jq -r '.access_token')
|
||||
|
||||
curl -X POST http://localhost:8000/api/v1/transactions \
|
||||
-H "Authorization: Bearer $JWT_TOKEN" \
|
||||
-H "X-Client-Id: manual_test" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"family_id": 1,
|
||||
"from_wallet_id": 10,
|
||||
"to_wallet_id": 11,
|
||||
"amount": 50.00,
|
||||
"category_id": 5,
|
||||
"description": "Test transaction"
|
||||
}'
|
||||
```
|
||||
|
||||
**Using Swagger UI:**
|
||||
```
|
||||
http://localhost:8000/docs
|
||||
```
|
||||
- All endpoints documented with interactive testing
|
||||
- Try endpoints directly from browser
|
||||
|
||||
**Using Postman:**
|
||||
1. Open Postman
|
||||
2. Create new request
|
||||
3. Set URL: `http://localhost:8000/api/v1/transactions`
|
||||
4. Set Method: `POST`
|
||||
5. Add Headers:
|
||||
- `Authorization: Bearer <your_jwt>`
|
||||
- `X-Client-Id: postman`
|
||||
6. Set Body (JSON):
|
||||
```json
|
||||
{
|
||||
"family_id": 1,
|
||||
"from_wallet_id": 10,
|
||||
"to_wallet_id": 11,
|
||||
"amount": 50.00,
|
||||
"category_id": 5,
|
||||
"description": "Postman test"
|
||||
}
|
||||
```
|
||||
7. Send!
|
||||
|
||||
---
|
||||
|
||||
## 🤖 Telegram Bot Testing
|
||||
|
||||
### 1. Local Bot Testing
|
||||
|
||||
**Create test bot:**
|
||||
1. Open Telegram
|
||||
2. Chat with @BotFather
|
||||
3. `/newbot`
|
||||
4. Follow instructions
|
||||
5. Get BOT_TOKEN
|
||||
|
||||
**Update .env:**
|
||||
```
|
||||
BOT_TOKEN=<your_test_bot_token>
|
||||
```
|
||||
|
||||
**Run bot:**
|
||||
```bash
|
||||
# Terminal 1: API server
|
||||
python -m uvicorn app.main:app --reload
|
||||
|
||||
# Terminal 2: Bot client (polling)
|
||||
# TODO: Implement bot polling in app/bot/worker.py
|
||||
```
|
||||
|
||||
**Test flow:**
|
||||
```
|
||||
Your Telegram → /start
|
||||
Bot → "Click link to bind: https://..."
|
||||
You → Click link (authenticate)
|
||||
API → Create TelegramIdentity
|
||||
You → Bot says "Connected!"
|
||||
You → /balance
|
||||
Bot → Shows wallets via API call
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 RBAC Testing
|
||||
|
||||
### Test Permission Checking
|
||||
|
||||
```python
|
||||
# python -i
|
||||
from app.security.rbac import RBACEngine, MemberRole, Permission, UserContext
|
||||
|
||||
# Create contexts for different roles
|
||||
owner = UserContext(
|
||||
user_id=1,
|
||||
family_id=1,
|
||||
role=MemberRole.OWNER,
|
||||
permissions=RBACEngine.get_permissions(MemberRole.OWNER),
|
||||
family_ids=[1],
|
||||
)
|
||||
|
||||
member = UserContext(
|
||||
user_id=2,
|
||||
family_id=1,
|
||||
role=MemberRole.MEMBER,
|
||||
permissions=RBACEngine.get_permissions(MemberRole.MEMBER),
|
||||
family_ids=[1],
|
||||
)
|
||||
|
||||
# Test owner permissions
|
||||
RBACEngine.has_permission(owner, Permission.DELETE_FAMILY) # True
|
||||
RBACEngine.has_permission(member, Permission.DELETE_FAMILY) # False
|
||||
|
||||
# Test family access
|
||||
RBACEngine.check_family_access(owner, 1) # OK
|
||||
RBACEngine.check_family_access(member, 2, raise_exception=False) # False
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment Steps
|
||||
|
||||
### Docker Compose Deployment
|
||||
|
||||
```bash
|
||||
# Navigate to project
|
||||
cd /home/data/finance_bot
|
||||
|
||||
# Build images
|
||||
docker-compose build
|
||||
|
||||
# Start services
|
||||
docker-compose up -d
|
||||
|
||||
# Run migrations
|
||||
docker-compose exec migrations python -m alembic upgrade head
|
||||
|
||||
# Verify
|
||||
docker-compose ps
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# Check logs
|
||||
docker-compose logs -f api
|
||||
docker-compose logs -f bot
|
||||
```
|
||||
|
||||
### Docker-Free Deployment
|
||||
|
||||
```bash
|
||||
# Setup environment
|
||||
source .venv/bin/activate
|
||||
|
||||
# Update .env
|
||||
export $(cat .env | grep -v '#' | xargs)
|
||||
|
||||
# Start services (in separate terminals)
|
||||
# Terminal 1: API
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
|
||||
# Terminal 2: Bot (polling)
|
||||
# TODO: Implement in app/bot/worker.py
|
||||
python -m app.bot.worker
|
||||
|
||||
# Terminal 3: Worker (event processing)
|
||||
# TODO: Implement in app/workers/event_processor.py
|
||||
python -m app.workers.event_processor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Monitoring & Debugging
|
||||
|
||||
### Enable Debug Logging
|
||||
|
||||
```python
|
||||
# In app/core/config.py
|
||||
log_level: str = "DEBUG"
|
||||
|
||||
# In .env
|
||||
LOG_LEVEL=DEBUG
|
||||
```
|
||||
|
||||
### View Event Log
|
||||
|
||||
```python
|
||||
# python -i
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.db.models import EventLog
|
||||
|
||||
engine = create_engine("postgresql://...")
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
|
||||
# Query recent events
|
||||
events = session.query(EventLog).order_by(EventLog.created_at.desc()).limit(10)
|
||||
for e in events:
|
||||
print(f"{e.created_at} | {e.action} | {e.entity_type} #{e.entity_id} | Actor: {e.actor_id}")
|
||||
```
|
||||
|
||||
### View Access Log
|
||||
|
||||
```python
|
||||
from app.db.models import AccessLog
|
||||
|
||||
access = session.query(AccessLog).order_by(AccessLog.created_at.desc()).limit(10)
|
||||
for a in access:
|
||||
print(f"{a.created_at} | {a.method} {a.endpoint} | {a.status_code} | {a.user_id} | {a.ip_address}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ❌ Troubleshooting
|
||||
|
||||
### Issue: "type already exists"
|
||||
**Solution:**
|
||||
```bash
|
||||
# Drop conflicting type in PostgreSQL
|
||||
docker exec finance_bot_postgres psql -U trevor -d finance_db -c \
|
||||
"DROP TYPE IF EXISTS family_role CASCADE;"
|
||||
|
||||
# Re-run migration
|
||||
docker-compose exec migrations python -m alembic downgrade -1
|
||||
docker-compose exec migrations python -m alembic upgrade head
|
||||
```
|
||||
|
||||
### Issue: JWT token verification fails
|
||||
**Solution:**
|
||||
```python
|
||||
# Check token expiration
|
||||
from app.security.jwt_manager import jwt_manager
|
||||
token_payload = jwt_manager.decode_token(token) # Ignore signature
|
||||
print(f"Expires at: {token_payload.get('exp')}")
|
||||
print(f"Current time: {datetime.utcnow().timestamp()}")
|
||||
```
|
||||
|
||||
### Issue: HMAC signature mismatch
|
||||
**Solution:**
|
||||
1. Verify base_string format: `METHOD:ENDPOINT:TIMESTAMP:BODY_HASH`
|
||||
2. Verify client_secret matches on both sides
|
||||
3. Check timestamp isn't too old (±30 seconds)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Next Steps
|
||||
|
||||
### To Complete MVP:
|
||||
1. ✅ Security foundation created
|
||||
2. ⏳ Add remaining endpoints (Wallets, Budgets, Goals, Reports)
|
||||
3. ⏳ Implement worker process (event consumer)
|
||||
4. ⏳ Implement Telegram bot webhook (instead of polling)
|
||||
5. ⏳ Add comprehensive tests
|
||||
6. ⏳ Generate API documentation
|
||||
|
||||
### To Extend MVP:
|
||||
1. Web frontend (React)
|
||||
2. Mobile app (React Native)
|
||||
3. Advanced reporting
|
||||
4. Kubernetes deployment
|
||||
5. Multi-region setup
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** 2025-12-10
|
||||
**Next Review:** 2025-12-24
|
||||
502
.history/docs/SECURITY_ARCHITECTURE_ADR_20251210210752.md
Normal file
502
.history/docs/SECURITY_ARCHITECTURE_ADR_20251210210752.md
Normal file
@@ -0,0 +1,502 @@
|
||||
# 🔐 Security Architecture Decision Records
|
||||
|
||||
## ADR-001: JWT + HMAC Dual Authentication
|
||||
|
||||
### Decision
|
||||
Use JWT for client authentication + HMAC for request integrity verification.
|
||||
|
||||
### Context
|
||||
- Single JWT alone vulnerable to token theft (XSS, interception)
|
||||
- HMAC ensures request wasn't tampered with in transit
|
||||
- Combined approach provides defense-in-depth
|
||||
|
||||
### Solution
|
||||
```
|
||||
Request Headers:
|
||||
├─ Authorization: Bearer <jwt_token> # WHO: Authenticate user
|
||||
├─ X-Signature: HMAC_SHA256(...) # WHAT: Verify content
|
||||
├─ X-Timestamp: unixtime # WHEN: Prevent replay
|
||||
└─ X-Client-Id: telegram_bot # WHERE: Track source
|
||||
```
|
||||
|
||||
### Trade-offs
|
||||
| Pros | Cons |
|
||||
|------|------|
|
||||
| More secure | Slight performance overhead |
|
||||
| Covers multiple attack vectors | More complex debugging |
|
||||
| MVP ready | Requires client cooperation |
|
||||
| Can be disabled in MVP | More header management |
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-002: Redis Streams for Event Bus (vs RabbitMQ)
|
||||
|
||||
### Decision
|
||||
Use Redis Streams instead of RabbitMQ for event-driven notifications.
|
||||
|
||||
### Context
|
||||
- Already using Redis for caching/sessions
|
||||
- Simpler setup for MVP
|
||||
- Don't need RabbitMQ's clustering (yet)
|
||||
- Redis Streams has built-in message ordering
|
||||
|
||||
### Solution
|
||||
```
|
||||
Event Stream: "events"
|
||||
├─ transaction.created
|
||||
├─ transaction.executed
|
||||
├─ budget.alert
|
||||
├─ goal.completed
|
||||
└─ member.invited
|
||||
|
||||
Consumer Groups:
|
||||
├─ telegram_bot (consumes all)
|
||||
├─ notification_worker (consumes alerts)
|
||||
└─ audit_logger (consumes all)
|
||||
```
|
||||
|
||||
### Trade-offs
|
||||
| Pros | Cons |
|
||||
|------|------|
|
||||
| Simple setup | No clustering (future issue) |
|
||||
| Less infrastructure | Limited to single Redis |
|
||||
| Good for MVP | Message limit at max memory |
|
||||
| Built-in ordering | No message durability guarantee |
|
||||
|
||||
### Upgrade Path
|
||||
When needed: Replace Redis Stream consumer with RabbitMQ consumer. Producer stays same (emit to Stream AND Queue).
|
||||
|
||||
### Status
|
||||
⏳ **DESIGNED, NOT YET IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-003: Compensation Transactions Instead of Deletion
|
||||
|
||||
### Decision
|
||||
Never delete transactions. Create compensation (reverse) transactions instead.
|
||||
|
||||
### Context
|
||||
- Financial system requires immutability
|
||||
- Audit trail must show all changes
|
||||
- Regulatory compliance (many jurisdictions require this)
|
||||
- User may reverse a reversal
|
||||
|
||||
### Solution
|
||||
```
|
||||
Transaction Reversal Flow:
|
||||
|
||||
Original Transaction (ID: 100)
|
||||
├─ amount: 50.00 USD
|
||||
├─ from_wallet: Cash
|
||||
├─ to_wallet: Bank
|
||||
└─ status: "executed"
|
||||
│
|
||||
└─▶ User requests reversal
|
||||
│
|
||||
├─ Create Reversal Transaction (ID: 102)
|
||||
│ ├─ amount: 50.00 USD
|
||||
│ ├─ from_wallet: Bank (REVERSED)
|
||||
│ ├─ to_wallet: Cash (REVERSED)
|
||||
│ ├─ type: "reversal"
|
||||
│ ├─ original_tx_id: 100
|
||||
│ └─ status: "executed"
|
||||
│
|
||||
└─ Update Original
|
||||
├─ status: "reversed"
|
||||
├─ reversed_at: now
|
||||
└─ reversal_reason: "User requested..."
|
||||
```
|
||||
|
||||
### Benefits
|
||||
✅ **Immutability**: No data loss
|
||||
✅ **Audit Trail**: See what happened and why
|
||||
✅ **Reversals of Reversals**: Can reverse the reversal
|
||||
✅ **Compliance**: Meets financial regulations
|
||||
✅ **Analytics**: Accurate historical data
|
||||
|
||||
### Implementation
|
||||
```python
|
||||
# Database
|
||||
TransactionStatus: draft | pending_approval | executed | reversed
|
||||
|
||||
# Fields
|
||||
original_transaction_id # FK self-reference
|
||||
reversed_at # When reversed
|
||||
reversal_reason # Why reversed
|
||||
```
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-004: Family-Level Isolation vs Database-Level
|
||||
|
||||
### Decision
|
||||
Implement family isolation at service/API layer (vs database constraints).
|
||||
|
||||
### Context
|
||||
- Easier testing (no DB constraints to work around)
|
||||
- More flexibility (can cross-family operations if needed)
|
||||
- Performance (single query vs complex JOINs)
|
||||
- Security (defense in depth)
|
||||
|
||||
### Solution
|
||||
```python
|
||||
# Every query includes family_id filter
|
||||
Transaction.query.filter(
|
||||
Transaction.family_id == user_context.family_id
|
||||
)
|
||||
|
||||
# RBAC middleware also checks:
|
||||
RBACEngine.check_family_access(user_context, requested_family_id)
|
||||
|
||||
# Service layer validates before operations
|
||||
WalletService.get_wallet(wallet_id, family_id=context.family_id)
|
||||
```
|
||||
|
||||
### Trade-offs
|
||||
| Approach | Pros | Cons |
|
||||
|----------|------|------|
|
||||
| **Service Layer (Selected)** | Flexible, testable, fast queries | Requires discipline |
|
||||
| **Database FK** | Enforced by DB | Inflexible, complex queries |
|
||||
| **Combined** | Both protections | Double overhead |
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-005: Approval Workflow in Domain Model
|
||||
|
||||
### Decision
|
||||
Implement transaction approval as state machine in domain model.
|
||||
|
||||
### Context
|
||||
- High-value transactions need approval
|
||||
- State transitions must be valid
|
||||
- Audit trail must show approvals
|
||||
- Different thresholds per role
|
||||
|
||||
### Solution
|
||||
```
|
||||
Transaction State Machine:
|
||||
|
||||
DRAFT (initial)
|
||||
└─▶ [Check amount vs threshold]
|
||||
├─ If small: EXECUTED (auto-approve)
|
||||
└─ If large: PENDING_APPROVAL (wait for approval)
|
||||
|
||||
PENDING_APPROVAL
|
||||
├─▶ [Owner approves] → EXECUTED
|
||||
└─▶ [User cancels] → DRAFT
|
||||
|
||||
EXECUTED
|
||||
└─▶ [User/Owner reverses] → Create REVERSED tx
|
||||
|
||||
REVERSED (final state)
|
||||
└─ Can't transition further
|
||||
```
|
||||
|
||||
### Threshold Rules
|
||||
```python
|
||||
APPROVAL_THRESHOLD = $500
|
||||
|
||||
# Child transactions
|
||||
if role == CHILD and amount > $50:
|
||||
status = PENDING_APPROVAL
|
||||
|
||||
# Member transactions
|
||||
if role == MEMBER and amount > $500:
|
||||
status = PENDING_APPROVAL
|
||||
|
||||
# Adult/Owner: Never need approval (auto-execute)
|
||||
```
|
||||
|
||||
### Implementation
|
||||
```python
|
||||
# Schema
|
||||
TransactionStatus = Enum['draft', 'pending_approval', 'executed', 'reversed']
|
||||
|
||||
# Fields
|
||||
status: TransactionStatus
|
||||
confirmation_required: bool
|
||||
confirmation_token: str # Verify it's real approval
|
||||
approved_by_id: int
|
||||
approved_at: datetime
|
||||
|
||||
# Service layer validates state transitions
|
||||
TransactionService.confirm_transaction():
|
||||
if tx.status != "pending_approval":
|
||||
raise ValueError("Invalid state transition")
|
||||
```
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-006: HS256 for MVP, RS256 for Production
|
||||
|
||||
### Decision
|
||||
Use symmetric HMAC-SHA256 (HS256) for MVP, upgrade to asymmetric RS256 for production.
|
||||
|
||||
### Context
|
||||
- HS256: Same secret for signing & verification (simple)
|
||||
- RS256: Private key to sign, public key to verify (scalable)
|
||||
- MVP: Simple deployment needed
|
||||
- Production: Multiple API instances need to verify tokens
|
||||
|
||||
### Solution
|
||||
```python
|
||||
# MVP: HS256 (symmetric)
|
||||
jwt_manager = JWTManager(secret_key="shared-secret")
|
||||
token = jwt.encode(payload, secret, algorithm="HS256")
|
||||
verified = jwt.decode(token, secret, algorithms=["HS256"])
|
||||
|
||||
# Production: RS256 (asymmetric)
|
||||
with open("private.pem") as f:
|
||||
private_key = f.read()
|
||||
with open("public.pem") as f:
|
||||
public_key = f.read()
|
||||
|
||||
token = jwt.encode(payload, private_key, algorithm="RS256")
|
||||
verified = jwt.decode(token, public_key, algorithms=["RS256"])
|
||||
```
|
||||
|
||||
### Migration Path
|
||||
1. Generate RSA key pair
|
||||
2. Update JWT manager to accept algorithm config
|
||||
3. Deploy new version with RS256 validation (backward compatible)
|
||||
4. Stop issuing HS256 tokens
|
||||
5. HS256 tokens expire naturally
|
||||
|
||||
### Status
|
||||
✅ **HS256 IMPLEMENTED, RS256 READY**
|
||||
|
||||
---
|
||||
|
||||
## ADR-007: Telegram Binding via Temporary Codes
|
||||
|
||||
### Decision
|
||||
Use temporary binding codes instead of direct token requests.
|
||||
|
||||
### Context
|
||||
- Security: Code has limited lifetime & single use
|
||||
- User Experience: Simple flow (click link)
|
||||
- Phishing Prevention: User confirms on web, not just in Telegram
|
||||
- Bot doesn't receive sensitive tokens
|
||||
|
||||
### Solution
|
||||
```
|
||||
Flow:
|
||||
1. User: /start
|
||||
2. Bot: Generate code (10-min TTL)
|
||||
3. Bot: Send link with code
|
||||
4. User: Clicks link (authenticate on web)
|
||||
5. Web: Confirm binding, create TelegramIdentity
|
||||
6. Web: Issue JWT for bot to use
|
||||
7. Bot: Stores JWT in Redis
|
||||
8. Bot: Uses JWT for API calls
|
||||
```
|
||||
|
||||
### Code Generation
|
||||
```python
|
||||
code = secrets.token_urlsafe(24) # 32-char random string
|
||||
|
||||
# Store in Redis: 10-min TTL
|
||||
redis.setex(f"telegram:code:{code}", 600, chat_id)
|
||||
|
||||
# Generate link
|
||||
url = f"https://app.com/auth/telegram?code={code}&chat_id={chat_id}"
|
||||
```
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-008: Service Token for Bot-to-API Communication
|
||||
|
||||
### Decision
|
||||
Issue separate service token (not user token) for bot API requests.
|
||||
|
||||
### Context
|
||||
- Bot needs to make requests independently (not as specific user)
|
||||
- Different permissions than user tokens
|
||||
- Different expiry (1 year vs 15 min)
|
||||
- Can be rotated independently
|
||||
|
||||
### Solution
|
||||
```python
|
||||
# Service Token Payload
|
||||
{
|
||||
"sub": "service:telegram_bot",
|
||||
"type": "service",
|
||||
"iat": 1702237800,
|
||||
"exp": 1733773800, # 1 year
|
||||
}
|
||||
|
||||
# Bot uses service token:
|
||||
Authorization: Bearer <service_token>
|
||||
X-Client-Id: telegram_bot
|
||||
```
|
||||
|
||||
### Use Cases
|
||||
- Service token: Schedule reminders, send notifications
|
||||
- User token: Create transaction as specific user
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-009: Middleware Order Matters
|
||||
|
||||
### Decision
|
||||
Security middleware must execute in specific order.
|
||||
|
||||
### Context
|
||||
- FastAPI adds middleware in reverse registration order
|
||||
- Each middleware depends on previous setup
|
||||
- Wrong order = security bypass
|
||||
|
||||
### Solution
|
||||
```python
|
||||
# Registration order (will execute in reverse):
|
||||
1. RequestLoggingMiddleware (last to execute)
|
||||
2. RBACMiddleware
|
||||
3. JWTAuthenticationMiddleware
|
||||
4. HMACVerificationMiddleware
|
||||
5. RateLimitMiddleware
|
||||
6. SecurityHeadersMiddleware (first to execute)
|
||||
|
||||
# Execution flow:
|
||||
SecurityHeaders
|
||||
├─ Add HSTS, X-Frame-Options, etc.
|
||||
↓
|
||||
RateLimit
|
||||
├─ Check IP-based rate limit
|
||||
├─ Increment counter in Redis
|
||||
↓
|
||||
HMACVerification
|
||||
├─ Verify X-Signature
|
||||
├─ Check timestamp freshness
|
||||
├─ Prevent replay attacks
|
||||
↓
|
||||
JWTAuthentication
|
||||
├─ Extract token from Authorization header
|
||||
├─ Verify signature & expiration
|
||||
├─ Store user context in request.state
|
||||
↓
|
||||
RBAC
|
||||
├─ Load user role
|
||||
├─ Verify family access
|
||||
├─ Store permissions
|
||||
↓
|
||||
RequestLogging
|
||||
├─ Log all requests
|
||||
├─ Record response time
|
||||
```
|
||||
|
||||
### Implementation
|
||||
```python
|
||||
def add_security_middleware(app: FastAPI, redis_client, db_session):
|
||||
# Order matters!
|
||||
app.add_middleware(RequestLoggingMiddleware)
|
||||
app.add_middleware(RBACMiddleware, db_session=db_session)
|
||||
app.add_middleware(JWTAuthenticationMiddleware)
|
||||
app.add_middleware(HMACVerificationMiddleware, redis_client=redis_client)
|
||||
app.add_middleware(RateLimitMiddleware, redis_client=redis_client)
|
||||
app.add_middleware(SecurityHeadersMiddleware)
|
||||
```
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-010: Event Logging is Mandatory
|
||||
|
||||
### Decision
|
||||
Every data modification is logged to event_log table.
|
||||
|
||||
### Context
|
||||
- Regulatory compliance (financial systems)
|
||||
- Audit trail for disputes
|
||||
- Debugging (understand what happened)
|
||||
- User transparency (show activity history)
|
||||
|
||||
### Solution
|
||||
```python
|
||||
# Every service method logs events
|
||||
event = EventLog(
|
||||
family_id=family_id,
|
||||
entity_type="transaction",
|
||||
entity_id=tx_id,
|
||||
action="create", # create|update|delete|confirm|execute|reverse
|
||||
actor_id=user_id,
|
||||
old_values={"balance": 100},
|
||||
new_values={"balance": 50},
|
||||
ip_address=request.client.host,
|
||||
user_agent=request.headers.get("user-agent"),
|
||||
reason="User requested cancellation",
|
||||
created_at=datetime.utcnow(),
|
||||
)
|
||||
db.add(event)
|
||||
```
|
||||
|
||||
### Fields Logged
|
||||
```
|
||||
EventLog:
|
||||
├─ entity_type: What was modified (transaction, wallet, budget)
|
||||
├─ entity_id: Which record (transaction #123)
|
||||
├─ action: What happened (create, update, delete, reverse)
|
||||
├─ actor_id: Who did it (user_id)
|
||||
├─ old_values: Before state (JSON)
|
||||
├─ new_values: After state (JSON)
|
||||
├─ ip_address: Where from
|
||||
├─ user_agent: What client
|
||||
├─ reason: Why (for deletions)
|
||||
└─ created_at: When
|
||||
```
|
||||
|
||||
### Access Control
|
||||
```python
|
||||
# Who can view event_log?
|
||||
├─ Owner: All events in family
|
||||
├─ Adult: All events in family
|
||||
├─ Member: Only own transactions' events
|
||||
├─ Child: Very limited
|
||||
└─ Read-Only: Selected events (audit/observer)
|
||||
```
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## Summary Table
|
||||
|
||||
| ADR | Title | Status | Risk | Notes |
|
||||
|-----|-------|--------|------|-------|
|
||||
| 001 | JWT + HMAC | ✅ | Low | Dual auth provides defense-in-depth |
|
||||
| 002 | Redis Streams | ⏳ | Medium | Upgrade path to RabbitMQ planned |
|
||||
| 003 | Compensation Tx | ✅ | Low | Immutability requirement met |
|
||||
| 004 | Family Isolation | ✅ | Low | Service-layer isolation + RBAC |
|
||||
| 005 | Approval Workflow | ✅ | Low | State machine properly designed |
|
||||
| 006 | HS256→RS256 | ✅ | Low | Migration path clear |
|
||||
| 007 | Binding Codes | ✅ | Low | Secure temporary code flow |
|
||||
| 008 | Service Tokens | ✅ | Low | Separate identity for bot |
|
||||
| 009 | Middleware Order | ✅ | Critical | Correctly implemented |
|
||||
| 010 | Event Logging | ✅ | Low | Audit trail complete |
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** 2025-12-10
|
||||
**Review Frequency:** Quarterly
|
||||
502
.history/docs/SECURITY_ARCHITECTURE_ADR_20251210210906.md
Normal file
502
.history/docs/SECURITY_ARCHITECTURE_ADR_20251210210906.md
Normal file
@@ -0,0 +1,502 @@
|
||||
# 🔐 Security Architecture Decision Records
|
||||
|
||||
## ADR-001: JWT + HMAC Dual Authentication
|
||||
|
||||
### Decision
|
||||
Use JWT for client authentication + HMAC for request integrity verification.
|
||||
|
||||
### Context
|
||||
- Single JWT alone vulnerable to token theft (XSS, interception)
|
||||
- HMAC ensures request wasn't tampered with in transit
|
||||
- Combined approach provides defense-in-depth
|
||||
|
||||
### Solution
|
||||
```
|
||||
Request Headers:
|
||||
├─ Authorization: Bearer <jwt_token> # WHO: Authenticate user
|
||||
├─ X-Signature: HMAC_SHA256(...) # WHAT: Verify content
|
||||
├─ X-Timestamp: unixtime # WHEN: Prevent replay
|
||||
└─ X-Client-Id: telegram_bot # WHERE: Track source
|
||||
```
|
||||
|
||||
### Trade-offs
|
||||
| Pros | Cons |
|
||||
|------|------|
|
||||
| More secure | Slight performance overhead |
|
||||
| Covers multiple attack vectors | More complex debugging |
|
||||
| MVP ready | Requires client cooperation |
|
||||
| Can be disabled in MVP | More header management |
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-002: Redis Streams for Event Bus (vs RabbitMQ)
|
||||
|
||||
### Decision
|
||||
Use Redis Streams instead of RabbitMQ for event-driven notifications.
|
||||
|
||||
### Context
|
||||
- Already using Redis for caching/sessions
|
||||
- Simpler setup for MVP
|
||||
- Don't need RabbitMQ's clustering (yet)
|
||||
- Redis Streams has built-in message ordering
|
||||
|
||||
### Solution
|
||||
```
|
||||
Event Stream: "events"
|
||||
├─ transaction.created
|
||||
├─ transaction.executed
|
||||
├─ budget.alert
|
||||
├─ goal.completed
|
||||
└─ member.invited
|
||||
|
||||
Consumer Groups:
|
||||
├─ telegram_bot (consumes all)
|
||||
├─ notification_worker (consumes alerts)
|
||||
└─ audit_logger (consumes all)
|
||||
```
|
||||
|
||||
### Trade-offs
|
||||
| Pros | Cons |
|
||||
|------|------|
|
||||
| Simple setup | No clustering (future issue) |
|
||||
| Less infrastructure | Limited to single Redis |
|
||||
| Good for MVP | Message limit at max memory |
|
||||
| Built-in ordering | No message durability guarantee |
|
||||
|
||||
### Upgrade Path
|
||||
When needed: Replace Redis Stream consumer with RabbitMQ consumer. Producer stays same (emit to Stream AND Queue).
|
||||
|
||||
### Status
|
||||
⏳ **DESIGNED, NOT YET IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-003: Compensation Transactions Instead of Deletion
|
||||
|
||||
### Decision
|
||||
Never delete transactions. Create compensation (reverse) transactions instead.
|
||||
|
||||
### Context
|
||||
- Financial system requires immutability
|
||||
- Audit trail must show all changes
|
||||
- Regulatory compliance (many jurisdictions require this)
|
||||
- User may reverse a reversal
|
||||
|
||||
### Solution
|
||||
```
|
||||
Transaction Reversal Flow:
|
||||
|
||||
Original Transaction (ID: 100)
|
||||
├─ amount: 50.00 USD
|
||||
├─ from_wallet: Cash
|
||||
├─ to_wallet: Bank
|
||||
└─ status: "executed"
|
||||
│
|
||||
└─▶ User requests reversal
|
||||
│
|
||||
├─ Create Reversal Transaction (ID: 102)
|
||||
│ ├─ amount: 50.00 USD
|
||||
│ ├─ from_wallet: Bank (REVERSED)
|
||||
│ ├─ to_wallet: Cash (REVERSED)
|
||||
│ ├─ type: "reversal"
|
||||
│ ├─ original_tx_id: 100
|
||||
│ └─ status: "executed"
|
||||
│
|
||||
└─ Update Original
|
||||
├─ status: "reversed"
|
||||
├─ reversed_at: now
|
||||
└─ reversal_reason: "User requested..."
|
||||
```
|
||||
|
||||
### Benefits
|
||||
✅ **Immutability**: No data loss
|
||||
✅ **Audit Trail**: See what happened and why
|
||||
✅ **Reversals of Reversals**: Can reverse the reversal
|
||||
✅ **Compliance**: Meets financial regulations
|
||||
✅ **Analytics**: Accurate historical data
|
||||
|
||||
### Implementation
|
||||
```python
|
||||
# Database
|
||||
TransactionStatus: draft | pending_approval | executed | reversed
|
||||
|
||||
# Fields
|
||||
original_transaction_id # FK self-reference
|
||||
reversed_at # When reversed
|
||||
reversal_reason # Why reversed
|
||||
```
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-004: Family-Level Isolation vs Database-Level
|
||||
|
||||
### Decision
|
||||
Implement family isolation at service/API layer (vs database constraints).
|
||||
|
||||
### Context
|
||||
- Easier testing (no DB constraints to work around)
|
||||
- More flexibility (can cross-family operations if needed)
|
||||
- Performance (single query vs complex JOINs)
|
||||
- Security (defense in depth)
|
||||
|
||||
### Solution
|
||||
```python
|
||||
# Every query includes family_id filter
|
||||
Transaction.query.filter(
|
||||
Transaction.family_id == user_context.family_id
|
||||
)
|
||||
|
||||
# RBAC middleware also checks:
|
||||
RBACEngine.check_family_access(user_context, requested_family_id)
|
||||
|
||||
# Service layer validates before operations
|
||||
WalletService.get_wallet(wallet_id, family_id=context.family_id)
|
||||
```
|
||||
|
||||
### Trade-offs
|
||||
| Approach | Pros | Cons |
|
||||
|----------|------|------|
|
||||
| **Service Layer (Selected)** | Flexible, testable, fast queries | Requires discipline |
|
||||
| **Database FK** | Enforced by DB | Inflexible, complex queries |
|
||||
| **Combined** | Both protections | Double overhead |
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-005: Approval Workflow in Domain Model
|
||||
|
||||
### Decision
|
||||
Implement transaction approval as state machine in domain model.
|
||||
|
||||
### Context
|
||||
- High-value transactions need approval
|
||||
- State transitions must be valid
|
||||
- Audit trail must show approvals
|
||||
- Different thresholds per role
|
||||
|
||||
### Solution
|
||||
```
|
||||
Transaction State Machine:
|
||||
|
||||
DRAFT (initial)
|
||||
└─▶ [Check amount vs threshold]
|
||||
├─ If small: EXECUTED (auto-approve)
|
||||
└─ If large: PENDING_APPROVAL (wait for approval)
|
||||
|
||||
PENDING_APPROVAL
|
||||
├─▶ [Owner approves] → EXECUTED
|
||||
└─▶ [User cancels] → DRAFT
|
||||
|
||||
EXECUTED
|
||||
└─▶ [User/Owner reverses] → Create REVERSED tx
|
||||
|
||||
REVERSED (final state)
|
||||
└─ Can't transition further
|
||||
```
|
||||
|
||||
### Threshold Rules
|
||||
```python
|
||||
APPROVAL_THRESHOLD = $500
|
||||
|
||||
# Child transactions
|
||||
if role == CHILD and amount > $50:
|
||||
status = PENDING_APPROVAL
|
||||
|
||||
# Member transactions
|
||||
if role == MEMBER and amount > $500:
|
||||
status = PENDING_APPROVAL
|
||||
|
||||
# Adult/Owner: Never need approval (auto-execute)
|
||||
```
|
||||
|
||||
### Implementation
|
||||
```python
|
||||
# Schema
|
||||
TransactionStatus = Enum['draft', 'pending_approval', 'executed', 'reversed']
|
||||
|
||||
# Fields
|
||||
status: TransactionStatus
|
||||
confirmation_required: bool
|
||||
confirmation_token: str # Verify it's real approval
|
||||
approved_by_id: int
|
||||
approved_at: datetime
|
||||
|
||||
# Service layer validates state transitions
|
||||
TransactionService.confirm_transaction():
|
||||
if tx.status != "pending_approval":
|
||||
raise ValueError("Invalid state transition")
|
||||
```
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-006: HS256 for MVP, RS256 for Production
|
||||
|
||||
### Decision
|
||||
Use symmetric HMAC-SHA256 (HS256) for MVP, upgrade to asymmetric RS256 for production.
|
||||
|
||||
### Context
|
||||
- HS256: Same secret for signing & verification (simple)
|
||||
- RS256: Private key to sign, public key to verify (scalable)
|
||||
- MVP: Simple deployment needed
|
||||
- Production: Multiple API instances need to verify tokens
|
||||
|
||||
### Solution
|
||||
```python
|
||||
# MVP: HS256 (symmetric)
|
||||
jwt_manager = JWTManager(secret_key="shared-secret")
|
||||
token = jwt.encode(payload, secret, algorithm="HS256")
|
||||
verified = jwt.decode(token, secret, algorithms=["HS256"])
|
||||
|
||||
# Production: RS256 (asymmetric)
|
||||
with open("private.pem") as f:
|
||||
private_key = f.read()
|
||||
with open("public.pem") as f:
|
||||
public_key = f.read()
|
||||
|
||||
token = jwt.encode(payload, private_key, algorithm="RS256")
|
||||
verified = jwt.decode(token, public_key, algorithms=["RS256"])
|
||||
```
|
||||
|
||||
### Migration Path
|
||||
1. Generate RSA key pair
|
||||
2. Update JWT manager to accept algorithm config
|
||||
3. Deploy new version with RS256 validation (backward compatible)
|
||||
4. Stop issuing HS256 tokens
|
||||
5. HS256 tokens expire naturally
|
||||
|
||||
### Status
|
||||
✅ **HS256 IMPLEMENTED, RS256 READY**
|
||||
|
||||
---
|
||||
|
||||
## ADR-007: Telegram Binding via Temporary Codes
|
||||
|
||||
### Decision
|
||||
Use temporary binding codes instead of direct token requests.
|
||||
|
||||
### Context
|
||||
- Security: Code has limited lifetime & single use
|
||||
- User Experience: Simple flow (click link)
|
||||
- Phishing Prevention: User confirms on web, not just in Telegram
|
||||
- Bot doesn't receive sensitive tokens
|
||||
|
||||
### Solution
|
||||
```
|
||||
Flow:
|
||||
1. User: /start
|
||||
2. Bot: Generate code (10-min TTL)
|
||||
3. Bot: Send link with code
|
||||
4. User: Clicks link (authenticate on web)
|
||||
5. Web: Confirm binding, create TelegramIdentity
|
||||
6. Web: Issue JWT for bot to use
|
||||
7. Bot: Stores JWT in Redis
|
||||
8. Bot: Uses JWT for API calls
|
||||
```
|
||||
|
||||
### Code Generation
|
||||
```python
|
||||
code = secrets.token_urlsafe(24) # 32-char random string
|
||||
|
||||
# Store in Redis: 10-min TTL
|
||||
redis.setex(f"telegram:code:{code}", 600, chat_id)
|
||||
|
||||
# Generate link
|
||||
url = f"https://app.com/auth/telegram?code={code}&chat_id={chat_id}"
|
||||
```
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-008: Service Token for Bot-to-API Communication
|
||||
|
||||
### Decision
|
||||
Issue separate service token (not user token) for bot API requests.
|
||||
|
||||
### Context
|
||||
- Bot needs to make requests independently (not as specific user)
|
||||
- Different permissions than user tokens
|
||||
- Different expiry (1 year vs 15 min)
|
||||
- Can be rotated independently
|
||||
|
||||
### Solution
|
||||
```python
|
||||
# Service Token Payload
|
||||
{
|
||||
"sub": "service:telegram_bot",
|
||||
"type": "service",
|
||||
"iat": 1702237800,
|
||||
"exp": 1733773800, # 1 year
|
||||
}
|
||||
|
||||
# Bot uses service token:
|
||||
Authorization: Bearer <service_token>
|
||||
X-Client-Id: telegram_bot
|
||||
```
|
||||
|
||||
### Use Cases
|
||||
- Service token: Schedule reminders, send notifications
|
||||
- User token: Create transaction as specific user
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-009: Middleware Order Matters
|
||||
|
||||
### Decision
|
||||
Security middleware must execute in specific order.
|
||||
|
||||
### Context
|
||||
- FastAPI adds middleware in reverse registration order
|
||||
- Each middleware depends on previous setup
|
||||
- Wrong order = security bypass
|
||||
|
||||
### Solution
|
||||
```python
|
||||
# Registration order (will execute in reverse):
|
||||
1. RequestLoggingMiddleware (last to execute)
|
||||
2. RBACMiddleware
|
||||
3. JWTAuthenticationMiddleware
|
||||
4. HMACVerificationMiddleware
|
||||
5. RateLimitMiddleware
|
||||
6. SecurityHeadersMiddleware (first to execute)
|
||||
|
||||
# Execution flow:
|
||||
SecurityHeaders
|
||||
├─ Add HSTS, X-Frame-Options, etc.
|
||||
↓
|
||||
RateLimit
|
||||
├─ Check IP-based rate limit
|
||||
├─ Increment counter in Redis
|
||||
↓
|
||||
HMACVerification
|
||||
├─ Verify X-Signature
|
||||
├─ Check timestamp freshness
|
||||
├─ Prevent replay attacks
|
||||
↓
|
||||
JWTAuthentication
|
||||
├─ Extract token from Authorization header
|
||||
├─ Verify signature & expiration
|
||||
├─ Store user context in request.state
|
||||
↓
|
||||
RBAC
|
||||
├─ Load user role
|
||||
├─ Verify family access
|
||||
├─ Store permissions
|
||||
↓
|
||||
RequestLogging
|
||||
├─ Log all requests
|
||||
├─ Record response time
|
||||
```
|
||||
|
||||
### Implementation
|
||||
```python
|
||||
def add_security_middleware(app: FastAPI, redis_client, db_session):
|
||||
# Order matters!
|
||||
app.add_middleware(RequestLoggingMiddleware)
|
||||
app.add_middleware(RBACMiddleware, db_session=db_session)
|
||||
app.add_middleware(JWTAuthenticationMiddleware)
|
||||
app.add_middleware(HMACVerificationMiddleware, redis_client=redis_client)
|
||||
app.add_middleware(RateLimitMiddleware, redis_client=redis_client)
|
||||
app.add_middleware(SecurityHeadersMiddleware)
|
||||
```
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## ADR-010: Event Logging is Mandatory
|
||||
|
||||
### Decision
|
||||
Every data modification is logged to event_log table.
|
||||
|
||||
### Context
|
||||
- Regulatory compliance (financial systems)
|
||||
- Audit trail for disputes
|
||||
- Debugging (understand what happened)
|
||||
- User transparency (show activity history)
|
||||
|
||||
### Solution
|
||||
```python
|
||||
# Every service method logs events
|
||||
event = EventLog(
|
||||
family_id=family_id,
|
||||
entity_type="transaction",
|
||||
entity_id=tx_id,
|
||||
action="create", # create|update|delete|confirm|execute|reverse
|
||||
actor_id=user_id,
|
||||
old_values={"balance": 100},
|
||||
new_values={"balance": 50},
|
||||
ip_address=request.client.host,
|
||||
user_agent=request.headers.get("user-agent"),
|
||||
reason="User requested cancellation",
|
||||
created_at=datetime.utcnow(),
|
||||
)
|
||||
db.add(event)
|
||||
```
|
||||
|
||||
### Fields Logged
|
||||
```
|
||||
EventLog:
|
||||
├─ entity_type: What was modified (transaction, wallet, budget)
|
||||
├─ entity_id: Which record (transaction #123)
|
||||
├─ action: What happened (create, update, delete, reverse)
|
||||
├─ actor_id: Who did it (user_id)
|
||||
├─ old_values: Before state (JSON)
|
||||
├─ new_values: After state (JSON)
|
||||
├─ ip_address: Where from
|
||||
├─ user_agent: What client
|
||||
├─ reason: Why (for deletions)
|
||||
└─ created_at: When
|
||||
```
|
||||
|
||||
### Access Control
|
||||
```python
|
||||
# Who can view event_log?
|
||||
├─ Owner: All events in family
|
||||
├─ Adult: All events in family
|
||||
├─ Member: Only own transactions' events
|
||||
├─ Child: Very limited
|
||||
└─ Read-Only: Selected events (audit/observer)
|
||||
```
|
||||
|
||||
### Status
|
||||
✅ **IMPLEMENTED**
|
||||
|
||||
---
|
||||
|
||||
## Summary Table
|
||||
|
||||
| ADR | Title | Status | Risk | Notes |
|
||||
|-----|-------|--------|------|-------|
|
||||
| 001 | JWT + HMAC | ✅ | Low | Dual auth provides defense-in-depth |
|
||||
| 002 | Redis Streams | ⏳ | Medium | Upgrade path to RabbitMQ planned |
|
||||
| 003 | Compensation Tx | ✅ | Low | Immutability requirement met |
|
||||
| 004 | Family Isolation | ✅ | Low | Service-layer isolation + RBAC |
|
||||
| 005 | Approval Workflow | ✅ | Low | State machine properly designed |
|
||||
| 006 | HS256→RS256 | ✅ | Low | Migration path clear |
|
||||
| 007 | Binding Codes | ✅ | Low | Secure temporary code flow |
|
||||
| 008 | Service Tokens | ✅ | Low | Separate identity for bot |
|
||||
| 009 | Middleware Order | ✅ | Critical | Correctly implemented |
|
||||
| 010 | Event Logging | ✅ | Low | Audit trail complete |
|
||||
|
||||
---
|
||||
|
||||
**Document Version:** 1.0
|
||||
**Last Updated:** 2025-12-10
|
||||
**Review Frequency:** Quarterly
|
||||
Reference in New Issue
Block a user