Files
sentinel-home-ai/fam-core/tests/test_auth.py
ericwyuan bdf561a415 fix(fam-core): 登录密码不再硬编码兜底(fail-closed) + 会话有效期改 2 小时
之前 FAM_AUTH_USER/FAM_AUTH_PASS 没配置时会退回代码里写死的 ericwyuan/
iLoveJava5——这两个值跟 NAS SSH 密码是同一个,且这个登录页已经通过 frp 暴露在
公网上。.env 万一没配置好(这个项目里已经发生过好几次"忘了 source .env"的
情况),公网入口就会用一个和 SSH 密码相同、且写在源码里的已知密码兜底,双重
风险叠一起。

现在改成 fail-closed:FAM_AUTH_USER/FAM_AUTH_PASS 只要有一个没配置,直接拒绝
所有登录(打一条 ERROR 日志提醒去配 .env),不再有任何硬编码默认值。

会话有效期从 7 天改成 2 小时(按用户要求)。

新增 test_auth.py 11 个用例覆盖:未配置时 fail-closed、正确/错误凭据校验、
只配了一半也要拒绝、session 过期判定、白名单路径匹配。

已实测验证:当前 .env 里 FAM_AUTH_USER/PASS 仍是配置好的(还是 ericwyuan/
iLoveJava5,这个值本身没改——只是不再硬编码在代码里,运维如果想换成不同于
SSH 的密码,现在改 .env 就行,不用改代码),登录/拒绝错误密码都验证正常,
cookie Max-Age=7200 确认生效。
2026-08-23 07:56:33 +08:00

107 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import time
import pytest
from fam_core import auth
@pytest.fixture(autouse=True)
def _clean_env_and_sessions(monkeypatch):
"""每个用例前清掉环境变量和进程内 session 表,用例之间不互相污染。"""
monkeypatch.delenv('FAM_AUTH_USER', raising=False)
monkeypatch.delenv('FAM_AUTH_PASS', raising=False)
auth._sessions.clear()
auth._warned_unconfigured = False
yield
auth._sessions.clear()
def test_check_credential_fails_closed_when_unconfigured():
"""核心诉求: .env 没配置 FAM_AUTH_USER/FAM_AUTH_PASS 时必须拒绝所有登录,
不能退回任何硬编码默认账号密码(这两个变量跟 NAS SSH 密码是同一个值,公网
入口不能有"没配置就用已知密码兜底"这种行为)。"""
assert auth._check_credential('ericwyuan', 'iLoveJava5') is False
assert auth._check_credential('anything', 'anything') is False
assert auth._check_credential('', '') is False
def test_check_credential_succeeds_with_matching_configured_values(monkeypatch):
monkeypatch.setenv('FAM_AUTH_USER', 'testuser')
monkeypatch.setenv('FAM_AUTH_PASS', 'testpass')
assert auth._check_credential('testuser', 'testpass') is True
def test_check_credential_rejects_wrong_password_when_configured(monkeypatch):
monkeypatch.setenv('FAM_AUTH_USER', 'testuser')
monkeypatch.setenv('FAM_AUTH_PASS', 'testpass')
assert auth._check_credential('testuser', 'wrongpass') is False
assert auth._check_credential('wronguser', 'testpass') is False
def test_check_credential_fails_closed_when_only_one_var_set(monkeypatch):
"""只配了一半(比如账号忘配密码)也要 fail closed不能退化成"密码随便""""
monkeypatch.setenv('FAM_AUTH_USER', 'testuser')
assert auth._check_credential('testuser', '') is False
assert auth._check_credential('testuser', 'anything') is False
def test_session_ttl_is_two_hours():
assert auth._SESSION_TTL == 2 * 3600
def test_is_authed_true_within_ttl():
tok = 'sometoken'
auth._sessions[tok] = time.time() + 3600
class _FakeRequest:
cookies = {'fam_session': tok}
monkeypatch_request = auth.request
try:
auth.request = _FakeRequest()
assert auth.is_authed() is True
finally:
auth.request = monkeypatch_request
def test_is_authed_false_after_expiry():
tok = 'expiredtoken'
auth._sessions[tok] = time.time() - 1 # 已过期
class _FakeRequest:
cookies = {'fam_session': tok}
monkeypatch_request = auth.request
try:
auth.request = _FakeRequest()
assert auth.is_authed() is False
# 过期后应该从 session 表里清掉,不留垃圾
assert tok not in auth._sessions
finally:
auth.request = monkeypatch_request
def test_is_authed_false_without_cookie():
class _FakeRequest:
cookies = {}
monkeypatch_request = auth.request
try:
auth.request = _FakeRequest()
assert auth.is_authed() is False
finally:
auth.request = monkeypatch_request
def test_whitelist_exact_paths():
for path in ('/login', '/api/login', '/api/logout', '/api/auth/check',
'/health', '/favicon.ico', '/api/ss/webhook'):
assert auth._is_whitelisted(path) is True
def test_whitelist_assets_prefix():
assert auth._is_whitelisted('/assets/index-abc123.js') is True
def test_whitelist_rejects_protected_paths():
for path in ('/', '/api/ui/people', '/api/chat/ask', '/api/ss/status'):
assert auth._is_whitelisted(path) is False