-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathkey-sync-service.js
More file actions
247 lines (210 loc) Β· 7.15 KB
/
key-sync-service.js
File metadata and controls
247 lines (210 loc) Β· 7.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/**
* @fileoverview Key synchronization service for QryptChat
* Handles syncing client-side generated public keys to the database
*/
import { browser } from '$app/environment';
import { postQuantumEncryption } from './post-quantum-encryption.js';
/**
* Service for synchronizing public keys between client and server
*/
export class KeySyncService {
constructor() {
this.syncInProgress = false;
this.lastSyncAttempt = null;
this.maxRetries = 3;
this.retryDelay = 1000; // 1 second
}
/**
* Sync user's public key to the database
* @param {boolean} force - Force sync even if recently attempted
* @returns {Promise<{success: boolean, error?: string}>}
*/
async syncPublicKey(force = false) {
if (!browser) {
return { success: false, error: 'Not in browser environment' };
}
// Prevent concurrent sync attempts
if (this.syncInProgress && !force) {
console.log('π Key sync already in progress, skipping');
return { success: false, error: 'Sync already in progress' };
}
// Rate limiting - don't sync more than once per minute unless forced
const now = Date.now();
if (!force && this.lastSyncAttempt && (now - this.lastSyncAttempt) < 60000) {
console.log('π Key sync rate limited, skipping');
return { success: false, error: 'Rate limited' };
}
this.syncInProgress = true;
this.lastSyncAttempt = now;
try {
// Ensure post-quantum encryption is initialized
if (!postQuantumEncryption.isInitialized) {
console.log('π Initializing post-quantum encryption for key sync');
await postQuantumEncryption.initialize();
}
// Get the user's public key
const publicKey = await postQuantumEncryption.getPublicKey();
if (!publicKey) {
throw new Error('No public key available to sync');
}
console.log('π Syncing public key to database...');
// Sync to database with retry logic
let lastError = null;
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
const response = await fetch('/api/crypto/public-keys', {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include', // Include cookies for authentication
body: JSON.stringify({
public_key: publicKey,
key_type: 'ML-KEM-1024'
})
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `HTTP ${response.status}`);
}
const result = await response.json();
console.log('π β
Public key synced successfully:', result.message);
this.syncInProgress = false;
return { success: true };
} catch (error) {
lastError = error;
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`π Key sync attempt ${attempt}/${this.maxRetries} failed:`, errorMessage);
if (attempt < this.maxRetries) {
// Wait before retrying
await new Promise(resolve => setTimeout(resolve, this.retryDelay * attempt));
}
}
}
// All attempts failed
throw lastError;
} catch (error) {
console.error('π β Failed to sync public key:', error);
this.syncInProgress = false;
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error during key sync'
};
}
}
/**
* Check if user needs key sync (has keys locally but not in database)
* @returns {Promise<boolean>}
*/
async needsKeySync() {
if (!browser) return false;
try {
// Check if we have local keys
if (!postQuantumEncryption.isInitialized) {
await postQuantumEncryption.initialize();
}
const publicKey = await postQuantumEncryption.getPublicKey();
if (!publicKey) {
console.log('π No local public key found, sync not needed');
return false;
}
// Check if key exists in database by trying to fetch it
// This is a simple check - if the API returns our own key, it's synced
const response = await fetch('/api/crypto/public-keys/all', {
method: 'GET',
credentials: 'include'
});
if (!response.ok) {
console.log('π Cannot check database keys, assuming sync needed');
return true;
}
const data = await response.json();
const currentUserId = this.getCurrentUserId();
if (!currentUserId) {
console.log('π No current user ID, cannot determine sync status');
return false;
}
// Check if our key exists in the database
const hasKeyInDb = data.public_keys && data.public_keys[currentUserId];
if (!hasKeyInDb) {
console.log('π Public key not found in database, sync needed');
return true;
}
console.log('π Public key already synced to database');
return false;
} catch (error) {
console.error('π Error checking key sync status:', error);
// If we can't check, assume sync is needed to be safe
return true;
}
}
/**
* Get current user ID from localStorage
* @returns {string|null}
* @private
*/
getCurrentUserId() {
try {
const storedUser = localStorage.getItem('qrypt_user');
if (storedUser) {
const user = JSON.parse(storedUser);
return user.id;
}
} catch (error) {
console.error('π Error getting current user ID:', error);
}
return null;
}
/**
* Auto-sync keys on login if needed
* This handles both scenarios:
* 1. User has local keys but not in database -> sync to database
* 2. User has keys in database but not locally -> pull from database (future feature)
* @returns {Promise<{success: boolean, error?: string}>}
*/
async autoSyncOnLogin() {
try {
console.log('π Checking if auto-sync is needed on login...');
// Check if we have local keys
if (!postQuantumEncryption.isInitialized) {
await postQuantumEncryption.initialize();
}
const localPublicKey = await postQuantumEncryption.getPublicKey();
const hasLocalKeys = !!localPublicKey;
console.log('π Local keys status:', { hasLocalKeys });
if (hasLocalKeys) {
// User has local keys - check if they need to be synced to database
const needsSync = await this.needsKeySync();
if (needsSync) {
console.log('π Auto-syncing local keys to database...');
return await this.syncPublicKey(false);
} else {
console.log('π Local keys already synced to database');
return { success: true };
}
} else {
// User has no local keys - this could be a new browser
// In the future, we could pull keys from database here
// For now, just log and let the user generate new keys if needed
console.log('π No local keys found - user may need to generate keys or this is a new browser');
console.log('π Note: Key pulling from database is not yet implemented');
return { success: true };
}
} catch (error) {
console.error('π Error during auto-sync on login:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Auto-sync failed'
};
}
}
/**
* Force sync public key (ignores rate limiting)
* @returns {Promise<{success: boolean, error?: string}>}
*/
async forceSyncPublicKey() {
return await this.syncPublicKey(true);
}
}
// Create and export singleton instance
export const keySyncService = new KeySyncService();