Allow bots to update and destroy their own messages

Bots can only create. A lifecycle notification — an alert that fires and then
resolves, a deploy that starts and finishes, a backup that runs — therefore has
to post a second message, and the room becomes an append-only log of states
rather than a view of the current one.

Adds PATCH and DELETE inside the existing bot_key scope, routed to
Messages::ByBotsController. The body is read the way create reads it, so
updating a message is the same request shape as posting one.

No new authorization: both actions already run through ensure_can_administer,
and can_administer? grants access only to a record the user created, so a bot
key reaches that bot's own messages and no others. set_room narrows it again by
looking the room up through the bot's own memberships. A leaked bot key gains
what it could already do by posting: write to rooms that bot belongs to.

update answers head :ok rather than the redirect, which meant extracting the
update and its broadcast into update_message — calling super and then head
would double render, since the parent redirects inside the action. destroy
needs no split, because the parent renders implicitly like create does.
This commit is contained in:
Ronald Lokers
2026-08-10 22:06:41 +02:00
committed by Stanko K.R.
parent 766bffae56
commit 3ca1dcbf77
4 changed files with 101 additions and 5 deletions
+8 -3
View File
@@ -34,9 +34,7 @@ class MessagesController < ApplicationController
end
def update
@message.update!(message_params)
@message.broadcast_replace_to @room, :messages, target: [ @message, :presentation ], partial: "messages/presentation", attributes: { maintain_scroll: true }
update_message
redirect_to room_message_url(@room, @message)
end
@@ -50,6 +48,13 @@ class MessagesController < ApplicationController
@message = @room.messages.find(params[:id])
end
# Extracted so bots can reuse the update and its broadcast while answering with
# a status code instead of a redirect.
def update_message
@message.update!(message_params)
@message.broadcast_replace_to @room, :messages, target: [ @message, :presentation ], partial: "messages/presentation", attributes: { maintain_scroll: true }
end
def ensure_can_administer
head :forbidden unless Current.user.can_administer?(@message)
end