@oxyhq/services 5.15.8 → 5.16.0
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/OxyServices.js +0 -1
- package/lib/commonjs/core/OxyServices.js.map +1 -1
- package/lib/commonjs/core/mixins/OxyServices.auth.js +3 -6
- package/lib/commonjs/core/mixins/OxyServices.auth.js.map +1 -1
- package/lib/commonjs/core/mixins/OxyServices.devices.js +1 -1
- package/lib/commonjs/core/mixins/OxyServices.devices.js.map +1 -1
- package/lib/commonjs/core/mixins/index.js +11 -12
- package/lib/commonjs/core/mixins/index.js.map +1 -1
- package/lib/commonjs/crypto/signatureService.js +3 -2
- package/lib/commonjs/crypto/signatureService.js.map +1 -1
- package/lib/commonjs/i18n/locales/ar-SA.json +1 -9
- package/lib/commonjs/i18n/locales/ca-ES.json +1 -9
- package/lib/commonjs/i18n/locales/de-DE.json +1 -9
- package/lib/commonjs/i18n/locales/en-US.json +3 -21
- package/lib/commonjs/i18n/locales/es-ES.json +3 -21
- package/lib/commonjs/i18n/locales/fr-FR.json +1 -9
- package/lib/commonjs/i18n/locales/it-IT.json +1 -9
- package/lib/commonjs/i18n/locales/ja-JP.json +1 -9
- package/lib/commonjs/i18n/locales/ko-KR.json +1 -9
- package/lib/commonjs/i18n/locales/pt-PT.json +1 -9
- package/lib/commonjs/i18n/locales/zh-CN.json +1 -9
- package/lib/commonjs/ui/context/OxyContext.js +24 -4
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +217 -100
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +2 -319
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +178 -77
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +0 -1
- package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SessionManagementScreen.js +43 -29
- package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/stores/authStore.js +14 -1
- package/lib/commonjs/ui/stores/authStore.js.map +1 -1
- package/lib/module/core/OxyServices.js +0 -1
- package/lib/module/core/OxyServices.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.auth.js +3 -6
- package/lib/module/core/mixins/OxyServices.auth.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.devices.js +1 -1
- package/lib/module/core/mixins/OxyServices.devices.js.map +1 -1
- package/lib/module/core/mixins/index.js +1 -2
- package/lib/module/core/mixins/index.js.map +1 -1
- package/lib/module/crypto/signatureService.js +3 -2
- package/lib/module/crypto/signatureService.js.map +1 -1
- package/lib/module/i18n/locales/ar-SA.json +1 -9
- package/lib/module/i18n/locales/ca-ES.json +1 -9
- package/lib/module/i18n/locales/de-DE.json +1 -9
- package/lib/module/i18n/locales/en-US.json +3 -21
- package/lib/module/i18n/locales/es-ES.json +3 -21
- package/lib/module/i18n/locales/fr-FR.json +1 -9
- package/lib/module/i18n/locales/it-IT.json +1 -9
- package/lib/module/i18n/locales/ja-JP.json +1 -9
- package/lib/module/i18n/locales/ko-KR.json +1 -9
- package/lib/module/i18n/locales/pt-PT.json +1 -9
- package/lib/module/i18n/locales/zh-CN.json +1 -9
- package/lib/module/ui/context/OxyContext.js +24 -4
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +217 -100
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +2 -319
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +179 -78
- package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/module/ui/screens/PrivacySettingsScreen.js +0 -1
- package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/SessionManagementScreen.js +44 -29
- package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/module/ui/stores/authStore.js +14 -1
- package/lib/module/ui/stores/authStore.js.map +1 -1
- package/lib/typescript/core/OxyServices.d.ts +0 -1
- package/lib/typescript/core/OxyServices.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.auth.d.ts +3 -4
- package/lib/typescript/core/mixins/OxyServices.auth.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.devices.d.ts +1 -4
- package/lib/typescript/core/mixins/OxyServices.devices.d.ts.map +1 -1
- package/lib/typescript/core/mixins/index.d.ts +1 -64
- package/lib/typescript/core/mixins/index.d.ts.map +1 -1
- package/lib/typescript/crypto/signatureService.d.ts +2 -1
- package/lib/typescript/crypto/signatureService.d.ts.map +1 -1
- package/lib/typescript/models/interfaces.d.ts +1 -1
- package/lib/typescript/models/interfaces.d.ts.map +1 -1
- package/lib/typescript/types/bip39.d.ts +1 -0
- package/lib/typescript/types/buffer.d.ts +1 -0
- package/lib/typescript/types/color.d.ts +1 -0
- package/lib/typescript/types/elliptic.d.ts +1 -0
- package/lib/typescript/types/expo-crypto.d.ts +1 -0
- package/lib/typescript/types/expo-secure-store.d.ts +1 -0
- package/lib/typescript/ui/context/OxyContext.d.ts +11 -3
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts +13 -5
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/OxyAuthScreen.d.ts +1 -0
- package/lib/typescript/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
- package/lib/typescript/ui/stores/authStore.d.ts +4 -0
- package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
- package/package.json +6 -5
- package/src/core/OxyServices.ts +0 -1
- package/src/core/mixins/OxyServices.auth.ts +3 -8
- package/src/core/mixins/OxyServices.devices.ts +1 -4
- package/src/core/mixins/index.ts +2 -5
- package/src/crypto/index.ts +1 -0
- package/src/crypto/keyManager.ts +1 -0
- package/src/crypto/polyfill.ts +1 -0
- package/src/crypto/recoveryPhrase.ts +1 -0
- package/src/crypto/signatureService.ts +4 -5
- package/src/i18n/locales/ar-SA.json +1 -9
- package/src/i18n/locales/ca-ES.json +1 -9
- package/src/i18n/locales/de-DE.json +1 -9
- package/src/i18n/locales/en-US.json +3 -21
- package/src/i18n/locales/es-ES.json +3 -21
- package/src/i18n/locales/fr-FR.json +1 -9
- package/src/i18n/locales/it-IT.json +1 -9
- package/src/i18n/locales/ja-JP.json +1 -9
- package/src/i18n/locales/ko-KR.json +1 -9
- package/src/i18n/locales/pt-PT.json +1 -9
- package/src/i18n/locales/zh-CN.json +1 -9
- package/src/models/interfaces.ts +1 -1
- package/src/types/bip39.d.ts +1 -0
- package/src/types/buffer.d.ts +1 -0
- package/src/types/color.d.ts +1 -0
- package/src/types/elliptic.d.ts +1 -0
- package/src/types/expo-crypto.d.ts +1 -0
- package/src/types/expo-secure-store.d.ts +1 -0
- package/src/ui/context/OxyContext.tsx +35 -3
- package/src/ui/context/hooks/useAuthOperations.ts +212 -98
- package/src/ui/screens/AccountSettingsScreen.tsx +1 -201
- package/src/ui/screens/OxyAuthScreen.tsx +193 -69
- package/src/ui/screens/PrivacySettingsScreen.tsx +0 -2
- package/src/ui/screens/SessionManagementScreen.tsx +43 -26
- package/src/ui/stores/authStore.ts +31 -2
- package/lib/commonjs/core/mixins/OxyServices.totp.js +0 -53
- package/lib/commonjs/core/mixins/OxyServices.totp.js.map +0 -1
- package/lib/commonjs/ui/components/profile/TwoFactorSetupModal.js +0 -467
- package/lib/commonjs/ui/components/profile/TwoFactorSetupModal.js.map +0 -1
- package/lib/module/core/mixins/OxyServices.totp.js +0 -49
- package/lib/module/core/mixins/OxyServices.totp.js.map +0 -1
- package/lib/module/ui/components/profile/TwoFactorSetupModal.js +0 -460
- package/lib/module/ui/components/profile/TwoFactorSetupModal.js.map +0 -1
- package/lib/typescript/core/mixins/OxyServices.totp.d.ts +0 -66
- package/lib/typescript/core/mixins/OxyServices.totp.d.ts.map +0 -1
- package/lib/typescript/ui/components/profile/TwoFactorSetupModal.d.ts +0 -11
- package/lib/typescript/ui/components/profile/TwoFactorSetupModal.d.ts.map +0 -1
- package/src/core/mixins/OxyServices.totp.ts +0 -36
- package/src/ui/components/profile/TwoFactorSetupModal.tsx +0 -442
|
@@ -46,14 +46,6 @@
|
|
|
46
46
|
"status": {
|
|
47
47
|
"accountSwitched": "正在使用{{name}}"
|
|
48
48
|
},
|
|
49
|
-
"totp": {
|
|
50
|
-
"title": "双因素验证码",
|
|
51
|
-
"subtitle": "输入@{{username}}的验证器应用中的6位数字代码",
|
|
52
|
-
"invalidCode": "无效的代码。请重试。",
|
|
53
|
-
"noAccess": "无法访问您的验证器?",
|
|
54
|
-
"useBackupCode": "使用备份代码",
|
|
55
|
-
"useRecoveryKey": "使用恢复密钥"
|
|
56
|
-
}
|
|
57
49
|
},
|
|
58
50
|
"signup": {
|
|
59
51
|
"welcome": {
|
|
@@ -98,7 +90,7 @@
|
|
|
98
90
|
"password": "密码"
|
|
99
91
|
},
|
|
100
92
|
"notSet": "未设置",
|
|
101
|
-
"securityTip": "
|
|
93
|
+
"securityTip": "为了更强的安全性,创建账户后,请在账户设置中启用生物识别身份验证。",
|
|
102
94
|
"legalReminder": "创建账户即表示您同意我们的服务条款和隐私政策。"
|
|
103
95
|
}
|
|
104
96
|
},
|
package/src/models/interfaces.ts
CHANGED
|
@@ -30,7 +30,6 @@ export interface User {
|
|
|
30
30
|
avatar?: string;
|
|
31
31
|
// Privacy and security settings
|
|
32
32
|
privacySettings?: {
|
|
33
|
-
twoFactorEnabled?: boolean;
|
|
34
33
|
[key: string]: unknown;
|
|
35
34
|
};
|
|
36
35
|
name?: {
|
|
@@ -60,6 +59,7 @@ export interface User {
|
|
|
60
59
|
followers?: number;
|
|
61
60
|
following?: number;
|
|
62
61
|
};
|
|
62
|
+
accountExpiresAfterInactivityDays?: number | null; // Days of inactivity before account expires (null = never expire)
|
|
63
63
|
[key: string]: unknown;
|
|
64
64
|
}
|
|
65
65
|
|
package/src/types/bip39.d.ts
CHANGED
package/src/types/buffer.d.ts
CHANGED
package/src/types/color.d.ts
CHANGED
package/src/types/elliptic.d.ts
CHANGED
|
@@ -40,12 +40,20 @@ export interface OxyContextState {
|
|
|
40
40
|
currentLanguageName: string;
|
|
41
41
|
currentNativeLanguageName: string;
|
|
42
42
|
|
|
43
|
-
// Identity management (public key authentication)
|
|
44
|
-
createIdentity: (
|
|
45
|
-
importIdentity: (phrase: string
|
|
43
|
+
// Identity management (public key authentication - offline-first)
|
|
44
|
+
createIdentity: () => Promise<{ recoveryPhrase: string[]; synced: boolean }>;
|
|
45
|
+
importIdentity: (phrase: string) => Promise<{ synced: boolean }>;
|
|
46
46
|
signIn: (deviceName?: string) => Promise<User>;
|
|
47
47
|
hasIdentity: () => Promise<boolean>;
|
|
48
48
|
getPublicKey: () => Promise<string | null>;
|
|
49
|
+
isIdentitySynced: () => Promise<boolean>;
|
|
50
|
+
syncIdentity: () => Promise<User>;
|
|
51
|
+
|
|
52
|
+
// Identity sync state (reactive, from Zustand store)
|
|
53
|
+
identitySyncState: {
|
|
54
|
+
isSynced: boolean;
|
|
55
|
+
isSyncing: boolean;
|
|
56
|
+
};
|
|
49
57
|
|
|
50
58
|
// Session management
|
|
51
59
|
logout: (targetSessionId?: string) => Promise<void>;
|
|
@@ -140,6 +148,11 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
140
148
|
loginSuccess,
|
|
141
149
|
loginFailure,
|
|
142
150
|
logoutStore,
|
|
151
|
+
// Identity sync state and actions
|
|
152
|
+
isIdentitySyncedStore,
|
|
153
|
+
isSyncing,
|
|
154
|
+
setIdentitySynced,
|
|
155
|
+
setSyncing,
|
|
143
156
|
} = useAuthStore(
|
|
144
157
|
useShallow((state: AuthState) => ({
|
|
145
158
|
user: state.user,
|
|
@@ -149,6 +162,11 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
149
162
|
loginSuccess: state.loginSuccess,
|
|
150
163
|
loginFailure: state.loginFailure,
|
|
151
164
|
logoutStore: state.logout,
|
|
165
|
+
// Identity sync state and actions
|
|
166
|
+
isIdentitySyncedStore: state.isIdentitySynced,
|
|
167
|
+
isSyncing: state.isSyncing,
|
|
168
|
+
setIdentitySynced: state.setIdentitySynced,
|
|
169
|
+
setSyncing: state.setSyncing,
|
|
152
170
|
})),
|
|
153
171
|
);
|
|
154
172
|
|
|
@@ -212,6 +230,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
212
230
|
logoutAll,
|
|
213
231
|
hasIdentity,
|
|
214
232
|
getPublicKey,
|
|
233
|
+
isIdentitySynced,
|
|
234
|
+
syncIdentity,
|
|
215
235
|
} = useAuthOperations({
|
|
216
236
|
oxyServices,
|
|
217
237
|
storage,
|
|
@@ -229,6 +249,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
229
249
|
loginFailure,
|
|
230
250
|
logoutStore,
|
|
231
251
|
setAuthState,
|
|
252
|
+
setIdentitySynced,
|
|
253
|
+
setSyncing,
|
|
232
254
|
logger,
|
|
233
255
|
});
|
|
234
256
|
|
|
@@ -392,6 +414,12 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
392
414
|
signIn,
|
|
393
415
|
hasIdentity,
|
|
394
416
|
getPublicKey,
|
|
417
|
+
isIdentitySynced,
|
|
418
|
+
syncIdentity,
|
|
419
|
+
identitySyncState: {
|
|
420
|
+
isSynced: isIdentitySyncedStore ?? true,
|
|
421
|
+
isSyncing: isSyncing ?? false,
|
|
422
|
+
},
|
|
395
423
|
logout,
|
|
396
424
|
logoutAll,
|
|
397
425
|
switchSession: switchSessionForContext,
|
|
@@ -411,6 +439,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
411
439
|
signIn,
|
|
412
440
|
hasIdentity,
|
|
413
441
|
getPublicKey,
|
|
442
|
+
isIdentitySynced,
|
|
443
|
+
syncIdentity,
|
|
444
|
+
isIdentitySyncedStore,
|
|
445
|
+
isSyncing,
|
|
414
446
|
currentLanguage,
|
|
415
447
|
currentLanguageMetadata,
|
|
416
448
|
currentLanguageName,
|
|
@@ -26,14 +26,17 @@ export interface UseAuthOperationsOptions {
|
|
|
26
26
|
loginFailure: (message: string) => void;
|
|
27
27
|
logoutStore: () => void;
|
|
28
28
|
setAuthState: (state: Partial<AuthState>) => void;
|
|
29
|
+
// Identity sync store actions
|
|
30
|
+
setIdentitySynced: (synced: boolean) => void;
|
|
31
|
+
setSyncing: (syncing: boolean) => void;
|
|
29
32
|
logger?: (message: string, error?: unknown) => void;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
export interface UseAuthOperationsResult {
|
|
33
|
-
/** Create a new identity and
|
|
34
|
-
createIdentity: (
|
|
36
|
+
/** Create a new identity locally (offline-first) and optionally sync with server */
|
|
37
|
+
createIdentity: () => Promise<{ recoveryPhrase: string[]; synced: boolean }>;
|
|
35
38
|
/** Import an existing identity from recovery phrase */
|
|
36
|
-
importIdentity: (phrase: string
|
|
39
|
+
importIdentity: (phrase: string) => Promise<{ synced: boolean }>;
|
|
37
40
|
/** Sign in with existing identity on device */
|
|
38
41
|
signIn: (deviceName?: string) => Promise<User>;
|
|
39
42
|
/** Logout from current session */
|
|
@@ -44,6 +47,10 @@ export interface UseAuthOperationsResult {
|
|
|
44
47
|
hasIdentity: () => Promise<boolean>;
|
|
45
48
|
/** Get the public key of the stored identity */
|
|
46
49
|
getPublicKey: () => Promise<string | null>;
|
|
50
|
+
/** Check if identity is synced with server */
|
|
51
|
+
isIdentitySynced: () => Promise<boolean>;
|
|
52
|
+
/** Sync local identity with server (when online) */
|
|
53
|
+
syncIdentity: () => Promise<User>;
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
const LOGIN_ERROR_CODE = 'LOGIN_ERROR';
|
|
@@ -71,103 +78,12 @@ export const useAuthOperations = ({
|
|
|
71
78
|
loginSuccess,
|
|
72
79
|
loginFailure,
|
|
73
80
|
logoutStore,
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
setAuthState,
|
|
82
|
+
setIdentitySynced,
|
|
83
|
+
setSyncing,
|
|
84
|
+
logger,
|
|
76
85
|
}: UseAuthOperationsOptions): UseAuthOperationsResult => {
|
|
77
86
|
|
|
78
|
-
/**
|
|
79
|
-
* Create a new identity with recovery phrase
|
|
80
|
-
*/
|
|
81
|
-
const createIdentity = useCallback(
|
|
82
|
-
async (username: string, email?: string): Promise<{ user: User; recoveryPhrase: string[] }> => {
|
|
83
|
-
if (!storage) throw new Error('Storage not initialized');
|
|
84
|
-
|
|
85
|
-
setAuthState({ isLoading: true, error: null });
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
// Generate new identity with recovery phrase
|
|
89
|
-
const { phrase, words, publicKey } = await RecoveryPhraseService.generateIdentityWithRecovery();
|
|
90
|
-
|
|
91
|
-
// Create registration signature
|
|
92
|
-
const { signature, timestamp } = await SignatureService.createRegistrationSignature(username, email);
|
|
93
|
-
|
|
94
|
-
// Register with server
|
|
95
|
-
const { user } = await oxyServices.register(publicKey, username, signature, timestamp, email);
|
|
96
|
-
|
|
97
|
-
// Now sign in to create a session
|
|
98
|
-
const fullUser = await performSignIn(publicKey);
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
user: fullUser,
|
|
102
|
-
recoveryPhrase: words,
|
|
103
|
-
};
|
|
104
|
-
} catch (error) {
|
|
105
|
-
// Clean up identity if registration failed
|
|
106
|
-
await KeyManager.deleteIdentity().catch(() => {});
|
|
107
|
-
|
|
108
|
-
const message = handleAuthError(error, {
|
|
109
|
-
defaultMessage: 'Failed to create identity',
|
|
110
|
-
code: REGISTER_ERROR_CODE,
|
|
111
|
-
onError,
|
|
112
|
-
setAuthError: (msg) => setAuthState({ error: msg }),
|
|
113
|
-
logger,
|
|
114
|
-
});
|
|
115
|
-
loginFailure(message);
|
|
116
|
-
throw error;
|
|
117
|
-
} finally {
|
|
118
|
-
setAuthState({ isLoading: false });
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
[oxyServices, storage, setAuthState, loginFailure, onError, logger],
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Import identity from recovery phrase
|
|
126
|
-
*/
|
|
127
|
-
const importIdentity = useCallback(
|
|
128
|
-
async (phrase: string, username?: string, email?: string): Promise<User> => {
|
|
129
|
-
if (!storage) throw new Error('Storage not initialized');
|
|
130
|
-
|
|
131
|
-
setAuthState({ isLoading: true, error: null });
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
// Restore identity from phrase
|
|
135
|
-
const publicKey = await RecoveryPhraseService.restoreFromPhrase(phrase);
|
|
136
|
-
|
|
137
|
-
// Check if this identity is already registered
|
|
138
|
-
const { registered } = await oxyServices.checkPublicKeyRegistered(publicKey);
|
|
139
|
-
|
|
140
|
-
if (registered) {
|
|
141
|
-
// Identity exists, just sign in
|
|
142
|
-
return await performSignIn(publicKey);
|
|
143
|
-
} else {
|
|
144
|
-
// Need to register this identity
|
|
145
|
-
if (!username) {
|
|
146
|
-
throw new Error('Username is required for new registration');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const { signature, timestamp } = await SignatureService.createRegistrationSignature(username, email);
|
|
150
|
-
await oxyServices.register(publicKey, username, signature, timestamp, email);
|
|
151
|
-
|
|
152
|
-
return await performSignIn(publicKey);
|
|
153
|
-
}
|
|
154
|
-
} catch (error) {
|
|
155
|
-
const message = handleAuthError(error, {
|
|
156
|
-
defaultMessage: 'Failed to import identity',
|
|
157
|
-
code: REGISTER_ERROR_CODE,
|
|
158
|
-
onError,
|
|
159
|
-
setAuthError: (msg) => setAuthState({ error: msg }),
|
|
160
|
-
logger,
|
|
161
|
-
});
|
|
162
|
-
loginFailure(message);
|
|
163
|
-
throw error;
|
|
164
|
-
} finally {
|
|
165
|
-
setAuthState({ isLoading: false });
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
[oxyServices, storage, setAuthState, loginFailure, onError, logger],
|
|
169
|
-
);
|
|
170
|
-
|
|
171
87
|
/**
|
|
172
88
|
* Internal function to perform challenge-response sign in
|
|
173
89
|
*/
|
|
@@ -181,6 +97,10 @@ export const useAuthOperations = ({
|
|
|
181
97
|
// Request challenge
|
|
182
98
|
const { challenge } = await oxyServices.requestChallenge(publicKey);
|
|
183
99
|
|
|
100
|
+
// Note: Biometric authentication check should be handled by the app layer
|
|
101
|
+
// (e.g., accounts app) before calling signIn. The biometric preference is stored
|
|
102
|
+
// in local storage as 'oxy_biometric_enabled' and can be checked there.
|
|
103
|
+
|
|
184
104
|
// Sign the challenge
|
|
185
105
|
const { challenge: signature, timestamp } = await SignatureService.signChallenge(challenge);
|
|
186
106
|
|
|
@@ -261,6 +181,198 @@ export const useAuthOperations = ({
|
|
|
261
181
|
],
|
|
262
182
|
);
|
|
263
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Create a new identity with recovery phrase (offline-first)
|
|
186
|
+
* Identity is purely cryptographic - no username or email required
|
|
187
|
+
*/
|
|
188
|
+
const createIdentity = useCallback(
|
|
189
|
+
async (): Promise<{ recoveryPhrase: string[]; synced: boolean }> => {
|
|
190
|
+
if (!storage) throw new Error('Storage not initialized');
|
|
191
|
+
|
|
192
|
+
setAuthState({ isLoading: true, error: null });
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
// Generate new identity with recovery phrase (works offline)
|
|
196
|
+
const { phrase, words, publicKey } = await RecoveryPhraseService.generateIdentityWithRecovery();
|
|
197
|
+
|
|
198
|
+
// Mark as not synced
|
|
199
|
+
await storage.setItem('oxy_identity_synced', 'false');
|
|
200
|
+
setIdentitySynced(false);
|
|
201
|
+
|
|
202
|
+
// Try to sync with server (will succeed if online)
|
|
203
|
+
try {
|
|
204
|
+
const { signature, timestamp } = await SignatureService.createRegistrationSignature();
|
|
205
|
+
await oxyServices.register(publicKey, signature, timestamp);
|
|
206
|
+
|
|
207
|
+
// Mark as synced (Zustand store + storage)
|
|
208
|
+
await storage.setItem('oxy_identity_synced', 'true');
|
|
209
|
+
setIdentitySynced(true);
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
recoveryPhrase: words,
|
|
213
|
+
synced: true,
|
|
214
|
+
};
|
|
215
|
+
} catch (syncError) {
|
|
216
|
+
// Offline or server error - identity is created locally but not synced
|
|
217
|
+
if (__DEV__) {
|
|
218
|
+
console.log('[Auth] Identity created locally, will sync when online:', syncError);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
recoveryPhrase: words,
|
|
223
|
+
synced: false,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
// Clean up identity if generation failed
|
|
228
|
+
await KeyManager.deleteIdentity().catch(() => {});
|
|
229
|
+
await storage.removeItem('oxy_identity_synced').catch(() => {});
|
|
230
|
+
setIdentitySynced(true);
|
|
231
|
+
|
|
232
|
+
const message = handleAuthError(error, {
|
|
233
|
+
defaultMessage: 'Failed to create identity',
|
|
234
|
+
code: REGISTER_ERROR_CODE,
|
|
235
|
+
onError,
|
|
236
|
+
setAuthError: (msg) => setAuthState({ error: msg }),
|
|
237
|
+
logger,
|
|
238
|
+
});
|
|
239
|
+
loginFailure(message);
|
|
240
|
+
throw error;
|
|
241
|
+
} finally {
|
|
242
|
+
setAuthState({ isLoading: false });
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
[oxyServices, storage, setAuthState, loginFailure, onError, logger, setIdentitySynced],
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Check if identity is synced with server (reads from storage for persistence)
|
|
250
|
+
*/
|
|
251
|
+
const isIdentitySyncedFn = useCallback(async (): Promise<boolean> => {
|
|
252
|
+
if (!storage) return true;
|
|
253
|
+
const synced = await storage.getItem('oxy_identity_synced');
|
|
254
|
+
const isSynced = synced !== 'false';
|
|
255
|
+
setIdentitySynced(isSynced);
|
|
256
|
+
return isSynced;
|
|
257
|
+
}, [storage, setIdentitySynced]);
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Sync local identity with server (call when online)
|
|
261
|
+
*/
|
|
262
|
+
const syncIdentity = useCallback(
|
|
263
|
+
async (): Promise<User> => {
|
|
264
|
+
if (!storage) throw new Error('Storage not initialized');
|
|
265
|
+
|
|
266
|
+
setAuthState({ isLoading: true, error: null });
|
|
267
|
+
setSyncing(true);
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const publicKey = await KeyManager.getPublicKey();
|
|
271
|
+
if (!publicKey) {
|
|
272
|
+
throw new Error('No identity found on this device');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Check if already synced
|
|
276
|
+
const alreadySynced = await storage.getItem('oxy_identity_synced');
|
|
277
|
+
if (alreadySynced === 'true') {
|
|
278
|
+
// Already synced, just sign in
|
|
279
|
+
setIdentitySynced(true);
|
|
280
|
+
return await performSignIn(publicKey);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Check if already registered on server
|
|
284
|
+
const { registered } = await oxyServices.checkPublicKeyRegistered(publicKey);
|
|
285
|
+
|
|
286
|
+
if (!registered) {
|
|
287
|
+
// Register with server (identity is just the publicKey)
|
|
288
|
+
const { signature, timestamp } = await SignatureService.createRegistrationSignature();
|
|
289
|
+
await oxyServices.register(publicKey, signature, timestamp);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Mark as synced (Zustand store + storage)
|
|
293
|
+
await storage.setItem('oxy_identity_synced', 'true');
|
|
294
|
+
setIdentitySynced(true);
|
|
295
|
+
|
|
296
|
+
// Sign in
|
|
297
|
+
return await performSignIn(publicKey);
|
|
298
|
+
} catch (error) {
|
|
299
|
+
const message = handleAuthError(error, {
|
|
300
|
+
defaultMessage: 'Failed to sync identity',
|
|
301
|
+
code: REGISTER_ERROR_CODE,
|
|
302
|
+
onError,
|
|
303
|
+
setAuthError: (msg) => setAuthState({ error: msg }),
|
|
304
|
+
logger,
|
|
305
|
+
});
|
|
306
|
+
loginFailure(message);
|
|
307
|
+
throw error;
|
|
308
|
+
} finally {
|
|
309
|
+
setAuthState({ isLoading: false });
|
|
310
|
+
setSyncing(false);
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
[oxyServices, storage, setAuthState, performSignIn, loginFailure, onError, logger, setSyncing, setIdentitySynced],
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Import identity from recovery phrase (offline-first)
|
|
318
|
+
*/
|
|
319
|
+
const importIdentity = useCallback(
|
|
320
|
+
async (phrase: string): Promise<{ synced: boolean }> => {
|
|
321
|
+
if (!storage) throw new Error('Storage not initialized');
|
|
322
|
+
|
|
323
|
+
setAuthState({ isLoading: true, error: null });
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
// Restore identity from phrase (works offline)
|
|
327
|
+
const publicKey = await RecoveryPhraseService.restoreFromPhrase(phrase);
|
|
328
|
+
|
|
329
|
+
// Mark as not synced
|
|
330
|
+
await storage.setItem('oxy_identity_synced', 'false');
|
|
331
|
+
setIdentitySynced(false);
|
|
332
|
+
|
|
333
|
+
// Try to sync with server
|
|
334
|
+
try {
|
|
335
|
+
// Check if this identity is already registered
|
|
336
|
+
const { registered } = await oxyServices.checkPublicKeyRegistered(publicKey);
|
|
337
|
+
|
|
338
|
+
if (registered) {
|
|
339
|
+
// Identity exists, mark as synced
|
|
340
|
+
await storage.setItem('oxy_identity_synced', 'true');
|
|
341
|
+
setIdentitySynced(true);
|
|
342
|
+
return { synced: true };
|
|
343
|
+
} else {
|
|
344
|
+
// Need to register this identity (identity is just the publicKey)
|
|
345
|
+
const { signature, timestamp } = await SignatureService.createRegistrationSignature();
|
|
346
|
+
await oxyServices.register(publicKey, signature, timestamp);
|
|
347
|
+
|
|
348
|
+
await storage.setItem('oxy_identity_synced', 'true');
|
|
349
|
+
setIdentitySynced(true);
|
|
350
|
+
return { synced: true };
|
|
351
|
+
}
|
|
352
|
+
} catch (syncError) {
|
|
353
|
+
// Offline - identity restored locally but not synced
|
|
354
|
+
if (__DEV__) {
|
|
355
|
+
console.log('[Auth] Identity imported locally, will sync when online:', syncError);
|
|
356
|
+
}
|
|
357
|
+
return { synced: false };
|
|
358
|
+
}
|
|
359
|
+
} catch (error) {
|
|
360
|
+
const message = handleAuthError(error, {
|
|
361
|
+
defaultMessage: 'Failed to import identity',
|
|
362
|
+
code: REGISTER_ERROR_CODE,
|
|
363
|
+
onError,
|
|
364
|
+
setAuthError: (msg) => setAuthState({ error: msg }),
|
|
365
|
+
logger,
|
|
366
|
+
});
|
|
367
|
+
loginFailure(message);
|
|
368
|
+
throw error;
|
|
369
|
+
} finally {
|
|
370
|
+
setAuthState({ isLoading: false });
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
[oxyServices, storage, setAuthState, loginFailure, onError, logger, setIdentitySynced],
|
|
374
|
+
);
|
|
375
|
+
|
|
264
376
|
/**
|
|
265
377
|
* Sign in with existing identity on device
|
|
266
378
|
*/
|
|
@@ -396,5 +508,7 @@ export const useAuthOperations = ({
|
|
|
396
508
|
logoutAll,
|
|
397
509
|
hasIdentity,
|
|
398
510
|
getPublicKey,
|
|
511
|
+
isIdentitySynced: isIdentitySyncedFn,
|
|
512
|
+
syncIdentity,
|
|
399
513
|
};
|
|
400
514
|
};
|