重組前檢查點:根目錄 main package

This commit is contained in:
2026-10-03 09:23:38 +08:00
commit f373cb8d37
45 changed files with 5332 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
package main
import (
"log"
"net/http"
"strings"
"gorm.io/gorm"
)
// logoutHandler 處理 POST /logout,依 Content-Type 分流(與登入一致):
// 表單走瀏覽器流程(需通過 CSRF 驗證,失敗時以 403 重繪目前狀態頁),
// JSON 走 API 流程。登出為冪等操作——查無 Session 亦視為成功;資料庫
// 刪除失敗僅記錄,仍清除 Cookie 並回應成功(Session 最遲於效期到期失效)。
func logoutHandler(db *gorm.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ct := r.Header.Get("Content-Type")
var isForm bool
switch {
case strings.HasPrefix(ct, "application/json"):
case strings.HasPrefix(ct, "application/x-www-form-urlencoded"),
strings.HasPrefix(ct, "multipart/form-data"):
isForm = true
default:
writeError(w, http.StatusUnsupportedMediaType, "Content-Type 須為 application/json 或表單")
return
}
if isForm {
if err := r.ParseForm(); err != nil {
renderAccountPage(w, r, db, http.StatusBadRequest, "無法解析表單內容")
return
}
if !verifyCSRF(r) {
renderAccountPage(w, r, db, http.StatusForbidden, "表單驗證失敗,請重新整理頁面後再試")
return
}
}
if c, err := r.Cookie(sessionCookieName); err == nil {
if err := deleteSession(db, c.Value); err != nil {
log.Printf("logout: %v", err)
}
}
clearSessionCookie(w, r)
if isForm {
// PRG:以 303 導向 /login 顯示登入表單,避免重新整理重複送出。
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
w.WriteHeader(http.StatusNoContent)
}
}
// clearSessionCookie 以 Max-Age=0 清除瀏覽器的 Session Cookie(與
// setSessionCookie 對稱,屬性一致以免因 Path 或 Secure 差異清不掉)。
func clearSessionCookie(w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, &http.Cookie{
Name: sessionCookieName,
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
Secure: r.TLS != nil,
})
}