Files
mce/internal/middleware/auth.go
Victor_Jay 0014bdb9ff feat: 品牌CMS配置化 + cookie_secure配置
- config.yaml 新增 server.cookie_secure 配置项
- site_settings 新增 site_name/site_tagline/contact_email 站点信息
- SiteInfo 结构体扩展,支持运行时从 DB 读取站点品牌信息
- cookie.go 使用 cfg.Server.CookieSecure 替代 Mode!=debug 判断
- SetSessionCookie/ClearSessionCookie 支持 siteSettings 运行时覆盖
- 模板替换硬编码 MetaLab → {{.SiteName}}(nav/header/footer/home/login/register/guidelines/admin,12+处)
- 管理后台站点设置页新增站点名称/标语/联系邮箱输入项
- 页脚版权年份改为动态 CurrentYear
- 注册成功消息 CMS 化(后端+前端)
2026-06-21 23:34:14 +08:00

160 lines
4.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package middleware
import (
"net/http"
"strings"
"metazone.cc/mce/internal/common"
"metazone.cc/mce/internal/config"
"metazone.cc/mce/internal/model"
"metazone.cc/mce/internal/session"
"github.com/gin-gonic/gin"
)
// autoCheckIner AuthMiddleware 对自动签到服务的最小依赖
type autoCheckIner interface {
CheckIn(userID uint) (checkedIn bool, newExp int, levelUp bool, err error)
}
// AuthMiddleware 认证中间件(结构体模式,持有 SessionManager
type AuthMiddleware struct {
cfg *config.Config
sessionManager *session.Manager
levelSvc autoCheckIner
siteSettings *config.SiteSettings
}
// NewAuthMiddleware 构造函数
func NewAuthMiddleware(cfg *config.Config, sm *session.Manager) *AuthMiddleware {
return &AuthMiddleware{cfg: cfg, sessionManager: sm}
}
// WithSiteSettings 链式注入站点设置(用于 cookie_secure 运行时覆盖)
func (am *AuthMiddleware) WithSiteSettings(ss *config.SiteSettings) *AuthMiddleware {
am.siteSettings = ss
return am
}
// WithLevelService 链式注入等级服务(用于自动签到)
func (am *AuthMiddleware) WithLevelService(svc autoCheckIner) *AuthMiddleware {
am.levelSvc = svc
return am
}
// Required 登录认证中间件:读 session cookie → 验证会话 → 注入用户信息
// 页面请求(非 /api/ 前缀)→ 验证失败清除 cookie 并重定向到登录页
// API 请求 → 验证失败返回 401 JSON
func (am *AuthMiddleware) Required() gin.HandlerFunc {
return func(c *gin.Context) {
sid, err := c.Cookie(common.SessionCookieName)
if err != nil || sid == "" {
am.abortUnauthorized(c)
return
}
s, err := am.sessionManager.Validate(sid)
if err != nil || s == nil {
am.abortUnauthorized(c)
return
}
injectSessionContext(c, s)
am.tryAutoCheckIn(c, s)
c.Next()
}
}
// abortUnauthorized 统一处理未认证API 返回 JSON页面请求跳转登录
func (am *AuthMiddleware) abortUnauthorized(c *gin.Context) {
common.ClearSessionCookie(c, am.cfg, am.siteSettings)
if strings.HasPrefix(c.Request.URL.Path, "/api/") {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"success": false, "message": "登录已过期,请重新登录",
})
} else {
common.RedirectToLogin(c)
}
}
// Optional 可选认证:已登录则注入用户信息,未登录也放行
func (am *AuthMiddleware) Optional() gin.HandlerFunc {
return func(c *gin.Context) {
sid, err := c.Cookie(common.SessionCookieName)
if err != nil || sid == "" {
c.Next()
return
}
s, err := am.sessionManager.Validate(sid)
if err != nil || s == nil {
common.ClearSessionCookie(c, am.cfg, am.siteSettings)
c.Next()
return
}
injectSessionContext(c, s)
am.tryAutoCheckIn(c, s)
c.Next()
}
}
// tryAutoCheckIn 尝试自动签到,并同步 session Exp 与 DB静默失败不影响主流程
// CheckIn 即使已签到也会返回 DB 中最新的 Exp这里始终与 session 比较,
// 确保 CompleteTask 等非签到路径的 Exp 变化也能同步到 session避免 NAV 显示旧等级
// 被封禁用户跳过自动签到
func (am *AuthMiddleware) tryAutoCheckIn(c *gin.Context, s *session.Session) {
if am.levelSvc == nil || s == nil {
return
}
if s.Status == model.StatusBanned {
return
}
_, newExp, _, _ := am.levelSvc.CheckIn(s.UserID)
// 只允许 Exp 只增不减DB 异常返回 0 或主从延迟读到旧值时不会回退
if newExp > s.Exp {
s.Exp = newExp
_ = am.sessionManager.UpdateSession(s)
c.Set("exp", newExp)
}
}
// injectSessionContext 将会话中的用户信息注入 gin context
func injectSessionContext(c *gin.Context, s *session.Session) {
if s == nil {
return
}
c.Set("uid", s.UserID)
c.Set("email", s.Email)
c.Set("username", s.Username)
c.Set("avatar", s.Avatar)
c.Set("role", s.Role)
c.Set("status", s.Status)
c.Set("exp", s.Exp)
c.Set("sid", s.ID)
}
// BannedWriteGuard 封禁用户写操作守卫
// 仅拦截 POST/PUT/DELETE/PATCH 请求GET/HEAD/OPTIONS 放行
// 需在 Required() 之后调用,确保上下文中已有用户 status
func (am *AuthMiddleware) BannedWriteGuard() gin.HandlerFunc {
return func(c *gin.Context) {
// 只检查写操作
switch c.Request.Method {
case "GET", "HEAD", "OPTIONS":
c.Next()
return
}
status, _ := c.Get("status")
if status == model.StatusBanned {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"success": false,
"message": "账号已被封禁,无法执行此操作",
})
return
}
c.Next()
}
}