后端基础层:模块定义、配置解析、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:
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user