feat: 平台认证(scrypt上移/内存token 8h/登录限流/改密/角色依赖/409语义)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+11
-11
@@ -70,8 +70,8 @@ async def test_bootstrap_creates_superuser_once(tmp_path):
|
||||
assert len(users) == 1 and users[0].username == "admin"
|
||||
assert users[0].tenant_id is None # NULL = 超管
|
||||
assert users[0].must_change_password is True # 引导密码必须首登改掉
|
||||
assert verify_password("s3cret!", users[0])
|
||||
assert not verify_password("other", users[0])
|
||||
assert verify_password("s3cret!", users[0].password_hash, users[0].salt)
|
||||
assert not verify_password("other", users[0].password_hash, users[0].salt)
|
||||
finally:
|
||||
await engine.dispose()
|
||||
|
||||
@@ -85,7 +85,7 @@ async def test_bootstrap_noop_when_any_user_exists(tmp_path):
|
||||
await bootstrap_superuser(engine, username="root", password="second")
|
||||
users = await list_admin_users(engine)
|
||||
assert len(users) == 1 and users[0].username == "admin"
|
||||
assert verify_password("first", users[0])
|
||||
assert verify_password("first", users[0].password_hash, users[0].salt)
|
||||
finally:
|
||||
await engine.dispose()
|
||||
|
||||
@@ -128,23 +128,23 @@ def test_hash_password_versioned_format_and_fresh_salt():
|
||||
|
||||
def test_verify_password_bad_stored_hash_returns_false():
|
||||
"""存储异常按验败处理不炸(M-4):坏 hex/坏段数/坏参数 → False;
|
||||
无前缀过渡行兼容:salt 列 + 默认参数仍可验真(M-1 解析兼容)。"""
|
||||
无前缀过渡行兼容:salt 列 + 默认参数仍可验真(M-1 解析兼容)。
|
||||
Task 2 起 verify 纯参式 (password, password_hash, salt)——不再需要
|
||||
ORM 实体即可直测。"""
|
||||
bad_stored = [
|
||||
"not-hex!", # 无前缀,坏 hex
|
||||
"scrypt$16384$8$1$deadbeef", # 段数不足
|
||||
"scrypt$abc$8$1$00$00", # 参数非整数
|
||||
]
|
||||
for stored in bad_stored:
|
||||
user = AdminUser(username="u", password_hash=stored, salt="zz")
|
||||
assert verify_password("whatever", user) is False
|
||||
assert verify_password("whatever", stored, "zz") is False
|
||||
|
||||
salt = bytes(16)
|
||||
digest = hashlib.scrypt( # 裸摘要 + salt 列的过渡行形态
|
||||
b"legacy", salt=salt, n=2 ** 14, r=8, p=1)
|
||||
legacy = AdminUser(
|
||||
username="u", password_hash=digest.hex(), salt=salt.hex())
|
||||
assert verify_password("legacy", legacy) is True
|
||||
assert verify_password("other", legacy) is False
|
||||
legacy_hash, legacy_salt = digest.hex(), salt.hex()
|
||||
assert verify_password("legacy", legacy_hash, legacy_salt) is True
|
||||
assert verify_password("other", legacy_hash, legacy_salt) is False
|
||||
|
||||
|
||||
# ---- 模型约束 -------------------------------------------------------------
|
||||
@@ -323,5 +323,5 @@ def test_lifespan_platform_engine_seam_and_bootstrap(tmp_path, monkeypatch):
|
||||
|
||||
users = asyncio.run(_probe())
|
||||
assert [u.username for u in users] == ["bootadmin"]
|
||||
assert verify_password("boot-pw", users[0])
|
||||
assert verify_password("boot-pw", users[0].password_hash, users[0].salt)
|
||||
assert users[0].must_change_password is True
|
||||
|
||||
Reference in New Issue
Block a user