B-64: 安全修复——随机初始密码与跨公司重复上传隔离
- 公司账号创建改为随机一次性初始密码,只在创建响应中显示一次, 密码保证不等于用户名;删除「用户名即初始密码」兼容分支,继续 强制首次登录改密。 - 跨公司相同字节文件上传只返回通用重复状态:不再返回其他公司的 原批次 ID、银行、模板、期间、交易数或诊断;同公司重复上传的 幂等摘要保持可用。 - 补充服务端回归测试,覆盖同公司与跨公司两个分支及随机密码; 完整测试 85 项全绿,node --check 通过。
This commit is contained in:
@@ -169,14 +169,15 @@ class ServerAuthMatrixTests(unittest.TestCase):
|
||||
assert status == 200, data
|
||||
cls.company_b = as_json(data)["company_id"]
|
||||
|
||||
# --- Company user A: initial password shown once, forced change. ---
|
||||
# --- Company user A: random one-time initial password, forced change. ---
|
||||
status, _, data = cls.admin.post_json(
|
||||
"/api/admin/users", {"username": "cashier-a", "company_id": cls.company_a}
|
||||
)
|
||||
assert status == 200, data
|
||||
payload = as_json(data)
|
||||
# Product decision: registration initial password equals the username.
|
||||
assert payload["initial_password"] == "cashier-a"
|
||||
# Security: the initial password is random and never equals the username.
|
||||
assert payload["initial_password"] != "cashier-a"
|
||||
assert len(payload["initial_password"]) >= 12
|
||||
cls.initial_password_a = payload["initial_password"]
|
||||
cls.known_passwords.add(cls.initial_password_a)
|
||||
|
||||
@@ -431,7 +432,13 @@ class ServerAuthMatrixTests(unittest.TestCase):
|
||||
CCB_SAMPLE.read_bytes(),
|
||||
)
|
||||
self.assertEqual(200, status, data)
|
||||
self.assertEqual("duplicate", as_json(data)["status"])
|
||||
payload = as_json(data)
|
||||
self.assertEqual("duplicate", payload["status"])
|
||||
# Cross-company duplicate: no original batch id, bank, template,
|
||||
# period, transaction count or diagnostics may be exposed.
|
||||
self.assertNotEqual(self.b_batch_id, payload.get("batch_id"))
|
||||
for leaked_key in ("bank", "template", "header_row", "period_start", "period_end", "transactions", "warnings"):
|
||||
self.assertNotIn(leaked_key, payload, leaked_key)
|
||||
connection = connect(self.db_path)
|
||||
try:
|
||||
duplicate = connection.execute(
|
||||
@@ -442,6 +449,21 @@ class ServerAuthMatrixTests(unittest.TestCase):
|
||||
self.assertIsNotNone(duplicate)
|
||||
self.assertEqual(self.company_a, duplicate["company_id"])
|
||||
|
||||
def test_same_company_duplicate_keeps_idempotent_summary(self) -> None:
|
||||
# A re-uploads its own file; the idempotent duplicate response keeps
|
||||
# the original batch id and the parsed summary.
|
||||
status, _, data = self.cashier_a.post_multipart(
|
||||
"/api/parse", {}, CITIC_SAMPLE.name, CITIC_SAMPLE.read_bytes()
|
||||
)
|
||||
self.assertEqual(200, status, data)
|
||||
payload = as_json(data)
|
||||
self.assertEqual("duplicate", payload["status"])
|
||||
self.assertEqual(self.a_batch_id, payload["batch_id"])
|
||||
self.assertEqual("中信银行", payload.get("bank"))
|
||||
self.assertTrue(payload.get("transactions", 0) > 0)
|
||||
self.assertIn("period_start", payload)
|
||||
self.assertIn("warnings", payload)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Disable / reset flows
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user