614 lines
14 KiB
Markdown
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
|
|
```
|