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