770 lines
22 KiB
TypeScript
770 lines
22 KiB
TypeScript
/**
|
|
* EncryptID Social Recovery Module
|
|
*
|
|
* Implements guardian-based account recovery with NO SEED PHRASES.
|
|
* This is Layer 4 of the EncryptID architecture.
|
|
*/
|
|
|
|
import { bufferToBase64url, base64urlToBuffer } from './webauthn';
|
|
|
|
// ============================================================================
|
|
// TYPES
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Types of guardians that can be added
|
|
*/
|
|
export enum GuardianType {
|
|
/** Another passkey owned by the user (e.g., YubiKey backup) */
|
|
SECONDARY_PASSKEY = 'secondary_passkey',
|
|
|
|
/** A trusted contact with their own EncryptID */
|
|
TRUSTED_CONTACT = 'trusted_contact',
|
|
|
|
/** A hardware security key stored offline */
|
|
HARDWARE_KEY = 'hardware_key',
|
|
|
|
/** An institutional guardian (service provider) */
|
|
INSTITUTIONAL = 'institutional',
|
|
|
|
/** Time-delayed self-recovery (requires waiting period) */
|
|
TIME_DELAYED_SELF = 'time_delayed_self',
|
|
}
|
|
|
|
/**
|
|
* Guardian configuration
|
|
*/
|
|
export interface Guardian {
|
|
id: string;
|
|
type: GuardianType;
|
|
name: string;
|
|
weight: number; // Contribution to threshold (usually 1)
|
|
|
|
// Type-specific data
|
|
credentialId?: string; // For SECONDARY_PASSKEY
|
|
contactDID?: string; // For TRUSTED_CONTACT
|
|
contactEmail?: string; // For notification
|
|
serviceUrl?: string; // For INSTITUTIONAL
|
|
delaySeconds?: number; // For TIME_DELAYED_SELF
|
|
|
|
// Metadata
|
|
addedAt: number;
|
|
lastVerified?: number;
|
|
}
|
|
|
|
/**
|
|
* Recovery configuration for an account
|
|
*/
|
|
export interface RecoveryConfig {
|
|
/** Required weight to recover (e.g., 3 for 3-of-5) */
|
|
threshold: number;
|
|
|
|
/** Time-lock delay in seconds before recovery completes */
|
|
delaySeconds: number;
|
|
|
|
/** List of guardians */
|
|
guardians: Guardian[];
|
|
|
|
/** Hash of guardian addresses (for privacy) */
|
|
guardianListHash: string;
|
|
|
|
/** When config was last updated */
|
|
updatedAt: number;
|
|
}
|
|
|
|
/**
|
|
* Active recovery request
|
|
*/
|
|
export interface RecoveryRequest {
|
|
id: string;
|
|
accountDID: string;
|
|
newCredentialId: string;
|
|
initiatedAt: number;
|
|
completesAt: number;
|
|
status: 'pending' | 'approved' | 'cancelled' | 'completed';
|
|
|
|
/** Guardians who have approved */
|
|
approvals: {
|
|
guardianId: string;
|
|
approvedAt: number;
|
|
signature: string;
|
|
}[];
|
|
|
|
/** Total weight of approvals */
|
|
approvalWeight: number;
|
|
}
|
|
|
|
// ============================================================================
|
|
// GUARDIAN MANAGEMENT
|
|
// ============================================================================
|
|
|
|
/**
|
|
* EncryptID Recovery Manager
|
|
*
|
|
* Handles guardian configuration and recovery flows.
|
|
*/
|
|
export class RecoveryManager {
|
|
private config: RecoveryConfig | null = null;
|
|
private activeRequest: RecoveryRequest | null = null;
|
|
private loaded = false;
|
|
|
|
constructor() {
|
|
this.loadLocalSettings();
|
|
}
|
|
|
|
/** Get the auth token for server API calls */
|
|
private getAuthToken(): string | null {
|
|
try {
|
|
return localStorage.getItem('encryptid_token');
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** Resolve EncryptID API base URL */
|
|
private getApiBase(): string {
|
|
// When running on the encryptid domain, use relative paths
|
|
if (typeof location !== 'undefined' && location.hostname.includes('auth.')) {
|
|
return '';
|
|
}
|
|
// From rspace canvas, reach encryptid via its public URL
|
|
return 'https://auth.rspace.online';
|
|
}
|
|
|
|
/**
|
|
* Initialize recovery with default configuration.
|
|
* Fetches existing guardians from server if authenticated.
|
|
*/
|
|
async initializeRecovery(threshold: number = 3): Promise<RecoveryConfig> {
|
|
this.config = {
|
|
threshold,
|
|
delaySeconds: 48 * 60 * 60, // 48 hours
|
|
guardians: [],
|
|
guardianListHash: '',
|
|
updatedAt: Date.now(),
|
|
};
|
|
|
|
// Try to load existing guardians from server
|
|
await this.syncFromServer();
|
|
|
|
await this.saveLocalSettings();
|
|
return this.config;
|
|
}
|
|
|
|
/**
|
|
* Ensure config is loaded (call before operations that need config)
|
|
*/
|
|
async ensureLoaded(): Promise<void> {
|
|
if (this.loaded) return;
|
|
await this.syncFromServer();
|
|
this.loaded = true;
|
|
}
|
|
|
|
/**
|
|
* Add a guardian — persists to server API (which sends invite email).
|
|
* Falls back to local-only if not authenticated.
|
|
*/
|
|
async addGuardian(guardian: Omit<Guardian, 'id' | 'addedAt'>): Promise<Guardian> {
|
|
if (!this.config) {
|
|
throw new Error('Recovery not initialized');
|
|
}
|
|
|
|
if (this.config.guardians.length >= 3) {
|
|
throw new Error('Maximum of 3 guardians allowed. Remove one first.');
|
|
}
|
|
|
|
const token = this.getAuthToken();
|
|
let serverId: string | undefined;
|
|
|
|
// POST to server — creates DB row + sends invite email
|
|
if (token) {
|
|
const res = await fetch(`${this.getApiBase()}/api/guardians`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify({
|
|
name: guardian.name,
|
|
email: guardian.contactEmail || undefined,
|
|
}),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ error: 'Server error' }));
|
|
throw new Error(err.error || `Server error: ${res.status}`);
|
|
}
|
|
|
|
const data = await res.json();
|
|
serverId = data.guardian?.id;
|
|
|
|
console.log('EncryptID: Guardian added via server', {
|
|
id: serverId,
|
|
inviteUrl: data.inviteUrl,
|
|
});
|
|
}
|
|
|
|
const newGuardian: Guardian = {
|
|
...guardian,
|
|
id: serverId || bufferToBase64url(crypto.getRandomValues(new Uint8Array(16)).buffer),
|
|
addedAt: Date.now(),
|
|
};
|
|
|
|
this.config.guardians.push(newGuardian);
|
|
this.config.guardianListHash = await this.hashGuardianList();
|
|
this.config.updatedAt = Date.now();
|
|
|
|
await this.saveLocalSettings();
|
|
|
|
console.log('EncryptID: Guardian added', {
|
|
type: GuardianType[guardian.type],
|
|
name: guardian.name,
|
|
});
|
|
|
|
return newGuardian;
|
|
}
|
|
|
|
/**
|
|
* Remove a guardian — deletes from server API and local config.
|
|
*/
|
|
async removeGuardian(guardianId: string): Promise<void> {
|
|
if (!this.config) {
|
|
throw new Error('Recovery not initialized');
|
|
}
|
|
|
|
const index = this.config.guardians.findIndex(g => g.id === guardianId);
|
|
if (index === -1) {
|
|
throw new Error('Guardian not found');
|
|
}
|
|
|
|
// DELETE from server
|
|
const token = this.getAuthToken();
|
|
if (token) {
|
|
const res = await fetch(`${this.getApiBase()}/api/guardians/${guardianId}`, {
|
|
method: 'DELETE',
|
|
headers: { 'Authorization': `Bearer ${token}` },
|
|
});
|
|
|
|
if (!res.ok && res.status !== 404) {
|
|
const err = await res.json().catch(() => ({ error: 'Server error' }));
|
|
throw new Error(err.error || `Server error: ${res.status}`);
|
|
}
|
|
}
|
|
|
|
this.config.guardians.splice(index, 1);
|
|
this.config.guardianListHash = await this.hashGuardianList();
|
|
this.config.updatedAt = Date.now();
|
|
|
|
await this.saveLocalSettings();
|
|
|
|
console.log('EncryptID: Guardian removed');
|
|
}
|
|
|
|
/**
|
|
* Update recovery threshold
|
|
*/
|
|
async setThreshold(threshold: number): Promise<void> {
|
|
if (!this.config) {
|
|
throw new Error('Recovery not initialized');
|
|
}
|
|
|
|
const totalWeight = this.config.guardians.reduce((sum, g) => sum + g.weight, 0);
|
|
if (threshold > totalWeight) {
|
|
throw new Error('Threshold cannot exceed total guardian weight');
|
|
}
|
|
|
|
if (threshold < 1) {
|
|
throw new Error('Threshold must be at least 1');
|
|
}
|
|
|
|
this.config.threshold = threshold;
|
|
this.config.updatedAt = Date.now();
|
|
|
|
await this.saveLocalSettings();
|
|
|
|
console.log('EncryptID: Threshold updated to', threshold);
|
|
}
|
|
|
|
/**
|
|
* Set recovery time-lock delay
|
|
*/
|
|
async setDelay(delaySeconds: number): Promise<void> {
|
|
if (!this.config) {
|
|
throw new Error('Recovery not initialized');
|
|
}
|
|
|
|
// Minimum 1 hour, maximum 7 days
|
|
if (delaySeconds < 3600 || delaySeconds > 7 * 24 * 3600) {
|
|
throw new Error('Delay must be between 1 hour and 7 days');
|
|
}
|
|
|
|
this.config.delaySeconds = delaySeconds;
|
|
this.config.updatedAt = Date.now();
|
|
|
|
await this.saveLocalSettings();
|
|
|
|
console.log('EncryptID: Delay updated to', delaySeconds, 'seconds');
|
|
}
|
|
|
|
/**
|
|
* Get current configuration
|
|
*/
|
|
getConfig(): RecoveryConfig | null {
|
|
return this.config;
|
|
}
|
|
|
|
/**
|
|
* Check if recovery is properly configured
|
|
*/
|
|
isConfigured(): boolean {
|
|
if (!this.config) return false;
|
|
|
|
const totalWeight = this.config.guardians.reduce((sum, g) => sum + g.weight, 0);
|
|
return totalWeight >= this.config.threshold;
|
|
}
|
|
|
|
/**
|
|
* Verify a guardian is still reachable/valid.
|
|
* Checks server for current guardian status (pending/accepted).
|
|
*/
|
|
async verifyGuardian(guardianId: string): Promise<boolean> {
|
|
if (!this.config) {
|
|
throw new Error('Recovery not initialized');
|
|
}
|
|
|
|
const guardian = this.config.guardians.find(g => g.id === guardianId);
|
|
if (!guardian) {
|
|
throw new Error('Guardian not found');
|
|
}
|
|
|
|
const token = this.getAuthToken();
|
|
if (token) {
|
|
// Fetch current guardian list from server to check status
|
|
const res = await fetch(`${this.getApiBase()}/api/guardians`, {
|
|
headers: { 'Authorization': `Bearer ${token}` },
|
|
});
|
|
|
|
if (res.ok) {
|
|
const data = await res.json();
|
|
const serverGuardian = data.guardians?.find((g: any) => g.id === guardianId);
|
|
|
|
if (!serverGuardian) {
|
|
throw new Error('Guardian not found on server — may have been removed');
|
|
}
|
|
|
|
// Check acceptance status
|
|
if (serverGuardian.status === 'accepted') {
|
|
guardian.lastVerified = Date.now();
|
|
await this.saveLocalSettings();
|
|
return true;
|
|
}
|
|
|
|
// Still pending — guardian hasn't accepted invite yet
|
|
if (serverGuardian.status === 'pending') {
|
|
console.log('EncryptID: Guardian invite still pending', { guardianId });
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback: mark as verified locally (offline mode)
|
|
guardian.lastVerified = Date.now();
|
|
await this.saveLocalSettings();
|
|
return true;
|
|
}
|
|
|
|
// ==========================================================================
|
|
// RECOVERY FLOW
|
|
// ==========================================================================
|
|
|
|
/**
|
|
* Initiate account recovery
|
|
*
|
|
* This starts the recovery process. Guardians must approve,
|
|
* and there's a time-lock before recovery completes.
|
|
*/
|
|
async initiateRecovery(newCredentialId: string): Promise<RecoveryRequest> {
|
|
if (!this.config) {
|
|
throw new Error('Recovery not configured');
|
|
}
|
|
|
|
if (this.activeRequest && this.activeRequest.status === 'pending') {
|
|
throw new Error('Recovery already in progress');
|
|
}
|
|
|
|
const now = Date.now();
|
|
|
|
this.activeRequest = {
|
|
id: bufferToBase64url(crypto.getRandomValues(new Uint8Array(16)).buffer),
|
|
accountDID: '', // Would be set from current session
|
|
newCredentialId,
|
|
initiatedAt: now,
|
|
completesAt: now + this.config.delaySeconds * 1000,
|
|
status: 'pending',
|
|
approvals: [],
|
|
approvalWeight: 0,
|
|
};
|
|
|
|
// Notify user through all available channels
|
|
await this.notifyUser('recovery_initiated', this.activeRequest);
|
|
|
|
// Notify guardians
|
|
await this.notifyGuardians('recovery_requested', this.activeRequest);
|
|
|
|
console.log('EncryptID: Recovery initiated', {
|
|
requestId: this.activeRequest.id,
|
|
completesAt: new Date(this.activeRequest.completesAt).toISOString(),
|
|
});
|
|
|
|
return this.activeRequest;
|
|
}
|
|
|
|
/**
|
|
* Guardian approves a recovery request
|
|
*/
|
|
async approveRecovery(
|
|
guardianId: string,
|
|
signature: string
|
|
): Promise<RecoveryRequest> {
|
|
if (!this.activeRequest || this.activeRequest.status !== 'pending') {
|
|
throw new Error('No pending recovery request');
|
|
}
|
|
|
|
if (!this.config) {
|
|
throw new Error('Recovery not configured');
|
|
}
|
|
|
|
const guardian = this.config.guardians.find(g => g.id === guardianId);
|
|
if (!guardian) {
|
|
throw new Error('Guardian not found');
|
|
}
|
|
|
|
// Check if guardian already approved
|
|
if (this.activeRequest.approvals.some(a => a.guardianId === guardianId)) {
|
|
throw new Error('Guardian already approved');
|
|
}
|
|
|
|
// Add approval
|
|
this.activeRequest.approvals.push({
|
|
guardianId,
|
|
approvedAt: Date.now(),
|
|
signature,
|
|
});
|
|
|
|
this.activeRequest.approvalWeight += guardian.weight;
|
|
|
|
// Check if threshold reached
|
|
if (this.activeRequest.approvalWeight >= this.config.threshold) {
|
|
this.activeRequest.status = 'approved';
|
|
await this.notifyUser('recovery_approved', this.activeRequest);
|
|
}
|
|
|
|
console.log('EncryptID: Guardian approved recovery', {
|
|
guardianId,
|
|
weight: guardian.weight,
|
|
totalWeight: this.activeRequest.approvalWeight,
|
|
threshold: this.config.threshold,
|
|
});
|
|
|
|
return this.activeRequest;
|
|
}
|
|
|
|
/**
|
|
* Cancel an active recovery request
|
|
*
|
|
* User can cancel if they still have access to any valid authenticator.
|
|
*/
|
|
async cancelRecovery(): Promise<void> {
|
|
if (!this.activeRequest || this.activeRequest.status !== 'pending') {
|
|
throw new Error('No pending recovery request to cancel');
|
|
}
|
|
|
|
this.activeRequest.status = 'cancelled';
|
|
|
|
// Notify guardians
|
|
await this.notifyGuardians('recovery_cancelled', this.activeRequest);
|
|
|
|
console.log('EncryptID: Recovery cancelled');
|
|
|
|
this.activeRequest = null;
|
|
}
|
|
|
|
/**
|
|
* Complete the recovery process
|
|
*
|
|
* Can only be called after time-lock expires and threshold is met.
|
|
*/
|
|
async completeRecovery(): Promise<void> {
|
|
if (!this.activeRequest) {
|
|
throw new Error('No recovery request');
|
|
}
|
|
|
|
if (this.activeRequest.status !== 'approved') {
|
|
throw new Error('Recovery not approved');
|
|
}
|
|
|
|
if (Date.now() < this.activeRequest.completesAt) {
|
|
const remaining = this.activeRequest.completesAt - Date.now();
|
|
throw new Error(`Time-lock not expired. ${Math.ceil(remaining / 1000 / 60)} minutes remaining.`);
|
|
}
|
|
|
|
// In production, this would:
|
|
// 1. Rotate the account owner to the new credential
|
|
// 2. Invalidate old credentials
|
|
// 3. Update on-chain state (for AA wallet)
|
|
|
|
this.activeRequest.status = 'completed';
|
|
|
|
console.log('EncryptID: Recovery completed successfully');
|
|
|
|
// Notify user
|
|
await this.notifyUser('recovery_completed', this.activeRequest);
|
|
|
|
this.activeRequest = null;
|
|
}
|
|
|
|
/**
|
|
* Get active recovery request
|
|
*/
|
|
getActiveRequest(): RecoveryRequest | null {
|
|
return this.activeRequest;
|
|
}
|
|
|
|
// ==========================================================================
|
|
// PRIVATE METHODS
|
|
// ==========================================================================
|
|
|
|
/**
|
|
* Hash guardian list for privacy-preserving on-chain storage
|
|
*/
|
|
private async hashGuardianList(): Promise<string> {
|
|
if (!this.config) return '';
|
|
|
|
// Sort guardian IDs for deterministic hash
|
|
const sortedIds = this.config.guardians
|
|
.map(g => g.id)
|
|
.sort()
|
|
.join(',');
|
|
|
|
const encoder = new TextEncoder();
|
|
const hash = await crypto.subtle.digest('SHA-256', encoder.encode(sortedIds));
|
|
|
|
return bufferToBase64url(hash);
|
|
}
|
|
|
|
/**
|
|
* Save local settings (threshold, delay) to localStorage.
|
|
* Guardian CRUD goes through the server API — this only persists
|
|
* config that the server doesn't track (threshold, delay, type metadata).
|
|
*/
|
|
private async saveLocalSettings(): Promise<void> {
|
|
if (!this.config) return;
|
|
|
|
try {
|
|
localStorage.setItem('encryptid_recovery', JSON.stringify(this.config));
|
|
} catch (error) {
|
|
console.warn('EncryptID: Failed to save recovery config', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load local settings from localStorage (synchronous, for constructor).
|
|
* Call syncFromServer() after construction for full server sync.
|
|
*/
|
|
private loadLocalSettings(): void {
|
|
try {
|
|
const stored = localStorage.getItem('encryptid_recovery');
|
|
if (stored) {
|
|
this.config = JSON.parse(stored);
|
|
}
|
|
} catch (error) {
|
|
console.warn('EncryptID: Failed to load recovery config', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sync guardian list from server API into local config.
|
|
* Maps server guardian model to client Guardian type.
|
|
*/
|
|
private async syncFromServer(): Promise<void> {
|
|
const token = this.getAuthToken();
|
|
if (!token) return;
|
|
|
|
try {
|
|
const res = await fetch(`${this.getApiBase()}/api/guardians`, {
|
|
headers: { 'Authorization': `Bearer ${token}` },
|
|
});
|
|
|
|
if (!res.ok) return;
|
|
|
|
const data = await res.json();
|
|
if (!data.guardians || !Array.isArray(data.guardians)) return;
|
|
|
|
if (!this.config) {
|
|
this.config = {
|
|
threshold: data.threshold || 2,
|
|
delaySeconds: 48 * 60 * 60,
|
|
guardians: [],
|
|
guardianListHash: '',
|
|
updatedAt: Date.now(),
|
|
};
|
|
}
|
|
|
|
// Build a map of existing local guardians for metadata preservation
|
|
const localMap = new Map(this.config.guardians.map(g => [g.id, g]));
|
|
|
|
// Merge server guardians with local type metadata
|
|
this.config.guardians = data.guardians.map((sg: any) => {
|
|
const local = localMap.get(sg.id);
|
|
return {
|
|
id: sg.id,
|
|
type: local?.type ?? GuardianType.TRUSTED_CONTACT,
|
|
name: sg.name,
|
|
weight: local?.weight ?? 1,
|
|
contactEmail: sg.email || local?.contactEmail,
|
|
addedAt: sg.createdAt ? new Date(sg.createdAt).getTime() : (local?.addedAt ?? Date.now()),
|
|
lastVerified: sg.status === 'accepted' ? (sg.acceptedAt ? new Date(sg.acceptedAt).getTime() : Date.now()) : local?.lastVerified,
|
|
// Preserve type-specific fields from local
|
|
credentialId: local?.credentialId,
|
|
contactDID: local?.contactDID,
|
|
serviceUrl: local?.serviceUrl,
|
|
delaySeconds: local?.delaySeconds,
|
|
} satisfies Guardian;
|
|
});
|
|
|
|
this.config.guardianListHash = await this.hashGuardianList();
|
|
this.config.updatedAt = Date.now();
|
|
|
|
await this.saveLocalSettings();
|
|
|
|
console.log('EncryptID: Synced guardians from server', {
|
|
count: this.config.guardians.length,
|
|
});
|
|
} catch (error) {
|
|
console.warn('EncryptID: Failed to sync guardians from server', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notify user of recovery events via CustomEvent on document.
|
|
*/
|
|
private async notifyUser(
|
|
event: string,
|
|
request: RecoveryRequest
|
|
): Promise<void> {
|
|
console.log('EncryptID: User notification', { event, requestId: request.id });
|
|
|
|
if (typeof document !== 'undefined') {
|
|
document.dispatchEvent(new CustomEvent('encryptid:recovery', {
|
|
detail: { event, requestId: request.id, status: request.status },
|
|
}));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Notify guardians of recovery events via CustomEvent on document.
|
|
* Server-side email notifications are handled by the guardian invite flow.
|
|
*/
|
|
private async notifyGuardians(
|
|
event: string,
|
|
request: RecoveryRequest
|
|
): Promise<void> {
|
|
if (!this.config) return;
|
|
|
|
console.log('EncryptID: Guardian notification', {
|
|
event,
|
|
guardianCount: this.config.guardians.length,
|
|
});
|
|
|
|
if (typeof document !== 'undefined') {
|
|
document.dispatchEvent(new CustomEvent('encryptid:guardian-notification', {
|
|
detail: {
|
|
event,
|
|
requestId: request.id,
|
|
guardianIds: this.config.guardians.map(g => g.id),
|
|
},
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// SINGLETON INSTANCE
|
|
// ============================================================================
|
|
|
|
let recoveryManagerInstance: RecoveryManager | null = null;
|
|
|
|
/**
|
|
* Get the global recovery manager instance
|
|
*/
|
|
export function getRecoveryManager(): RecoveryManager {
|
|
if (!recoveryManagerInstance) {
|
|
recoveryManagerInstance = new RecoveryManager();
|
|
}
|
|
return recoveryManagerInstance;
|
|
}
|
|
|
|
// ============================================================================
|
|
// GUARDIAN TYPE METADATA
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Get display information for guardian types
|
|
*/
|
|
export function getGuardianTypeInfo(type: GuardianType): {
|
|
name: string;
|
|
description: string;
|
|
icon: string;
|
|
setupInstructions: string;
|
|
} {
|
|
switch (type) {
|
|
case GuardianType.SECONDARY_PASSKEY:
|
|
return {
|
|
name: 'Backup Passkey',
|
|
description: 'Another device you own (phone, YubiKey, etc.)',
|
|
icon: 'key',
|
|
setupInstructions: 'Register a passkey on a second device you control. Store it securely as a backup.',
|
|
};
|
|
|
|
case GuardianType.TRUSTED_CONTACT:
|
|
return {
|
|
name: 'Trusted Contact',
|
|
description: 'A friend or family member with their own EncryptID',
|
|
icon: 'user',
|
|
setupInstructions: 'Ask a trusted person to create an EncryptID account. They can help recover your account if needed.',
|
|
};
|
|
|
|
case GuardianType.HARDWARE_KEY:
|
|
return {
|
|
name: 'Hardware Security Key',
|
|
description: 'A YubiKey or similar device stored offline',
|
|
icon: 'shield',
|
|
setupInstructions: 'Register a hardware security key and store it in a safe place (e.g., safe deposit box).',
|
|
};
|
|
|
|
case GuardianType.INSTITUTIONAL:
|
|
return {
|
|
name: 'Recovery Service',
|
|
description: 'A professional recovery service provider',
|
|
icon: 'building',
|
|
setupInstructions: 'Connect with a trusted recovery service that can help verify your identity.',
|
|
};
|
|
|
|
case GuardianType.TIME_DELAYED_SELF:
|
|
return {
|
|
name: 'Time-Delayed Self',
|
|
description: 'Recover yourself after a waiting period',
|
|
icon: 'clock',
|
|
setupInstructions: 'Set up a recovery option that requires waiting (e.g., 7 days) before completing.',
|
|
};
|
|
|
|
default:
|
|
return {
|
|
name: 'Unknown',
|
|
description: 'Unknown guardian type',
|
|
icon: 'question',
|
|
setupInstructions: '',
|
|
};
|
|
}
|
|
}
|