forked from alterminal/alterminal
77 lines
2.4 KiB
Go
77 lines
2.4 KiB
Go
package auth
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
ClearSession(db, w, r)
|
|
|
|
if isForm {
|
|
// PRG:以 303 導向 /login 顯示登入表單,避免重新整理重複送出。
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
}
|
|
|
|
// ClearSession 刪除資料庫中的 Session 並清除瀏覽器 Cookie,冪等——查無
|
|
// Session 亦清除 Cookie;資料庫刪除失敗僅記錄不中斷(Session 最遲於效期
|
|
// 到期失效)。供 LogoutHandler 與 oidc 套件的 RP-Initiated Logout 端點
|
|
// 共用同一套清理邏輯。
|
|
func ClearSession(db *gorm.DB, w http.ResponseWriter, r *http.Request) {
|
|
if c, err := r.Cookie(CookieName); err == nil {
|
|
if err := DeleteSession(db, c.Value); err != nil {
|
|
log.Printf("logout: %v", err)
|
|
}
|
|
}
|
|
clearSessionCookie(w, r)
|
|
}
|
|
|
|
// clearSessionCookie 以 Max-Age=0 清除瀏覽器的 Session Cookie(與
|
|
// setSessionCookie 對稱,屬性一致以免因 Path 或 Secure 差異清不掉)。
|
|
func clearSessionCookie(w http.ResponseWriter, r *http.Request) {
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: CookieName,
|
|
Value: "",
|
|
Path: "/",
|
|
MaxAge: -1,
|
|
HttpOnly: true,
|
|
SameSite: http.SameSiteLaxMode,
|
|
Secure: r.TLS != nil,
|
|
})
|
|
}
|