package middleware import ( "net/http" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "gorm.io/gorm" "go_blog/i18n" "go_blog/models" ) // sessionUserID extracts the logged-in user's numeric ID from the session, // defending against int/uint/int64/float64 storage. ok=false if absent or of // an unexpected type. func sessionUserID(session sessions.Session) (uint, bool) { userID := session.Get("user_id") if userID == nil { return 0, false } switch v := userID.(type) { case uint: return v, true case int: return uint(v), true case int64: return uint(v), true case float64: return uint(v), true default: return 0, false } } // clearUserSession drops the authentication state from a session, keeping only // the harmless UI preferences (language and CSRF token, mirroring the login // handler's rotation) so forms already rendered in other tabs stay valid. func clearUserSession(session sessions.Session) { lang, _ := session.Get("lang").(string) csrfTok, _ := session.Get(CSRFSessionKey).(string) session.Clear() if lang != "" { session.Set("lang", lang) } if csrfTok != "" { session.Set(CSRFSessionKey, csrfTok) } session.Save() } // AuthRequired is middleware that protects routes. If the user is not logged // in, they are redirected to /login. The session user is also re-validated // against the database on every request: an account that has since been // disabled, locked or soft-deleted loses access immediately instead of when // its cookie expires (SECURITY_TODO #20). func AuthRequired(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { session := sessions.Default(c) uid, ok := sessionUserID(session) if !ok { c.Redirect(http.StatusFound, "/login") c.Abort() return } var user models.User if err := db.First(&user, uid).Error; err != nil || user.Status != models.StatusNormal { // Account no longer usable — kill the session so the stale cookie // cannot be replayed. clearUserSession(session) c.Redirect(http.StatusFound, "/login") c.Abort() return } c.Next() } } // AdminRequired is middleware that restricts a route to admin-role users. It // must run after AuthRequired (which guarantees a live, normal-status session // user). Non-admin users are redirected back to the admin dashboard. func AdminRequired(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { session := sessions.Default(c) uid, ok := sessionUserID(session) if !ok { c.Redirect(http.StatusFound, "/login") c.Abort() return } var user models.User if err := db.First(&user, uid).Error; err != nil || user.Role != models.RoleAdmin { c.Redirect(http.StatusFound, "/admin") c.Abort() return } c.Next() } } // SetUserContext is global middleware that reads the session and sets // template-friendly context values for all pages (language, auth state, etc.). func SetUserContext(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { session := sessions.Default(c) // --- Language detection --- // Priority: query param > session > Accept-Language header > default EN var lang i18n.Lang queryLang := c.Query("lang") switch queryLang { case "zh": lang = i18n.ZH case "en": lang = i18n.EN case "": // Try session if saved, ok := session.Get("lang").(string); ok { lang = i18n.Lang(saved) } if lang == "" { // Try Accept-Language header lang = i18n.DetectLang(c.GetHeader("Accept-Language")) } default: // Unsupported language in query — fall back to English. lang = i18n.EN } // Persist language in session. session.Set("lang", string(lang)) session.Save() // Make translations available in the Gin context. c.Set("tr", i18n.T(lang)) c.Set("lang", string(lang)) // Set the opposite language code for the language switcher link. switchLang := "zh" if lang == i18n.ZH { switchLang = "en" } c.Set("switch_lang", switchLang) // --- Auth state --- // The user is only considered logged in if the account still exists // and is in normal status: a disabled/locked/soft-deleted account must // not keep template-level privileges (e.g. comment auto-approval) // after its session was invalidated (SECURITY_TODO #20). isLoggedIn := false var username string var avatar string var displayName string var role string if uid, ok := sessionUserID(session); ok { var user models.User if err := db.First(&user, uid).Error; err == nil && user.Status == models.StatusNormal { isLoggedIn = true username = user.Username avatar = user.Avatar displayName = user.DisplayName role = user.Role } } c.Set("is_logged_in", isLoggedIn) c.Set("username", username) c.Set("avatar", avatar) c.Set("display_name", displayName) c.Set("role", role) // --- Site platform configuration (from DB cache) --- site := models.GetSiteSetting() c.Set("site_setting", site) c.Set("site_logo", site.Logo) c.Set("site_logo_is_url", site.LogoIsURL()) c.Set("site_favicon", site.Favicon) c.Set("site_favicon_is_url", site.FaviconIsURL()) c.Set("site_logo_text", site.LogoText(string(lang))) c.Set("site_header_text", site.HeaderText(string(lang))) c.Set("site_home_welcome", site.HomeWelcome(string(lang))) c.Set("site_home_subtitle", site.HomeSubtitle(string(lang))) c.Set("site_footer_text", site.FooterText(string(lang))) // --- Navigation links (from DB cache) --- navLinks := models.GetNavLinks() c.Set("nav_links", navLinks) c.Next() } }