页面密码(P0,2026-11-05 复核发现) - 旧实现 `ver, _ := Session.Get(key).(uint); if ver != user.PagePasswordVer` 把「session 里没有授权记录」(断言失败得 0)与「新增列默认值 0」判成相等, 升级瞬间所有存量页面密码静默失效:匿名访客不带 cookie 即可读箱主页。 - 收口为 pagePasswordAuthorized:缺 key 一律视为未授权, key 名由 pagePassKey 统一生成,Pager / ListAPI / AskStatusAPI 三处共用。 - 新增迁移 v6 回填 page_password_ver / ask_password_ver(仅仍为 0 且已设密码的行, 幂等),让模型语义与「改过一次密码」一致,并作废升级前的布尔授权。 - AskStatusAPI 的 L4 查询补日志(保持与 Pager 一致的 fail-open 口径)。 导出(P1) - JSON 导出的投票提交不再输出 voter_ip。库里照常保留, 防刷、取证与站方 CLI 处置不受影响,只是不进箱主的自助导出文件。 测试 - route/question/page_password_test.go:8 个用例钉住零值/类型不符/轮换失效等边界。 - internal/db/page_password_migration_test.go:回填只碰该碰的行且幂等。 验证:WSL2 Debian + PostgreSQL 全量测试通过;隔离实例实测 ver=0 匿名访问返回密码页、API 401,输对密码后放行、改密后旧会话立即失效, 删除迁移账本后重启可正确回填。
59 lines
2.3 KiB
Go
59 lines
2.3 KiB
Go
package db
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestBackfillPagePasswordVer 覆盖 2026-11-05 那个 P0 的数据面:
|
|
// 新增列 AutoMigrate 只补 0,而 0 恰好等于「session 里没有授权记录」的零值,
|
|
// 不回填的话存量页面密码在升级瞬间全部失效。
|
|
func TestBackfillPagePasswordVer(t *testing.T) {
|
|
conn, clean := newTestDB(t)
|
|
defer clean("users")
|
|
|
|
mustCreate := func(domain string, pagePassword string, pageVer uint, askPassword string, askVer uint) *User {
|
|
u := &User{
|
|
Name: domain,
|
|
Email: domain + "@example.com",
|
|
Password: "x",
|
|
Domain: domain,
|
|
PagePassword: pagePassword,
|
|
PagePasswordVer: pageVer,
|
|
AskPassword: askPassword,
|
|
AskPasswordVer: askVer,
|
|
}
|
|
require.NoError(t, conn.Create(u).Error)
|
|
return u
|
|
}
|
|
|
|
// 存量:有密码但版本号仍为默认 0 —— 必须被回填。
|
|
legacy := mustCreate("legacy", "hashed-page", 0, "hashed-ask", 0)
|
|
// 已轮换过密码:版本号非 0,不能被重置。
|
|
rotated := mustCreate("rotated", "hashed-page", 3, "hashed-ask", 5)
|
|
// 没有密码:版本号保持 0,回填不应凭空造出授权。
|
|
noPassword := mustCreate("nopassword", "", 0, "", 0)
|
|
|
|
require.NoError(t, backfillPagePasswordVer(conn))
|
|
|
|
var gotLegacy, gotRotated, gotNoPassword User
|
|
require.NoError(t, conn.First(&gotLegacy, legacy.ID).Error)
|
|
require.NoError(t, conn.First(&gotRotated, rotated.ID).Error)
|
|
require.NoError(t, conn.First(&gotNoPassword, noPassword.ID).Error)
|
|
|
|
require.EqualValues(t, 1, gotLegacy.PagePasswordVer, "存量页面密码应回填为 1")
|
|
require.EqualValues(t, 1, gotLegacy.AskPasswordVer, "存量提问密码应回填为 1")
|
|
|
|
require.EqualValues(t, 3, gotRotated.PagePasswordVer, "已轮换的版本号不能被覆盖")
|
|
require.EqualValues(t, 5, gotRotated.AskPasswordVer, "已轮换的版本号不能被覆盖")
|
|
|
|
require.EqualValues(t, 0, gotNoPassword.PagePasswordVer, "没设密码的账号不应被写版本号")
|
|
require.EqualValues(t, 0, gotNoPassword.AskPasswordVer, "没设密码的账号不应被写版本号")
|
|
|
|
// 幂等:再跑一次不应把已回填的行推到 2。
|
|
require.NoError(t, backfillPagePasswordVer(conn))
|
|
require.NoError(t, conn.First(&gotLegacy, legacy.ID).Error)
|
|
require.EqualValues(t, 1, gotLegacy.PagePasswordVer, "迁移必须幂等")
|
|
}
|