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

refactor(logout): require POST for /logout

Logout is a state-changing action and should not be reachable via GET.
Switching to POST routes the request through the CSRF middleware so
prefetchers and cross-site GETs can no longer terminate the session.
Frédéric Guillot 1 неделя назад
Родитель
Сommit
16902a2297

+ 4 - 1
internal/template/templates/common/layout.html

@@ -107,7 +107,10 @@
                 </li>
                 {{ if not hasAuthProxy }}
                     <li>
-                        <a href="{{ routePath "/logout" }}" title="{{ t "tooltip.logged_user" .user.Username }}">{{ icon "logout" }}{{ t "menu.logout" }}</a>
+                        <form action="{{ routePath "/logout" }}" method="post" class="logout-form">
+                            <input type="hidden" name="csrf" value="{{ .csrf }}">
+                            <button type="submit" class="logout-button" title="{{ t "tooltip.logged_user" .user.Username }}">{{ icon "logout" }}{{ t "menu.logout" }}</button>
+                        </form>
                     </li>
                 {{ end }}
             </ul>

+ 13 - 2
internal/ui/static/css/common.css

@@ -132,11 +132,11 @@ a:hover {
     border-bottom: 1px dotted var(--header-list-border-color);
 }
 
-.header li a:hover {
+.header li :is(a, .logout-button):hover {
     color: #888;
 }
 
-.header :is(a, summary) {
+.header :is(a, summary, .logout-button) {
     font-size: 0.9em;
     color: var(--header-link-color);
     text-decoration: none;
@@ -144,6 +144,17 @@ a:hover {
     font-weight: 400;
 }
 
+.header .logout-form {
+    display: inline;
+}
+
+.header .logout-button {
+    background: none;
+    padding: 0;
+    cursor: pointer;
+    font-family: inherit;
+}
+
 .header .active a {
     color: var(--header-active-link-color);
     /* Note: Firefox on Windows does not show the link as bold if the value is under 600 */

+ 1 - 1
internal/ui/ui.go

@@ -160,7 +160,7 @@ func Serve(store *storage.Storage, pool *worker.Pool) http.Handler {
 
 	// Authentication pages.
 	mux.HandleFunc("POST /login", handler.checkLogin)
-	mux.HandleFunc("GET /logout", handler.logout)
+	mux.HandleFunc("POST /logout", handler.logout)
 	mux.Handle("GET /{$}", authProxyMiddleware.handle(http.HandlerFunc(handler.showLoginPage)))
 
 	// WebAuthn flow.