Files
wehub-resource-sync 1443d3fdf9
Ruff Format Check / Ruff Format & Lint (push) Failing after 7m39s
Deploy VitePress site to Pages / build (push) Failing after 9m11s
Deploy VitePress site to Pages / Deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:26 +08:00

128 lines
4.7 KiB
Python

from __future__ import annotations
import hashlib
import os
from datetime import timedelta
import jwt
import pytest
from yuxi.utils.datetime_utils import utc_now
from yuxi.utils.auth_utils import JWT_ALGORITHM, JWT_AUDIENCE, AuthUtils
def test_generate_api_key_returns_secret_hash_and_prefix():
full_key, key_hash, key_prefix = AuthUtils.generate_api_key()
assert full_key.startswith("yxkey_")
assert len(full_key) == len("yxkey_") + 48
assert key_prefix == full_key[:12]
assert key_hash == hashlib.sha256(full_key.encode()).hexdigest()
def test_hash_password_uses_argon2():
hashed = AuthUtils.hash_password("secret-password")
assert hashed.startswith("$argon2")
assert AuthUtils.verify_password(hashed, "secret-password") is True
assert AuthUtils.verify_password(hashed, "wrong-password") is False
def test_access_token_contains_instance_claims(monkeypatch):
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-with-enough-randomness")
monkeypatch.setenv("YUXI_INSTANCE_ID", "pytest-instance")
token = AuthUtils.create_access_token({"sub": "1"})
payload = AuthUtils.verify_access_token(token)
assert payload["sub"] == "1"
assert payload["iss"] == "yuxi-know:pytest-instance"
assert payload["aud"] == JWT_AUDIENCE
def test_access_token_auto_generates_dev_secret(monkeypatch):
monkeypatch.setenv("YUXI_ENV", "development")
monkeypatch.delenv("JWT_SECRET_KEY", raising=False)
monkeypatch.setenv("YUXI_INSTANCE_ID", "pytest-instance")
token = AuthUtils.create_access_token({"sub": "1"})
assert AuthUtils.verify_access_token(token)["sub"] == "1"
assert len(os.environ["JWT_SECRET_KEY"]) == 64
def test_access_token_requires_configured_secret_in_production(monkeypatch):
monkeypatch.setenv("YUXI_ENV", "production")
monkeypatch.delenv("JWT_SECRET_KEY", raising=False)
monkeypatch.setenv("YUXI_INSTANCE_ID", "pytest-instance")
with pytest.raises(ValueError, match="JWT_SECRET_KEY"):
AuthUtils.create_access_token({"sub": "1"})
def test_access_token_rejects_public_default_secret_in_production(monkeypatch):
monkeypatch.setenv("YUXI_ENV", "production")
monkeypatch.setenv("JWT_SECRET_KEY", "yuxi_know_secure_key")
monkeypatch.setenv("YUXI_INSTANCE_ID", "pytest-instance")
with pytest.raises(ValueError, match="公开默认密钥"):
AuthUtils.create_access_token({"sub": "1"})
def test_access_token_auto_generates_dev_instance_id(monkeypatch):
monkeypatch.setenv("YUXI_ENV", "development")
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-with-enough-randomness")
monkeypatch.delenv("YUXI_INSTANCE_ID", raising=False)
token = AuthUtils.create_access_token({"sub": "1"})
assert AuthUtils.verify_access_token(token)["iss"].startswith("yuxi-know:instance-")
assert os.environ["YUXI_INSTANCE_ID"].startswith("instance-")
def test_access_token_requires_instance_id_in_production(monkeypatch):
monkeypatch.setenv("YUXI_ENV", "production")
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-with-enough-randomness")
monkeypatch.delenv("YUXI_INSTANCE_ID", raising=False)
with pytest.raises(ValueError, match="YUXI_INSTANCE_ID"):
AuthUtils.create_access_token({"sub": "1"})
def test_verify_access_token_rejects_wrong_issuer(monkeypatch):
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-with-enough-randomness")
monkeypatch.setenv("YUXI_INSTANCE_ID", "pytest-instance")
token = jwt.encode(
{"sub": "1", "exp": utc_now() + timedelta(minutes=5), "iss": "yuxi-know:other", "aud": JWT_AUDIENCE},
"test-secret-key-with-enough-randomness",
algorithm=JWT_ALGORITHM,
)
assert AuthUtils.decode_token(token) is None
def test_verify_access_token_rejects_wrong_audience(monkeypatch):
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-with-enough-randomness")
monkeypatch.setenv("YUXI_INSTANCE_ID", "pytest-instance")
token = jwt.encode(
{"sub": "1", "exp": utc_now() + timedelta(minutes=5), "iss": "yuxi-know:pytest-instance", "aud": "other-api"},
"test-secret-key-with-enough-randomness",
algorithm=JWT_ALGORITHM,
)
with pytest.raises(ValueError, match="无效的令牌"):
AuthUtils.verify_access_token(token)
def test_verify_access_token_requires_claims(monkeypatch):
monkeypatch.setenv("JWT_SECRET_KEY", "test-secret-key-with-enough-randomness")
monkeypatch.setenv("YUXI_INSTANCE_ID", "pytest-instance")
token = jwt.encode(
{"sub": "1", "exp": utc_now() + timedelta(minutes=5)},
"test-secret-key-with-enough-randomness",
algorithm=JWT_ALGORITHM,
)
with pytest.raises(ValueError, match="无效的令牌"):
AuthUtils.verify_access_token(token)