开源前加固认证与部署契约:加密随机 JWT、改密事务吊销、登录 CSRF,并禁止生产回落 localhost。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -59,27 +59,46 @@ func setAuthCookies(c *gin.Context, accessToken, refreshToken string, secure boo
|
||||
})
|
||||
}
|
||||
|
||||
// clearAuthCookies 清除当前契约下的三枚认证 cookie(名称随 ConfigureCookieNames,Path=/)。
|
||||
// clearAuthCookies 清除当前契约下的认证 cookie,并顺带清历史 Path=/api/auth 与无前缀名。
|
||||
func clearAuthCookies(c *gin.Context, secure bool) {
|
||||
cookies := []struct {
|
||||
type ck struct {
|
||||
name string
|
||||
httpOnly bool
|
||||
}{
|
||||
}
|
||||
cookies := []ck{
|
||||
{service.CookieName, true},
|
||||
{service.RefreshCookieName, true},
|
||||
{service.CSRFCookieName, false},
|
||||
// 历史无 __Host- 前缀(切换 DEV_MODE 后避免双 cookie)
|
||||
{"j13_token", true},
|
||||
{"j13_refresh", true},
|
||||
{"j13_csrf", false},
|
||||
{"__Host-j13_token", true},
|
||||
{"__Host-j13_refresh", true},
|
||||
{"__Host-j13_csrf", false},
|
||||
}
|
||||
paths := []string{"/", "/api/auth"}
|
||||
for _, ck := range cookies {
|
||||
http.SetCookie(c.Writer, &http.Cookie{
|
||||
Name: ck.name,
|
||||
Value: "",
|
||||
Path: "/",
|
||||
MaxAge: -1,
|
||||
HttpOnly: ck.httpOnly,
|
||||
Secure: secure,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Expires: time.Unix(0, 0),
|
||||
})
|
||||
for _, path := range paths {
|
||||
// __Host- 要求 Secure=true 且 Path=/;非 / 路径跳过 Host 前缀名
|
||||
if strings.HasPrefix(ck.name, "__Host-") && path != "/" {
|
||||
continue
|
||||
}
|
||||
sec := secure
|
||||
if strings.HasPrefix(ck.name, "__Host-") {
|
||||
sec = true
|
||||
}
|
||||
http.SetCookie(c.Writer, &http.Cookie{
|
||||
Name: ck.name,
|
||||
Value: "",
|
||||
Path: path,
|
||||
MaxAge: -1,
|
||||
HttpOnly: ck.httpOnly,
|
||||
Secure: sec,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Expires: time.Unix(0, 0),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +252,7 @@ func (h *Handlers) Refresh(c *gin.Context) {
|
||||
// access JWT 无状态、15 分钟自然过期;登出后 tv 不递增,属可接受的短窗口。
|
||||
func (h *Handlers) Logout(c *gin.Context) {
|
||||
if refreshToken, err := c.Cookie(service.RefreshCookieName); err == nil && refreshToken != "" {
|
||||
h.Auth.RevokeRefreshToken(refreshToken)
|
||||
_ = h.Auth.RevokeRefreshToken(refreshToken)
|
||||
}
|
||||
clearAuthCookies(c, !h.Cfg.DevMode)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "已登出"})
|
||||
|
||||
@@ -13,7 +13,6 @@ func (h *Handlers) Health(c *gin.Context) {
|
||||
if err := model.PingDB(); err != nil {
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||
"status": "error",
|
||||
"error": err.Error(),
|
||||
"version": version.Version,
|
||||
})
|
||||
return
|
||||
|
||||
@@ -538,10 +538,11 @@ func (h *Handlers) ToggleRecommend(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"recommended": rec})
|
||||
}
|
||||
|
||||
// requireAdminOrAbove 置顶/加精仅管理员及以上可用;校验失败已写响应,返回 false
|
||||
// requireAdminOrAbove 置顶/加精仅管理员及以上可用;以 DB Actor 为准
|
||||
func (h *Handlers) requireAdminOrAbove(c *gin.Context) bool {
|
||||
claims := middleware.CurrentUser(c)
|
||||
if model.RoleLevel(model.Role(claims.Role)) < model.RoleLevel(model.RoleAdmin) {
|
||||
actor, err := h.Auth.LoadActor(claims.ID)
|
||||
if err != nil || model.RoleLevel(actor.Role) < model.RoleLevel(model.RoleAdmin) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "仅管理员可操作"})
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user