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

14 KiB

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

{
  "service": "identity-service",
  "iat": 1704067200,
  "exp": 1704153600
}

Generating Service Tokens

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:

{
  "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):

{
  "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:

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:

{
  "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):

{
  "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:

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:

{
  "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):

{
  "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:

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):

{
  "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):

{
  "status": "ready",
  "database": "connected",
  "timestamp": "2025-01-15T10:30:45.123Z"
}

Failure Response (503 Service Unavailable):

{
  "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):

{
  "status": "alive",
  "timestamp": "2025-01-15T10:30:45.123Z"
}

Error Response Format

All error responses follow this standardized format:

{
  "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

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

# 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