18 KiB
Backup Service Architecture
Overview
Service Name: backup-service
Version: 1.0.0
Description: RWA Durian MPC Backup Share Storage Service
Primary Purpose: Securely store and manage MPC backup shares (Party 2/3) for account recovery
Core Responsibilities
- Store encrypted MPC backup share data (Party 2)
- Provide share retrieval for account recovery scenarios
- Support share revocation for key rotation or account closure
- Maintain comprehensive audit logs for all operations
- Implement rate limiting and access controls
Critical Security Requirement
Physical server isolation from identity-service is MANDATORY. The backup-service must be deployed on a physically separate server to maintain MPC security. If compromised alone, attackers can only obtain 1 of 3 shares, making key reconstruction impossible.
┌─────────────────────────────────────────────────────────────────────────┐
│ MPC Key Distribution │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Party 0 (Server Share) Party 1 (Client Share) Party 2 (Backup) │
│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │
│ │ identity-service│ │ User Device │ │backup-service│ │
│ │ (Server A) │ │ (Mobile/Web) │ │ (Server B) │ │
│ └─────────────────┘ └─────────────────┘ └──────────────┘ │
│ │
│ 2-of-3 Threshold: Any 2 shares can reconstruct the private key │
│ │
└─────────────────────────────────────────────────────────────────────────┘
DDD + Hexagonal Architecture
The service follows a layered architecture with clear separation of concerns:
┌─────────────────────────────────────────────────────────────────────────┐
│ API Layer (Adapters) │
│ Controllers, DTOs, HTTP Request/Response Handling │
├─────────────────────────────────────────────────────────────────────────┤
│ Application Layer │
│ Use Cases, Commands, Queries, Handlers, Services │
├─────────────────────────────────────────────────────────────────────────┤
│ Domain Layer │
│ Entities, Value Objects, Repositories (Interfaces), Business Logic │
├─────────────────────────────────────────────────────────────────────────┤
│ Infrastructure Layer (Adapters) │
│ Persistence (Prisma/PostgreSQL), Encryption, Crypto │
└─────────────────────────────────────────────────────────────────────────┘
Layer Dependencies (Dependency Rule)
API Layer ──────────────▶ Application Layer
│
▼
Domain Layer ◀─────── Infrastructure Layer
│ │
▼ ▼
(Interfaces defined) (Implementations)
Key Principle: Dependencies point inward. Domain layer has no external dependencies.
Design Patterns
| Pattern | Implementation | Files |
|---|---|---|
| Command Pattern | Store, Revoke operations | store-backup-share.command.ts, revoke-share.command.ts |
| Query Pattern | Retrieve operation | get-backup-share.query.ts |
| Repository Pattern | Data access abstraction | backup-share.repository.interface.ts, backup-share.repository.impl.ts |
| Dependency Injection | NestJS DI Container | *.module.ts |
| Guard Pattern | Authentication & Authorization | service-auth.guard.ts |
| Filter Pattern | Global exception handling | global-exception.filter.ts |
| Interceptor Pattern | Request/response processing | audit-log.interceptor.ts |
| Value Objects | Immutable domain concepts | share-id.vo.ts, encrypted-data.vo.ts |
Directory Structure
backup-service/
├── prisma/
│ ├── schema.prisma # Database schema definition
│ └── migrations/ # Database migration history
│
├── src/
│ ├── api/ # Adapter Layer (External Interface)
│ │ ├── controllers/
│ │ │ ├── backup-share.controller.ts # Main API endpoints
│ │ │ └── health.controller.ts # Health check endpoints
│ │ ├── dto/
│ │ │ ├── request/
│ │ │ │ ├── store-share.dto.ts # Store share request
│ │ │ │ ├── retrieve-share.dto.ts # Retrieve share request
│ │ │ │ └── revoke-share.dto.ts # Revoke share request
│ │ │ └── response/
│ │ │ └── share-info.dto.ts # Response DTOs
│ │ └── api.module.ts
│ │
│ ├── application/ # Use Cases Layer
│ │ ├── commands/
│ │ │ ├── store-backup-share/
│ │ │ │ ├── store-backup-share.command.ts
│ │ │ │ └── store-backup-share.handler.ts
│ │ │ └── revoke-share/
│ │ │ ├── revoke-share.command.ts
│ │ │ └── revoke-share.handler.ts
│ │ ├── queries/
│ │ │ └── get-backup-share/
│ │ │ ├── get-backup-share.query.ts
│ │ │ └── get-backup-share.handler.ts
│ │ ├── services/
│ │ │ └── backup-share-application.service.ts
│ │ ├── errors/
│ │ │ └── application.error.ts
│ │ └── application.module.ts
│ │
│ ├── domain/ # Core Business Logic
│ │ ├── entities/
│ │ │ └── backup-share.entity.ts # BackupShare aggregate root
│ │ ├── repositories/
│ │ │ └── backup-share.repository.interface.ts
│ │ ├── value-objects/
│ │ │ ├── share-id.vo.ts # Immutable share identifier
│ │ │ └── encrypted-data.vo.ts # Encrypted data structure
│ │ ├── errors/
│ │ │ └── domain.error.ts
│ │ └── domain.module.ts
│ │
│ ├── infrastructure/ # Adapter Layer (Services)
│ │ ├── persistence/
│ │ │ ├── prisma/
│ │ │ │ └── prisma.service.ts # Prisma ORM service
│ │ │ └── repositories/
│ │ │ ├── backup-share.repository.impl.ts # Repository implementation
│ │ │ └── audit-log.repository.ts # Audit logging
│ │ ├── crypto/
│ │ │ └── aes-encryption.service.ts # AES-256-GCM encryption
│ │ └── infrastructure.module.ts
│ │
│ ├── shared/ # Cross-cutting Concerns
│ │ ├── guards/
│ │ │ └── service-auth.guard.ts # JWT service authentication
│ │ ├── filters/
│ │ │ └── global-exception.filter.ts # Exception handling
│ │ └── interceptors/
│ │ └── audit-log.interceptor.ts # Request/response logging
│ │
│ ├── config/
│ │ └── index.ts # Centralized configuration
│ ├── app.module.ts # Root NestJS module
│ └── main.ts # Application entry point
│
├── test/ # Test files
│ ├── unit/
│ ├── integration/
│ ├── e2e/
│ ├── setup/
│ └── utils/
│
└── docs/ # Documentation
Domain Layer Details
BackupShare Entity (Aggregate Root)
File: src/domain/entities/backup-share.entity.ts
class BackupShare {
// Identity
shareId: bigint | null // Auto-increment primary key
userId: bigint // From identity-service
accountSequence: bigint // Account identifier
// MPC Configuration
publicKey: string // MPC public key (66-130 chars)
partyIndex: number // Always 2 for backup share
threshold: number // Default 2 (for 2-of-3 scheme)
totalParties: number // Default 3
// Encrypted Data
encryptedShareData: string // AES-256-GCM encrypted data
encryptionKeyId: string // For key rotation support
// State Management
status: BackupShareStatus // ACTIVE | REVOKED | ROTATED
accessCount: number // Track access frequency
lastAccessedAt: Date | null
// Timestamps
createdAt: Date
updatedAt: Date
revokedAt: Date | null
}
// Factory Methods
BackupShare.create(params): BackupShare
BackupShare.reconstitute(props): BackupShare
// Domain Methods
recordAccess(): void // Increment access counter
revoke(reason: string): void // Mark as revoked
rotate(newData, newKeyId): void // Key rotation support
isActive(): boolean
Value Objects
ShareId
class ShareId {
static create(value: bigint | string | number): ShareId
get value(): bigint
toString(): string
equals(other: ShareId): boolean
}
EncryptedData
class EncryptedData {
ciphertext: string // Base64 encoded
iv: string // Base64 encoded
authTag: string // Base64 encoded
keyId: string
static create(params): EncryptedData
static fromSerializedString(serialized, keyId): EncryptedData
toSerializedString(): string
}
Repository Interface
interface BackupShareRepository {
save(share: BackupShare): Promise<BackupShare>
findById(shareId: bigint): Promise<BackupShare | null>
findByUserId(userId: bigint): Promise<BackupShare | null>
findByPublicKey(publicKey: string): Promise<BackupShare | null>
findByUserIdAndPublicKey(userId: bigint, publicKey: string): Promise<BackupShare | null>
findByAccountSequence(accountSequence: bigint): Promise<BackupShare | null>
delete(shareId: bigint): Promise<void>
}
Application Layer Details
Command Handlers
StoreBackupShareHandler
Flow:
- Check if share already exists for user (uniqueness constraint)
- Check if share already exists for public key (uniqueness constraint)
- Apply double encryption (AES-256-GCM)
- Create BackupShare domain entity
- Save to repository
- Log audit event
- Return shareId
RevokeShareHandler
Flow:
- Find share by userId and publicKey
- Call entity's
revoke()method - Save changes to repository
- Log audit event
Query Handlers
GetBackupShareHandler
Flow:
- Check rate limit (max 3 retrieves per day per user)
- Find share by userId and publicKey
- Verify share is ACTIVE
- Record access in entity
- Save entity state
- Decrypt share data (removes our encryption layer)
- Log audit event
- Return decrypted data
Infrastructure Layer Details
Encryption Service
Algorithm: AES-256-GCM (authenticated encryption)
IV Length: 12 bytes (96 bits)
Key Size: 32 bytes (256 bits)
Output Format: {ciphertext}:{iv}:{authTag} (colon-separated base64)
class AesEncryptionService {
async encrypt(plaintext: string): Promise<EncryptionResult>
async decrypt(encryptedData: string, keyId: string): Promise<string>
addKey(keyId: string, keyHex: string): void
getCurrentKeyId(): string
}
Prisma ORM Service
Uses @prisma/adapter-pg for Prisma 7.x compatibility with PostgreSQL.
class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() // Connect on startup
async onModuleDestroy() // Disconnect on shutdown
async cleanDatabase() // Test utility - delete all tables
}
Database Schema
BackupShare Table
| Column | Type | Constraints | Description |
|---|---|---|---|
| share_id | BIGSERIAL | PK | Auto-increment ID |
| user_id | BIGINT | UNIQUE, NOT NULL | User identifier |
| account_sequence | BIGINT | UNIQUE, NOT NULL | Account sequence |
| public_key | VARCHAR(130) | UNIQUE, NOT NULL | MPC public key |
| party_index | INT | DEFAULT 2 | Party index (always 2) |
| threshold | INT | DEFAULT 2 | Threshold for reconstruction |
| total_parties | INT | DEFAULT 3 | Total parties |
| encrypted_share_data | TEXT | NOT NULL | Encrypted share data |
| encryption_key_id | VARCHAR(64) | NOT NULL | Encryption key ID |
| status | VARCHAR(20) | DEFAULT 'ACTIVE' | Share status |
| access_count | INT | DEFAULT 0 | Access counter |
| last_accessed_at | TIMESTAMP | NULLABLE | Last access time |
| created_at | TIMESTAMP | DEFAULT NOW() | Creation time |
| updated_at | TIMESTAMP | AUTO | Update time |
| revoked_at | TIMESTAMP | NULLABLE | Revocation time |
ShareAccessLog Table
| Column | Type | Constraints | Description |
|---|---|---|---|
| log_id | BIGSERIAL | PK | Auto-increment ID |
| share_id | BIGINT | NOT NULL | Reference to share |
| user_id | BIGINT | NOT NULL | User identifier |
| action | VARCHAR(20) | NOT NULL | STORE/RETRIEVE/REVOKE/ROTATE |
| source_service | VARCHAR(50) | NOT NULL | Calling service |
| source_ip | VARCHAR(45) | NOT NULL | Client IP |
| success | BOOLEAN | DEFAULT TRUE | Operation success |
| error_message | TEXT | NULLABLE | Error details |
| created_at | TIMESTAMP | DEFAULT NOW() | Log time |
Key Architectural Decisions
1. Double Encryption
- Identity-service encrypts data once
- Backup-service encrypts again (AES-256-GCM)
- Defense-in-depth: even if one system is compromised, data remains encrypted
2. Physical Server Isolation
- MPC scheme is 2-of-3: requires at least 2 shares to reconstruct key
- Party 0 (Server Share) on identity-service
- Party 2 (Backup Share) on separate backup-service
- Party 1 (Client Share) on user device
- If only one server is compromised, MPC security remains intact
3. Audit Logging
- All operations logged with timestamp, user, action, source service, source IP
- Non-blocking writes (errors don't affect main operations)
- Supports compliance and security investigations
4. Rate Limiting
- Max 3 retrieves per user per day (prevents brute force recovery attempts)
- Configurable via
MAX_RETRIEVE_PER_DAY - Tracked in database, can be monitored for anomalies
5. Service-to-Service Auth
- JWT tokens with service identity
- No user authentication on backup-service (identity-service responsible)
- Simplified client trust model: only trust from known services
6. Error Handling
- Structured error codes for programmatic handling
- Sensitive data redacted from logs
- Standard error response format
Key Files Reference
| File Path | Purpose |
|---|---|
src/main.ts |
Entry point, NestFactory bootstrap |
src/app.module.ts |
Root module, global filters/interceptors |
src/config/index.ts |
Centralized configuration |
src/domain/entities/backup-share.entity.ts |
Core domain entity |
src/domain/repositories/backup-share.repository.interface.ts |
Repository interface |
src/application/commands/store-backup-share/ |
Store use case |
src/application/queries/get-backup-share/ |
Retrieve use case |
src/infrastructure/crypto/aes-encryption.service.ts |
Encryption service |
src/shared/guards/service-auth.guard.ts |
Authentication guard |
prisma/schema.prisma |
Database schema |