0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-14 13:58:53 +08:00
discourse/plugins/discourse-ai/config/routes.rb
Roman Rizzi aa3e44b32c
FIX: State-changing summary generation is exposed via GET (CSRFable navigation) (#40232)
## Summary

State-changing summary generation/streaming is exposed via GET endpoint,
bypassing CSRF protection and allowing cross-site triggering of summary
generation and credit consumption.

## Source

- Patch Triage: https://patch.discourse.org/patch-triage/334
- Original commit:
https://github.com/discourse/discourse/blob/main/plugins/discourse-ai/app/controllers/discourse_ai/summarization/summary_controller.rb

---

🤖 Auto-generated from the patch diff via Patch Triage. Review carefully
before merging.

Co-authored-by: discourse-patch-triage
<272280883+discourse-patch-triage[bot]@users.noreply.github.com>

---------

Co-authored-by: discourse-patch-triage[bot] <272280883+discourse-patch-triage[bot]@users.noreply.github.com>
2026-05-22 13:59:21 -03:00

186 lines
7.7 KiB
Ruby
Vendored

# frozen_string_literal: true
DiscourseAi::Engine.routes.draw do
scope path: "/credits", defaults: { format: :json } do
get "status" => "ai_credits#status"
end
scope module: :ai_helper, path: "/ai-helper", defaults: { format: :json } do
post "suggest" => "assistant#suggest"
post "suggest_title" => "assistant#suggest_title"
post "suggest_category" => "assistant#suggest_category"
post "suggest_tags" => "assistant#suggest_tags"
post "stream_suggestion" => "assistant#stream_suggestion"
post "caption_image" => "assistant#caption_image"
end
scope module: :embeddings, path: "/embeddings", defaults: { format: :json } do
get "semantic-search" => "embeddings#search"
get "quick-search" => "embeddings#quick_search"
end
scope module: :discord, path: "/discord", defaults: { format: :json } do
post "interactions" => "bot#interactions"
end
scope module: :ai_bot, path: "/ai-bot", defaults: { format: :json } do
get "bot-username" => "bot#show_bot_username"
get "post/:post_id/show-debug-info" => "bot#show_debug_info"
get "show-debug-info/:id" => "bot#show_debug_info_by_id"
post "post/:post_id/stop-streaming" => "bot#stop_streaming_response"
post "post/:post_id/retry" => "bot#retry_response"
end
scope module: :discover, path: "/discoveries", defaults: { format: :json } do
post "reply" => "discoveries#reply"
post "continue-convo" => "discoveries#continue_convo"
end
scope module: :ai_bot, path: "/ai-bot/shared-ai-conversations" do
post "/" => "shared_ai_conversations#create"
delete "/:share_key" => "shared_ai_conversations#destroy"
get "/:share_key" => "shared_ai_conversations#show"
get "/asset/:version/:name" => "shared_ai_conversations#asset"
get "/preview/:topic_id" => "shared_ai_conversations#preview"
end
scope module: :ai_bot, path: "/ai-bot/conversations" do
get "/" => "conversations#index"
put "/:topic_id/starred" => "conversations#update_starred"
end
scope module: :ai_bot, path: "/ai-bot/artifacts" do
get "/:id" => "artifacts#show"
get "/:id/:version" => "artifacts#show"
end
scope module: :ai_bot, path: "/ai-bot/artifact-key-values/:artifact_id" do
get "/" => "artifact_key_values#index"
post "/" => "artifact_key_values#set"
delete "/:key" => "artifact_key_values#destroy"
delete "/" => "artifact_key_values#destroy"
end
scope module: :summarization, path: "/summarization", defaults: { format: :json } do
get "/t/:topic_id" => "summary#show", :constraints => { topic_id: /\d+/ }
post "/t/:topic_id" => "summary#create", :constraints => { topic_id: /\d+/ }
put "/regen_gist" => "summary#regen_gist"
put "/regen_summary" => "summary#regen_summary"
post "/channels/:channel_id" => "chat_summary#show"
end
scope module: :sentiment, path: "/sentiment", defaults: { format: :json } do
get "/posts" => "sentiment#posts", :constraints => StaffConstraint.new
end
scope module: :translation, path: "/translate", defaults: { format: :json } do
post "/posts/:post_id" => "translation#translate"
post "/topics/:topic_id" => "translation#schedule_topic"
end
scope path: "/mcp/oauth", defaults: { format: :json } do
get "client-metadata" => "mcp_oauth#client_metadata"
end
end
Discourse::Application.routes.draw do
mount DiscourseAi::Engine, at: "discourse-ai"
get "admin/dashboard/sentiment" => "discourse_ai/admin/dashboard#sentiment",
:constraints => StaffConstraint.new
scope "/admin/plugins/discourse-ai", constraints: AdminConstraint.new do
get "/ai-personas", to: redirect("/admin/plugins/discourse-ai/ai-agents")
get "/ai-personas/new", to: redirect("/admin/plugins/discourse-ai/ai-agents/new")
get "/ai-personas/:id/edit", to: redirect("/admin/plugins/discourse-ai/ai-agents/%{id}/edit")
resources :ai_artifacts,
only: %i[index show create update destroy],
path: "ai-artifacts",
controller: "discourse_ai/admin/ai_artifacts",
defaults: {
format: :json,
}
resources :ai_agents,
only: %i[index new create edit update destroy],
path: "ai-agents",
controller: "discourse_ai/admin/ai_agents"
post "/ai-agents/stream-reply" => "discourse_ai/admin/ai_agents#stream_reply"
post "/ai-agents/:id/create-user", to: "discourse_ai/admin/ai_agents#create_user"
get "/ai-agents/:id/export", to: "discourse_ai/admin/ai_agents#export", format: :json
post "/ai-agents/import", to: "discourse_ai/admin/ai_agents#import"
put "/ai-agents/:id/files/remove", to: "discourse_ai/admin/ai_agents#remove_file"
get "/ai-agents/:id/files/status", to: "discourse_ai/admin/ai_agents#indexing_status_check"
resources(
:ai_tools,
only: %i[index new create edit update destroy],
path: "ai-tools",
controller: "discourse_ai/admin/ai_tools",
)
post "/ai-tools/:id/test", to: "discourse_ai/admin/ai_tools#test"
get "/ai-tools/:id/export", to: "discourse_ai/admin/ai_tools#export", format: :json
post "/ai-tools/import", to: "discourse_ai/admin/ai_tools#import"
get "/ai-tools/mcp-servers/new", to: "discourse_ai/admin/ai_mcp_servers#new"
get "/ai-tools/mcp-servers/:id/edit", to: "discourse_ai/admin/ai_mcp_servers#edit"
resources :ai_mcp_servers,
only: %i[index new create edit update destroy],
path: "ai-mcp-servers",
controller: "discourse_ai/admin/ai_mcp_servers"
post "/ai-mcp-servers/test", to: "discourse_ai/admin/ai_mcp_servers#test"
post "/ai-mcp-servers/:id/test", to: "discourse_ai/admin/ai_mcp_servers#test"
get "/ai-mcp-servers/:id/oauth/start", to: "discourse_ai/admin/ai_mcp_servers#oauth_start"
get "/ai-mcp-servers/oauth/callback", to: "discourse_ai/admin/ai_mcp_servers#oauth_callback"
delete "/ai-mcp-servers/:id/oauth/disconnect",
to: "discourse_ai/admin/ai_mcp_servers#oauth_disconnect"
post "/rag-document-fragments/files/upload",
to: "discourse_ai/admin/rag_document_fragments#upload_file"
get "/rag-document-fragments/files/status",
to: "discourse_ai/admin/rag_document_fragments#indexing_status_check"
get "/ai-usage", to: "discourse_ai/admin/ai_usage#show"
get "/ai-usage-report", to: "discourse_ai/admin/ai_usage#report"
get "/ai-spam", to: "discourse_ai/admin/ai_spam#show"
put "/ai-spam", to: "discourse_ai/admin/ai_spam#update"
post "/ai-spam/test", to: "discourse_ai/admin/ai_spam#test"
post "/ai-spam/fix-errors", to: "discourse_ai/admin/ai_spam#fix_errors"
get "/ai-translations", to: "discourse_ai/admin/ai_translations#show"
get "/ai-translations/progress", to: "discourse_ai/admin/ai_translations#progress"
post "/ai-theme-translations", to: "discourse_ai/admin/ai_theme_translations#create"
resources :ai_llms,
only: %i[index new create edit update destroy],
path: "ai-llms",
controller: "discourse_ai/admin/ai_llms" do
collection { post :test }
end
resources :ai_llm_quotas,
controller: "discourse_ai/admin/ai_llm_quotas",
path: "quotas",
only: %i[index create update destroy]
resources :ai_embeddings,
only: %i[index new create edit update destroy],
path: "ai-embeddings",
controller: "discourse_ai/admin/ai_embeddings" do
collection { post :test }
end
resources :ai_features,
only: %i[index edit],
path: "ai-features",
controller: "discourse_ai/admin/ai_features"
resources :ai_secrets,
only: %i[index show new create edit update destroy],
path: "ai-secrets",
controller: "discourse_ai/admin/ai_secrets"
end
end