@oxyhq/services 5.16.37 → 5.16.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/core/mixins/OxyServices.auth.js +10 -3
- package/lib/commonjs/core/mixins/OxyServices.auth.js.map +1 -1
- package/lib/commonjs/index.js +0 -7
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +84 -14
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +93 -37
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/stores/transferStore.js +4 -0
- package/lib/commonjs/ui/stores/transferStore.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.auth.js +10 -3
- package/lib/module/core/mixins/OxyServices.auth.js.map +1 -1
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +85 -15
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +93 -37
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/stores/transferStore.js +4 -0
- package/lib/module/ui/stores/transferStore.js.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.auth.d.ts +2 -1
- package/lib/typescript/core/mixins/OxyServices.auth.d.ts.map +1 -1
- package/lib/typescript/core/mixins/index.d.ts +1 -1
- package/lib/typescript/index.d.ts +2 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +3 -3
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts +2 -2
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/ui/stores/transferStore.d.ts +1 -0
- package/lib/typescript/ui/stores/transferStore.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/mixins/OxyServices.auth.ts +12 -3
- package/src/index.ts +1 -1
- package/src/ui/context/OxyContext.tsx +84 -17
- package/src/ui/context/hooks/useAuthOperations.ts +89 -38
- package/src/ui/stores/transferStore.ts +6 -0
|
@@ -34,7 +34,7 @@ export interface UseAuthOperationsOptions {
|
|
|
34
34
|
|
|
35
35
|
export interface UseAuthOperationsResult {
|
|
36
36
|
/** Create a new identity locally (offline-first) and optionally sync with server */
|
|
37
|
-
createIdentity: () => Promise<{ synced: boolean }>;
|
|
37
|
+
createIdentity: (username?: string) => Promise<{ synced: boolean }>;
|
|
38
38
|
/** Import an existing identity from backup file data */
|
|
39
39
|
importIdentity: (backupData: BackupData, password: string) => Promise<{ synced: boolean }>;
|
|
40
40
|
/** Sign in with existing identity on device */
|
|
@@ -50,7 +50,7 @@ export interface UseAuthOperationsResult {
|
|
|
50
50
|
/** Check if identity is synced with server */
|
|
51
51
|
isIdentitySynced: () => Promise<boolean>;
|
|
52
52
|
/** Sync local identity with server (when online) */
|
|
53
|
-
syncIdentity: () => Promise<User>;
|
|
53
|
+
syncIdentity: (username?: string) => Promise<User>;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const LOGIN_ERROR_CODE = 'LOGIN_ERROR';
|
|
@@ -91,13 +91,28 @@ export const useAuthOperations = ({
|
|
|
91
91
|
const clearSessionsIfIdentityChanged = useCallback(
|
|
92
92
|
async (oldPublicKey: string | null, newPublicKey: string): Promise<void> => {
|
|
93
93
|
if (oldPublicKey && oldPublicKey !== newPublicKey) {
|
|
94
|
+
if (__DEV__ && logger) {
|
|
95
|
+
logger('CRITICAL: Identity changed - clearing all session data', {
|
|
96
|
+
oldPublicKey: oldPublicKey.substring(0, 16) + '...',
|
|
97
|
+
newPublicKey: newPublicKey.substring(0, 16) + '...',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
94
101
|
// Clear all session state to prevent old identity's data from showing up
|
|
95
102
|
await clearSessionState();
|
|
96
|
-
|
|
103
|
+
|
|
104
|
+
// Logout from auth store (clears user, isAuthenticated, etc.)
|
|
97
105
|
logoutStore();
|
|
106
|
+
|
|
107
|
+
// Force KeyManager cache invalidation
|
|
108
|
+
KeyManager.invalidateCache();
|
|
109
|
+
|
|
110
|
+
if (__DEV__ && logger) {
|
|
111
|
+
logger('Session state cleared for new identity');
|
|
112
|
+
}
|
|
98
113
|
}
|
|
99
114
|
},
|
|
100
|
-
[clearSessionState, logoutStore]
|
|
115
|
+
[clearSessionState, logoutStore, logger]
|
|
101
116
|
);
|
|
102
117
|
|
|
103
118
|
/**
|
|
@@ -300,9 +315,14 @@ export const useAuthOperations = ({
|
|
|
300
315
|
/**
|
|
301
316
|
* Create a new identity (offline-first)
|
|
302
317
|
* Identity is purely cryptographic - no username or email required
|
|
318
|
+
*
|
|
319
|
+
* This function generates keys locally and does NOT register with the server yet.
|
|
320
|
+
* Registration will happen during syncIdentity() or when username is provided.
|
|
321
|
+
*
|
|
322
|
+
* @param username - Optional username to set during registration (if online)
|
|
303
323
|
*/
|
|
304
324
|
const createIdentity = useCallback(
|
|
305
|
-
async (): Promise<{ synced: boolean }> => {
|
|
325
|
+
async (username?: string): Promise<{ synced: boolean }> => {
|
|
306
326
|
if (!storage) throw new Error('Storage not initialized');
|
|
307
327
|
|
|
308
328
|
setAuthState({ isLoading: true, error: null });
|
|
@@ -311,40 +331,61 @@ export const useAuthOperations = ({
|
|
|
311
331
|
// CRITICAL: Get old public key before creating new identity
|
|
312
332
|
// If identity changes, we must clear all session data to prevent data leakage
|
|
313
333
|
const oldPublicKey = await KeyManager.getPublicKey().catch(() => null);
|
|
334
|
+
|
|
335
|
+
if (__DEV__ && logger) {
|
|
336
|
+
logger('Creating new identity', { hadPreviousIdentity: !!oldPublicKey });
|
|
337
|
+
}
|
|
314
338
|
|
|
315
339
|
// Generate new key pair directly (works offline)
|
|
316
340
|
const { publicKey, privateKey } = await KeyManager.generateKeyPair();
|
|
317
341
|
await KeyManager.importKeyPair(privateKey);
|
|
342
|
+
|
|
343
|
+
if (__DEV__ && logger) {
|
|
344
|
+
logger('Identity keys generated', { publicKey: publicKey.substring(0, 16) + '...' });
|
|
345
|
+
}
|
|
318
346
|
|
|
319
347
|
// Clear sessions if identity changed (prevents data leakage)
|
|
320
348
|
await clearSessionsIfIdentityChanged(oldPublicKey, publicKey);
|
|
321
349
|
|
|
322
|
-
// Mark as not synced
|
|
350
|
+
// Mark as not synced initially
|
|
323
351
|
await storage.setItem('oxy_identity_synced', 'false');
|
|
324
352
|
setIdentitySynced(false);
|
|
325
353
|
|
|
326
|
-
//
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
354
|
+
// If username provided, try to register immediately (online only)
|
|
355
|
+
if (username) {
|
|
356
|
+
// Validate username format before attempting registration
|
|
357
|
+
const trimmedUsername = username.trim();
|
|
358
|
+
if (trimmedUsername && /^[a-zA-Z0-9]{3,30}$/.test(trimmedUsername)) {
|
|
359
|
+
try {
|
|
360
|
+
const { signature, timestamp } = await SignatureService.createRegistrationSignature();
|
|
361
|
+
await oxyServices.register(publicKey, signature, timestamp, trimmedUsername);
|
|
362
|
+
|
|
363
|
+
// Mark as synced (Zustand store + storage)
|
|
364
|
+
await storage.setItem('oxy_identity_synced', 'true');
|
|
365
|
+
setIdentitySynced(true);
|
|
366
|
+
|
|
367
|
+
if (__DEV__ && logger) {
|
|
368
|
+
logger('Identity synced with server successfully with username');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return { synced: true };
|
|
372
|
+
} catch (syncError) {
|
|
373
|
+
// Offline or server error - identity created locally but not synced
|
|
374
|
+
if (__DEV__ && logger) {
|
|
375
|
+
logger('Identity created locally with username (offline), will sync when online', syncError);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return { synced: false };
|
|
379
|
+
}
|
|
342
380
|
}
|
|
343
|
-
|
|
344
|
-
return {
|
|
345
|
-
synced: false,
|
|
346
|
-
};
|
|
347
381
|
}
|
|
382
|
+
|
|
383
|
+
// No username provided - defer registration until later
|
|
384
|
+
if (__DEV__ && logger) {
|
|
385
|
+
logger('Identity created locally without username, will register during sync');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return { synced: false };
|
|
348
389
|
} catch (error) {
|
|
349
390
|
// CRITICAL: Never delete identity on error - it may have been successfully created
|
|
350
391
|
// Only log the error and let the user recover using their backup file
|
|
@@ -360,7 +401,7 @@ export const useAuthOperations = ({
|
|
|
360
401
|
await storage.setItem('oxy_identity_synced', 'false').catch(() => {});
|
|
361
402
|
setIdentitySynced(false);
|
|
362
403
|
if (__DEV__ && logger) {
|
|
363
|
-
logger('Identity was created but sync failed - user can sync later
|
|
404
|
+
logger('Identity was created but sync failed - user can sync later');
|
|
364
405
|
}
|
|
365
406
|
} else {
|
|
366
407
|
// No identity exists - this was a generation failure, safe to clean up sync flag
|
|
@@ -398,9 +439,11 @@ export const useAuthOperations = ({
|
|
|
398
439
|
/**
|
|
399
440
|
* Sync local identity with server (call when online)
|
|
400
441
|
* TanStack Query handles offline mutations automatically
|
|
442
|
+
*
|
|
443
|
+
* @param username - Optional username to set during sync
|
|
401
444
|
*/
|
|
402
445
|
const syncIdentity = useCallback(
|
|
403
|
-
async (): Promise<User> => {
|
|
446
|
+
async (username?: string): Promise<User> => {
|
|
404
447
|
if (!storage) throw new Error('Storage not initialized');
|
|
405
448
|
|
|
406
449
|
setAuthState({ isLoading: true, error: null });
|
|
@@ -425,7 +468,7 @@ export const useAuthOperations = ({
|
|
|
425
468
|
if (!registered) {
|
|
426
469
|
// Register with server (identity is just the publicKey)
|
|
427
470
|
const { signature, timestamp } = await SignatureService.createRegistrationSignature();
|
|
428
|
-
await oxyServices.register(publicKey, signature, timestamp);
|
|
471
|
+
await oxyServices.register(publicKey, signature, timestamp, username);
|
|
429
472
|
}
|
|
430
473
|
|
|
431
474
|
// Mark as synced (Zustand store + storage)
|
|
@@ -435,13 +478,6 @@ export const useAuthOperations = ({
|
|
|
435
478
|
// Sign in
|
|
436
479
|
const user = await performSignIn(publicKey);
|
|
437
480
|
|
|
438
|
-
// Check if user has username - required for syncing
|
|
439
|
-
if (!user.username) {
|
|
440
|
-
const usernameError = new Error('USERNAME_REQUIRED');
|
|
441
|
-
(usernameError as any).code = 'USERNAME_REQUIRED';
|
|
442
|
-
throw usernameError;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
481
|
// TanStack Query will automatically retry any pending mutations
|
|
446
482
|
|
|
447
483
|
return user;
|
|
@@ -465,9 +501,13 @@ export const useAuthOperations = ({
|
|
|
465
501
|
|
|
466
502
|
/**
|
|
467
503
|
* Import identity from backup file data (offline-first)
|
|
504
|
+
*
|
|
505
|
+
* @param backupData - The backup data to import
|
|
506
|
+
* @param password - Password to decrypt the backup
|
|
507
|
+
* @param username - Optional username to set during registration
|
|
468
508
|
*/
|
|
469
509
|
const importIdentity = useCallback(
|
|
470
|
-
async (backupData: BackupData, password: string): Promise<{ synced: boolean }> => {
|
|
510
|
+
async (backupData: BackupData, password: string, username?: string): Promise<{ synced: boolean }> => {
|
|
471
511
|
if (!storage) throw new Error('Storage not initialized');
|
|
472
512
|
|
|
473
513
|
// Validate arguments - ensure backupData is an object, not a string (old signature)
|
|
@@ -489,6 +529,13 @@ export const useAuthOperations = ({
|
|
|
489
529
|
// CRITICAL: Get old public key before importing new identity
|
|
490
530
|
// If identity changes, we must clear all session data to prevent data leakage
|
|
491
531
|
const oldPublicKey = await KeyManager.getPublicKey().catch(() => null);
|
|
532
|
+
|
|
533
|
+
if (__DEV__ && logger) {
|
|
534
|
+
logger('Importing identity from backup', {
|
|
535
|
+
hadPreviousIdentity: !!oldPublicKey,
|
|
536
|
+
backupPublicKey: backupData.publicKey.substring(0, 16) + '...'
|
|
537
|
+
});
|
|
538
|
+
}
|
|
492
539
|
|
|
493
540
|
// Decrypt private key from backup data
|
|
494
541
|
const Crypto = await import('expo-crypto');
|
|
@@ -525,6 +572,10 @@ export const useAuthOperations = ({
|
|
|
525
572
|
|
|
526
573
|
// Import the key pair
|
|
527
574
|
const publicKey = await KeyManager.importKeyPair(privateKey);
|
|
575
|
+
|
|
576
|
+
if (__DEV__ && logger) {
|
|
577
|
+
logger('Identity keys imported', { publicKey: publicKey.substring(0, 16) + '...' });
|
|
578
|
+
}
|
|
528
579
|
|
|
529
580
|
// Verify public key matches
|
|
530
581
|
if (publicKey !== backupData.publicKey) {
|
|
@@ -551,7 +602,7 @@ export const useAuthOperations = ({
|
|
|
551
602
|
} else {
|
|
552
603
|
// Need to register this identity (identity is just the publicKey)
|
|
553
604
|
const { signature, timestamp } = await SignatureService.createRegistrationSignature();
|
|
554
|
-
await oxyServices.register(publicKey, signature, timestamp);
|
|
605
|
+
await oxyServices.register(publicKey, signature, timestamp, username);
|
|
555
606
|
|
|
556
607
|
await storage.setItem('oxy_identity_synced', 'true');
|
|
557
608
|
setIdentitySynced(true);
|
|
@@ -31,6 +31,7 @@ export interface TransferState {
|
|
|
31
31
|
markRestored: () => void;
|
|
32
32
|
cleanupExpired: () => void;
|
|
33
33
|
reset: () => void;
|
|
34
|
+
clearAll: () => void; // Alias for reset - use when clearing on identity change for semantic clarity
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
const FIFTEEN_MINUTES = 15 * 60 * 1000;
|
|
@@ -178,6 +179,11 @@ export const useTransferStore = create<TransferState>((set, get) => ({
|
|
|
178
179
|
reset: () => {
|
|
179
180
|
set(initialState);
|
|
180
181
|
},
|
|
182
|
+
|
|
183
|
+
clearAll: () => {
|
|
184
|
+
// Delegate to reset to maintain single source of truth
|
|
185
|
+
get().reset();
|
|
186
|
+
},
|
|
181
187
|
}));
|
|
182
188
|
|
|
183
189
|
/**
|