Repository navigation
fix: save ASK Chat chats and artifacts per user on the server - #91
Merged
Merged
Conversation
ASK Chat kept chats and artifacts in the browser's localStorage. A user
who made one in Chrome could not see it in Edge, and because the storage
key carried no user and signing out did not clear it, the next person to
sign in on the same browser saw the previous user's chats and artifacts,
result rows included.
Add a per-user store in OpenSearch (ask-chats-v1, ask-artifacts-v1) and
the routes the SPA will use instead of localStorage:
GET/PUT/DELETE /v1/chats[/{id}]
GET/PUT /v1/artifacts[/{id}]
Every item is owned by <issuer>:<sub> of the validated token. An id that
belongs to someone else answers 404, like one that does not exist, PUT
included. Messages, documents and rows are Fernet-encrypted with
ONIBEX_ENCRYPTION_KEY in a payload field mapped enabled:false, so result
columns never become index fields; only what the lists need stays in
cleartext. /v1/query and /v1/artifact are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saved chats and artifacts are encrypted with ONIBEX_ENCRYPTION_KEY, so a key change that skipped them would leave every one unreadable. rotate_master_key.py now re-encrypts the payload of every document in ask-chats-v1 and ask-artifacts-v1, leaving updated_at alone. The script names the two indices instead of importing them, because it also runs in the admin-api image, which does not install the orchestrator; a test keeps both lists equal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The artifact gallery read and wrote localStorage, so an artifact made in
one browser did not exist in another, and the next user on the same
browser saw the previous user's documents.
The gallery now lists the user's artifacts from GET /v1/artifacts, opens
one with GET /v1/artifacts/{id}, and saves every created, regenerated or
edited artifact with PUT /v1/artifacts/{id}. Generation is unchanged. A
failed save keeps the artifact open with a sticky toast that retries.
The old onibex_artifacts_v1 key is cleared; nothing is migrated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chats lived in localStorage (zustand persist), so they did not exist in another browser, and the next user on the same browser saw them. The store now keeps chats in memory and syncs them with /v1/chats: the list (titles only) loads once per page load, a chat's messages are fetched when it is opened, and the chat is saved after each exchange and after its title arrives, never while the answer is being revealed. Saves and the delete of one chat run in a queue, so a slow save cannot overwrite a later one. An empty "New Chat" is never sent. persist keeps only the browser preferences; version 1 with a migrate drops the chats version 0 stored. Nothing is migrated to the server. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The manual said artifacts are stored in the browser's localStorage and are browser-specific. Chats and artifacts are now saved per user on the server, visible from any browser the user signs in on and to nobody else, and encrypted at rest. The ONIBEX_ENCRYPTION_KEY pages list them among what the key decrypts and what a lost key makes unreadable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
generic-api-key fires on LEGACY_ARTIFACTS_KEY in ArtifactsPage.tsx: a constant whose name ends in KEY holding a quoted identifier. The value is the name of a localStorage key, the same one already recorded as a false positive under its previous constant name. Nothing to rotate. The line now carries `gitleaks:allow`, which travels with it; the fingerprint in .gitleaksignore covers commit 52cbe47, which the pull request scan reads as written, before the marker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and env The gallery listed every artifact the user had, so nothing said which workspace or which environment an artifact belonged to, and dev (QAS) and prod (PRD) hold different figures. GET /v1/artifacts takes workspace_id and env, and the gallery asks for the ones the sidebar selects, like the chat does per workspace. The page names both under its title and in the viewer, reloads when either changes and closes any open artifact. A late list or save from a previous selection is dropped instead of shown. With no workspace the gallery is empty and New artifact is disabled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was no way to delete an artifact, before or after artifacts moved
to the server; chats already had one.
DELETE /v1/artifacts/{id} removes it for its owner, with the same 404
for someone else's id as the other routes. The viewer has a Delete
button after Regenerate that asks for confirmation, removes the artifact
from the gallery and returns to it. A 404 counts as done: the artifact
was never saved, or was deleted from another browser.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alberthflores95
added a commit
that referenced
this pull request
Oct 27, 2026
Brings the artifact delete of PR #91 into this stacked branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Problem
ASK Chat kept chats and artifacts in the browser's
localStorage:Change
The browser still builds chats and artifacts as before; the orchestrator now keeps them per user.
ask_orchestrator/user_store.py): OpenSearch indicesask-chats-v1andask-artifacts-v1, one document per item, owned by<issuer>:<sub>of the validated token. Messages, documents and rows are Fernet-encrypted withONIBEX_ENCRYPTION_KEYin apayloadfield mappedenabled: false(result columns never become index fields); the mapping isdynamic: strict. Only what lists need stays in cleartext.routers/saved_items.py):GET/PUT/DELETE /v1/chats[/{id}],GET/PUT /v1/artifacts[/{id}]. Someone else's id answers 404 like a missing one, PUT included. Token withoutsub: 401. Payload over 20 MB: 413.scripts/rotate_master_key.pyre-encrypts both indices.persistkeeps only browser preferences (version 1; itsmigratedrops the chats version 0 stored). The oldonibex_artifacts_v1key is cleared./v1/queryand/v1/artifactare unchanged. Nothing is migrated: existing chats and artifacts in browsers are dropped (agreed).Out of scope
Regenerate still uses the selector's env/workspace (as before), no artifact delete, no sharing between users.
Tests
tests/unit/test_saved_items.py: 25 tests (owner isolation, encryption at rest, lists without payload, timestamps, size limit, unreadable payload, key rotation). Checked against mutants without the owner filter and without encryption: they fail.🤖 Generated with Claude Code