refactor: move backup-service client from identity-service to mpc-service
Architecture change: delegate share storage is now handled by mpc-service. - identity-service no longer calls backup-service directly - mpc-service calls backup-service after keygen completion - This follows proper domain boundaries (MPC domain handles share storage) Flow: 1. identity-service publishes mpc.KeygenRequested 2. mpc-service calls mpc-system for keygen 3. mpc-service stores delegate share to backup-service 4. mpc-service publishes mpc.KeygenCompleted 5. identity-service updates user wallet address 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f4f0466616
commit
383a9540a0
|
|
@ -32,7 +32,6 @@ import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.se
|
||||||
import { MpcEventConsumerService } from '@/infrastructure/kafka/mpc-event-consumer.service';
|
import { MpcEventConsumerService } from '@/infrastructure/kafka/mpc-event-consumer.service';
|
||||||
import { SmsService } from '@/infrastructure/external/sms/sms.service';
|
import { SmsService } from '@/infrastructure/external/sms/sms.service';
|
||||||
import { MpcClientService, MpcWalletService } from '@/infrastructure/external/mpc';
|
import { MpcClientService, MpcWalletService } from '@/infrastructure/external/mpc';
|
||||||
import { BackupClientService, MpcShareStorageService } from '@/infrastructure/external/backup';
|
|
||||||
import { WalletGeneratorServiceImpl } from '@/infrastructure/external/blockchain/wallet-generator.service.impl';
|
import { WalletGeneratorServiceImpl } from '@/infrastructure/external/blockchain/wallet-generator.service.impl';
|
||||||
|
|
||||||
// Shared
|
// Shared
|
||||||
|
|
@ -56,8 +55,6 @@ import { JwtAuthGuard } from '@/shared/guards/jwt-auth.guard';
|
||||||
SmsService,
|
SmsService,
|
||||||
MpcClientService,
|
MpcClientService,
|
||||||
MpcWalletService,
|
MpcWalletService,
|
||||||
BackupClientService,
|
|
||||||
MpcShareStorageService,
|
|
||||||
// WalletGeneratorService 抽象类由 WalletGeneratorServiceImpl 实现
|
// WalletGeneratorService 抽象类由 WalletGeneratorServiceImpl 实现
|
||||||
WalletGeneratorServiceImpl,
|
WalletGeneratorServiceImpl,
|
||||||
{ provide: WalletGeneratorService, useExisting: WalletGeneratorServiceImpl },
|
{ provide: WalletGeneratorService, useExisting: WalletGeneratorServiceImpl },
|
||||||
|
|
@ -71,8 +68,6 @@ import { JwtAuthGuard } from '@/shared/guards/jwt-auth.guard';
|
||||||
SmsService,
|
SmsService,
|
||||||
MpcClientService,
|
MpcClientService,
|
||||||
MpcWalletService,
|
MpcWalletService,
|
||||||
BackupClientService,
|
|
||||||
MpcShareStorageService,
|
|
||||||
WalletGeneratorService,
|
WalletGeneratorService,
|
||||||
MPC_KEY_SHARE_REPOSITORY,
|
MPC_KEY_SHARE_REPOSITORY,
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import { RedisService } from '@/infrastructure/redis/redis.service';
|
||||||
import { SmsService } from '@/infrastructure/external/sms/sms.service';
|
import { SmsService } from '@/infrastructure/external/sms/sms.service';
|
||||||
import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service';
|
import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service';
|
||||||
import { MpcWalletService } from '@/infrastructure/external/mpc';
|
import { MpcWalletService } from '@/infrastructure/external/mpc';
|
||||||
import { BackupClientService } from '@/infrastructure/external/backup';
|
|
||||||
|
|
||||||
describe('UserApplicationService - Referral APIs', () => {
|
describe('UserApplicationService - Referral APIs', () => {
|
||||||
let service: UserApplicationService;
|
let service: UserApplicationService;
|
||||||
|
|
@ -131,10 +130,6 @@ describe('UserApplicationService - Referral APIs', () => {
|
||||||
generateMpcWallet: jest.fn(),
|
generateMpcWallet: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockBackupClientService = {
|
|
||||||
storeBackupShare: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
UserApplicationService,
|
UserApplicationService,
|
||||||
|
|
@ -182,10 +177,6 @@ describe('UserApplicationService - Referral APIs', () => {
|
||||||
provide: MpcWalletService,
|
provide: MpcWalletService,
|
||||||
useValue: mockMpcWalletService,
|
useValue: mockMpcWalletService,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: BackupClientService,
|
|
||||||
useValue: mockBackupClientService,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import { RedisService } from '@/infrastructure/redis/redis.service';
|
||||||
import { SmsService } from '@/infrastructure/external/sms/sms.service';
|
import { SmsService } from '@/infrastructure/external/sms/sms.service';
|
||||||
import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service';
|
import { EventPublisherService } from '@/infrastructure/kafka/event-publisher.service';
|
||||||
import { MpcWalletService } from '@/infrastructure/external/mpc';
|
import { MpcWalletService } from '@/infrastructure/external/mpc';
|
||||||
import { BackupClientService } from '@/infrastructure/external/backup';
|
|
||||||
import { ApplicationError } from '@/shared/exceptions/domain.exception';
|
import { ApplicationError } from '@/shared/exceptions/domain.exception';
|
||||||
import { generateRandomIdentity } from '@/shared/utils';
|
import { generateRandomIdentity } from '@/shared/utils';
|
||||||
import {
|
import {
|
||||||
|
|
@ -43,7 +42,6 @@ export class UserApplicationService {
|
||||||
private readonly validatorService: UserValidatorService,
|
private readonly validatorService: UserValidatorService,
|
||||||
private readonly walletGenerator: WalletGeneratorService,
|
private readonly walletGenerator: WalletGeneratorService,
|
||||||
private readonly mpcWalletService: MpcWalletService,
|
private readonly mpcWalletService: MpcWalletService,
|
||||||
private readonly backupClient: BackupClientService,
|
|
||||||
private readonly tokenService: TokenService,
|
private readonly tokenService: TokenService,
|
||||||
private readonly redisService: RedisService,
|
private readonly redisService: RedisService,
|
||||||
private readonly smsService: SmsService,
|
private readonly smsService: SmsService,
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
export * from './backup-client.service';
|
|
||||||
export * from './mpc-share-storage.service';
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
/**
|
|
||||||
* MPC Share Storage Service
|
|
||||||
*
|
|
||||||
* 封装 MPC 分片存储逻辑,提供简化接口给应用层
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
|
||||||
import { BackupClientService } from './backup-client.service';
|
|
||||||
|
|
||||||
export interface MpcStoreBackupShareParams {
|
|
||||||
userId: string;
|
|
||||||
shareData: string;
|
|
||||||
publicKey: string;
|
|
||||||
accountSequence?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MpcRetrieveBackupShareParams {
|
|
||||||
userId: string;
|
|
||||||
publicKey: string;
|
|
||||||
recoveryToken: string;
|
|
||||||
deviceId?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MpcBackupShareData {
|
|
||||||
encryptedShareData: string;
|
|
||||||
partyIndex: number;
|
|
||||||
publicKey: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class MpcShareStorageService {
|
|
||||||
private readonly logger = new Logger(MpcShareStorageService.name);
|
|
||||||
|
|
||||||
constructor(private readonly backupClient: BackupClientService) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 存储备份分片
|
|
||||||
*
|
|
||||||
* @param params 分片存储参数
|
|
||||||
*/
|
|
||||||
async storeBackupShare(params: MpcStoreBackupShareParams): Promise<void> {
|
|
||||||
this.logger.log(`Storing backup share for user=${params.userId}`);
|
|
||||||
|
|
||||||
await this.backupClient.storeBackupShare({
|
|
||||||
userId: params.userId,
|
|
||||||
accountSequence: params.accountSequence || 0,
|
|
||||||
publicKey: params.publicKey,
|
|
||||||
encryptedShareData: params.shareData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取备份分片 (用于账户恢复)
|
|
||||||
*
|
|
||||||
* @param params 分片获取参数
|
|
||||||
* @returns 备份分片数据或 null
|
|
||||||
*/
|
|
||||||
async retrieveBackupShare(
|
|
||||||
params: MpcRetrieveBackupShareParams,
|
|
||||||
): Promise<MpcBackupShareData | null> {
|
|
||||||
this.logger.log(`Retrieving backup share for user=${params.userId}`);
|
|
||||||
|
|
||||||
return this.backupClient.retrieveBackupShare(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 撤销备份分片
|
|
||||||
*
|
|
||||||
* @param userId 用户ID
|
|
||||||
* @param publicKey MPC 公钥
|
|
||||||
* @param reason 撤销原因
|
|
||||||
*/
|
|
||||||
async revokeBackupShare(
|
|
||||||
userId: string,
|
|
||||||
publicKey: string,
|
|
||||||
reason: string,
|
|
||||||
): Promise<void> {
|
|
||||||
this.logger.log(`Revoking backup share for user=${userId}`);
|
|
||||||
|
|
||||||
await this.backupClient.revokeBackupShare(userId, publicKey, reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查备份服务是否可用
|
|
||||||
*/
|
|
||||||
isEnabled(): boolean {
|
|
||||||
return this.backupClient.isEnabled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -11,7 +11,6 @@ import { SmsService } from './external/sms/sms.service';
|
||||||
import { WalletGeneratorServiceImpl } from './external/blockchain/wallet-generator.service.impl';
|
import { WalletGeneratorServiceImpl } from './external/blockchain/wallet-generator.service.impl';
|
||||||
import { BlockchainQueryService } from './external/blockchain/blockchain-query.service';
|
import { BlockchainQueryService } from './external/blockchain/blockchain-query.service';
|
||||||
import { MpcClientService, MpcWalletService } from './external/mpc';
|
import { MpcClientService, MpcWalletService } from './external/mpc';
|
||||||
import { BackupClientService, MpcShareStorageService } from './external/backup';
|
|
||||||
import { MPC_KEY_SHARE_REPOSITORY } from '@/domain/repositories/mpc-key-share.repository.interface';
|
import { MPC_KEY_SHARE_REPOSITORY } from '@/domain/repositories/mpc-key-share.repository.interface';
|
||||||
import { WalletGeneratorService } from '@/domain/services/wallet-generator.service';
|
import { WalletGeneratorService } from '@/domain/services/wallet-generator.service';
|
||||||
|
|
||||||
|
|
@ -44,8 +43,6 @@ import { WalletGeneratorService } from '@/domain/services/wallet-generator.servi
|
||||||
BlockchainQueryService,
|
BlockchainQueryService,
|
||||||
MpcClientService,
|
MpcClientService,
|
||||||
MpcWalletService,
|
MpcWalletService,
|
||||||
BackupClientService,
|
|
||||||
MpcShareStorageService,
|
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
PrismaService,
|
PrismaService,
|
||||||
|
|
@ -64,8 +61,6 @@ import { WalletGeneratorService } from '@/domain/services/wallet-generator.servi
|
||||||
BlockchainQueryService,
|
BlockchainQueryService,
|
||||||
MpcClientService,
|
MpcClientService,
|
||||||
MpcWalletService,
|
MpcWalletService,
|
||||||
BackupClientService,
|
|
||||||
MpcShareStorageService,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class InfrastructureModule {}
|
export class InfrastructureModule {}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
MPC_CONSUME_TOPICS,
|
MPC_CONSUME_TOPICS,
|
||||||
KeygenRequestedPayload,
|
KeygenRequestedPayload,
|
||||||
} from '../../infrastructure/messaging/kafka/event-consumer.service';
|
} from '../../infrastructure/messaging/kafka/event-consumer.service';
|
||||||
|
import { BackupClientService } from '../../infrastructure/external/backup';
|
||||||
import { KeygenStartedEvent } from '../../domain/events/keygen-started.event';
|
import { KeygenStartedEvent } from '../../domain/events/keygen-started.event';
|
||||||
import { KeygenCompletedEvent } from '../../domain/events/keygen-completed.event';
|
import { KeygenCompletedEvent } from '../../domain/events/keygen-completed.event';
|
||||||
import { SessionFailedEvent } from '../../domain/events/session-failed.event';
|
import { SessionFailedEvent } from '../../domain/events/session-failed.event';
|
||||||
|
|
@ -26,6 +27,7 @@ export class KeygenRequestedHandler implements OnModuleInit {
|
||||||
private readonly eventConsumer: EventConsumerService,
|
private readonly eventConsumer: EventConsumerService,
|
||||||
private readonly eventPublisher: EventPublisherService,
|
private readonly eventPublisher: EventPublisherService,
|
||||||
private readonly mpcCoordinator: MPCCoordinatorService,
|
private readonly mpcCoordinator: MPCCoordinatorService,
|
||||||
|
private readonly backupClient: BackupClientService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
|
|
@ -72,14 +74,31 @@ export class KeygenRequestedHandler implements OnModuleInit {
|
||||||
// Cache public key
|
// Cache public key
|
||||||
await this.mpcCoordinator.savePublicKeyCache(username, result.publicKey);
|
await this.mpcCoordinator.savePublicKeyCache(username, result.publicKey);
|
||||||
|
|
||||||
// Save delegate share if exists
|
// Save delegate share to backup-service if exists
|
||||||
if (result.delegateShare) {
|
if (result.delegateShare) {
|
||||||
|
// 1. 保存到本地缓存(用于签名时快速访问)
|
||||||
await this.mpcCoordinator.saveDelegateShare({
|
await this.mpcCoordinator.saveDelegateShare({
|
||||||
username,
|
username,
|
||||||
partyId: result.delegateShare.partyId,
|
partyId: result.delegateShare.partyId,
|
||||||
partyIndex: result.delegateShare.partyIndex,
|
partyIndex: result.delegateShare.partyIndex,
|
||||||
encryptedShare: result.delegateShare.encryptedShare,
|
encryptedShare: result.delegateShare.encryptedShare,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 2. 存储到 backup-service(用于灾备恢复)
|
||||||
|
try {
|
||||||
|
await this.backupClient.storeBackupShare({
|
||||||
|
userId,
|
||||||
|
username,
|
||||||
|
publicKey: result.publicKey,
|
||||||
|
partyId: result.delegateShare.partyId,
|
||||||
|
partyIndex: result.delegateShare.partyIndex,
|
||||||
|
encryptedShare: result.delegateShare.encryptedShare,
|
||||||
|
});
|
||||||
|
this.logger.log(`Delegate share stored to backup-service: userId=${userId}`);
|
||||||
|
} catch (backupError) {
|
||||||
|
// 备份失败不阻塞主流程,但记录错误
|
||||||
|
this.logger.error(`Failed to store delegate share to backup-service: userId=${userId}`, backupError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish success event
|
// Publish success event
|
||||||
|
|
|
||||||
|
|
@ -1,192 +1,155 @@
|
||||||
/**
|
/**
|
||||||
* Backup Client Service
|
* Backup Client Service
|
||||||
*
|
*
|
||||||
* 与 backup-service 通信的客户端服务
|
* mpc-service 调用 backup-service 存储/获取 delegate share
|
||||||
* 负责存储和获取 MPC Backup Share (Party 2)
|
* backup-service 负责安全存储 MPC 分片备份
|
||||||
*
|
*/
|
||||||
* 安全要求: backup-service 必须部署在与 identity-service 不同的物理服务器上
|
|
||||||
*/
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { HttpService } from '@nestjs/axios';
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { HttpService } from '@nestjs/axios';
|
import { firstValueFrom } from 'rxjs';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import * as jwt from 'jsonwebtoken';
|
||||||
import { firstValueFrom } from 'rxjs';
|
|
||||||
import * as jwt from 'jsonwebtoken';
|
export interface StoreBackupShareParams {
|
||||||
|
userId: string;
|
||||||
export interface StoreBackupShareParams {
|
username: string;
|
||||||
userId: string;
|
publicKey: string;
|
||||||
accountSequence: number;
|
partyId: string;
|
||||||
publicKey: string;
|
partyIndex: number;
|
||||||
encryptedShareData: string;
|
encryptedShare: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RetrieveBackupShareParams {
|
export interface RetrieveBackupShareParams {
|
||||||
userId: string;
|
userId: string;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
recoveryToken: string;
|
}
|
||||||
deviceId?: string;
|
|
||||||
}
|
export interface BackupShareResult {
|
||||||
|
encryptedShareData: string;
|
||||||
export interface BackupShareResult {
|
partyIndex: number;
|
||||||
encryptedShareData: string;
|
publicKey: string;
|
||||||
partyIndex: number;
|
}
|
||||||
publicKey: string;
|
|
||||||
}
|
@Injectable()
|
||||||
|
export class BackupClientService {
|
||||||
@Injectable()
|
private readonly logger = new Logger(BackupClientService.name);
|
||||||
export class BackupClientService {
|
private readonly backupServiceUrl: string;
|
||||||
private readonly logger = new Logger(BackupClientService.name);
|
private readonly serviceJwtSecret: string;
|
||||||
private readonly backupServiceUrl: string;
|
private readonly enabled: boolean;
|
||||||
private readonly serviceJwtSecret: string;
|
|
||||||
private readonly enabled: boolean;
|
constructor(
|
||||||
|
private readonly httpService: HttpService,
|
||||||
constructor(
|
private readonly configService: ConfigService,
|
||||||
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.backupServiceUrl = this.configService.get<string>('BACKUP_SERVICE_URL', 'http://localhost:3002');
|
this.enabled = this.configService.get<string>('BACKUP_SERVICE_ENABLED', 'true') === 'true';
|
||||||
this.serviceJwtSecret = this.configService.get<string>('SERVICE_JWT_SECRET', '');
|
|
||||||
this.enabled = this.configService.get<string>('BACKUP_SERVICE_ENABLED', 'false') === '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 {
|
* 检查 backup-service 是否启用
|
||||||
return this.enabled && !!this.serviceJwtSecret;
|
*/
|
||||||
}
|
isEnabled(): boolean {
|
||||||
|
return this.enabled && !!this.serviceJwtSecret;
|
||||||
/**
|
}
|
||||||
* 存储备份分片到 backup-service
|
|
||||||
*/
|
/**
|
||||||
async storeBackupShare(params: StoreBackupShareParams): Promise<void> {
|
* 存储 delegate share 到 backup-service
|
||||||
if (!this.isEnabled()) {
|
*/
|
||||||
this.logger.warn('Backup service is disabled, skipping backup share storage');
|
async storeBackupShare(params: StoreBackupShareParams): Promise<void> {
|
||||||
return;
|
if (!this.isEnabled()) {
|
||||||
}
|
this.logger.warn('Backup service is disabled, skipping backup share storage');
|
||||||
|
return;
|
||||||
this.logger.log(`Storing backup share for user: ${params.userId}`);
|
}
|
||||||
|
|
||||||
try {
|
this.logger.log(`Storing backup share: userId=${params.userId}, username=${params.username}`);
|
||||||
const serviceToken = this.generateServiceToken();
|
|
||||||
|
try {
|
||||||
await firstValueFrom(
|
const serviceToken = this.generateServiceToken();
|
||||||
this.httpService.post(
|
|
||||||
`${this.backupServiceUrl}/backup-share/store`,
|
await firstValueFrom(
|
||||||
{
|
this.httpService.post(
|
||||||
userId: params.userId,
|
`${this.backupServiceUrl}/backup-share/store`,
|
||||||
accountSequence: params.accountSequence,
|
{
|
||||||
publicKey: params.publicKey,
|
userId: params.userId,
|
||||||
encryptedShareData: params.encryptedShareData,
|
username: params.username,
|
||||||
},
|
publicKey: params.publicKey,
|
||||||
{
|
partyId: params.partyId,
|
||||||
headers: {
|
partyIndex: params.partyIndex,
|
||||||
'Content-Type': 'application/json',
|
encryptedShareData: params.encryptedShare,
|
||||||
'X-Service-Token': serviceToken,
|
},
|
||||||
},
|
{
|
||||||
timeout: 30000, // 30秒超时
|
headers: {
|
||||||
},
|
'Content-Type': 'application/json',
|
||||||
),
|
'X-Service-Token': serviceToken,
|
||||||
);
|
},
|
||||||
|
timeout: 30000,
|
||||||
this.logger.log(`Backup share stored successfully for user: ${params.userId}`);
|
},
|
||||||
} catch (error) {
|
),
|
||||||
this.logger.error(`Failed to store backup share for user: ${params.userId}`, error);
|
);
|
||||||
// 不抛出异常,允许账户创建继续
|
|
||||||
// 可以通过补偿任务稍后重试
|
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 获取备份分片 (用于账户恢复)
|
}
|
||||||
*/
|
|
||||||
async retrieveBackupShare(params: RetrieveBackupShareParams): Promise<BackupShareResult | null> {
|
/**
|
||||||
if (!this.isEnabled()) {
|
* 从 backup-service 获取 delegate share (用于签名)
|
||||||
this.logger.warn('Backup service is disabled');
|
*/
|
||||||
return null;
|
async retrieveBackupShare(params: RetrieveBackupShareParams): Promise<BackupShareResult | null> {
|
||||||
}
|
if (!this.isEnabled()) {
|
||||||
|
this.logger.warn('Backup service is disabled');
|
||||||
this.logger.log(`Retrieving backup share for user: ${params.userId}`);
|
return null;
|
||||||
|
}
|
||||||
try {
|
|
||||||
const serviceToken = this.generateServiceToken();
|
this.logger.log(`Retrieving backup share: userId=${params.userId}`);
|
||||||
|
|
||||||
const response = await firstValueFrom(
|
try {
|
||||||
this.httpService.post<BackupShareResult>(
|
const serviceToken = this.generateServiceToken();
|
||||||
`${this.backupServiceUrl}/backup-share/retrieve`,
|
|
||||||
{
|
const response = await firstValueFrom(
|
||||||
userId: params.userId,
|
this.httpService.post<BackupShareResult>(
|
||||||
publicKey: params.publicKey,
|
`${this.backupServiceUrl}/backup-share/retrieve`,
|
||||||
recoveryToken: params.recoveryToken,
|
{
|
||||||
deviceId: params.deviceId,
|
userId: params.userId,
|
||||||
},
|
publicKey: params.publicKey,
|
||||||
{
|
},
|
||||||
headers: {
|
{
|
||||||
'Content-Type': 'application/json',
|
headers: {
|
||||||
'X-Service-Token': serviceToken,
|
'Content-Type': 'application/json',
|
||||||
},
|
'X-Service-Token': serviceToken,
|
||||||
timeout: 30000,
|
},
|
||||||
},
|
timeout: 30000,
|
||||||
),
|
},
|
||||||
);
|
),
|
||||||
|
);
|
||||||
this.logger.log(`Backup share retrieved successfully for user: ${params.userId}`);
|
|
||||||
return response.data;
|
this.logger.log(`Backup share retrieved: userId=${params.userId}`);
|
||||||
} catch (error) {
|
return response.data;
|
||||||
this.logger.error(`Failed to retrieve backup share for user: ${params.userId}`, error);
|
} catch (error) {
|
||||||
throw new Error(`Failed to retrieve backup share: ${error.message}`);
|
this.logger.error(`Failed to retrieve backup share: userId=${params.userId}`, error);
|
||||||
}
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* 撤销备份分片 (用于密钥轮换或账户注销)
|
/**
|
||||||
*/
|
* 生成服务间认证 JWT
|
||||||
async revokeBackupShare(userId: string, publicKey: string, reason: string): Promise<void> {
|
*/
|
||||||
if (!this.isEnabled()) {
|
private generateServiceToken(): string {
|
||||||
this.logger.warn('Backup service is disabled');
|
return jwt.sign(
|
||||||
return;
|
{
|
||||||
}
|
service: 'mpc-service',
|
||||||
|
iat: Math.floor(Date.now() / 1000),
|
||||||
this.logger.log(`Revoking backup share for user: ${userId}, reason: ${reason}`);
|
},
|
||||||
|
this.serviceJwtSecret,
|
||||||
try {
|
{ expiresIn: '5m' },
|
||||||
const serviceToken = this.generateServiceToken();
|
);
|
||||||
|
}
|
||||||
await firstValueFrom(
|
}
|
||||||
this.httpService.post(
|
|
||||||
`${this.backupServiceUrl}/backup-share/revoke`,
|
|
||||||
{
|
|
||||||
userId,
|
|
||||||
publicKey,
|
|
||||||
reason,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Service-Token': serviceToken,
|
|
||||||
},
|
|
||||||
timeout: 30000,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.logger.log(`Backup share revoked successfully for user: ${userId}`);
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(`Failed to revoke backup share for user: ${userId}`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成服务间认证 JWT
|
|
||||||
*/
|
|
||||||
private generateServiceToken(): string {
|
|
||||||
return jwt.sign(
|
|
||||||
{
|
|
||||||
service: 'identity-service',
|
|
||||||
iat: Math.floor(Date.now() / 1000),
|
|
||||||
},
|
|
||||||
this.serviceJwtSecret,
|
|
||||||
{ expiresIn: '5m' },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export { BackupClientService } from './backup-client.service';
|
||||||
|
|
@ -4,10 +4,12 @@
|
||||||
* mpc-service 作为网关,需要:
|
* mpc-service 作为网关,需要:
|
||||||
* - PrismaService 用于缓存公钥和 delegate share
|
* - PrismaService 用于缓存公钥和 delegate share
|
||||||
* - Kafka 事件发布和消费
|
* - Kafka 事件发布和消费
|
||||||
|
* - BackupClientService 用于存储 delegate share 到 backup-service
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Global, Module } from '@nestjs/common';
|
import { Global, Module } from '@nestjs/common';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { HttpModule } from '@nestjs/axios';
|
||||||
|
|
||||||
// Persistence
|
// Persistence
|
||||||
import { PrismaService } from './persistence/prisma/prisma.service';
|
import { PrismaService } from './persistence/prisma/prisma.service';
|
||||||
|
|
@ -16,9 +18,18 @@ import { PrismaService } from './persistence/prisma/prisma.service';
|
||||||
import { EventPublisherService } from './messaging/kafka/event-publisher.service';
|
import { EventPublisherService } from './messaging/kafka/event-publisher.service';
|
||||||
import { EventConsumerService } from './messaging/kafka/event-consumer.service';
|
import { EventConsumerService } from './messaging/kafka/event-consumer.service';
|
||||||
|
|
||||||
|
// External Services
|
||||||
|
import { BackupClientService } from './external/backup';
|
||||||
|
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ConfigModule],
|
imports: [
|
||||||
|
ConfigModule,
|
||||||
|
HttpModule.register({
|
||||||
|
timeout: 30000,
|
||||||
|
maxRedirects: 5,
|
||||||
|
}),
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
// Prisma (用于缓存公钥和 delegate share)
|
// Prisma (用于缓存公钥和 delegate share)
|
||||||
PrismaService,
|
PrismaService,
|
||||||
|
|
@ -26,11 +37,15 @@ import { EventConsumerService } from './messaging/kafka/event-consumer.service';
|
||||||
// Kafka (事件发布和消费)
|
// Kafka (事件发布和消费)
|
||||||
EventPublisherService,
|
EventPublisherService,
|
||||||
EventConsumerService,
|
EventConsumerService,
|
||||||
|
|
||||||
|
// External Services
|
||||||
|
BackupClientService,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
PrismaService,
|
PrismaService,
|
||||||
EventPublisherService,
|
EventPublisherService,
|
||||||
EventConsumerService,
|
EventConsumerService,
|
||||||
|
BackupClientService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class InfrastructureModule {}
|
export class InfrastructureModule {}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue