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
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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— thedelete-media-btnis gated on@if (editable()), not ownership.blog-list-component.htmlhard-codes[editable]=\"true\"for every rendered post, so every viewer gets the button.isOwner()(Keycloaksub===blogPost().authorId) — the media button just never got the same gate. Theeditable()flag also exposes the "Add more media" section to non-owners.ConfirmDeleteComponent's "Remove" button doesn't emit a confirm result (dialog close returns nothing), andremoveMedia()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;UpdateBlogPostCommandHandlerclears and replaces the wholeMedialist with no author/owner check. Anyone can strip media from (or rewrite) any post by id.POST api/blog/posts/{id}/media(AddMediaToPost) andPOST api/blog/posts/{id}/media/upload(AddMediaFilesToPost) — same gap.DeletePostdoes it right ([Authorize]+ owner-or-staff check inDeleteBlogPostCommandHandler).Fix
isOwner(), matching the post-delete pattern; fix ConfirmDelete result emission.[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
Resolved and merged:
151ac59d): added[Authorize]+ server-derived actor + owner-or-staff enforcement toPUT posts/{id},POST posts/{id}/mediaandPOST posts/{id}/media/upload(previously anonymous with no owner check — any caller could rewrite/strip media from any post). Non-owners now get 403.009121f2): media "Confirm Removal" button and the add-more-media section are now gated onisOwner()(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.