@oxyhq/core 12.10.4 → 12.10.6
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/i18n/locales/en-US.json +1 -0
- package/dist/cjs/i18n/locales/es-ES.json +1 -0
- package/dist/cjs/i18n/locales/locales/en-US.json +1 -0
- package/dist/cjs/i18n/locales/locales/es-ES.json +1 -0
- package/dist/cjs/index.js +5 -1
- package/dist/cjs/session/hubSync.js +1 -1
- package/dist/cjs/utils/oauthPkce.js +80 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/i18n/locales/en-US.json +1 -0
- package/dist/esm/i18n/locales/es-ES.json +1 -0
- package/dist/esm/i18n/locales/locales/en-US.json +1 -0
- package/dist/esm/i18n/locales/locales/es-ES.json +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/session/hubSync.js +1 -1
- package/dist/esm/utils/oauthPkce.js +77 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/utils/oauthPkce.d.ts +32 -0
- package/package.json +1 -1
- package/src/i18n/locales/en-US.json +1 -0
- package/src/i18n/locales/es-ES.json +1 -0
- package/src/index.ts +3 -0
- package/src/session/__tests__/hubSync.test.ts +51 -0
- package/src/session/hubSync.ts +1 -1
- package/src/utils/__tests__/oauthPkce.test.ts +67 -0
- package/src/utils/oauthPkce.ts +77 -0
|
@@ -1552,6 +1552,7 @@
|
|
|
1552
1552
|
"fileEmpty": "File \"{{name}}\" is empty",
|
|
1553
1553
|
"fileTooLarge": "\"{{name}}\" is too large. Maximum file size is {{maxSize}}",
|
|
1554
1554
|
"noValidFiles": "No valid files to upload",
|
|
1555
|
+
"imagesOnlyRequired": "Only image files can be selected",
|
|
1555
1556
|
"waitForSelection": "Please wait for the current file selection to complete",
|
|
1556
1557
|
"noFilesSelected": "No files were selected",
|
|
1557
1558
|
"loadSomeFailed": "Failed to load some files:\n{{errors}}",
|
|
@@ -1552,6 +1552,7 @@
|
|
|
1552
1552
|
"fileEmpty": "El archivo \"{{name}}\" está vacío",
|
|
1553
1553
|
"fileTooLarge": "\"{{name}}\" es demasiado grande. El tamaño máximo es {{maxSize}}",
|
|
1554
1554
|
"noValidFiles": "No hay archivos válidos para subir",
|
|
1555
|
+
"imagesOnlyRequired": "Solo se pueden seleccionar archivos de imagen",
|
|
1555
1556
|
"waitForSelection": "Espera a que se complete la selección actual",
|
|
1556
1557
|
"noFilesSelected": "No se seleccionaron archivos",
|
|
1557
1558
|
"loadSomeFailed": "Error al cargar algunos archivos:\n{{errors}}",
|
|
@@ -1552,6 +1552,7 @@
|
|
|
1552
1552
|
"fileEmpty": "File \"{{name}}\" is empty",
|
|
1553
1553
|
"fileTooLarge": "\"{{name}}\" is too large. Maximum file size is {{maxSize}}",
|
|
1554
1554
|
"noValidFiles": "No valid files to upload",
|
|
1555
|
+
"imagesOnlyRequired": "Only image files can be selected",
|
|
1555
1556
|
"waitForSelection": "Please wait for the current file selection to complete",
|
|
1556
1557
|
"noFilesSelected": "No files were selected",
|
|
1557
1558
|
"loadSomeFailed": "Failed to load some files:\n{{errors}}",
|
|
@@ -1552,6 +1552,7 @@
|
|
|
1552
1552
|
"fileEmpty": "El archivo \"{{name}}\" está vacío",
|
|
1553
1553
|
"fileTooLarge": "\"{{name}}\" es demasiado grande. El tamaño máximo es {{maxSize}}",
|
|
1554
1554
|
"noValidFiles": "No hay archivos válidos para subir",
|
|
1555
|
+
"imagesOnlyRequired": "Solo se pueden seleccionar archivos de imagen",
|
|
1555
1556
|
"waitForSelection": "Espera a que se complete la selección actual",
|
|
1556
1557
|
"noFilesSelected": "No se seleccionaron archivos",
|
|
1557
1558
|
"loadSomeFailed": "Error al cargar algunos archivos:\n{{errors}}",
|
package/dist/esm/index.js
CHANGED
|
@@ -145,7 +145,7 @@ export { runColdBoot } from './utils/coldBoot.js';
|
|
|
145
145
|
// OAuth 2.0 Authorization Code + PKCE helpers ("Sign in with Oxy" third party).
|
|
146
146
|
// Standard OAuth against auth.oxy.so/authorize — no FedCM/cookies/SSO bounce.
|
|
147
147
|
// ---------------------------------------------------------------------------
|
|
148
|
-
export { buildOAuthAuthorizeUrl, computeCodeChallenge, generateOAuthState, generatePkcePair, DEFAULT_OAUTH_SCOPE, OXY_AUTHORIZE_URL, OXY_OAUTH_STATE_STORAGE_KEY, OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY, OXY_SILENT_OAUTH_ATTEMPTED_KEY, OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY, normalizeOAuthRedirectUri, persistOAuthHandshake, readOAuthHandshake, clearOAuthHandshake, } from './utils/oauthPkce.js';
|
|
148
|
+
export { buildOAuthAuthorizeUrl, computeCodeChallenge, generateOAuthState, generatePkcePair, DEFAULT_OAUTH_SCOPE, OXY_AUTHORIZE_URL, OXY_OAUTH_STATE_STORAGE_KEY, OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY, OXY_SILENT_OAUTH_ATTEMPTED_KEY, OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY, OXY_OAUTH_RETURN_PATH_STORAGE_KEY, normalizeOAuthRedirectUri, persistOAuthHandshake, readOAuthHandshake, clearOAuthHandshake, persistOAuthReturnPath, consumeOAuthReturnPath, } from './utils/oauthPkce.js';
|
|
149
149
|
export { buildIdpHubOrigin, buildHubSyncUrl, isIdpHubOrigin, isLoopbackOrigin, isOfficialWebOrigin, isAllowedDeviceJoinOrigin, normalizeOfficialReturnOrigin, parseHubSyncReturnUrl, } from './utils/officialOrigins.js';
|
|
150
150
|
export { syncHubAfterSignIn, redeemHubTicketOnHub, } from './session/hubSync.js';
|
|
151
151
|
// ---------------------------------------------------------------------------
|
|
@@ -29,7 +29,7 @@ export async function syncHubAfterSignIn(oxy, opts) {
|
|
|
29
29
|
}
|
|
30
30
|
const hubOrigin = buildIdpHubOrigin();
|
|
31
31
|
const issued = await oxy.issueHubTicket(hubOrigin);
|
|
32
|
-
const returnUrl = `${location.origin}${location.pathname}${location.search}`;
|
|
32
|
+
const returnUrl = `${location.origin}${location.pathname}${location.search}${location.hash}`;
|
|
33
33
|
const syncUrl = buildHubSyncUrl(issued.ticket, returnUrl);
|
|
34
34
|
window.location.assign(syncUrl);
|
|
35
35
|
return true;
|
|
@@ -144,9 +144,19 @@ export const OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY = 'oxy_oauth_code_verifier';
|
|
|
144
144
|
export const OXY_SILENT_OAUTH_ATTEMPTED_KEY = 'oxy.silent_oauth_attempted';
|
|
145
145
|
/** `sessionStorage` key — blocks further cross-origin auto-restore in this tab. */
|
|
146
146
|
export const OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY = 'oxy.cross_origin_restore_attempted';
|
|
147
|
+
/**
|
|
148
|
+
* `sessionStorage` key for the in-app path to return to after an authorize
|
|
149
|
+
* round trip. See {@link persistOAuthReturnPath}.
|
|
150
|
+
*/
|
|
151
|
+
export const OXY_OAUTH_RETURN_PATH_STORAGE_KEY = 'oxy.oauth_return_path';
|
|
147
152
|
/**
|
|
148
153
|
* Normalize a redirect URI to its origin. Official Oxy apps register apex
|
|
149
154
|
* origins (`https://inbox.oxy.so`) — never path-qualified URLs.
|
|
155
|
+
*
|
|
156
|
+
* This is why the path cannot ride along in `redirect_uri`, and why
|
|
157
|
+
* {@link persistOAuthReturnPath} exists: the authorize round trip always lands
|
|
158
|
+
* back on the bare origin, so the page the user was on has to be carried
|
|
159
|
+
* out-of-band and restored on return.
|
|
150
160
|
*/
|
|
151
161
|
export function normalizeOAuthRedirectUri(input) {
|
|
152
162
|
try {
|
|
@@ -156,6 +166,70 @@ export function normalizeOAuthRedirectUri(input) {
|
|
|
156
166
|
return input;
|
|
157
167
|
}
|
|
158
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Is this a safe same-origin path to restore into the address bar?
|
|
171
|
+
*
|
|
172
|
+
* Only site-relative paths are accepted. `//evil.com` and `/\evil.com` are
|
|
173
|
+
* rejected because browsers resolve both as protocol-relative *absolute* URLs —
|
|
174
|
+
* without this check, anything able to write one key of `sessionStorage` could
|
|
175
|
+
* turn the post-authorize restore into an open redirect.
|
|
176
|
+
*/
|
|
177
|
+
function isSafeReturnPath(value) {
|
|
178
|
+
if (!value.startsWith('/'))
|
|
179
|
+
return false;
|
|
180
|
+
if (value.startsWith('//') || value.startsWith('/\\'))
|
|
181
|
+
return false;
|
|
182
|
+
if (value.length > 2048)
|
|
183
|
+
return false;
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Remember the page the user was on before a full-page authorize redirect.
|
|
188
|
+
*
|
|
189
|
+
* Silent cross-origin restore navigates the whole tab to the IdP and back, and
|
|
190
|
+
* `redirect_uri` must be a registered apex origin, so the browser returns to
|
|
191
|
+
* `/` no matter which page the visit started on. Without this, every deep link
|
|
192
|
+
* — a shared URL, a search result, an ad landing page — silently became the
|
|
193
|
+
* home page for signed-out visitors on their first navigation in a tab.
|
|
194
|
+
*
|
|
195
|
+
* Stored next to the PKCE handshake it belongs to, and cleared by
|
|
196
|
+
* {@link clearOAuthHandshake} so a stale entry can never be applied to an
|
|
197
|
+
* unrelated later navigation.
|
|
198
|
+
*/
|
|
199
|
+
export function persistOAuthReturnPath(path) {
|
|
200
|
+
const store = globalThis.sessionStorage;
|
|
201
|
+
if (!store || !isSafeReturnPath(path))
|
|
202
|
+
return;
|
|
203
|
+
try {
|
|
204
|
+
store.setItem(OXY_OAUTH_RETURN_PATH_STORAGE_KEY, path);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
// Non-fatal: the round trip still completes, it just lands on the origin.
|
|
208
|
+
logger.warn('Could not persist OAuth return path to sessionStorage', { component: 'oauthPkce' }, error);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Read and remove the persisted return path, or `null` when there is none or
|
|
213
|
+
* it failed validation.
|
|
214
|
+
*
|
|
215
|
+
* Single-use by design: it is consumed on the first return so a leftover value
|
|
216
|
+
* cannot redirect a later navigation in the same tab.
|
|
217
|
+
*/
|
|
218
|
+
export function consumeOAuthReturnPath() {
|
|
219
|
+
const store = globalThis.sessionStorage;
|
|
220
|
+
if (!store)
|
|
221
|
+
return null;
|
|
222
|
+
try {
|
|
223
|
+
const value = store.getItem(OXY_OAUTH_RETURN_PATH_STORAGE_KEY);
|
|
224
|
+
store.removeItem(OXY_OAUTH_RETURN_PATH_STORAGE_KEY);
|
|
225
|
+
if (!value || !isSafeReturnPath(value))
|
|
226
|
+
return null;
|
|
227
|
+
return value;
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
159
233
|
/** Persist the OAuth handshake for a full-page redirect return (web only). */
|
|
160
234
|
export function persistOAuthHandshake(state, codeVerifier) {
|
|
161
235
|
const store = globalThis.sessionStorage;
|
|
@@ -188,6 +262,9 @@ export function clearOAuthHandshake() {
|
|
|
188
262
|
try {
|
|
189
263
|
store?.removeItem(OXY_OAUTH_STATE_STORAGE_KEY);
|
|
190
264
|
store?.removeItem(OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY);
|
|
265
|
+
// The return path belongs to this handshake. Leaving it behind would let a
|
|
266
|
+
// later, unrelated navigation in the same tab be redirected by it.
|
|
267
|
+
store?.removeItem(OXY_OAUTH_RETURN_PATH_STORAGE_KEY);
|
|
191
268
|
}
|
|
192
269
|
catch {
|
|
193
270
|
// Best-effort cleanup only.
|