first commit

This commit is contained in:
2026-08-14 23:41:57 +08:00
commit 086803a8dd
471 changed files with 91938 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
package server
import (
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// authCookieName 保存已通过授权校验的凭证。值为授权码的 SHA-256 十六进制摘要,
// 不落明文;HttpOnly 使前端 JS 无法读取,降低泄露面。
const authCookieName = "blackbean_auth"
// authCookieValue 计算授权码的稳定凭证值(SHA-256 摘要)。
func authCookieValue(code string) string {
sum := sha256.Sum256([]byte(code))
return hex.EncodeToString(sum[:])
}
// authRequired 是访问授权中间件:cfg.AuthCode 为空(未启用授权)时直接放行;
// 否则要求请求携带与授权码匹配的 Cookie,不匹配则 401 并中止后续处理。
func (s *Server) authRequired(c *gin.Context) {
if s.cfg.AuthCode == "" {
c.Next()
return
}
if cookie, err := c.Cookie(authCookieName); err == nil && cookie == authCookieValue(s.cfg.AuthCode) {
c.Next()
return
}
fail(c, http.StatusUnauthorized, "需要授权码才能访问,请先在首页输入授权码")
c.Abort()
}
// authStatus 返回授权状态:required=是否启用了授权,authorized=当前请求是否已通过授权。
// 该接口始终放行(不挂授权中间件),前端据此决定是否展示授权界面。
func (s *Server) authStatus(c *gin.Context) {
required := s.cfg.AuthCode != ""
authorized := false
if required {
if cookie, err := c.Cookie(authCookieName); err == nil {
authorized = cookie == authCookieValue(s.cfg.AuthCode)
}
}
ok(c, gin.H{"required": required, "authorized": authorized})
}
// authLogin 校验授权码并签发 Cookie。
// 该接口始终放行(不挂授权中间件);未启用授权时返回 400。
func (s *Server) authLogin(c *gin.Context) {
if s.cfg.AuthCode == "" {
fail(c, http.StatusBadRequest, "本实例未启用访问授权")
return
}
var req struct {
Code string `json:"code"`
}
if err := c.ShouldBindJSON(&req); err != nil {
fail(c, http.StatusBadRequest, "请求体格式错误")
return
}
// 常量时间比较,避免通过响应时差探测授权码
want := []byte(s.cfg.AuthCode)
got := []byte(strings.TrimSpace(req.Code))
if len(want) != len(got) || subtle.ConstantTimeCompare(want, got) != 1 {
fail(c, http.StatusUnauthorized, "授权码错误")
return
}
http.SetCookie(c.Writer, &http.Cookie{
Name: authCookieName,
Value: authCookieValue(s.cfg.AuthCode),
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
ok(c, gin.H{"authorized": true})
}