202 lines
5.4 KiB
TypeScript
202 lines
5.4 KiB
TypeScript
import { DomainError } from '../errors/domain.error';
|
|
|
|
export enum BackupShareStatus {
|
|
ACTIVE = 'ACTIVE',
|
|
REVOKED = 'REVOKED',
|
|
ROTATED = 'ROTATED',
|
|
}
|
|
|
|
export interface BackupShareProps {
|
|
shareId: bigint | null;
|
|
userId: bigint;
|
|
accountSequence: bigint;
|
|
publicKey: string;
|
|
partyIndex: number;
|
|
threshold: number;
|
|
totalParties: number;
|
|
encryptedShareData: string;
|
|
encryptionKeyId: string;
|
|
status: BackupShareStatus;
|
|
accessCount: number;
|
|
lastAccessedAt: Date | null;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
revokedAt: Date | null;
|
|
}
|
|
|
|
export class BackupShare {
|
|
private _shareId: bigint | null;
|
|
private readonly _userId: bigint;
|
|
private readonly _accountSequence: bigint;
|
|
private readonly _publicKey: string;
|
|
private readonly _partyIndex: number;
|
|
private readonly _threshold: number;
|
|
private readonly _totalParties: number;
|
|
private _encryptedShareData: string;
|
|
private _encryptionKeyId: string;
|
|
private _status: BackupShareStatus;
|
|
private _accessCount: number;
|
|
private _lastAccessedAt: Date | null;
|
|
private readonly _createdAt: Date;
|
|
private _updatedAt: Date;
|
|
private _revokedAt: Date | null;
|
|
|
|
private constructor(props: BackupShareProps) {
|
|
this._shareId = props.shareId;
|
|
this._userId = props.userId;
|
|
this._accountSequence = props.accountSequence;
|
|
this._publicKey = props.publicKey;
|
|
this._partyIndex = props.partyIndex;
|
|
this._threshold = props.threshold;
|
|
this._totalParties = props.totalParties;
|
|
this._encryptedShareData = props.encryptedShareData;
|
|
this._encryptionKeyId = props.encryptionKeyId;
|
|
this._status = props.status;
|
|
this._accessCount = props.accessCount;
|
|
this._lastAccessedAt = props.lastAccessedAt;
|
|
this._createdAt = props.createdAt;
|
|
this._updatedAt = props.updatedAt;
|
|
this._revokedAt = props.revokedAt;
|
|
}
|
|
|
|
static create(params: {
|
|
userId: bigint;
|
|
accountSequence: bigint;
|
|
publicKey: string;
|
|
encryptedShareData: string;
|
|
encryptionKeyId: string;
|
|
threshold?: number;
|
|
totalParties?: number;
|
|
}): BackupShare {
|
|
const now = new Date();
|
|
return new BackupShare({
|
|
shareId: null,
|
|
userId: params.userId,
|
|
accountSequence: params.accountSequence,
|
|
publicKey: params.publicKey,
|
|
partyIndex: 2, // Backup = Party 2
|
|
threshold: params.threshold ?? 2,
|
|
totalParties: params.totalParties ?? 3,
|
|
encryptedShareData: params.encryptedShareData,
|
|
encryptionKeyId: params.encryptionKeyId,
|
|
status: BackupShareStatus.ACTIVE,
|
|
accessCount: 0,
|
|
lastAccessedAt: null,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
revokedAt: null,
|
|
});
|
|
}
|
|
|
|
static reconstitute(props: BackupShareProps): BackupShare {
|
|
return new BackupShare(props);
|
|
}
|
|
|
|
recordAccess(): void {
|
|
if (this._status !== BackupShareStatus.ACTIVE) {
|
|
throw new DomainError('Cannot access revoked or rotated share');
|
|
}
|
|
this._accessCount++;
|
|
this._lastAccessedAt = new Date();
|
|
this._updatedAt = new Date();
|
|
}
|
|
|
|
revoke(reason: string): void {
|
|
if (this._status === BackupShareStatus.REVOKED) {
|
|
throw new DomainError('Share already revoked');
|
|
}
|
|
this._status = BackupShareStatus.REVOKED;
|
|
this._revokedAt = new Date();
|
|
this._updatedAt = new Date();
|
|
}
|
|
|
|
rotate(newEncryptedData: string, newKeyId: string): void {
|
|
if (this._status === BackupShareStatus.REVOKED) {
|
|
throw new DomainError('Cannot rotate revoked share');
|
|
}
|
|
this._encryptedShareData = newEncryptedData;
|
|
this._encryptionKeyId = newKeyId;
|
|
this._status = BackupShareStatus.ACTIVE;
|
|
this._updatedAt = new Date();
|
|
}
|
|
|
|
isActive(): boolean {
|
|
return this._status === BackupShareStatus.ACTIVE;
|
|
}
|
|
|
|
// Getters
|
|
get shareId(): bigint | null {
|
|
return this._shareId;
|
|
}
|
|
get userId(): bigint {
|
|
return this._userId;
|
|
}
|
|
get accountSequence(): bigint {
|
|
return this._accountSequence;
|
|
}
|
|
get publicKey(): string {
|
|
return this._publicKey;
|
|
}
|
|
get partyIndex(): number {
|
|
return this._partyIndex;
|
|
}
|
|
get threshold(): number {
|
|
return this._threshold;
|
|
}
|
|
get totalParties(): number {
|
|
return this._totalParties;
|
|
}
|
|
get encryptedShareData(): string {
|
|
return this._encryptedShareData;
|
|
}
|
|
get encryptionKeyId(): string {
|
|
return this._encryptionKeyId;
|
|
}
|
|
get status(): BackupShareStatus {
|
|
return this._status;
|
|
}
|
|
get accessCount(): number {
|
|
return this._accessCount;
|
|
}
|
|
get lastAccessedAt(): Date | null {
|
|
return this._lastAccessedAt;
|
|
}
|
|
get createdAt(): Date {
|
|
return this._createdAt;
|
|
}
|
|
get updatedAt(): Date {
|
|
return this._updatedAt;
|
|
}
|
|
get revokedAt(): Date | null {
|
|
return this._revokedAt;
|
|
}
|
|
|
|
// For persistence
|
|
setShareId(id: bigint): void {
|
|
if (this._shareId !== null) {
|
|
throw new DomainError('Share ID already set');
|
|
}
|
|
this._shareId = id;
|
|
}
|
|
|
|
toProps(): BackupShareProps {
|
|
return {
|
|
shareId: this._shareId,
|
|
userId: this._userId,
|
|
accountSequence: this._accountSequence,
|
|
publicKey: this._publicKey,
|
|
partyIndex: this._partyIndex,
|
|
threshold: this._threshold,
|
|
totalParties: this._totalParties,
|
|
encryptedShareData: this._encryptedShareData,
|
|
encryptionKeyId: this._encryptionKeyId,
|
|
status: this._status,
|
|
accessCount: this._accessCount,
|
|
lastAccessedAt: this._lastAccessedAt,
|
|
createdAt: this._createdAt,
|
|
updatedAt: this._updatedAt,
|
|
revokedAt: this._revokedAt,
|
|
};
|
|
}
|
|
}
|