Skip to content

fix(storage): bind the browser workspace to the signed-in account - #1576

Merged
cevheri merged 28 commits into
mainfrom
fix/storage-sign-in-scope
Oct 7, 2026
Merged

cevheri merged 28 commits into
mainfrom
fix/storage-sign-in-scope

Conversation

@cevheri

@cevheri cevheri commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

In server storage mode (STORAGE_PROVIDER sqlite or postgres), the browser copy of the workspace now belongs to the signed-in account.

  • The account that wrote the copy is recorded, and a sign-in as a different account starts from its own server data. Every signed-in page waits for that check, and a tab whose session ended goes to sign in.
  • A page's requests then name that account in an X-LibreDB-Workspace-Owner header; the server answers 409 WORKSPACE_OWNER_MISMATCH when it is not the signed-in account, and the tab reloads.
  • Sign-out pushes pending changes, signs out, then clears the copy. If server storage is unreachable, the session still ends and the unsaved changes stay for this account. A refused sign-out says so and stays on the page, in local mode too.
  • Local mode is otherwise unchanged; docs/STORAGE.md, docs/SECURITY.md and docs/API_DOCS.md describe 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.

cevheri added 15 commits October 7, 2026 12:25
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

codecov Bot commented Oct 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

cevheri added 13 commits October 7, 2026 13:45
…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.
…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.
…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.
…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.
@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 2026

Copy link
Copy Markdown

@cevheri
cevheri merged commit f612168 into main Oct 7, 2026
36 checks passed
@cevheri
cevheri deleted the fix/storage-sign-in-scope branch October 7, 2026 12:47
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant