Repository navigation
fix(storage): bind the browser workspace to the signed-in account - #1576
Merged
Merged
Conversation
In server storage mode the browser copy belongs to the signed-in account. useStorageSync reads the username from /api/auth/me and records it in libredb_workspace_owner. Signing in as a different account starts from that account's server data: a copy with another owner, or with no owner that was already migrated, is cleared before the migration and the pull. A copy with no owner that was never migrated is local-mode data and is migrated as before. When the username cannot be read, the copy is neither used nor pushed and syncError says why. Signing out from the editor, the admin dashboard or the launch page pushes pending collections while the session is still valid, then clears this browser's copy, editor tabs included, then ends the session. clearAccountWorkspace() in local-storage.ts is the one list of the account's keys; per-browser preferences stay. Local mode is unchanged.
STORAGE.md and SECURITY.md state that in server mode the browser copy belongs to the signed-in account and sign-out clears it, and that in local mode the next account on the same browser profile sees and can open the connections saved there.
Signing out pushes what is pending, ends the session, and only then clears this browser's copy, so a sign-out that does not happen keeps the signed-in account's copy and resumes the sync. The cleared copy stays marked as handed to a server account, so anything written to it afterwards is cleared at the next sign-in instead of being migrated into that account. The admin dashboard reports a sign-out that cannot complete.
…y page that reads it The editor, the admin dashboard and the monitoring page render once this browser's copy is the signed-in account's: in server storage mode a copy another account left behind is cleared before the page reads it, so signing in as a different account starts from that account's server data wherever it lands. When the signed-in account cannot be read the page says so instead of showing the copy.
…browser copy A browser copy with no owner that was never migrated is local-mode data. The first account that signs in now becomes its owner, so only that account migrates it, and a different account that signs in later starts from its own server data.
…e cannot be read When GET /api/storage/config cannot be read and the browser copy carries an owner or the migration flag, both written only in server mode, the pages that read the copy show their message instead of it. A copy with neither renders as before, so local mode does not change.
…ser copy it is Every tab of a browser profile shares the copy. When another tab records a different owner or clears it, a page inside WorkspaceOwnerGate stops rendering and reloads, so the owner check runs again for the account signed in now.
The editor and the admin dashboard now check the sign-out response: a refused sign-out shows the failure toast and does not go to /login, matching the launch page, which already said so.
…ing the first pull The pull writes the server answer only while the copy still belongs to the account it was claimed for, and the owner is recorded by the claim alone. A sign-out that cleared the copy while the pull was out keeps it cleared.
…in after upgrading The owner key and the migration flag comments name every function that writes them. ARCHITECTURE.md points to the owner rule in STORAGE.md, and STORAGE.md says that the first sign-in after upgrading in server storage mode starts from the account's server data, so open editor tabs from before are not kept.
The pages inside WorkspaceOwnerGate rendered an empty body until the check answered. They now show the same loading indicator the code-split views use, labelled for assistive technology.
A sign-in can land on /settings/mcp or /settings/authenticator, so both now render inside WorkspaceOwnerGate through a settings layout. The browser copy is recorded as the signed-in account's there too, and every other open tab of the browser profile follows that account.
… storage mode was unread The settings pages render inside the owner gate, and the owner check at the next sign-in does not cover a copy written while the storage mode could not be read on a browser profile with no owner yet.
…n again A sign-in can land where no owner is recorded, such as an unknown address, so the storage event never reaches an open tab. The gate now also asks GET /api/auth/me whenever its tab becomes visible and reloads when the account is no longer the one it rendered for. STORAGE.md names this and says sign-out pushes only the signing-out tab's pending collections.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…owner check GET /api/auth/me answering 401 means no account is signed in. The owner check, at the first claim and when the tab is shown again, now shows neither the page nor its message for it and reports the ended session, so the tab goes to sign in as every page does.
After a server-mode claim, appFetch sends the claimed username in X-LibreDB-Workspace-Owner. The proxy answers 409 WORKSPACE_OWNER_MISMATCH when it is not the session's username, before any route runs, and the tab reloads so the owner check runs again. A request without the header, and the public routes, behave as before.
…ed account's In server mode a page writes the copy (the storage facade, the editor tabs, the object-source drafts) only while the owner key still names the account it claimed. The pull checks the owner again before each collection it writes, over STORAGE_COLLECTIONS, which also brings its complexity under the limit.
…for a session left after a refused one A push that fails with the session already ended clears the copy and finishes to sign in. One that fails otherwise still ends the session and keeps the copy for this account, and the sign-out says so. After a refused or failed sign-out, GET /api/auth/me decides: with no session left the copy is cleared as for a sign-out. The flush clears its timers again after the push it waited for, and no timer pushes while a sign-out is under way. An unreadable storage mode on a copy with neither the owner key nor the migration flag signs out as local mode.
… when the same name follows A removed owner key means the copy was cleared, so a page inside WorkspaceOwnerGate reloads even when another tab claiming for the same account writes the name again right after.
…and assert lengths with toHaveLength
…ow the owner is matched SECURITY.md names the sign-out that could not complete and the session that ended without one. STORAGE.md keeps only claims the code holds: the pending collections pushed first are the signing-out tab's, and the owner is matched when the sync starts and then on every request through the header.
… test without a dash
…signs in again A sign-out whose push did not land records the collections it could not push in the account's browser copy. The same account's next sign-in on this browser pushes them before it pulls, and the pull keeps a collection still waiting to be pushed. The sync also reloads when the page's owner check matched another account than the one signed in now, instead of claiming the copy again.
…claimed account's
…and describe the kept changes
…nc signs out A sign-out from the admin dashboard, which mounts no sync, now keeps the copy while it holds changes an earlier sign-out could not push, and the record of those collections is read as a list of known names only. STORAGE.md names the routes the owner header is checked on.
…ace keeps no owner
|
cevheri
added a commit
that referenced
this pull request
Oct 8, 2026
…fix (#1602) Carries Databend as a connection type of its own over its HTTP query API (#1593, with #1588, #1589 and #1590), the Seed sources card and two stricter seed-file checks (#1570), Markdown and HTML result exports (#1567), the object tree filter (#1587) and result columns that keep their own names and values (#1575), plus fixes across PostgreSQL, the MySQL family, MongoDB, Redis, Neo4j, Oracle and the CapRover templates. One security fix: with server storage, the browser copy of the workspace is bound to the signed-in account (#1576, GHSA-jwvh-6phv-j9jh). Chart 0.1.79 is flagged containsSecurityUpdates, and its description, keywords and README name Databend, as the operator CSVs now do; the CapRover templates move to 0.18.1.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



In server storage mode (
STORAGE_PROVIDERsqliteorpostgres), the browser copy of the workspace now belongs to the signed-in account.X-LibreDB-Workspace-Ownerheader; the server answers409 WORKSPACE_OWNER_MISMATCHwhen it is not the signed-in account, and the tab reloads.docs/STORAGE.md,docs/SECURITY.mdanddocs/API_DOCS.mddescribe both modes.After upgrading, the first sign-in in server storage mode starts from the account's server data, so editor tabs from before are not kept.
Verified: the full gate set with 100% line coverage, the CI images, and a browser with SQLite and PostgreSQL storage.