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