bug(blog): media 'Confirm Removal' button shown to all viewers + blog update/add-media endpoints missing authorization #745

Closed
opened 2026-07-20 19:19:00 +00:00 by spikerj · 1 comment
Owner

Symptom

Any viewer of a blog post — e.g. a child account viewing a parent's (spikerj-like account's) post — sees the X / Confirm Removal button overlaid on every embedded photo and can click through the removal dialog. That control should only exist for the original poster.

Frontend (spikersoft-angular)

  • libraries/features/blog/src/lib/components/blog-entry/blog-entry-component.html — the delete-media-btn is gated on @if (editable()), not ownership.
  • blog-list-component.html hard-codes [editable]=\"true\" for every rendered post, so every viewer gets the button.
  • The post-level delete button in the same template is correctly gated on isOwner() (Keycloak sub === blogPost().authorId) — the media button just never got the same gate. The editable() flag also exposes the "Add more media" section to non-owners.
  • Related latent bug: ConfirmDeleteComponent's "Remove" button doesn't emit a confirm result (dialog close returns nothing), and removeMedia() is still a placeholder snackbar.

Backend (spikersoft-backend) — NOT UI-only, real authz hole

SpikerSoft.Api/Domain/Blog/BlogController.cs:

  • PUT api/blog/posts/{id} (UpdatePost) has no [Authorize] and captures no actor; UpdateBlogPostCommandHandler clears and replaces the whole Media list with no author/owner check. Anyone can strip media from (or rewrite) any post by id.
  • POST api/blog/posts/{id}/media (AddMediaToPost) and POST api/blog/posts/{id}/media/upload (AddMediaFilesToPost) — same gap.
  • Contrast: DeletePost does it right ([Authorize] + owner-or-staff check in DeleteBlogPostCommandHandler).

Fix

  • Frontend: gate media remove (and add-media affordances) on isOwner(), matching the post-delete pattern; fix ConfirmDelete result emission.
  • Backend: add [Authorize] + actor capture (User.GetKeycloakUserId()) to UpdatePost/AddMediaToPost/AddMediaFilesToPost and enforce owner-or-staff in the handlers, following the DeleteBlogPost pattern.

Repos: spikersoft-angular, spikersoft-backend

## Symptom Any viewer of a blog post — e.g. a child account viewing a parent's (spikerj-like account's) post — sees the X / **Confirm Removal** button overlaid on every embedded photo and can click through the removal dialog. That control should only exist for the original poster. ## Frontend (spikersoft-angular) - `libraries/features/blog/src/lib/components/blog-entry/blog-entry-component.html` — the `delete-media-btn` is gated on `@if (editable())`, not ownership. - `blog-list-component.html` hard-codes `[editable]=\"true\"` for every rendered post, so every viewer gets the button. - The post-level delete button in the same template is correctly gated on `isOwner()` (Keycloak `sub` === `blogPost().authorId`) — the media button just never got the same gate. The `editable()` flag also exposes the "Add more media" section to non-owners. - Related latent bug: `ConfirmDeleteComponent`'s "Remove" button doesn't emit a confirm result (dialog close returns nothing), and `removeMedia()` is still a placeholder snackbar. ## Backend (spikersoft-backend) — NOT UI-only, real authz hole `SpikerSoft.Api/Domain/Blog/BlogController.cs`: - `PUT api/blog/posts/{id}` (`UpdatePost`) has **no `[Authorize]`** and captures no actor; `UpdateBlogPostCommandHandler` clears and replaces the whole `Media` list with **no author/owner check**. Anyone can strip media from (or rewrite) any post by id. - `POST api/blog/posts/{id}/media` (`AddMediaToPost`) and `POST api/blog/posts/{id}/media/upload` (`AddMediaFilesToPost`) — same gap. - Contrast: `DeletePost` does it right (`[Authorize]` + owner-or-staff check in `DeleteBlogPostCommandHandler`). ## Fix - Frontend: gate media remove (and add-media affordances) on `isOwner()`, matching the post-delete pattern; fix ConfirmDelete result emission. - Backend: add `[Authorize]` + actor capture (`User.GetKeycloakUserId()`) to UpdatePost/AddMediaToPost/AddMediaFilesToPost and enforce owner-or-staff in the handlers, following the DeleteBlogPost pattern. Repos: spikersoft-angular, spikersoft-backend
Author
Owner

Resolved and merged:

  • spikersoft-backend PR #436 (merged to master, 151ac59d): added [Authorize] + server-derived actor + owner-or-staff enforcement to PUT posts/{id}, POST posts/{id}/media and POST posts/{id}/media/upload (previously anonymous with no owner check — any caller could rewrite/strip media from any post). Non-owners now get 403.
  • spikersoft-angular PR #475 (merged to master, 009121f2): media "Confirm Removal" button and the add-more-media section are now gated on isOwner() (a child viewing a parent's post no longer sees them), and the ConfirmDelete dialog's Remove button now actually emits its confirmation result.

Closing.

Resolved and merged: - **spikersoft-backend PR #436** (merged to master, `151ac59d`): added `[Authorize]` + server-derived actor + owner-or-staff enforcement to `PUT posts/{id}`, `POST posts/{id}/media` and `POST posts/{id}/media/upload` (previously anonymous with no owner check — any caller could rewrite/strip media from any post). Non-owners now get 403. - **spikersoft-angular PR #475** (merged to master, `009121f2`): media "Confirm Removal" button and the add-more-media section are now gated on `isOwner()` (a child viewing a parent's post no longer sees them), and the ConfirmDelete dialog's Remove button now actually emits its confirmation result. Closing.
Sign in to join this conversation.