后端基础层:模块定义、配置解析、SQLite 存储层、Argon2id 认证与 Markdown 渲染

- 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 同源校验
This commit is contained in:
2026-09-08 08:14:12 +08:00
parent 685f628e26
commit 247e88c4fb
17 changed files with 1786 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
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()
}