@oxyhq/core 3.4.0 → 3.4.2
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/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/AuthManager.js +91 -319
- package/dist/cjs/CrossDomainAuth.js +19 -106
- package/dist/cjs/HttpService.js +49 -73
- package/dist/cjs/OxyServices.base.js +2 -2
- package/dist/cjs/i18n/index.js +7 -1
- package/dist/cjs/i18n/locales/ar-SA.json +18 -2
- package/dist/cjs/i18n/locales/ca-ES.json +18 -2
- package/dist/cjs/i18n/locales/de-DE.json +18 -2
- package/dist/cjs/i18n/locales/en-US.json +16 -2
- package/dist/cjs/i18n/locales/es-ES.json +16 -2
- package/dist/cjs/i18n/locales/fr-FR.json +18 -2
- package/dist/cjs/i18n/locales/it-IT.json +18 -2
- package/dist/cjs/i18n/locales/ja-JP.json +18 -2
- package/dist/cjs/i18n/locales/ko-KR.json +18 -2
- package/dist/cjs/i18n/locales/locales/ar-SA.json +18 -2
- package/dist/cjs/i18n/locales/locales/ca-ES.json +18 -2
- package/dist/cjs/i18n/locales/locales/de-DE.json +18 -2
- package/dist/cjs/i18n/locales/locales/en-US.json +17 -3
- package/dist/cjs/i18n/locales/locales/es-ES.json +16 -2
- package/dist/cjs/i18n/locales/locales/fr-FR.json +18 -2
- package/dist/cjs/i18n/locales/locales/it-IT.json +18 -2
- package/dist/cjs/i18n/locales/locales/ja-JP.json +18 -2
- package/dist/cjs/i18n/locales/locales/ko-KR.json +18 -2
- package/dist/cjs/i18n/locales/locales/pt-PT.json +18 -2
- package/dist/cjs/i18n/locales/locales/zh-CN.json +18 -2
- package/dist/cjs/i18n/locales/pt-PT.json +18 -2
- package/dist/cjs/i18n/locales/zh-CN.json +18 -2
- package/dist/cjs/mixins/OxyServices.auth.js +20 -63
- package/dist/cjs/mixins/OxyServices.fedcm.js +10 -12
- package/dist/cjs/mixins/OxyServices.popup.js +50 -299
- package/dist/cjs/mixins/OxyServices.redirect.js +84 -348
- package/dist/cjs/mixins/OxyServices.silent.js +204 -0
- package/dist/cjs/mixins/OxyServices.sso.js +4 -5
- package/dist/cjs/mixins/OxyServices.utility.js +6 -15
- package/dist/cjs/mixins/index.js +5 -6
- package/dist/cjs/server/index.js +21 -0
- package/dist/cjs/server/rateLimit.js +77 -0
- package/dist/cjs/shared/utils/debugUtils.js +1 -1
- package/dist/cjs/utils/accountUtils.js +4 -4
- package/dist/cjs/utils/authHelpers.js +21 -15
- package/dist/cjs/utils/coldBoot.js +3 -3
- package/dist/cjs/utils/fapiAutoDetect.js +1 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/AuthManager.js +91 -319
- package/dist/esm/CrossDomainAuth.js +19 -106
- package/dist/esm/HttpService.js +49 -73
- package/dist/esm/OxyServices.base.js +2 -2
- package/dist/esm/i18n/index.js +7 -1
- package/dist/esm/i18n/locales/ar-SA.json +18 -2
- package/dist/esm/i18n/locales/ca-ES.json +18 -2
- package/dist/esm/i18n/locales/de-DE.json +18 -2
- package/dist/esm/i18n/locales/en-US.json +16 -2
- package/dist/esm/i18n/locales/es-ES.json +16 -2
- package/dist/esm/i18n/locales/fr-FR.json +18 -2
- package/dist/esm/i18n/locales/it-IT.json +18 -2
- package/dist/esm/i18n/locales/ja-JP.json +18 -2
- package/dist/esm/i18n/locales/ko-KR.json +18 -2
- package/dist/esm/i18n/locales/locales/ar-SA.json +18 -2
- package/dist/esm/i18n/locales/locales/ca-ES.json +18 -2
- package/dist/esm/i18n/locales/locales/de-DE.json +18 -2
- package/dist/esm/i18n/locales/locales/en-US.json +17 -3
- package/dist/esm/i18n/locales/locales/es-ES.json +16 -2
- package/dist/esm/i18n/locales/locales/fr-FR.json +18 -2
- package/dist/esm/i18n/locales/locales/it-IT.json +18 -2
- package/dist/esm/i18n/locales/locales/ja-JP.json +18 -2
- package/dist/esm/i18n/locales/locales/ko-KR.json +18 -2
- package/dist/esm/i18n/locales/locales/pt-PT.json +18 -2
- package/dist/esm/i18n/locales/locales/zh-CN.json +18 -2
- package/dist/esm/i18n/locales/pt-PT.json +18 -2
- package/dist/esm/i18n/locales/zh-CN.json +18 -2
- package/dist/esm/mixins/OxyServices.auth.js +20 -63
- package/dist/esm/mixins/OxyServices.fedcm.js +10 -12
- package/dist/esm/mixins/OxyServices.popup.js +52 -301
- package/dist/esm/mixins/OxyServices.redirect.js +84 -349
- package/dist/esm/mixins/OxyServices.silent.js +202 -0
- package/dist/esm/mixins/OxyServices.sso.js +4 -5
- package/dist/esm/mixins/OxyServices.utility.js +6 -15
- package/dist/esm/mixins/index.js +5 -6
- package/dist/esm/server/index.js +17 -0
- package/dist/esm/server/rateLimit.js +71 -0
- package/dist/esm/shared/utils/debugUtils.js +1 -1
- package/dist/esm/utils/accountUtils.js +4 -4
- package/dist/esm/utils/authHelpers.js +21 -15
- package/dist/esm/utils/coldBoot.js +3 -3
- package/dist/esm/utils/fapiAutoDetect.js +1 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/AuthManager.d.ts +26 -53
- package/dist/types/AuthManagerTypes.d.ts +5 -9
- package/dist/types/CrossDomainAuth.d.ts +13 -52
- package/dist/types/HttpService.d.ts +9 -8
- package/dist/types/OxyServices.base.d.ts +1 -1
- package/dist/types/OxyServices.d.ts +4 -10
- package/dist/types/index.d.ts +1 -1
- package/dist/types/mixins/OxyServices.analytics.d.ts +1 -1
- package/dist/types/mixins/OxyServices.appData.d.ts +1 -1
- package/dist/types/mixins/OxyServices.applications.d.ts +1 -1
- package/dist/types/mixins/OxyServices.assets.d.ts +1 -1
- package/dist/types/mixins/OxyServices.auth.d.ts +10 -31
- package/dist/types/mixins/OxyServices.contacts.d.ts +1 -1
- package/dist/types/mixins/OxyServices.devices.d.ts +1 -1
- package/dist/types/mixins/OxyServices.features.d.ts +1 -1
- package/dist/types/mixins/OxyServices.fedcm.d.ts +5 -5
- package/dist/types/mixins/OxyServices.language.d.ts +1 -1
- package/dist/types/mixins/OxyServices.location.d.ts +1 -1
- package/dist/types/mixins/OxyServices.managedAccounts.d.ts +1 -1
- package/dist/types/mixins/OxyServices.payment.d.ts +1 -1
- package/dist/types/mixins/OxyServices.popup.d.ts +18 -120
- package/dist/types/mixins/OxyServices.privacy.d.ts +1 -1
- package/dist/types/mixins/OxyServices.redirect.d.ts +13 -174
- package/dist/types/mixins/OxyServices.reputation.d.ts +1 -1
- package/dist/types/mixins/OxyServices.security.d.ts +1 -1
- package/dist/types/mixins/OxyServices.silent.d.ts +131 -0
- package/dist/types/mixins/OxyServices.sso.d.ts +4 -5
- package/dist/types/mixins/OxyServices.topics.d.ts +1 -1
- package/dist/types/mixins/OxyServices.user.d.ts +1 -1
- package/dist/types/mixins/OxyServices.utility.d.ts +3 -8
- package/dist/types/mixins/OxyServices.workspaces.d.ts +1 -1
- package/dist/types/mixins/index.d.ts +3 -3
- package/dist/types/models/interfaces.d.ts +5 -16
- package/dist/types/models/session.d.ts +0 -2
- package/dist/types/server/index.d.ts +18 -0
- package/dist/types/server/rateLimit.d.ts +40 -0
- package/dist/types/shared/utils/debugUtils.d.ts +1 -1
- package/dist/types/utils/authHelpers.d.ts +4 -3
- package/dist/types/utils/coldBoot.d.ts +2 -2
- package/dist/types/utils/fapiAutoDetect.d.ts +1 -1
- package/package.json +25 -3
- package/src/AuthManager.ts +100 -370
- package/src/AuthManagerTypes.ts +5 -9
- package/src/CrossDomainAuth.ts +22 -129
- package/src/HttpService.ts +55 -73
- package/src/OxyServices.base.ts +2 -3
- package/src/OxyServices.ts +9 -11
- package/src/__tests__/authManager.cookiePath.test.ts +19 -17
- package/src/__tests__/authManager.security.test.ts +7 -3
- package/src/__tests__/crossDomainAuth.test.ts +26 -118
- package/src/i18n/index.ts +7 -1
- package/src/i18n/locales/ar-SA.json +18 -2
- package/src/i18n/locales/ca-ES.json +18 -2
- package/src/i18n/locales/de-DE.json +18 -2
- package/src/i18n/locales/en-US.json +17 -3
- package/src/i18n/locales/es-ES.json +16 -2
- package/src/i18n/locales/fr-FR.json +18 -2
- package/src/i18n/locales/it-IT.json +18 -2
- package/src/i18n/locales/ja-JP.json +18 -2
- package/src/i18n/locales/ko-KR.json +18 -2
- package/src/i18n/locales/pt-PT.json +18 -2
- package/src/i18n/locales/zh-CN.json +18 -2
- package/src/index.ts +1 -1
- package/src/mixins/OxyServices.auth.ts +23 -75
- package/src/mixins/OxyServices.fedcm.ts +10 -12
- package/src/mixins/OxyServices.redirect.ts +82 -371
- package/src/mixins/OxyServices.silent.ts +272 -0
- package/src/mixins/OxyServices.sso.ts +5 -6
- package/src/mixins/OxyServices.utility.ts +9 -22
- package/src/mixins/__tests__/appData.test.ts +1 -1
- package/src/mixins/__tests__/onTokensChanged.test.ts +1 -1
- package/src/mixins/__tests__/reputation.test.ts +1 -1
- package/src/mixins/__tests__/serviceAuth.test.ts +7 -5
- package/src/mixins/__tests__/silent.test.ts +102 -0
- package/src/mixins/__tests__/verifyChallenge.test.ts +9 -14
- package/src/mixins/index.ts +6 -8
- package/src/models/interfaces.ts +5 -16
- package/src/models/session.ts +1 -3
- package/src/server/index.ts +19 -0
- package/src/server/rateLimit.ts +170 -0
- package/src/shared/utils/debugUtils.ts +1 -1
- package/src/utils/accountUtils.ts +4 -4
- package/src/utils/authHelpers.ts +23 -15
- package/src/utils/coldBoot.ts +4 -4
- package/src/utils/fapiAutoDetect.ts +1 -1
- package/src/mixins/OxyServices.popup.ts +0 -631
- package/src/mixins/__tests__/popup.test.ts +0 -374
|
@@ -1,27 +1,12 @@
|
|
|
1
|
-
import { OxyAuthenticationError } from
|
|
2
|
-
import { createDebugLogger } from
|
|
3
|
-
const debug = createDebugLogger(
|
|
1
|
+
import { OxyAuthenticationError } from "../OxyServices.errors.js";
|
|
2
|
+
import { createDebugLogger } from "../shared/utils/debugUtils.js";
|
|
3
|
+
const debug = createDebugLogger("PopupAuth");
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Cross-domain browser auth helpers.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* Flow:
|
|
12
|
-
* 1. Opens small popup window to auth.oxy.so
|
|
13
|
-
* 2. User signs in (auth.oxy.so sets its own first-party cookie)
|
|
14
|
-
* 3. auth.oxy.so sends token back via postMessage
|
|
15
|
-
* 4. Popup closes, parent app has the session
|
|
16
|
-
*
|
|
17
|
-
* Features:
|
|
18
|
-
* - No full page redirect (preserves app state)
|
|
19
|
-
* - Works across different domains (homiio.com, mention.earth, etc.)
|
|
20
|
-
* - Silent refresh using hidden iframe for seamless SSO
|
|
21
|
-
* - CSRF protection via state parameter
|
|
22
|
-
* - XSS protection via origin validation
|
|
23
|
-
*
|
|
24
|
-
* Browser Support: All modern browsers (IE11+)
|
|
7
|
+
* Popup sign-in is intentionally fail-closed in the clean session model because
|
|
8
|
+
* the historical implementation required bearer-token callback URLs. FedCM,
|
|
9
|
+
* redirect SSO, and silent iframe SSO are the supported browser paths.
|
|
25
10
|
*/
|
|
26
11
|
export function OxyServicesPopupAuthMixin(Base) {
|
|
27
12
|
var _a;
|
|
@@ -31,125 +16,25 @@ export function OxyServicesPopupAuthMixin(Base) {
|
|
|
31
16
|
}
|
|
32
17
|
/** Resolve auth URL from config or static default (method, not getter — getters break in TS mixins) */
|
|
33
18
|
resolveAuthUrl() {
|
|
34
|
-
return this.config.authWebUrl || this.constructor.DEFAULT_AUTH_URL;
|
|
19
|
+
return (this.config.authWebUrl || this.constructor.DEFAULT_AUTH_URL);
|
|
35
20
|
}
|
|
36
21
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* Opens a centered popup window to auth.oxy.so where the user can sign in.
|
|
40
|
-
* The popup automatically closes after successful authentication and the
|
|
41
|
-
* session is returned to the parent window.
|
|
42
|
-
*
|
|
43
|
-
* @param options - Popup configuration options
|
|
44
|
-
* @returns Session with access token and user data
|
|
45
|
-
* @throws {OxyAuthenticationError} If popup is blocked or auth fails
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```typescript
|
|
49
|
-
* const handleSignIn = async () => {
|
|
50
|
-
* try {
|
|
51
|
-
* const session = await oxyServices.signInWithPopup();
|
|
52
|
-
* console.log('Signed in:', session.user);
|
|
53
|
-
* } catch (error) {
|
|
54
|
-
* if (error.message.includes('blocked')) {
|
|
55
|
-
* alert('Please allow popups for this site');
|
|
56
|
-
* }
|
|
57
|
-
* }
|
|
58
|
-
* };
|
|
59
|
-
* ```
|
|
22
|
+
* Removed popup sign-in. Closes a caller-supplied popup handle and throws.
|
|
60
23
|
*/
|
|
61
24
|
async signInWithPopup(options = {}) {
|
|
62
|
-
if (typeof window ===
|
|
63
|
-
throw new OxyAuthenticationError(
|
|
64
|
-
}
|
|
65
|
-
const state = this.generateState();
|
|
66
|
-
const nonce = this.generateNonce();
|
|
67
|
-
// Store state for CSRF protection
|
|
68
|
-
this.storeAuthState(state, nonce);
|
|
69
|
-
const width = options.width || this.constructor.POPUP_WIDTH;
|
|
70
|
-
const height = options.height || this.constructor.POPUP_HEIGHT;
|
|
71
|
-
const timeout = options.timeout || this.constructor.POPUP_TIMEOUT;
|
|
72
|
-
const mode = options.mode || 'login';
|
|
73
|
-
const authUrl = this.buildAuthUrl({
|
|
74
|
-
mode,
|
|
75
|
-
state,
|
|
76
|
-
nonce,
|
|
77
|
-
clientId: window.location.origin,
|
|
78
|
-
redirectUri: `${this.resolveAuthUrl()}/auth/callback`,
|
|
79
|
-
});
|
|
80
|
-
// If the caller pre-opened a popup on the raw user gesture (recommended
|
|
81
|
-
// path — see `openBlankPopup` and `PopupAuthOptions.popup`), navigate it
|
|
82
|
-
// to the auth URL instead of issuing a fresh `window.open` (which would
|
|
83
|
-
// be blocked once any prior `await` has consumed the user activation).
|
|
84
|
-
let popup;
|
|
85
|
-
const preOpened = options.popup ?? null;
|
|
86
|
-
if (preOpened) {
|
|
87
|
-
if (preOpened.closed) {
|
|
88
|
-
// The pre-opened popup is gone — distinguish a user cancel (they
|
|
89
|
-
// closed the blank window before sign-in could navigate it) from a
|
|
90
|
-
// blocker rejection. Lumping these together as "Popup blocked" is
|
|
91
|
-
// misleading: the popup was NOT blocked, it was opened successfully
|
|
92
|
-
// and then dismissed.
|
|
93
|
-
throw new OxyAuthenticationError('Sign-in window was closed before authentication could start.');
|
|
94
|
-
}
|
|
95
|
-
try {
|
|
96
|
-
preOpened.location.replace(authUrl);
|
|
97
|
-
}
|
|
98
|
-
catch (replaceError) {
|
|
99
|
-
// `location.replace` can throw in sandboxed / cross-origin-locked
|
|
100
|
-
// environments. Fall back to `href` assignment, which is more
|
|
101
|
-
// permissive. Logged at debug-level so consumers can correlate
|
|
102
|
-
// unusual sign-in behaviour without producing noise in normal flows.
|
|
103
|
-
debug.warn('location.replace failed, falling back to location.href', replaceError);
|
|
104
|
-
preOpened.location.href = authUrl;
|
|
105
|
-
}
|
|
106
|
-
popup = preOpened;
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
popup = this.openCenteredPopup(authUrl, 'Oxy Sign In', width, height);
|
|
25
|
+
if (typeof window === "undefined") {
|
|
26
|
+
throw new OxyAuthenticationError("Popup authentication requires browser environment");
|
|
110
27
|
}
|
|
111
|
-
if (!popup) {
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
try {
|
|
115
|
-
const session = await this.waitForPopupAuth(popup, state, timeout);
|
|
116
|
-
// Store access token if present
|
|
117
|
-
if (session && session.accessToken) {
|
|
118
|
-
this.httpService.setTokens(session.accessToken);
|
|
119
|
-
}
|
|
120
|
-
// Fetch user data using the session ID
|
|
121
|
-
// The callback page only sends sessionId/accessToken, not user data
|
|
122
|
-
if (session && session.sessionId && !session.user) {
|
|
123
|
-
try {
|
|
124
|
-
const userData = await this.makeRequest('GET', `/session/user/${session.sessionId}`, undefined, { cache: false });
|
|
125
|
-
if (userData) {
|
|
126
|
-
session.user = userData;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
catch (userError) {
|
|
130
|
-
debug.warn('Failed to fetch user data:', userError);
|
|
131
|
-
// Continue without user data - caller can fetch separately
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return session;
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
throw error;
|
|
138
|
-
}
|
|
139
|
-
finally {
|
|
140
|
-
this.clearAuthState(state);
|
|
28
|
+
if (options.popup && !options.popup.closed) {
|
|
29
|
+
options.popup.close();
|
|
141
30
|
}
|
|
31
|
+
throw new OxyAuthenticationError("Popup authentication has been removed because it required access-token callback URLs. Use FedCM or redirect authentication.");
|
|
142
32
|
}
|
|
143
33
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* Same as signInWithPopup but opens the signup page by default.
|
|
147
|
-
*
|
|
148
|
-
* @param options - Popup configuration options
|
|
149
|
-
* @returns Session with access token and user data
|
|
34
|
+
* Removed popup signup. Closes a caller-supplied popup handle and throws.
|
|
150
35
|
*/
|
|
151
36
|
async signUpWithPopup(options = {}) {
|
|
152
|
-
return this.signInWithPopup({ ...options, mode:
|
|
37
|
+
return this.signInWithPopup({ ...options, mode: "signup" });
|
|
153
38
|
}
|
|
154
39
|
/**
|
|
155
40
|
* Silent sign-in using hidden iframe
|
|
@@ -159,7 +44,7 @@ export function OxyServicesPopupAuthMixin(Base) {
|
|
|
159
44
|
*
|
|
160
45
|
* How it works:
|
|
161
46
|
* 1. Creates hidden iframe pointing to auth.oxy.so/silent-auth
|
|
162
|
-
* 2. If user has valid session at auth.oxy.so, it
|
|
47
|
+
* 2. If user has valid session at auth.oxy.so, it exchanges an opaque SSO code
|
|
163
48
|
* 3. If not, iframe responds with null (no error thrown)
|
|
164
49
|
*
|
|
165
50
|
* This should be called on app startup to check for existing sessions.
|
|
@@ -181,7 +66,7 @@ export function OxyServicesPopupAuthMixin(Base) {
|
|
|
181
66
|
* ```
|
|
182
67
|
*/
|
|
183
68
|
async silentSignIn(options = {}) {
|
|
184
|
-
if (typeof window ===
|
|
69
|
+
if (typeof window === "undefined") {
|
|
185
70
|
return null;
|
|
186
71
|
}
|
|
187
72
|
const timeout = options.timeout || this.constructor.SILENT_TIMEOUT;
|
|
@@ -195,13 +80,15 @@ export function OxyServicesPopupAuthMixin(Base) {
|
|
|
195
80
|
const authOrigin = options.authWebUrlOverride && options.authWebUrlOverride.length > 0
|
|
196
81
|
? options.authWebUrlOverride
|
|
197
82
|
: this.resolveAuthUrl();
|
|
198
|
-
const iframe = document.createElement(
|
|
199
|
-
iframe.style.display =
|
|
200
|
-
iframe.style.position =
|
|
201
|
-
iframe.style.width =
|
|
202
|
-
iframe.style.height =
|
|
203
|
-
iframe.style.border =
|
|
204
|
-
const silentUrl = `${authOrigin}/auth/silent?` +
|
|
83
|
+
const iframe = document.createElement("iframe");
|
|
84
|
+
iframe.style.display = "none";
|
|
85
|
+
iframe.style.position = "absolute";
|
|
86
|
+
iframe.style.width = "0";
|
|
87
|
+
iframe.style.height = "0";
|
|
88
|
+
iframe.style.border = "none";
|
|
89
|
+
const silentUrl = `${authOrigin}/auth/silent?` +
|
|
90
|
+
`client_id=${encodeURIComponent(clientId)}&` +
|
|
91
|
+
`nonce=${nonce}`;
|
|
205
92
|
iframe.src = silentUrl;
|
|
206
93
|
document.body.appendChild(iframe);
|
|
207
94
|
try {
|
|
@@ -214,7 +101,9 @@ export function OxyServicesPopupAuthMixin(Base) {
|
|
|
214
101
|
// -> 401 -> token-clear loop in consumer apps because callers gated
|
|
215
102
|
// on `session?.user` and never installed the user via
|
|
216
103
|
// `handleAuthSuccess`, while HttpService quietly held the token.
|
|
217
|
-
const accessToken = session
|
|
104
|
+
const accessToken = session
|
|
105
|
+
? session.accessToken
|
|
106
|
+
: undefined;
|
|
218
107
|
if (!session || !accessToken || !session.sessionId) {
|
|
219
108
|
return null;
|
|
220
109
|
}
|
|
@@ -232,14 +121,14 @@ export function OxyServicesPopupAuthMixin(Base) {
|
|
|
232
121
|
// missing-session response.
|
|
233
122
|
if (!session.user) {
|
|
234
123
|
try {
|
|
235
|
-
const userData = await this.makeRequest(
|
|
124
|
+
const userData = await this.makeRequest("GET", `/session/user/${session.sessionId}`, undefined, { cache: false, retry: false });
|
|
236
125
|
if (!userData) {
|
|
237
|
-
throw new Error(
|
|
126
|
+
throw new Error("Empty user response");
|
|
238
127
|
}
|
|
239
128
|
session.user = userData;
|
|
240
129
|
}
|
|
241
130
|
catch (userError) {
|
|
242
|
-
debug.warn(
|
|
131
|
+
debug.warn("silentSignIn: failed to fetch user data, rolling back token", userError);
|
|
243
132
|
if (previousAccessToken) {
|
|
244
133
|
this.httpService.setTokens(previousAccessToken);
|
|
245
134
|
}
|
|
@@ -261,32 +150,17 @@ export function OxyServicesPopupAuthMixin(Base) {
|
|
|
261
150
|
/**
|
|
262
151
|
* Open a blank, centered popup window SYNCHRONOUSLY.
|
|
263
152
|
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
* `signInWithPopup({ popup })` once the async portion of the flow runs.
|
|
267
|
-
*
|
|
268
|
-
* Returns `null` if the browser's popup blocker rejected the open.
|
|
269
|
-
*
|
|
270
|
-
* @example
|
|
271
|
-
* ```typescript
|
|
272
|
-
* const onSignInClick = () => {
|
|
273
|
-
* const popup = oxyServices.openBlankPopup();
|
|
274
|
-
* (async () => {
|
|
275
|
-
* const silent = await oxyServices.silentSignInWithFedCM();
|
|
276
|
-
* if (silent) { popup?.close(); return; }
|
|
277
|
-
* await oxyServices.signInWithPopup({ popup });
|
|
278
|
-
* })();
|
|
279
|
-
* };
|
|
280
|
-
* ```
|
|
153
|
+
* Kept only so legacy callers can pass a handle to the removed popup method,
|
|
154
|
+
* which closes it before throwing. New auth code should use FedCM or redirect.
|
|
281
155
|
*/
|
|
282
156
|
openBlankPopup(width, height) {
|
|
283
|
-
if (typeof window ===
|
|
157
|
+
if (typeof window === "undefined") {
|
|
284
158
|
return null;
|
|
285
159
|
}
|
|
286
160
|
const ctor = this.constructor;
|
|
287
161
|
const w = width ?? ctor.POPUP_WIDTH;
|
|
288
162
|
const h = height ?? ctor.POPUP_HEIGHT;
|
|
289
|
-
return this.openCenteredPopup(
|
|
163
|
+
return this.openCenteredPopup("about:blank", "Oxy Sign In", w, h);
|
|
290
164
|
}
|
|
291
165
|
/**
|
|
292
166
|
* Open a centered popup window
|
|
@@ -301,86 +175,15 @@ export function OxyServicesPopupAuthMixin(Base) {
|
|
|
301
175
|
`height=${height}`,
|
|
302
176
|
`left=${left}`,
|
|
303
177
|
`top=${top}`,
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
].join(
|
|
178
|
+
"toolbar=no",
|
|
179
|
+
"menubar=no",
|
|
180
|
+
"scrollbars=yes",
|
|
181
|
+
"resizable=yes",
|
|
182
|
+
"status=no",
|
|
183
|
+
"location=no",
|
|
184
|
+
].join(",");
|
|
311
185
|
return window.open(url, title, features);
|
|
312
186
|
}
|
|
313
|
-
/**
|
|
314
|
-
* Wait for authentication response from popup
|
|
315
|
-
*
|
|
316
|
-
* @private
|
|
317
|
-
*/
|
|
318
|
-
async waitForPopupAuth(popup, expectedState, timeout) {
|
|
319
|
-
return new Promise((resolve, reject) => {
|
|
320
|
-
const timeoutId = setTimeout(() => {
|
|
321
|
-
cleanup();
|
|
322
|
-
reject(new OxyAuthenticationError('Authentication timeout'));
|
|
323
|
-
}, timeout);
|
|
324
|
-
const messageHandler = (event) => {
|
|
325
|
-
const authUrl = this.resolveAuthUrl();
|
|
326
|
-
// Log all messages for debugging
|
|
327
|
-
if (event.data && typeof event.data === 'object' && event.data.type) {
|
|
328
|
-
debug.log('Message received:', {
|
|
329
|
-
origin: event.origin,
|
|
330
|
-
expectedOrigin: authUrl,
|
|
331
|
-
type: event.data.type,
|
|
332
|
-
hasSession: !!event.data.session,
|
|
333
|
-
hasError: !!event.data.error,
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
// CRITICAL: Verify origin to prevent XSS attacks
|
|
337
|
-
if (event.origin !== authUrl) {
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
const { type, state, session, error } = event.data;
|
|
341
|
-
if (type !== 'oxy_auth_response') {
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
debug.log('Valid auth response:', { state, expectedState, hasSession: !!session, error });
|
|
345
|
-
// Verify state parameter to prevent CSRF attacks
|
|
346
|
-
if (state !== expectedState) {
|
|
347
|
-
cleanup();
|
|
348
|
-
debug.error('State mismatch');
|
|
349
|
-
reject(new OxyAuthenticationError('Invalid state parameter. Possible CSRF attack.'));
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
cleanup();
|
|
353
|
-
if (error) {
|
|
354
|
-
debug.error('Auth error:', error);
|
|
355
|
-
reject(new OxyAuthenticationError(error));
|
|
356
|
-
}
|
|
357
|
-
else if (session) {
|
|
358
|
-
debug.log('Session received successfully');
|
|
359
|
-
resolve(session);
|
|
360
|
-
}
|
|
361
|
-
else {
|
|
362
|
-
debug.error('No session in response');
|
|
363
|
-
reject(new OxyAuthenticationError('No session received from authentication server'));
|
|
364
|
-
}
|
|
365
|
-
};
|
|
366
|
-
// Poll to detect if user closed the popup
|
|
367
|
-
const pollInterval = setInterval(() => {
|
|
368
|
-
if (popup.closed) {
|
|
369
|
-
cleanup();
|
|
370
|
-
reject(new OxyAuthenticationError('Authentication cancelled by user'));
|
|
371
|
-
}
|
|
372
|
-
}, 500);
|
|
373
|
-
const cleanup = () => {
|
|
374
|
-
clearTimeout(timeoutId);
|
|
375
|
-
clearInterval(pollInterval);
|
|
376
|
-
window.removeEventListener('message', messageHandler);
|
|
377
|
-
if (!popup.closed) {
|
|
378
|
-
popup.close();
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
window.addEventListener('message', messageHandler);
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
187
|
/**
|
|
385
188
|
* Wait for authentication response from iframe
|
|
386
189
|
*
|
|
@@ -401,7 +204,7 @@ export function OxyServicesPopupAuthMixin(Base) {
|
|
|
401
204
|
return;
|
|
402
205
|
}
|
|
403
206
|
const { type, session } = event.data;
|
|
404
|
-
if (type !==
|
|
207
|
+
if (type !== "oxy_silent_auth") {
|
|
405
208
|
return;
|
|
406
209
|
}
|
|
407
210
|
cleanup();
|
|
@@ -425,83 +228,31 @@ export function OxyServicesPopupAuthMixin(Base) {
|
|
|
425
228
|
clearTimeout(timeoutId);
|
|
426
229
|
iframe.onerror = null;
|
|
427
230
|
iframe.onabort = null;
|
|
428
|
-
window.removeEventListener(
|
|
231
|
+
window.removeEventListener("message", messageHandler);
|
|
429
232
|
};
|
|
430
|
-
window.addEventListener(
|
|
233
|
+
window.addEventListener("message", messageHandler);
|
|
431
234
|
});
|
|
432
235
|
}
|
|
433
|
-
/**
|
|
434
|
-
* Build authentication URL with query parameters
|
|
435
|
-
*
|
|
436
|
-
* @private
|
|
437
|
-
*/
|
|
438
|
-
buildAuthUrl(params) {
|
|
439
|
-
const url = new URL(`${this.resolveAuthUrl()}/${params.mode}`);
|
|
440
|
-
url.searchParams.set('response_type', 'token');
|
|
441
|
-
url.searchParams.set('client_id', params.clientId);
|
|
442
|
-
url.searchParams.set('redirect_uri', params.redirectUri);
|
|
443
|
-
url.searchParams.set('state', params.state);
|
|
444
|
-
url.searchParams.set('nonce', params.nonce);
|
|
445
|
-
return url.toString();
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Generate cryptographically secure state for CSRF protection
|
|
449
|
-
*
|
|
450
|
-
* @private
|
|
451
|
-
*/
|
|
452
|
-
generateState() {
|
|
453
|
-
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
454
|
-
return crypto.randomUUID();
|
|
455
|
-
}
|
|
456
|
-
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
|
|
457
|
-
const bytes = new Uint8Array(16);
|
|
458
|
-
crypto.getRandomValues(bytes);
|
|
459
|
-
return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
|
|
460
|
-
}
|
|
461
|
-
throw new Error('No secure random source available for state generation');
|
|
462
|
-
}
|
|
463
236
|
/**
|
|
464
237
|
* Generate nonce for replay attack prevention
|
|
465
238
|
*
|
|
466
239
|
* @private
|
|
467
240
|
*/
|
|
468
241
|
generateNonce() {
|
|
469
|
-
if (typeof crypto !==
|
|
242
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
470
243
|
return crypto.randomUUID();
|
|
471
244
|
}
|
|
472
|
-
if (typeof crypto !==
|
|
245
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
473
246
|
const bytes = new Uint8Array(16);
|
|
474
247
|
crypto.getRandomValues(bytes);
|
|
475
|
-
return Array.from(bytes, b => b.toString(16).padStart(2,
|
|
476
|
-
}
|
|
477
|
-
throw new Error('No secure random source available for nonce generation');
|
|
478
|
-
}
|
|
479
|
-
/**
|
|
480
|
-
* Store auth state in session storage
|
|
481
|
-
*
|
|
482
|
-
* @private
|
|
483
|
-
*/
|
|
484
|
-
storeAuthState(state, nonce) {
|
|
485
|
-
if (typeof window !== 'undefined' && window.sessionStorage) {
|
|
486
|
-
sessionStorage.setItem(`oxy_auth_state_${state}`, JSON.stringify({ nonce, timestamp: Date.now() }));
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* Clear auth state from session storage
|
|
491
|
-
*
|
|
492
|
-
* @private
|
|
493
|
-
*/
|
|
494
|
-
clearAuthState(state) {
|
|
495
|
-
if (typeof window !== 'undefined' && window.sessionStorage) {
|
|
496
|
-
sessionStorage.removeItem(`oxy_auth_state_${state}`);
|
|
248
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
497
249
|
}
|
|
250
|
+
throw new Error("No secure random source available for nonce generation");
|
|
498
251
|
}
|
|
499
252
|
},
|
|
500
|
-
_a.DEFAULT_AUTH_URL =
|
|
253
|
+
_a.DEFAULT_AUTH_URL = "https://auth.oxy.so",
|
|
501
254
|
_a.POPUP_WIDTH = 500,
|
|
502
255
|
_a.POPUP_HEIGHT = 700,
|
|
503
|
-
_a.POPUP_TIMEOUT = 60000 // 1 minute
|
|
504
|
-
,
|
|
505
256
|
_a.SILENT_TIMEOUT = 5000 // 5 seconds
|
|
506
257
|
,
|
|
507
258
|
_a;
|