fix(db): SQLite 时间绑定改用 sqlite 格式 + UTC 时区
modernc.org/sqlite 在 writeTimeFormat 为空时回落到 t.String(),写出 `2026-09-03 16:45:38.5 +0800 HKT m=+0.009` —— 带单调时钟后缀, datetime() 返回 NULL,与 NOW() 的字符串比较必然错。 后果是安全 bug:user_sessions.expires_at 无法被 datetime() 解析, 过期令牌照样通过校验 —— 会话永不过期。 修法是一行 DSN(`_time_format=sqlite&_timezone=UTC`)而不是改 40 处 db.Ts() 绑定点:DSN 是全局的、不可能漏,辅助函数要求每个新绑定点都记得调用。 排除 `_time_format=datetime`:它把亚秒截断,会重新引入「同秒插入多封邮件 排序不确定」——「会话里最早那封」(决定联系人身份)与「最后那封」 (决定最新进展)都会取错行。 新增 usersession_time_test.go 5 例。反向验证:摘掉 DSN 修复后 4 个失败, 报文即「过期令牌竟然解析成功 —— 会话永不过期」。
This commit is contained in:
@ -153,7 +153,28 @@ func sqliteDSN(path string) string {
|
||||
return "file:" + path +
|
||||
"?_pragma=journal_mode(WAL)" +
|
||||
"&_pragma=busy_timeout(5000)" +
|
||||
"&_pragma=foreign_keys(1)"
|
||||
"&_pragma=foreign_keys(1)" +
|
||||
// _time_format / _timezone 决定 time.Time 参数怎么落成文本。
|
||||
//
|
||||
// 驱动的**默认行为是 Go 的 t.String()**,写出来是
|
||||
// 2026-09-10 15:10:36.122781994 +0800 HKT m=+607182.882153215
|
||||
// SQLite 的 datetime() 解析不了这种串(返回 NULL),而它跟我们注册的
|
||||
// NOW()("2006-01-02 15:04:05.000000" UTC)做的是**字符串**比较。
|
||||
// 后果实测有两条,都不是「显示不好看」级别的:
|
||||
// 1. 安全:`expires_at > NOW()` 比较两种格式且时区不同(+0800 vs UTC),
|
||||
// 用户会话永不过期,`DELETE ... WHERE expires_at < NOW()` 删 0 行。
|
||||
// 2. 功能:日历 `datetime(event_time,'-N minutes') <= NOW()` 恒为假,
|
||||
// 一条提醒都发不出去。
|
||||
//
|
||||
// _time_format=sqlite 给 "2006-01-02 15:04:05.999999999-07:00"(驱动
|
||||
// parseTimeFormats[0],读回时原样认得);_timezone=UTC 让偏移固定为
|
||||
// +00:00,与 NOW() 同一时间轴。
|
||||
//
|
||||
// 不用 _time_format=datetime("2006-01-02 15:04:05"):它把亚秒截断,
|
||||
// 会重新引入同秒插入多封邮件排序不确定的老问题 —— 「会话里最早那封」
|
||||
// (决定联系人身份)与「最后那封」(决定最新进展)都会取错行。
|
||||
"&_time_format=sqlite" +
|
||||
"&_timezone=UTC"
|
||||
}
|
||||
|
||||
func Close() {
|
||||
|
||||
Reference in New Issue
Block a user