Skip to content

fix: tenancy-adoption review follow-ups (permissions editor, branding reaper) - #404

Merged
antosubash merged 2 commits into
mainfrom
fix/396-tenancy-review-followups
Oct 7, 2026
Merged

antosubash merged 2 commits into
mainfrom
fix/396-tenancy-review-followups

Conversation

@antosubash

Copy link
Copy Markdown
Owner

Fixes #396

  1. Permissions editor drops code-mapped grants. PermissionRegistry now keeps DB-sourced (admin-editor) grants in a separate overlay (set_role_overlay, a replace) merged with code mappings (map_role) in role_map. set_role_permissions, load_all_into_registry and sync_admin_all_permissions use it, so saving a role no longer pops code grants (users.self_profile, file_storage, dashboard.view) and removed DB keys still drop immediately. Tests: test_permissions_role_overlay.py.
  2. Branding reaper ordering. FileStorageService.delete gains drop_object=True (default behaviour unchanged) and a public drop_object(row). The reaper soft-deletes, commits, then drops the object; a failed commit leaves row and bytes intact, a failed drop only orphans an object. Tests: test_branding_reap_ordering.py; two existing fakes updated for the new kwarg.
  3. Tenant-branding in-flight map. On main it is already per-app (TenantCache.inflight on app.state.branding; no module-global _INFLIGHT remains), so only the second half needed work: the shared read's done-callback now retrieves the exception, so a failed read whose waiters were all cancelled no longer logs "exception was never retrieved". Test included.

Verification: make lint exit 0; make test-py 3576 passed, 9 skipped. Does not touch #398's files beyond permissions/service.py (different hunks).

https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

- permissions: keep code-registered role grants when the editor saves a role
  (separate DB overlay on PermissionRegistry, replaced not popped)
- branding reaper: commit the soft-delete before dropping the backend object
- branding: retrieve failed in-flight read exceptions (in-flight map is already per-app)

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ✅ Completed 2026-10-05T10:46:21.592349Z 3427efb PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Deploying simple-module-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9b76eff
Status: ✅  Deploy successful!
Preview URL: https://04bbb542.simple-module-python.pages.dev
Branch Preview URL: https://fix-396-tenancy-review-follo.simple-module-python.pages.dev

View logs

@antosubash

Copy link
Copy Markdown
Owner Author

/ship results (review + QA + local CI)

Pushed: 9b76eff (test-only tidy: removed a stray drop_object kwarg in test_logo_dark.py, restored the loop exception handler in the reap-ordering test, dropped a tautological test).

Code review (2 passes, sonnet): 1 fixed. Accepted/not changed (design or out of scope):

  • file_storage.delete/bulk_delete still drop bytes before the request commit (the reaper path is fixed; changing the default is outside this PR).
  • Role editor cannot remove a code-registered grant (by design of the overlay; needs a deny overlay, follow-up).

QA (live app, fresh SQLite DB, API 8100 / Vite 5250):

  • Created a user-role account. Saved the user role via the editor API and via the real role-edit UI (checkbox + Save, redirect OK, rows persisted). Without restart the user's effective permissions still included code-mapped dashboard.view, users.self.profile, file_storage.{upload,download,delete}; saving an empty set removed only the overlay grant (feature_flags.view). /dashboard/ stayed 200.
  • Branding: uploaded two logos (replace), then cleared; both stored rows ended soft-deleted, /api/branding/logo 404 after clear, no tracebacks in the server log.
  • No bugs found. Note: a fresh migrate-only DB has no user role row (seed lives elsewhere); pre-existing, unrelated.

Local checks: make lint exit 0; make test-py 3575 passed, 9 skipped; branding + permissions suites 255 passed. TS untouched (test-js not run).

https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

@antosubash
antosubash merged commit 3fbac0a into main Oct 7, 2026
13 checks passed
antosubash added a commit that referenced this pull request Oct 7, 2026
…, thumbnails (#410)

* feat(file_storage): public files with anonymous serving, list search/sort, thumbnails

Fixes #353, fixes #352.

- StoredFile.public (default false; migration uses sa.false()), set on upload
  (public=true) or PATCH /files/{id}; StoredFileOut gains public/public_url.
- Anonymous GET /public/{id}[/{filename}] and /public/{id}/thumbnail, exempted
  via register_public_routes (GET only). Only public, non-deleted rows resolve;
  other cases are one 404. Cross-tenant lookup by id uses all_tenants().
  ETag/304, Cache-Control public, nosniff, sandbox CSP; active content (HTML,
  SVG, JS) forced to attachment and streamed.
- SecurityHeadersMiddleware keeps a CSP the response already set.
- GET /files: q, content_type (exact or "image/" prefix), sort.
- GET /files/{id}/thumbnail?w=: Pillow WebP, width snapped to a whitelist,
  pixel-budget guard, variants cached in the storage backend and dropped with
  the file.
- Browse screen: Public badge and make public/private action.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

* fix(file_storage): harden thumbnails (format allowlist, size/pixel caps, concurrency) and add tenant/permission tests

Pillow only opens JPEG/PNG/WEBP/GIF, sniffed format must match the declared
type, DecompressionBombWarning is an error, source bytes are capped while
reading, decodes are bounded by a semaphore, first frame only, metadata not
carried over.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

* fix(file_storage): hold the decode slot until the worker thread finishes; stop mutating Pillow's global pixel cap

Decode runs as a shielded single-flight task per variant, so a cancelled
request neither frees its slot early nor lets retries multiply decodes. The
semaphore is per event loop. The explicit header-only pixel budget replaces the
racy process-global MAX_IMAGE_PIXELS toggle.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

* refactor(optimize): dedupe 404/headers helpers, drop dead code and one-use wrappers in file_storage

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

* fix: address code review findings (round 1, pass 1)

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

* fix: address code review findings (round 1, pass 1b)

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

* fix(file_storage): no session cookie on public responses, strict PATCH bool, 16-bit grayscale thumbnails (QA round 1)

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

* fix(file_storage): log orphaned thumbnail variant deletes (review round 1, pass 3)

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

* test: reconcile file_storage/branding tests with #401 and #404 on main

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

* fix(migrations): chain file_storage public column after #406's timestamp revision

Both branched from c7f2d9a41e83; repoint down_revision so main keeps a single
mainline head.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4

* feat(file_storage): own rate-limit bucket for anonymous public file GETs

With #408's shared limiter on main, public file reads shared the 120/minute
anonymous default; a page embedding many images would throttle itself.

Claude-Session: https://claude.ai/code/session_01M9neheZZEe3sVpDi2S3zT4
@antosubash
antosubash deleted the fix/396-tenancy-review-followups branch October 8, 2026 11:40
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.

permissions/branding: low-severity follow-ups from the tenancy-adoption review

1 participant