Step-up auth on image delete + show where an image is referenced#76
Merged
Conversation
Two related fixes to the uploaded-files surface.
Image delete never prompted for step-up auth: the card called deleteFile
with no password/TOTP, so when the system's delete-confirmation tier
required them the backend rejected the request and the user had no way
to satisfy it. Route image delete through the shared
DestructiveConfirmDialog (collects password/TOTP per tier) and handle
the System Safety grace-period (202) response with the standard
'scheduled for deletion' toast, matching every other delete. A sweep of
the other destructive deletes found this was the only one bypassing the
dialog.
References view: selecting a file now opens a detail dialog listing
where it's used (system avatar, member avatars/bios, journals, and edit
history) so the owner can see what a delete would break. References are
computed on demand via find_file_references (the inverse of the orphan
scan); there is no persistent reference table. New endpoint
GET /v1/files/{id}/references.
Build on the file-references view: - References deep-link to where they're used: system avatar to settings, members to /members?member=<id> (the members page now opens that member's dialog from the param), journals to the entry page. Revision references link to the member/journal that owns the history. - Split the list into 'Used in' (live references) and 'Edit history', and when an image is ONLY in edit history, show a callout that it isn't shown anywhere current and is safe to delete unless an older revision is restored (orphan cleanup intentionally leaves these in place).
This was referenced May 25, 2026
Merged
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.
Summary
Two related fixes to the uploaded-files surface, plus a sweep of the other destructive deletes.
Image delete: step-up auth was never prompted
Deleting an uploaded image called the delete endpoint with no password/TOTP, so when the system's delete-confirmation tier required them the backend rejected the request ("TOTP code required") and the user had no way to satisfy it. Image delete now routes through the shared
DestructiveConfirmDialog(collects password/TOTP per tier, gated ontotp_enabled) and handles the System Safety grace-period (202) response with the standard "scheduled for deletion" toast, matching every other delete.A sweep of all
verify_destructive_authcallers found image delete was the only frontend delete bypassing the dialog. Members and journals use bespoke TOTP-collecting dialogs; fronts, polls, messages/threads, reminders, groups, tags, custom fields, watch-token revoke, and channel delete all use the shared dialog.File references: "where is this used?"
Selecting an uploaded file now opens a detail dialog listing where it's referenced (system avatar, member avatars/bios, journals, and edit history) so the owner sees what a delete would break.
find_file_references(the inverse of the orphan scan); there is no persistent reference table. New endpointGET /v1/files/{id}/references./members?member=<id>(the members page opens that member's dialog from the param), journals to the entry page; revision references link to the owning member/journal.Tests
tests/test_files.pygains coverage for the member-avatar reference and the 404 path. ruff / tsc / eslint clean.Migrations
None.