Files
jiang13-bbs/backend/service/hide_password_cookie.go
freefire ff2ab286fb feat: 书库导入导出/图片变体/书籍搜索/小组件运行时等
新增:
- 书库导入导出(library_import/library_export)及测试
- 图片变体生成(image_variants)与响应式图片(responsiveImage)
- 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件
- 小组件运行时(widgetRuntime)与静态检查(widgetLint)
- 上传缓存中间件(upload_cache)与字体 CSS 提取脚本

其它:
- 后端 handlers/services 全量调整
- 前端页面、组件、库函数与配置更新
2026-10-01 03:06:05 +08:00

158 lines
3.5 KiB
Go
Raw Permalink 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 service
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
)
const (
hidePwdCookiePrefix = "j13_hp_"
hidePwdCookieMaxAge = 30 * 24 * 3600 // 30 天
hidePwdCookieVersion = "1"
)
// HidePasswordCookie 帖子密码隐藏块解锁 cookie(游客可用)
type HidePasswordCookie struct {
secret []byte
secure bool
}
func NewHidePasswordCookie(jwtSecret string, secure bool) *HidePasswordCookie {
sum := sha256.Sum256([]byte("j13-hide-pwd-v1:" + jwtSecret))
return &HidePasswordCookie{secret: sum[:], secure: secure}
}
func (h *HidePasswordCookie) cookieName(postID uint) string {
return hidePwdCookiePrefix + strconv.FormatUint(uint64(postID), 10)
}
// ReadUnlocked 读取某帖已解锁的隐藏块下标
func (h *HidePasswordCookie) ReadUnlocked(c *gin.Context, postID uint) map[int]bool {
out := map[int]bool{}
if h == nil || c == nil {
return out
}
raw, err := c.Cookie(h.cookieName(postID))
if err != nil || raw == "" {
return out
}
idxs, ok := h.verify(postID, raw)
if !ok {
return out
}
for _, i := range idxs {
out[i] = true
}
return out
}
// WriteUnlocked 写入/合并已解锁下标
func (h *HidePasswordCookie) WriteUnlocked(c *gin.Context, postID uint, idxs []int) {
if h == nil || c == nil || len(idxs) == 0 {
return
}
merged := h.ReadUnlocked(c, postID)
for _, i := range idxs {
merged[i] = true
}
list := make([]int, 0, len(merged))
for i := range merged {
list = append(list, i)
}
sort.Ints(list)
val := h.sign(postID, list)
http.SetCookie(c.Writer, &http.Cookie{
Name: h.cookieName(postID),
Value: val,
Path: "/",
MaxAge: hidePwdCookieMaxAge,
HttpOnly: true,
Secure: h.secure,
SameSite: http.SameSiteLaxMode,
})
}
func (h *HidePasswordCookie) sign(postID uint, idxs []int) string {
payload := fmt.Sprintf("%s|%d|%s|%d",
hidePwdCookieVersion,
postID,
joinInts(idxs),
time.Now().Add(hidePwdCookieMaxAge*time.Second).Unix(),
)
mac := hmac.New(sha256.New, h.secret)
_, _ = mac.Write([]byte(payload))
sig := hex.EncodeToString(mac.Sum(nil))
return base64.RawURLEncoding.EncodeToString([]byte(payload)) + "." + sig
}
func (h *HidePasswordCookie) verify(postID uint, raw string) ([]int, bool) {
parts := strings.Split(raw, ".")
if len(parts) != 2 {
return nil, false
}
payloadBytes, err := base64.RawURLEncoding.DecodeString(parts[0])
if err != nil {
return nil, false
}
payload := string(payloadBytes)
mac := hmac.New(sha256.New, h.secret)
_, _ = mac.Write([]byte(payload))
expect := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expect), []byte(parts[1])) {
return nil, false
}
fields := strings.Split(payload, "|")
if len(fields) != 4 || fields[0] != hidePwdCookieVersion {
return nil, false
}
pid, err := strconv.ParseUint(fields[1], 10, 64)
if err != nil || uint(pid) != postID {
return nil, false
}
exp, err := strconv.ParseInt(fields[3], 10, 64)
if err != nil || time.Now().Unix() > exp {
return nil, false
}
return parseInts(fields[2]), true
}
func joinInts(idxs []int) string {
if len(idxs) == 0 {
return ""
}
var b strings.Builder
for i, n := range idxs {
if i > 0 {
b.WriteByte(',')
}
b.WriteString(strconv.Itoa(n))
}
return b.String()
}
func parseInts(s string) []int {
if s == "" {
return nil
}
parts := strings.Split(s, ",")
out := make([]int, 0, len(parts))
for _, p := range parts {
n, err := strconv.Atoi(p)
if err != nil || n < 0 {
continue
}
out = append(out, n)
}
return out
}