rwadurian/backend/services/backup-service/docs/API.md

614 lines
14 KiB
Markdown

# Backup Service API Reference
## Overview
The backup-service exposes RESTful APIs for managing MPC backup shares. All endpoints (except health checks) require service-to-service JWT authentication.
**Base URL:** `http://localhost:3002`
---
## Authentication
All `/backup-share/*` endpoints require a service JWT token in the `X-Service-Token` header.
### Token Format
```json
{
"service": "identity-service",
"iat": 1704067200,
"exp": 1704153600
}
```
### Generating Service Tokens
```typescript
import jwt from 'jsonwebtoken';
const token = jwt.sign(
{ service: 'identity-service' },
process.env.SERVICE_JWT_SECRET,
{ expiresIn: '24h' }
);
```
### Allowed Services
Only the following services can access the backup-service:
- `identity-service` - Primary service for MPC operations
- `recovery-service` - Account recovery operations
Configure via `ALLOWED_SERVICES` environment variable.
---
## API Endpoints
### 1. Store Backup Share
Store an encrypted MPC backup share for a user.
**Endpoint:** `POST /backup-share/store`
**Headers:**
| Header | Required | Description |
|--------|----------|-------------|
| `X-Service-Token` | Yes | Service JWT token |
| `Content-Type` | Yes | `application/json` |
**Request Body:**
```json
{
"userId": "12345",
"accountSequence": 1001,
"publicKey": "02aabbccdd...",
"encryptedShareData": "base64-encoded-encrypted-data",
"threshold": 2,
"totalParties": 3
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `userId` | string | Yes | User identifier from identity-service |
| `accountSequence` | number | Yes | Account sequence number (min: 1) |
| `publicKey` | string | Yes | MPC public key (66-130 characters) |
| `encryptedShareData` | string | Yes | Pre-encrypted share data (AES-256-GCM by identity-service) |
| `threshold` | number | No | Reconstruction threshold (default: 2, range: 2-10) |
| `totalParties` | number | No | Total MPC parties (default: 3, range: 2-10) |
**Success Response (201 Created):**
```json
{
"success": true,
"shareId": "1",
"message": "Backup share stored successfully"
}
```
**Error Responses:**
| Status | Code | Description |
|--------|------|-------------|
| 400 | `VALIDATION_ERROR` | Invalid request body |
| 401 | `UNAUTHORIZED` | Missing or invalid service token |
| 409 | `SHARE_ALREADY_EXISTS` | Share already exists for this user |
**Example:**
```bash
curl -X POST http://localhost:3002/backup-share/store \
-H "X-Service-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"userId": "12345",
"accountSequence": 1001,
"publicKey": "02aabbccddee...",
"encryptedShareData": "encrypted-share-data-here"
}'
```
---
### 2. Retrieve Backup Share
Retrieve an encrypted MPC backup share for account recovery.
**Endpoint:** `POST /backup-share/retrieve`
**Headers:**
| Header | Required | Description |
|--------|----------|-------------|
| `X-Service-Token` | Yes | Service JWT token |
| `Content-Type` | Yes | `application/json` |
**Request Body:**
```json
{
"userId": "12345",
"publicKey": "02aabbccdd...",
"recoveryToken": "valid-recovery-token",
"deviceId": "device-uuid"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `userId` | string | Yes | User identifier |
| `publicKey` | string | Yes | MPC public key (66-130 characters) |
| `recoveryToken` | string | Yes | Recovery token verified by identity-service |
| `deviceId` | string | No | Device identifier for audit logging |
**Success Response (200 OK):**
```json
{
"success": true,
"encryptedShareData": "base64-encoded-encrypted-data",
"partyIndex": 2,
"publicKey": "02aabbccdd..."
}
```
| Field | Type | Description |
|-------|------|-------------|
| `success` | boolean | Operation success status |
| `encryptedShareData` | string | Decrypted share data (still encrypted by identity-service) |
| `partyIndex` | number | Party index (always 2 for backup share) |
| `publicKey` | string | MPC public key |
**Error Responses:**
| Status | Code | Description |
|--------|------|-------------|
| 400 | `SHARE_NOT_ACTIVE` | Share has been revoked |
| 401 | `UNAUTHORIZED` | Missing or invalid service token |
| 404 | `SHARE_NOT_FOUND` | No share found for user/publicKey |
| 429 | `RATE_LIMIT_EXCEEDED` | Max 3 retrievals per day exceeded |
**Rate Limiting:**
- Maximum 3 retrieves per user per day
- Configurable via `MAX_RETRIEVE_PER_DAY` environment variable
- Counter resets at midnight UTC
**Example:**
```bash
curl -X POST http://localhost:3002/backup-share/retrieve \
-H "X-Service-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"userId": "12345",
"publicKey": "02aabbccddee...",
"recoveryToken": "valid-token-from-identity-service"
}'
```
---
### 3. Revoke Backup Share
Revoke an existing backup share (for key rotation or account closure).
**Endpoint:** `POST /backup-share/revoke`
**Headers:**
| Header | Required | Description |
|--------|----------|-------------|
| `X-Service-Token` | Yes | Service JWT token |
| `Content-Type` | Yes | `application/json` |
**Request Body:**
```json
{
"userId": "12345",
"publicKey": "02aabbccdd...",
"reason": "ROTATION"
}
```
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `userId` | string | Yes | User identifier |
| `publicKey` | string | Yes | MPC public key (66-130 characters) |
| `reason` | string | Yes | Revocation reason (see below) |
**Valid Revocation Reasons:**
| Reason | Description |
|--------|-------------|
| `ROTATION` | Key rotation (new share will be stored) |
| `ACCOUNT_CLOSED` | User account permanently closed |
| `SECURITY_BREACH` | Security incident requiring key invalidation |
| `USER_REQUEST` | User requested share removal |
**Success Response (200 OK):**
```json
{
"success": true,
"message": "Backup share revoked successfully"
}
```
**Error Responses:**
| Status | Code | Description |
|--------|------|-------------|
| 400 | `VALIDATION_ERROR` | Invalid reason value |
| 401 | `UNAUTHORIZED` | Missing or invalid service token |
| 404 | `SHARE_NOT_FOUND` | No share found for user/publicKey |
**Example:**
```bash
curl -X POST http://localhost:3002/backup-share/revoke \
-H "X-Service-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"userId": "12345",
"publicKey": "02aabbccddee...",
"reason": "ROTATION"
}'
```
---
### 4. Health Check
Basic health check endpoint (no authentication required).
**Endpoint:** `GET /health`
**Response (200 OK):**
```json
{
"status": "ok",
"timestamp": "2025-01-15T10:30:45.123Z",
"service": "backup-service"
}
```
---
### 5. Readiness Probe
Kubernetes readiness probe with database connectivity check.
**Endpoint:** `GET /health/ready`
**Success Response (200 OK):**
```json
{
"status": "ready",
"database": "connected",
"timestamp": "2025-01-15T10:30:45.123Z"
}
```
**Failure Response (503 Service Unavailable):**
```json
{
"status": "not ready",
"database": "disconnected",
"error": "Connection refused",
"timestamp": "2025-01-15T10:30:45.123Z"
}
```
---
### 6. Liveness Probe
Kubernetes liveness probe.
**Endpoint:** `GET /health/live`
**Response (200 OK):**
```json
{
"status": "alive",
"timestamp": "2025-01-15T10:30:45.123Z"
}
```
---
## Error Response Format
All error responses follow this standardized format:
```json
{
"success": false,
"error": "Human-readable error message",
"code": "MACHINE_READABLE_CODE",
"timestamp": "2025-01-15T10:30:45.123Z",
"path": "/backup-share/store"
}
```
### Error Codes Reference
| Code | HTTP Status | Description |
|------|-------------|-------------|
| `VALIDATION_ERROR` | 400 | Request validation failed |
| `SHARE_NOT_ACTIVE` | 400 | Share has been revoked |
| `UNAUTHORIZED` | 401 | Authentication failed |
| `FORBIDDEN` | 403 | Service not authorized |
| `SHARE_NOT_FOUND` | 404 | Share not found |
| `SHARE_ALREADY_EXISTS` | 409 | Duplicate share |
| `RATE_LIMIT_EXCEEDED` | 429 | Rate limit exceeded |
| `INTERNAL_ERROR` | 500 | Internal server error |
---
## Data Validation Rules
### Public Key
- Length: 66-130 characters
- Format: Hexadecimal string
- 66 chars = compressed ECDSA public key (02/03 prefix + 64 hex chars)
- 130 chars = uncompressed ECDSA public key (04 prefix + 128 hex chars)
### User ID
- Type: String (numeric format)
- Required: Yes
- Pattern: Positive integer as string
### Account Sequence
- Type: Number
- Minimum: 1
- Required: Yes
### Encrypted Share Data
- Type: String
- Required: Yes
- Format: Base64-encoded encrypted data (from identity-service)
---
## Workflow Examples
### Complete Account Setup Flow
```
1. User creates account on identity-service
2. identity-service generates MPC key shares (3 parties)
3. identity-service stores Party 0 (Server Share) locally
4. identity-service sends Party 1 (Client Share) to user device
5. identity-service calls backup-service to store Party 2 (Backup Share)
POST /backup-share/store
{
"userId": "12345",
"accountSequence": 1001,
"publicKey": "02...",
"encryptedShareData": "encrypted-party-2-data"
}
```
### Account Recovery Flow
```
1. User loses device and initiates recovery
2. User verifies identity through identity-service
3. identity-service generates recovery token
4. identity-service retrieves backup share
POST /backup-share/retrieve
{
"userId": "12345",
"publicKey": "02...",
"recoveryToken": "valid-recovery-token"
}
5. identity-service combines Party 0 + Party 2 to reconstruct key
6. New Party 1 share generated for new device
```
### Key Rotation Flow
```
1. Security event triggers key rotation
2. New key shares generated
3. Old backup share revoked
POST /backup-share/revoke
{
"userId": "12345",
"publicKey": "02...",
"reason": "ROTATION"
}
4. New backup share stored
POST /backup-share/store
{
"userId": "12345",
"accountSequence": 1002,
"publicKey": "03...",
"encryptedShareData": "new-encrypted-data"
}
```
---
## Security Considerations
### Double Encryption
Data is encrypted twice:
1. **First layer:** identity-service encrypts share data before sending
2. **Second layer:** backup-service encrypts again using AES-256-GCM
This ensures data remains protected even if one system is compromised.
### Rate Limiting
- Retrieves are limited to prevent brute-force attacks
- Default: 3 retrieves per user per day
- Configurable via environment variable
### Audit Logging
All operations are logged with:
- Timestamp
- User ID
- Action (STORE/RETRIEVE/REVOKE)
- Source service
- Source IP address
- Success/failure status
### Sensitive Data Handling
The following fields are redacted in logs:
- `encryptedShareData`
- `recoveryToken`
- `password`
- `secret`
---
## SDK Examples
### TypeScript/JavaScript
```typescript
import axios from 'axios';
import jwt from 'jsonwebtoken';
const BACKUP_SERVICE_URL = 'http://localhost:3002';
const SERVICE_JWT_SECRET = process.env.SERVICE_JWT_SECRET;
function generateServiceToken(): string {
return jwt.sign(
{ service: 'identity-service' },
SERVICE_JWT_SECRET,
{ expiresIn: '1h' }
);
}
async function storeBackupShare(
userId: string,
accountSequence: number,
publicKey: string,
encryptedShareData: string
): Promise<string> {
const response = await axios.post(
`${BACKUP_SERVICE_URL}/backup-share/store`,
{
userId,
accountSequence,
publicKey,
encryptedShareData,
},
{
headers: {
'X-Service-Token': generateServiceToken(),
'Content-Type': 'application/json',
},
}
);
return response.data.shareId;
}
async function retrieveBackupShare(
userId: string,
publicKey: string,
recoveryToken: string
): Promise<{ encryptedShareData: string; partyIndex: number }> {
const response = await axios.post(
`${BACKUP_SERVICE_URL}/backup-share/retrieve`,
{
userId,
publicKey,
recoveryToken,
},
{
headers: {
'X-Service-Token': generateServiceToken(),
'Content-Type': 'application/json',
},
}
);
return {
encryptedShareData: response.data.encryptedShareData,
partyIndex: response.data.partyIndex,
};
}
async function revokeBackupShare(
userId: string,
publicKey: string,
reason: 'ROTATION' | 'ACCOUNT_CLOSED' | 'SECURITY_BREACH' | 'USER_REQUEST'
): Promise<void> {
await axios.post(
`${BACKUP_SERVICE_URL}/backup-share/revoke`,
{
userId,
publicKey,
reason,
},
{
headers: {
'X-Service-Token': generateServiceToken(),
'Content-Type': 'application/json',
},
}
);
}
```
### cURL Examples
```bash
# Store a backup share
curl -X POST http://localhost:3002/backup-share/store \
-H "X-Service-Token: $SERVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userId": "12345",
"accountSequence": 1001,
"publicKey": "02aabbccddee1122334455667788990011223344556677889900112233445566",
"encryptedShareData": "encrypted-data-here"
}'
# Retrieve a backup share
curl -X POST http://localhost:3002/backup-share/retrieve \
-H "X-Service-Token: $SERVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userId": "12345",
"publicKey": "02aabbccddee1122334455667788990011223344556677889900112233445566",
"recoveryToken": "recovery-token-here"
}'
# Revoke a backup share
curl -X POST http://localhost:3002/backup-share/revoke \
-H "X-Service-Token: $SERVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userId": "12345",
"publicKey": "02aabbccddee1122334455667788990011223344556677889900112233445566",
"reason": "ROTATION"
}'
# Health check
curl http://localhost:3002/health
```