Просмотр исходного кода

refactor(ui): require POST for feed refresh endpoints

Feed refresh endpoints mutate state and should not be reachable via
GET. Drop the GET registrations on /feeds/refresh, /feed/{id}/refresh,
/category/{id}/feeds/refresh and /category/{id}/entries/refresh, and
update the templates and the R keyboard shortcut to submit POST forms
with a CSRF token.
Frédéric Guillot 1 неделя назад
Родитель
Сommit
30ede1caa1

+ 6 - 3
internal/template/templates/common/feed_list.html

@@ -51,9 +51,12 @@
                 </ul>
                 <ul class="item-meta-icons">
                     <li class="item-meta-icons-refresh">
-                        <a href="{{ routePath "/feed/%d/refresh" .ID }}" aria-describedby="feed-title-{{ .ID }}">
-                            {{ icon "refresh" }}<span class="icon-label">{{ t "menu.refresh_feed" }}</span>
-                        </a>
+                        <form action="{{ routePath "/feed/%d/refresh" .ID }}" method="post">
+                            <input type="hidden" name="csrf" value="{{ $.csrf }}">
+                            <button aria-describedby="feed-title-{{ .ID }}">
+                                {{ icon "refresh" }}<span class="icon-label">{{ t "menu.refresh_feed" }}</span>
+                            </button>
+                        </form>
                     </li>
                     <li class="item-meta-icons-edit">
                         <a href="{{ routePath "/feed/%d/edit" .ID }}" aria-describedby="feed-title-{{ .ID }}">

+ 2 - 1
internal/template/templates/common/feed_menu.html

@@ -13,7 +13,8 @@
         <a class="page-link" href="{{ routePath "/import" }}">{{ icon "feed-import" }}{{ t "menu.import" }}</a>
     </li>
     <li>
-        <form action="{{ routePath "/feeds/refresh" }}" class="page-header-action-form">
+        <form action="{{ routePath "/feeds/refresh" }}" method="post" class="page-header-action-form">
+            <input type="hidden" name="csrf" value="{{ .csrf }}">
             <button class="page-button" data-label-loading="{{ t "confirm.loading" }}">
                 {{ icon "refresh" }}{{ t "menu.refresh_all_feeds" }}
             </button>

+ 1 - 1
internal/template/templates/views/add_subscription.html

@@ -3,7 +3,7 @@
 {{ define "page_header"}}
 <section class="page-header" aria-labelledby="page-header-title">
     <h1 id="page-header-title">{{ t "page.add_feed.title" }}</h1>
-    {{ template "feed_menu" }}
+    {{ template "feed_menu" dict "csrf" .csrf }}
 </section>
 {{ end }}
 

+ 2 - 0
internal/template/templates/views/category_entries.html

@@ -65,8 +65,10 @@
             <li>
                 <form
                     action="{{ routePath "/category/%d/entries/refresh" .category.ID }}"
+                    method="post"
                     class="page-header-action-form"
                 >
+                    <input type="hidden" name="csrf" value="{{ .csrf }}">
                     <button class="page-button" data-label-loading="{{ t "confirm.loading" }}">
                         {{ icon "refresh" }}{{ t "menu.refresh_all_feeds" }}
                     </button>

+ 3 - 1
internal/template/templates/views/category_feeds.html

@@ -35,7 +35,9 @@
                 <form
                     class="page-header-action-form"
                     action="{{ routePath "/category/%d/feeds/refresh" .category.ID }}"
+                    method="post"
                 >
+                    <input type="hidden" name="csrf" value="{{ .csrf }}">
                     <button
                         class="page-button"
                         data-label-loading="{{ t "confirm.loading" }}"
@@ -53,7 +55,7 @@
 {{ if not .feeds }}
     <p role="alert" class="alert">{{ t "alert.no_feed_in_category" }}</p>
 {{ else }}
-    {{ template "feed_list" dict "categoryID" .category.ID "user" .user "feeds" .feeds "ParsingErrorCount" .ParsingErrorCount }}
+    {{ template "feed_list" dict "categoryID" .category.ID "user" .user "feeds" .feeds "ParsingErrorCount" .ParsingErrorCount "csrf" .csrf }}
 {{ end }}
 
 {{ end }}

+ 1 - 1
internal/template/templates/views/choose_subscription.html

@@ -3,7 +3,7 @@
 {{ define "page_header"}}
 <section class="page-header" aria-labelledby="page-header-title">
     <h1 id="page-header-title">{{ t "page.add_feed.title" }}</h1>
-    {{ template "feed_menu" }}
+    {{ template "feed_menu" dict "csrf" .csrf }}
 </section>
 {{ end }}
 

+ 2 - 2
internal/template/templates/views/feeds.html

@@ -3,7 +3,7 @@
 {{ define "page_header"}}
 <section class="page-header" aria-labelledby="page-header-title">
     <h1 id="page-header-title">{{ t "page.feeds.title" }} ({{ .total }})</h1>
-    {{ template "feed_menu" }}
+    {{ template "feed_menu" dict "csrf" .csrf }}
 </section>
 {{ end }}
 
@@ -11,7 +11,7 @@
 {{ if not .feeds }}
     <p role="alert" class="alert">{{ t "alert.no_feed" }}</p>
 {{ else }}
-    {{ template "feed_list" dict "user" .user "feeds" .feeds "ParsingErrorCount" .ParsingErrorCount }}
+    {{ template "feed_list" dict "user" .user "feeds" .feeds "ParsingErrorCount" .ParsingErrorCount "csrf" .csrf }}
 {{ end }}
 
 {{ end }}

+ 1 - 1
internal/template/templates/views/import.html

@@ -3,7 +3,7 @@
 {{ define "page_header"}}
 <section class="page-header" aria-labelledby="page-header-title">
     <h1 id="page-header-title">{{ t "page.import.title" }}</h1>
-    {{ template "feed_menu" }}
+    {{ template "feed_menu" dict "csrf" .csrf }}
 </section>
 {{ end }}
 

+ 5 - 1
internal/ui/static/css/common.css

@@ -910,7 +910,7 @@ dialog {
     margin-right: 0;
 }
 
-.item-meta-icons li > :is(a, button) {
+.item-meta-icons li :is(a, button) {
     color: #777;
     text-decoration: none;
     font-size: 0.8rem;
@@ -919,6 +919,10 @@ dialog {
     cursor: pointer;
 }
 
+.item-meta-icons li form {
+    display: contents;
+}
+
 .item-meta-icons a span {
     text-decoration: underline;
 }

+ 8 - 2
internal/ui/static/js/app.js

@@ -658,12 +658,18 @@ function toggleEntryStatus(element, toasting) {
 /**
  * Handle the refresh of all feeds.
  *
- * This function redirects the user to the URL specified in the data-refresh-all-feeds-url attribute of the body element.
+ * This function POSTs to the URL specified in the data-refresh-all-feeds-url attribute of the body element.
  */
 function handleRefreshAllFeedsAction() {
     const refreshAllFeedsUrl = document.body.dataset.refreshAllFeedsUrl;
     if (refreshAllFeedsUrl) {
-        window.location.href = refreshAllFeedsUrl;
+        sendPOSTRequest(refreshAllFeedsUrl).then((response) => {
+            if (response?.redirected && response.url) {
+                window.location.href = response.url;
+            } else {
+                window.location.reload();
+            }
+        });
     }
 }
 

+ 3 - 4
internal/ui/ui.go

@@ -60,10 +60,9 @@ func Serve(store *storage.Storage, pool *worker.Pool) http.Handler {
 
 	// Feed listing pages.
 	mux.HandleFunc("GET /feeds", handler.showFeedsPage)
-	mux.HandleFunc("GET /feeds/refresh", handler.refreshAllFeeds)
+	mux.HandleFunc("POST /feeds/refresh", handler.refreshAllFeeds)
 
 	// Individual feed pages.
-	mux.HandleFunc("GET /feed/{feedID}/refresh", handler.refreshFeed)
 	mux.HandleFunc("POST /feed/{feedID}/refresh", handler.refreshFeed)
 	mux.HandleFunc("GET /feed/{feedID}/edit", handler.showEditFeedPage)
 	mux.HandleFunc("POST /feed/{feedID}/remove", handler.removeFeed)
@@ -85,9 +84,9 @@ func Serve(store *storage.Storage, pool *worker.Pool) http.Handler {
 	mux.HandleFunc("GET /category/{categoryID}/feeds", handler.showCategoryFeedsPage)
 	mux.HandleFunc("POST /category/{categoryID}/feed/{feedID}/remove", handler.removeCategoryFeed)
 	mux.HandleFunc("POST /category/{categoryID}/feed/{feedID}/mark-all-as-read", handler.markCategoryFeedAsRead)
-	mux.HandleFunc("GET /category/{categoryID}/feeds/refresh", handler.refreshCategoryFeedsPage)
+	mux.HandleFunc("POST /category/{categoryID}/feeds/refresh", handler.refreshCategoryFeedsPage)
 	mux.HandleFunc("GET /category/{categoryID}/entries", handler.showCategoryEntriesPage)
-	mux.HandleFunc("GET /category/{categoryID}/entries/refresh", handler.refreshCategoryEntriesPage)
+	mux.HandleFunc("POST /category/{categoryID}/entries/refresh", handler.refreshCategoryEntriesPage)
 	mux.HandleFunc("GET /category/{categoryID}/entries/all", handler.showCategoryEntriesAllPage)
 	mux.HandleFunc("GET /category/{categoryID}/entries/starred", handler.showCategoryEntriesStarredPage)
 	mux.HandleFunc("GET /category/{categoryID}/edit", handler.showEditCategoryPage)