chore: 初始化项目结构与登录鉴权模块
后端 FastAPI 六层结构(api/services/repositories/models/schemas/core), 统一响应信封、全局异常、日志脱敏;6 张表经 Alembic 迁移建库,SQL 留存 db/migration;登录鉴权含图形验证码(Redis GETDEL 防重放)、JWT、登录限流、 登出黑名单(TTL=令牌剩余有效期)、登录日志。 前端 Vue3 + TS + Element Plus 骨架,含登录页、路由守卫、请求封装与 Vite 代理。 工程配置:uv 锁定 Python 3.12、ruff 检查与格式化、editorconfig、gitattributes。 测试 184 项(单元 155 / 集成 29)全绿,浏览器端到端 8 项通过。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
commit
273ebf4657
101 files changed
+8725
No files matched your search
@@ -0,0 +1,66 @@
|
||||
"""密码哈希单元测试。
|
||||
|
||||
重点覆盖 bcrypt 5.0 的 72 字节硬限制 —— 此前是静默截断,现在是抛 ValueError,
|
||||
若不在边界上把关,用户设一个长密码就会在登录时炸出 500。
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from app.core.constants import PASSWORD_MAX_BYTES
|
||||
from app.core.security import hash_password, verify_password
|
||||
|
||||
|
||||
class TestHashPassword:
|
||||
"""哈希生成。"""
|
||||
|
||||
def test_输出为BCrypt格式且定长60字符(self) -> None:
|
||||
hashed = hash_password("Admin@2026")
|
||||
assert hashed.startswith("$2")
|
||||
assert len(hashed) == 60
|
||||
|
||||
def test_相同明文两次哈希结果不同(self) -> None:
|
||||
"""BCrypt 内含随机盐,相同明文每次结果不同 —— 这是防彩虹表的基础。"""
|
||||
assert hash_password("SamePwd@1") != hash_password("SamePwd@1")
|
||||
|
||||
def test_恰好72字节时允许(self) -> None:
|
||||
password = "a" * PASSWORD_MAX_BYTES
|
||||
assert len(password.encode("utf-8")) == PASSWORD_MAX_BYTES
|
||||
assert len(hash_password(password)) == 60
|
||||
|
||||
def test_超过72字节时抛ValueError(self) -> None:
|
||||
with pytest.raises(ValueError):
|
||||
hash_password("a" * (PASSWORD_MAX_BYTES + 1))
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("text", "count", "should_pass"),
|
||||
[("中", 24, True), ("中", 25, False)],
|
||||
)
|
||||
def test_中文按字节而非字符计算边界(self, text: str, count: int, should_pass: bool) -> None:
|
||||
"""一个汉字 3 字节:24 个正好 72 字节,25 个即 75 字节超限。"""
|
||||
password = text * count
|
||||
if should_pass:
|
||||
assert len(hash_password(password)) == 60
|
||||
else:
|
||||
with pytest.raises(ValueError):
|
||||
hash_password(password)
|
||||
|
||||
|
||||
class TestVerifyPassword:
|
||||
"""哈希校验。"""
|
||||
|
||||
def test_正确密码校验通过(self) -> None:
|
||||
assert verify_password("Admin@2026", hash_password("Admin@2026")) is True
|
||||
|
||||
def test_错误密码校验失败(self) -> None:
|
||||
assert verify_password("WrongPwd@1", hash_password("Admin@2026")) is False
|
||||
|
||||
def test_超长密码返回False而不抛异常(self) -> None:
|
||||
"""校验路径不得因输入长度报错 —— 否则会形成「密码过长」与「密码错误」
|
||||
可区分的侧信道,且会让恶意长输入直接打出 500。"""
|
||||
assert verify_password("a" * 200, hash_password("Admin@2026")) is False
|
||||
|
||||
def test_损坏的哈希串返回False而不抛异常(self) -> None:
|
||||
assert verify_password("Admin@2026", "not-a-bcrypt-hash") is False
|
||||
|
||||
def test_空哈希串返回False(self) -> None:
|
||||
assert verify_password("Admin@2026", "") is False
|
||||
Reference in new issue
Block a user