From 5e2c7e0495e4e8982d08458f0583eb8e235f8db0 Mon Sep 17 00:00:00 2001 From: Daniel Salazar Date: Thu, 19 Mar 2026 12:24:14 -0700 Subject: [PATCH] Revert "fix: tighten cors logic for socket io (#2688)" (#2694) This reverts commit a94620de49ccf0937be2a7dc6b498dad6c37fe85. --- package-lock.json | 2 +- src/backend/src/helpers.js | 9 ++-- .../src/modules/web/SocketioService.js | 54 +++---------------- .../src/modules/web/WebServerService.js | 30 +++++------ src/backend/src/routers/_default.js | 3 +- src/backend/src/services/WSPushService.js | 37 +++++++------ src/gui/src/helpers/download.js | 1 - src/puter-js/package-lock.json | 4 +- src/puter-js/package.json | 2 +- src/puter-js/src/lib/utils.js | 31 +++++------ .../modules/FileSystem/operations/upload.js | 1 - 11 files changed, 58 insertions(+), 116 deletions(-) diff --git a/package-lock.json b/package-lock.json index 55ec9f68d..6d46b3d5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24422,7 +24422,7 @@ }, "src/puter-js": { "name": "@heyputer/puter.js", - "version": "2.2.14", + "version": "2.2.12", "license": "Apache-2.0", "dependencies": { "@heyputer/kv.js": "^0.2.1", diff --git a/src/backend/src/helpers.js b/src/backend/src/helpers.js index 037e7ca28..ede5d814b 100644 --- a/src/backend/src/helpers.js +++ b/src/backend/src/helpers.js @@ -1546,7 +1546,7 @@ export function subdomain (req) { return req.hostname.slice(0, -1 * (config.domain.length + 1)); } -export async function jwt_auth (req, authService) { +export async function jwt_auth (req) { let token; // HTTML Auth header if ( req.header && req.header('Authorization') ) @@ -1583,11 +1583,8 @@ export async function jwt_auth (req, authService) { } try { - if ( ! authService ) { - throw new Error('jwt_auth requires authService'); - } - - const actor = await authService.authenticate_from_token(token); + const svc_auth = Context.get('services').get('auth'); + const actor = await svc_auth.authenticate_from_token(token); if ( !actor.type?.constructor?.name === 'UserActorType' ) { throw ({ diff --git a/src/backend/src/modules/web/SocketioService.js b/src/backend/src/modules/web/SocketioService.js index b445e95cc..de0c954b1 100644 --- a/src/backend/src/modules/web/SocketioService.js +++ b/src/backend/src/modules/web/SocketioService.js @@ -22,22 +22,6 @@ const socketio = require('socket.io'); const { createAdapter } = require('@socket.io/redis-streams-adapter'); const { redisClient } = require('../../clients/redis/redisSingleton'); -const normalizeHost = (value) => { - if ( typeof value !== 'string' ) return null; - const trimmedValue = value.trim().toLowerCase().replace(/^\./, ''); - if ( ! trimmedValue ) return null; - return trimmedValue.split(':')[0]; -}; - -const extractOriginHost = (origin) => { - if ( typeof origin !== 'string' || origin.length === 0 ) return null; - try { - return normalizeHost(new URL(origin).host); - } catch { - return null; - } -}; - /** * SocketioService provides a service for sending messages to clients. * socket.io is used behind the scenes. This service provides a simpler @@ -50,39 +34,15 @@ class SocketioService extends BaseService { * @evtparam server The server to attach socket.io to. */ '__on_install.socketio' (_, { server }) { - const uiHost = normalizeHost(this.global_config.domain); - const apiHost = uiHost ? `api.${uiHost}` : null; - const isApiRequest = (req) => normalizeHost(req?.headers?.host) === apiHost; - const isUiOriginAllowed = (req) => { - const origin = req?.headers?.origin; - if ( ! origin ) return false; - return extractOriginHost(origin) === uiHost; - }; - /** * @type {import('socket.io').Server} */ const socketioOptions = { - cors: (req, callback) => { - if ( isApiRequest(req) ) { - callback(null, { - origin: true, - credentials: true, - }); - return; - } - - callback(null, { - origin: isUiOriginAllowed(req), - credentials: true, - }); - }, - allowRequest: (req, callback) => { - if ( isApiRequest(req) ) { - callback(null, true); - return; - } - callback(null, isUiOriginAllowed(req)); + cors: { + origin: (origin, callback) => { + callback(null, origin); + }, + credentials: true, }, adapter: createAdapter(redisClient), }; @@ -106,7 +66,9 @@ class SocketioService extends BaseService { if ( socket_specifier.room ) { this.io.to(socket_specifier.room).emit(key, data); } else if ( socket_specifier.socket ) { - this.io.to(socket_specifier.socket).emit(key, data); + const io = this.io.sockets.sockets.get(socket_specifier.socket); + if ( ! io ) continue; + io.emit(key, data); } } } diff --git a/src/backend/src/modules/web/WebServerService.js b/src/backend/src/modules/web/WebServerService.js index 9408307d9..70769efb1 100644 --- a/src/backend/src/modules/web/WebServerService.js +++ b/src/backend/src/modules/web/WebServerService.js @@ -674,16 +674,6 @@ class WebServerService extends BaseService { app.use(function (req, res, next) { const origin = req.headers.origin; - const subdomain = req.subdomains[req.subdomains.length - 1]; - const isApiOrDavRequest = - config.experimental_no_subdomain || - subdomain === 'api' || - subdomain === 'dav'; - const isCrossOriginAuthRoute = - req.path === '/signup' || - req.path === '/login' || - req.path.startsWith('/extensions/') || - req.path.startsWith('/auth/oidc'); const is_site = hostMatchesDomain(req.hostname, config.static_hosting_domain) || @@ -702,16 +692,16 @@ class WebServerService extends BaseService { req.co_isolation_enabled ; - if ( isCrossOriginAuthRoute || isApiOrDavRequest ) { + if ( req.path === '/signup' || req.path === '/login' || req.path.startsWith('/extensions/') || req.path.startsWith('/auth/oidc') ) { res.setHeader('Access-Control-Allow-Origin', origin ?? '*'); - if ( origin ) { - res.vary('Origin'); - } } - - // Allow browser credentials on API/DAV cross-origin requests. - if ( isApiOrDavRequest && origin ) { - res.setHeader('Access-Control-Allow-Credentials', 'true'); + // Website(s) to allow to connect + if ( + config.experimental_no_subdomain || + req.subdomains[req.subdomains.length - 1] === 'api' || + req.subdomains[req.subdomains.length - 1] === 'dav' + ) { + res.setHeader('Access-Control-Allow-Origin', origin ?? '*'); } // Request methods to allow @@ -725,6 +715,10 @@ class WebServerService extends BaseService { // Request headers to allow res.header('Access-Control-Allow-Headers', allowed_headers.join(', ')); + // Set to true if you need the website to include cookies in the requests sent + // to the API (e.g. in case you use sessions) + // res.setHeader('Access-Control-Allow-Credentials', true); + // Needed for SharedArrayBuffer // NOTE: This is put behind a configuration flag because we // need some experimentation to ensure the interface diff --git a/src/backend/src/routers/_default.js b/src/backend/src/routers/_default.js index 7295d6581..c1ce46b2e 100644 --- a/src/backend/src/routers/_default.js +++ b/src/backend/src/routers/_default.js @@ -113,7 +113,6 @@ router.all('*', async function (req, res, next) { } const db = Context.get('services').get('database').get(DB_READ, 'default'); - const authService = Context.get('services').get('auth'); // -------------------------------------- // POST to login/signup/logout @@ -138,7 +137,7 @@ router.all('*', async function (req, res, next) { let authed = false; try { try { - auth_user = await jwt_auth(req, authService); + auth_user = await jwt_auth(req); auth_user = auth_user.user; authed = true; } catch (e) { diff --git a/src/backend/src/services/WSPushService.js b/src/backend/src/services/WSPushService.js index 86f4c2703..af9a95c44 100644 --- a/src/backend/src/services/WSPushService.js +++ b/src/backend/src/services/WSPushService.js @@ -34,22 +34,14 @@ class WSPushService extends BaseService { this.svc_event.on('fs.write.*', this._on_fs_update.bind(this)); this.svc_event.on('fs.move.*', this._on_fs_move.bind(this)); this.svc_event.on('fs.pending.*', this._on_fs_pending.bind(this)); - this.svc_event.on( - 'fs.storage.upload-progress', - this._on_upload_progress.bind(this), - ); - this.svc_event.on( - 'fs.storage.progress.*', - this._on_upload_progress.bind(this), - ); - this.svc_event.on( - 'puter-exec.submission.done', - this._on_submission_done.bind(this), - ); - this.svc_event.on( - 'outer.gui.*', - this._on_outer_gui.bind(this), - ); + this.svc_event.on('fs.storage.upload-progress', + this._on_upload_progress.bind(this)); + this.svc_event.on('fs.storage.progress.*', + this._on_upload_progress.bind(this)); + this.svc_event.on('puter-exec.submission.done', + this._on_submission_done.bind(this)); + this.svc_event.on('outer.gui.*', + this._on_outer_gui.bind(this)); } async _on_fs_create (key, data) { @@ -259,11 +251,15 @@ class WSPushService extends BaseService { const { socket_id } = metadata; if ( ! socket_id ) { - console.warn('missing socket id', { metadata }); - return; + this.log.warn('missing socket id', { metadata }); } + this.log.info(`socket id: ${ socket_id}`); + const svc_socketio = context.get('services').get('socketio'); + if ( ! svc_socketio.has({ socket: socket_id }) ) { + return; + } const ws_event_name = metadata.call_it_download ? 'download.progress' : 'upload.progress'; @@ -315,6 +311,9 @@ class WSPushService extends BaseService { const svc_socketio = this.services.get('socketio'); for ( const user_id of user_id_list ) { + if ( ! svc_socketio.has({ room: user_id }) ) { + continue; + } svc_socketio.send({ room: user_id }, key, response); this.svc_event.emit(`sent-to-user.${key}`, { user_id, @@ -341,7 +340,7 @@ class WSPushService extends BaseService { const kvStore = Context.get('services').get('puter-kvstore'); await kvStore.set({ key: key, value: ts }); } catch ( error ) { - console.error('Failed to update user timestamp in kvstore', { user_id, error: error.message }); + this.log.error('Failed to update user timestamp in kvstore', { user_id, error: error.message }); } } diff --git a/src/gui/src/helpers/download.js b/src/gui/src/helpers/download.js index ada557582..2322e3797 100644 --- a/src/gui/src/helpers/download.js +++ b/src/gui/src/helpers/download.js @@ -49,7 +49,6 @@ const download = function (options) { let xhr = new XMLHttpRequest(); xhr.open('post', (`${window.api_origin }/download`), true); - xhr.withCredentials = true; xhr.setRequestHeader('Authorization', `Bearer ${ window.auth_token}`); xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); diff --git a/src/puter-js/package-lock.json b/src/puter-js/package-lock.json index 97841416f..3ef83eabd 100644 --- a/src/puter-js/package-lock.json +++ b/src/puter-js/package-lock.json @@ -1,12 +1,12 @@ { "name": "puter", - "version": "2.2.14", + "version": "2.2.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "puter", - "version": "2.2.14", + "version": "2.2.12", "license": "Apache-2.0", "dependencies": { "@heyputer/kv.js": "^0.1.92", diff --git a/src/puter-js/package.json b/src/puter-js/package.json index 01edd7424..ba8e7542a 100644 --- a/src/puter-js/package.json +++ b/src/puter-js/package.json @@ -1,6 +1,6 @@ { "name": "@heyputer/puter.js", - "version": "2.2.14", + "version": "2.2.12", "description": "Puter.js - A JavaScript library for interacting with Puter services.", "homepage": "https://developer.puter.com", "main": "src/index.js", diff --git a/src/puter-js/src/lib/utils.js b/src/puter-js/src/lib/utils.js index 3ee6a0f0a..2f62b4cce 100644 --- a/src/puter-js/src/lib/utils.js +++ b/src/puter-js/src/lib/utils.js @@ -92,7 +92,6 @@ const createDeferred = () => { function initXhr (endpoint, APIOrigin, authToken, method = 'post', contentType = 'text/plain;actually=json', responseType = undefined) { const xhr = new XMLHttpRequest(); xhr.open(method, APIOrigin + endpoint, true); - xhr.withCredentials = true; if ( authToken ) { xhr.setRequestHeader('Authorization', `Bearer ${ authToken}`); @@ -281,18 +280,16 @@ function make_driver_method (arg_defs, driverInterface, driverName, driverMethod async function driverCall (options, driverInterface, driverName, driverMethod, driverArgs, settings) { const deferred = createDeferred(); - driverCall_( - options, - deferred.resolve, - deferred.reject, - driverInterface, - driverName, - driverMethod, - driverArgs, - undefined, - undefined, - settings, - ); + driverCall_(options, + deferred.resolve, + deferred.reject, + driverInterface, + driverName, + driverMethod, + driverArgs, + undefined, + undefined, + settings); return await deferred.promise; } @@ -300,12 +297,8 @@ async function driverCall (options, driverInterface, driverName, driverMethod, d // This function encapsulates the logic for sending a driver call request async function driverCall_ ( options = {}, - resolve_func, - reject_func, - driverInterface, - driverName, - driverMethod, - driverArgs, + resolve_func, reject_func, + driverInterface, driverName, driverMethod, driverArgs, method, contentType = 'text/plain;actually=json', settings = {}, diff --git a/src/puter-js/src/modules/FileSystem/operations/upload.js b/src/puter-js/src/modules/FileSystem/operations/upload.js index fb462de13..bfbb9d216 100644 --- a/src/puter-js/src/modules/FileSystem/operations/upload.js +++ b/src/puter-js/src/modules/FileSystem/operations/upload.js @@ -457,7 +457,6 @@ const upload = async function (items, dirPath, options = {}) { // open request to server xhr.open('post', (`${this.APIOrigin }/batch`), true); - xhr.withCredentials = true; // set auth header xhr.setRequestHeader('Authorization', `Bearer ${ this.authToken}`);