- internal/config:serve/init/backup/gc 子命令参数解析(--dev 强制 loopback 守卫) - internal/store:user_version 版本化迁移(仅追加式 + 越界拒启 + --allow-newer)、 笔记/图片/引用/会话/设置 DAO、并集可见性查询、VACUUM INTO 在线备份、 回收站 30 天 + 孤儿图 7 天宽限 gc - internal/auth:Argon2id PHC 串(m=19456,t=2,p=1)、256bit token 与 SHA-256 摘要 - internal/markdown:goldmark(默认转义)+ bluemonday 双保险,摘要纯文本提取 - internal/middleware:安全头(CSP/HSTS/nosniff 等)、错误日志、 有界令牌桶限流(per-IP 桶上限 + TTL 逐出)、Origin/Referer 同源校验
79 lines
2.3 KiB
Go
79 lines
2.3 KiB
Go
package store
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"strconv"
|
|
)
|
|
|
|
func fmtSscanInt(s string, out *int64) (int, error) {
|
|
v, err := strconv.ParseInt(s, 10, 64)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
*out = v
|
|
return 1, nil
|
|
}
|
|
|
|
// Session 会话行(token 明文不入库,只存 SHA-256)。
|
|
type Session struct {
|
|
TokenHash string
|
|
CSRFToken string
|
|
CreatedAt int64
|
|
ExpiresAt int64
|
|
}
|
|
|
|
// CreateSession 插入会话行。
|
|
func (s *Store) CreateSession(tokenHash, csrfToken string, createdAt, expiresAt int64) error {
|
|
_, err := s.db.Exec(
|
|
`INSERT INTO sessions (token_hash, csrf_token, created_at, expires_at) VALUES (?, ?, ?, ?)`,
|
|
tokenHash, csrfToken, createdAt, expiresAt)
|
|
return err
|
|
}
|
|
|
|
// RotateSession 重建会话行:新 token、**csrf_token 保持不变**(§7.3-5)、新过期时间。
|
|
// 原子替换,避免窗口期内两行并存。
|
|
func (s *Store) RotateSession(oldTokenHash, newTokenHash, csrfToken string, createdAt, expiresAt int64) error {
|
|
tx, err := s.db.Begin()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer tx.Rollback()
|
|
if _, err := tx.Exec(`DELETE FROM sessions WHERE token_hash=?`, oldTokenHash); err != nil {
|
|
return err
|
|
}
|
|
if _, err := tx.Exec(
|
|
`INSERT INTO sessions (token_hash, csrf_token, created_at, expires_at) VALUES (?, ?, ?, ?)`,
|
|
newTokenHash, csrfToken, createdAt, expiresAt); err != nil {
|
|
return err
|
|
}
|
|
return tx.Commit()
|
|
}
|
|
|
|
// GetSession 按 token 摘要取会话(含已过期行;过期判定在调用方)。
|
|
func (s *Store) GetSession(tokenHash string) (*Session, error) {
|
|
row := s.db.QueryRow(
|
|
`SELECT token_hash, csrf_token, created_at, expires_at FROM sessions WHERE token_hash=?`, tokenHash)
|
|
var sess Session
|
|
err := row.Scan(&sess.TokenHash, &sess.CSRFToken, &sess.CreatedAt, &sess.ExpiresAt)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, ErrNotFound
|
|
}
|
|
return &sess, err
|
|
}
|
|
|
|
// DeleteSession 删除会话行(登出/轮换)。
|
|
func (s *Store) DeleteSession(tokenHash string) error {
|
|
_, err := s.db.Exec(`DELETE FROM sessions WHERE token_hash=?`, tokenHash)
|
|
return err
|
|
}
|
|
|
|
// DeleteExpiredSessions 清理过期会话,返回删除行数。
|
|
func (s *Store) DeleteExpiredSessions(now int64) (int64, error) {
|
|
res, err := s.db.Exec(`DELETE FROM sessions WHERE expires_at < ?`, now)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return res.RowsAffected()
|
|
}
|