P0(高危): - 新增全局 CSRF 中间件(同步器令牌),覆盖全部 30 个表单与 AJAX 请求 - 修复附件上传/列表/删除越权(IDOR),增加 admin/上传者/文章作者所有权校验 - 登录/注册成功后会话轮换,修复会话固定 - 会话密钥改用 crypto/rand 生成,配置缺失 secret 时拒绝启动 P1(中危): - session 与 comment_uid cookie 增加 Secure/SameSite 标志 - 新增安全响应头:CSP、X-Content-Type-Options、X-Frame-Options、HSTS 等 - 新增 web.trusted_proxies 配置,修复 X-Forwarded-For 伪造 - 修复浏览量记录 goroutine 访问已回收 gin.Context 的数据竞争 补充 17 个安全回归测试(middleware/handlers),go test -race 全绿
207 lines
5.4 KiB
Go
207 lines
5.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-contrib/sessions"
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
|
|
"go_blog/models"
|
|
)
|
|
|
|
// LoginPage renders the login form.
|
|
func LoginPage() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
tr := getTr(c)
|
|
data := DefaultData(c)
|
|
data["Title"] = tr["page_login"]
|
|
if c.Query("error") == "1" {
|
|
data["Error"] = tr["login_error"]
|
|
}
|
|
// Check if registration is allowed from site settings
|
|
siteSetting, _ := c.Get("site_setting")
|
|
if s, ok := siteSetting.(*models.SiteSetting); ok && s != nil {
|
|
data["AllowRegistration"] = s.AllowRegistration
|
|
}
|
|
c.HTML(http.StatusOK, "login", data)
|
|
}
|
|
}
|
|
|
|
// Login processes the login form submission.
|
|
func Login(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
username := c.PostForm("username")
|
|
password := c.PostForm("password")
|
|
|
|
var user models.User
|
|
if err := db.Where("username = ?", username).First(&user).Error; err != nil {
|
|
c.Redirect(http.StatusFound, "/login?error=1")
|
|
return
|
|
}
|
|
|
|
if !user.CheckPassword(password) {
|
|
c.Redirect(http.StatusFound, "/login?error=1")
|
|
return
|
|
}
|
|
|
|
// Refuse login for non-normal accounts (disabled / locked / unactivated).
|
|
if user.Status != models.StatusNormal {
|
|
c.Redirect(http.StatusFound, "/login?error=1")
|
|
return
|
|
}
|
|
|
|
// Rotate the session on privilege change to prevent session
|
|
// fixation: drop all pre-authentication state, keep only the
|
|
// harmless UI preferences (language and CSRF token so forms
|
|
// already rendered in other tabs stay valid).
|
|
session := sessions.Default(c)
|
|
lang, _ := session.Get("lang").(string)
|
|
csrfTok, _ := session.Get("csrf_token").(string)
|
|
session.Clear()
|
|
if lang != "" {
|
|
session.Set("lang", lang)
|
|
}
|
|
if csrfTok != "" {
|
|
session.Set("csrf_token", csrfTok)
|
|
}
|
|
session.Set("user_id", user.ID)
|
|
session.Set("username", user.Username)
|
|
if err := session.Save(); err != nil {
|
|
c.String(http.StatusInternalServerError, "Failed to save session")
|
|
return
|
|
}
|
|
|
|
// Redirect based on user role: admins to /admin, others to home
|
|
if user.Role == models.RoleAdmin {
|
|
c.Redirect(http.StatusFound, "/admin")
|
|
} else {
|
|
c.Redirect(http.StatusFound, "/")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Logout clears the session and redirects home.
|
|
func Logout() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
session := sessions.Default(c)
|
|
session.Clear()
|
|
session.Save()
|
|
c.Redirect(http.StatusFound, "/")
|
|
}
|
|
}
|
|
|
|
// RegisterPage renders the registration form (only when registration is enabled).
|
|
func RegisterPage(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Check if registration is allowed
|
|
var s models.SiteSetting
|
|
if err := db.First(&s, 1).Error; err != nil || !s.AllowRegistration {
|
|
c.Redirect(http.StatusFound, "/login")
|
|
return
|
|
}
|
|
|
|
tr := getTr(c)
|
|
data := DefaultData(c)
|
|
data["Title"] = tr["page_register"]
|
|
|
|
if errMsg := c.Query("error"); errMsg != "" {
|
|
data["Error"] = tr[errMsg]
|
|
}
|
|
|
|
c.HTML(http.StatusOK, "register", data)
|
|
}
|
|
}
|
|
|
|
// Register processes the registration form submission.
|
|
func Register(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Check if registration is allowed
|
|
var s models.SiteSetting
|
|
if err := db.First(&s, 1).Error; err != nil || !s.AllowRegistration {
|
|
c.Redirect(http.StatusFound, "/login")
|
|
return
|
|
}
|
|
|
|
username := strings.TrimSpace(c.PostForm("username"))
|
|
password := c.PostForm("password")
|
|
confirmPassword := c.PostForm("confirm_password")
|
|
email := strings.TrimSpace(c.PostForm("email"))
|
|
displayName := strings.TrimSpace(c.PostForm("display_name"))
|
|
|
|
// Validate inputs
|
|
if username == "" || password == "" {
|
|
c.Redirect(http.StatusFound, "/register?error=register_required")
|
|
return
|
|
}
|
|
|
|
if len(username) < 3 || len(username) > 32 {
|
|
c.Redirect(http.StatusFound, "/register?error=register_username_length")
|
|
return
|
|
}
|
|
|
|
if len(password) < 6 {
|
|
c.Redirect(http.StatusFound, "/register?error=register_password_length")
|
|
return
|
|
}
|
|
|
|
if password != confirmPassword {
|
|
c.Redirect(http.StatusFound, "/register?error=register_password_mismatch")
|
|
return
|
|
}
|
|
|
|
// Check if username already exists
|
|
var existingUser models.User
|
|
if err := db.Where("username = ?", username).First(&existingUser).Error; err == nil {
|
|
c.Redirect(http.StatusFound, "/register?error=user_username_exists")
|
|
return
|
|
}
|
|
|
|
// Create new user
|
|
user := models.User{
|
|
Username: username,
|
|
Email: email,
|
|
DisplayName: displayName,
|
|
Role: models.RoleAuthor,
|
|
Status: models.StatusNormal,
|
|
}
|
|
|
|
if displayName == "" {
|
|
user.DisplayName = username
|
|
}
|
|
|
|
if err := user.SetPassword(password); err != nil {
|
|
c.Redirect(http.StatusFound, "/register?error=register_error")
|
|
return
|
|
}
|
|
|
|
if err := db.Create(&user).Error; err != nil {
|
|
c.Redirect(http.StatusFound, "/register?error=register_error")
|
|
return
|
|
}
|
|
|
|
// Auto-login after successful registration (with session
|
|
// rotation, mirroring the login handler).
|
|
session := sessions.Default(c)
|
|
lang, _ := session.Get("lang").(string)
|
|
csrfTok, _ := session.Get("csrf_token").(string)
|
|
session.Clear()
|
|
if lang != "" {
|
|
session.Set("lang", lang)
|
|
}
|
|
if csrfTok != "" {
|
|
session.Set("csrf_token", csrfTok)
|
|
}
|
|
session.Set("user_id", user.ID)
|
|
session.Set("username", user.Username)
|
|
if err := session.Save(); err != nil {
|
|
c.Redirect(http.StatusFound, "/login")
|
|
return
|
|
}
|
|
|
|
// Redirect to home page
|
|
c.Redirect(http.StatusFound, "/")
|
|
}
|
|
}
|