rwadurian/backend/services/mpc-service/src/infrastructure/external/backup/backup-client.service.ts

155 lines
4.3 KiB
TypeScript

/**
* Backup Client Service
*
* mpc-service 调用 backup-service 存储/获取 delegate share
* backup-service 负责安全存储 MPC 分片备份
*/
import { Injectable, Logger } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import { firstValueFrom } from 'rxjs';
import * as jwt from 'jsonwebtoken';
export interface StoreBackupShareParams {
userId: string;
accountSequence: number;
username: string;
publicKey: string;
partyId: string;
partyIndex: number;
encryptedShare: string;
}
export interface RetrieveBackupShareParams {
userId: string;
publicKey: string;
}
export interface BackupShareResult {
encryptedShareData: string;
partyIndex: number;
publicKey: string;
}
@Injectable()
export class BackupClientService {
private readonly logger = new Logger(BackupClientService.name);
private readonly backupServiceUrl: string;
private readonly serviceJwtSecret: string;
private readonly enabled: boolean;
constructor(
private readonly httpService: HttpService,
private readonly configService: ConfigService,
) {
this.backupServiceUrl = this.configService.get<string>('BACKUP_SERVICE_URL', 'http://localhost:3002');
this.serviceJwtSecret = this.configService.get<string>('SERVICE_JWT_SECRET', '');
this.enabled = this.configService.get<string>('BACKUP_SERVICE_ENABLED', 'true') === 'true';
this.logger.log(`[INIT] BackupClientService initialized`);
this.logger.log(`[INIT] URL: ${this.backupServiceUrl}`);
this.logger.log(`[INIT] Enabled: ${this.enabled}`);
}
/**
* 检查 backup-service 是否启用
*/
isEnabled(): boolean {
return this.enabled && !!this.serviceJwtSecret;
}
/**
* 存储 delegate share 到 backup-service
*/
async storeBackupShare(params: StoreBackupShareParams): Promise<void> {
if (!this.isEnabled()) {
this.logger.warn('Backup service is disabled, skipping backup share storage');
return;
}
this.logger.log(`Storing backup share: userId=${params.userId}, username=${params.username}`);
try {
const serviceToken = this.generateServiceToken();
await firstValueFrom(
this.httpService.post(
`${this.backupServiceUrl}/backup-share/store`,
{
userId: params.userId,
accountSequence: params.accountSequence,
publicKey: params.publicKey,
encryptedShareData: params.encryptedShare,
},
{
headers: {
'Content-Type': 'application/json',
'X-Service-Token': serviceToken,
},
timeout: 30000,
},
),
);
this.logger.log(`Backup share stored successfully: userId=${params.userId}`);
} catch (error) {
this.logger.error(`Failed to store backup share: userId=${params.userId}`, error);
throw error; // 抛出异常,让调用方处理
}
}
/**
* 从 backup-service 获取 delegate share (用于签名)
*/
async retrieveBackupShare(params: RetrieveBackupShareParams): Promise<BackupShareResult | null> {
if (!this.isEnabled()) {
this.logger.warn('Backup service is disabled');
return null;
}
this.logger.log(`Retrieving backup share: userId=${params.userId}`);
try {
const serviceToken = this.generateServiceToken();
const response = await firstValueFrom(
this.httpService.post<BackupShareResult>(
`${this.backupServiceUrl}/backup-share/retrieve`,
{
userId: params.userId,
publicKey: params.publicKey,
},
{
headers: {
'Content-Type': 'application/json',
'X-Service-Token': serviceToken,
},
timeout: 30000,
},
),
);
this.logger.log(`Backup share retrieved: userId=${params.userId}`);
return response.data;
} catch (error) {
this.logger.error(`Failed to retrieve backup share: userId=${params.userId}`, error);
return null;
}
}
/**
* 生成服务间认证 JWT
*/
private generateServiceToken(): string {
return jwt.sign(
{
service: 'mpc-service',
iat: Math.floor(Date.now() / 1000),
},
this.serviceJwtSecret,
{ expiresIn: '5m' },
);
}
}