@oxyhq/services 5.20.1 → 5.20.3
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.fedcm.js +26 -6
- package/lib/commonjs/core/mixins/OxyServices.fedcm.js.map +1 -1
- package/lib/commonjs/core/mixins/OxyServices.popup.js +40 -1
- package/lib/commonjs/core/mixins/OxyServices.popup.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +23 -1
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/useAuth.js +15 -15
- package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.fedcm.js +26 -6
- package/lib/module/core/mixins/OxyServices.fedcm.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.popup.js +40 -1
- package/lib/module/core/mixins/OxyServices.popup.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +23 -1
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useAuth.js +15 -15
- package/lib/module/ui/hooks/useAuth.js.map +1 -1
- package/lib/typescript/commonjs/core/mixins/OxyServices.fedcm.d.ts +3 -7
- package/lib/typescript/commonjs/core/mixins/OxyServices.fedcm.d.ts.map +1 -1
- package/lib/typescript/commonjs/core/mixins/OxyServices.popup.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +11 -0
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/module/core/mixins/OxyServices.fedcm.d.ts +3 -7
- package/lib/typescript/module/core/mixins/OxyServices.fedcm.d.ts.map +1 -1
- package/lib/typescript/module/core/mixins/OxyServices.popup.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +11 -0
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/mixins/OxyServices.fedcm.ts +26 -6
- package/src/core/mixins/OxyServices.popup.ts +39 -1
- package/src/ui/context/OxyContext.tsx +25 -0
- package/src/ui/hooks/useAuth.ts +15 -20
|
@@ -111,6 +111,25 @@ export function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBase>(Base
|
|
|
111
111
|
this.httpService.setTokens((session as any).accessToken);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// Fetch user data using the session ID
|
|
115
|
+
// The callback page only sends sessionId/accessToken, not user data
|
|
116
|
+
if (session && session.sessionId && !session.user) {
|
|
117
|
+
try {
|
|
118
|
+
const userData = await this.makeRequest<any>(
|
|
119
|
+
'GET',
|
|
120
|
+
`/api/session/user/${session.sessionId}`,
|
|
121
|
+
undefined,
|
|
122
|
+
{ cache: false }
|
|
123
|
+
);
|
|
124
|
+
if (userData) {
|
|
125
|
+
(session as any).user = userData;
|
|
126
|
+
}
|
|
127
|
+
} catch (userError) {
|
|
128
|
+
console.warn('[PopupAuth] Failed to fetch user data:', userError);
|
|
129
|
+
// Continue without user data - caller can fetch separately
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
114
133
|
return session;
|
|
115
134
|
} catch (error) {
|
|
116
135
|
throw error;
|
|
@@ -238,8 +257,21 @@ export function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBase>(Base
|
|
|
238
257
|
}, timeout);
|
|
239
258
|
|
|
240
259
|
const messageHandler = (event: MessageEvent) => {
|
|
260
|
+
const authUrl = (this.constructor as any).AUTH_URL;
|
|
261
|
+
|
|
262
|
+
// Log all messages for debugging
|
|
263
|
+
if (event.data && typeof event.data === 'object' && event.data.type) {
|
|
264
|
+
console.log('[PopupAuth] Message received:', {
|
|
265
|
+
origin: event.origin,
|
|
266
|
+
expectedOrigin: authUrl,
|
|
267
|
+
type: event.data.type,
|
|
268
|
+
hasSession: !!event.data.session,
|
|
269
|
+
hasError: !!event.data.error,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
241
273
|
// CRITICAL: Verify origin to prevent XSS attacks
|
|
242
|
-
if (event.origin !==
|
|
274
|
+
if (event.origin !== authUrl) {
|
|
243
275
|
return;
|
|
244
276
|
}
|
|
245
277
|
|
|
@@ -249,9 +281,12 @@ export function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBase>(Base
|
|
|
249
281
|
return;
|
|
250
282
|
}
|
|
251
283
|
|
|
284
|
+
console.log('[PopupAuth] Valid auth response:', { state, expectedState, hasSession: !!session, error });
|
|
285
|
+
|
|
252
286
|
// Verify state parameter to prevent CSRF attacks
|
|
253
287
|
if (state !== expectedState) {
|
|
254
288
|
cleanup();
|
|
289
|
+
console.error('[PopupAuth] State mismatch');
|
|
255
290
|
reject(new OxyAuthenticationError('Invalid state parameter. Possible CSRF attack.'));
|
|
256
291
|
return;
|
|
257
292
|
}
|
|
@@ -259,10 +294,13 @@ export function OxyServicesPopupAuthMixin<T extends typeof OxyServicesBase>(Base
|
|
|
259
294
|
cleanup();
|
|
260
295
|
|
|
261
296
|
if (error) {
|
|
297
|
+
console.error('[PopupAuth] Auth error:', error);
|
|
262
298
|
reject(new OxyAuthenticationError(error));
|
|
263
299
|
} else if (session) {
|
|
300
|
+
console.log('[PopupAuth] Session received successfully');
|
|
264
301
|
resolve(session);
|
|
265
302
|
} else {
|
|
303
|
+
console.error('[PopupAuth] No session in response');
|
|
266
304
|
reject(new OxyAuthenticationError('No session received from authentication server'));
|
|
267
305
|
}
|
|
268
306
|
};
|
|
@@ -55,6 +55,18 @@ export interface OxyContextState {
|
|
|
55
55
|
// Authentication
|
|
56
56
|
signIn: (publicKey: string, deviceName?: string) => Promise<User>;
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Handle session from popup authentication
|
|
60
|
+
* Updates auth state, persists session to storage
|
|
61
|
+
*/
|
|
62
|
+
handlePopupSession: (session: {
|
|
63
|
+
sessionId: string;
|
|
64
|
+
accessToken?: string;
|
|
65
|
+
expiresAt?: string;
|
|
66
|
+
user: User;
|
|
67
|
+
deviceId?: string;
|
|
68
|
+
}) => Promise<void>;
|
|
69
|
+
|
|
58
70
|
// Session management
|
|
59
71
|
logout: (targetSessionId?: string) => Promise<void>;
|
|
60
72
|
logoutAll: () => Promise<void>;
|
|
@@ -414,11 +426,19 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
414
426
|
}, [restoreSessionsFromStorage, storage]);
|
|
415
427
|
|
|
416
428
|
// Web SSO: Automatically check for cross-domain session on web platforms
|
|
429
|
+
// Also used for popup auth - updates all state and persists session
|
|
417
430
|
const handleWebSSOSession = useCallback(async (session: any) => {
|
|
418
431
|
if (!session?.user || !session?.sessionId) {
|
|
432
|
+
if (__DEV__) {
|
|
433
|
+
loggerUtil.warn('handleWebSSOSession called with invalid session', { component: 'OxyContext' }, { session });
|
|
434
|
+
}
|
|
419
435
|
return;
|
|
420
436
|
}
|
|
421
437
|
|
|
438
|
+
if (__DEV__) {
|
|
439
|
+
loggerUtil.debug('handleWebSSOSession: Updating auth state', { component: 'OxyContext' }, { sessionId: session.sessionId, userId: session.user?.id });
|
|
440
|
+
}
|
|
441
|
+
|
|
422
442
|
// Update sessions state
|
|
423
443
|
const clientSession = {
|
|
424
444
|
sessionId: session.sessionId,
|
|
@@ -443,6 +463,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
443
463
|
sessionIds.push(session.sessionId);
|
|
444
464
|
await storage.setItem(storageKeys.sessionIds, JSON.stringify(sessionIds));
|
|
445
465
|
}
|
|
466
|
+
if (__DEV__) {
|
|
467
|
+
loggerUtil.debug('handleWebSSOSession: Session persisted to storage', { component: 'OxyContext' });
|
|
468
|
+
}
|
|
446
469
|
}
|
|
447
470
|
}, [updateSessions, setActiveSessionId, loginSuccess, onAuthStateChange, storage, storageKeys]);
|
|
448
471
|
|
|
@@ -571,6 +594,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
571
594
|
hasIdentity,
|
|
572
595
|
getPublicKey,
|
|
573
596
|
signIn,
|
|
597
|
+
handlePopupSession: handleWebSSOSession,
|
|
574
598
|
logout,
|
|
575
599
|
logoutAll,
|
|
576
600
|
switchSession: switchSessionForContext,
|
|
@@ -589,6 +613,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
589
613
|
}), [
|
|
590
614
|
activeSessionId,
|
|
591
615
|
signIn,
|
|
616
|
+
handleWebSSOSession,
|
|
592
617
|
currentLanguage,
|
|
593
618
|
currentLanguageMetadata,
|
|
594
619
|
currentLanguageName,
|
package/src/ui/hooks/useAuth.ts
CHANGED
|
@@ -91,6 +91,7 @@ export function useAuth(): UseAuthReturn {
|
|
|
91
91
|
isTokenReady,
|
|
92
92
|
error,
|
|
93
93
|
signIn: oxySignIn,
|
|
94
|
+
handlePopupSession,
|
|
94
95
|
logout,
|
|
95
96
|
logoutAll,
|
|
96
97
|
refreshSessions,
|
|
@@ -106,31 +107,25 @@ export function useAuth(): UseAuthReturn {
|
|
|
106
107
|
const isIdentityProvider = isWebBrowser() &&
|
|
107
108
|
window.location.hostname === 'auth.oxy.so';
|
|
108
109
|
|
|
109
|
-
// Web (not on IdP): Use
|
|
110
|
+
// Web (not on IdP): Use popup-based authentication
|
|
111
|
+
// We skip FedCM here because:
|
|
112
|
+
// 1. Silent FedCM already ran on page load (via useWebSSO)
|
|
113
|
+
// 2. If user is clicking "Sign In", they need interactive auth
|
|
114
|
+
// 3. Doing FedCM first loses the "user gesture" needed for popups
|
|
110
115
|
if (isWebBrowser() && !publicKey && !isIdentityProvider) {
|
|
111
116
|
try {
|
|
112
|
-
// Try FedCM first (instant if user already signed in)
|
|
113
|
-
if ((oxyServices as any).isFedCMSupported?.()) {
|
|
114
|
-
const fedcmSession = await (oxyServices as any).signInWithFedCM?.();
|
|
115
|
-
if (fedcmSession?.user) {
|
|
116
|
-
return fedcmSession.user;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Fallback to popup (opens auth.oxy.so in popup window)
|
|
121
117
|
const popupSession = await (oxyServices as any).signInWithPopup?.();
|
|
122
118
|
if (popupSession?.user) {
|
|
119
|
+
// Update context state with the session (this updates user, sessions, storage)
|
|
120
|
+
await handlePopupSession(popupSession);
|
|
123
121
|
return popupSession.user;
|
|
124
122
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
? 'Popup blocked. Please allow popups or try again.'
|
|
132
|
-
: 'Sign-in failed. Please try again.'
|
|
133
|
-
);
|
|
123
|
+
throw new Error('Sign-in failed. Please try again.');
|
|
124
|
+
} catch (popupError) {
|
|
125
|
+
if (popupError instanceof Error && popupError.message.includes('blocked')) {
|
|
126
|
+
throw new Error('Popup blocked. Please allow popups for this site.');
|
|
127
|
+
}
|
|
128
|
+
throw popupError;
|
|
134
129
|
}
|
|
135
130
|
}
|
|
136
131
|
|
|
@@ -169,7 +164,7 @@ export function useAuth(): UseAuthReturn {
|
|
|
169
164
|
}
|
|
170
165
|
|
|
171
166
|
throw new Error('No authentication method available');
|
|
172
|
-
}, [oxySignIn, hasIdentity, getPublicKey, showBottomSheet, oxyServices]);
|
|
167
|
+
}, [oxySignIn, hasIdentity, getPublicKey, showBottomSheet, oxyServices, handlePopupSession]);
|
|
173
168
|
|
|
174
169
|
const signOut = useCallback(async (): Promise<void> => {
|
|
175
170
|
await logout();
|