|
3 | 3 | import unittest |
4 | 4 |
|
5 | 5 | import apps.admin.services as admin_services |
| 6 | +import apps.admin.views as admin_views |
| 7 | +import apps.admin.dependencies as admin_dependencies |
6 | 8 | import core.config as core_config |
7 | 9 | from apps.admin.dependencies import create_token, verify_token |
| 10 | +from apps.admin.schemas import LoginData |
8 | 11 | from apps.admin.services import ConfigService |
| 12 | +from fastapi import HTTPException |
9 | 13 | from main import parse_setup_options |
10 | 14 | from core.security import ( |
11 | 15 | LEGACY_DEFAULT_ADMIN_TOKEN, |
@@ -119,6 +123,26 @@ def test_admin_jwt_signature_uses_independent_secret(self): |
119 | 123 | with self.assertRaises(ValueError): |
120 | 124 | verify_token(token) |
121 | 125 |
|
| 126 | + def test_configured_session_lifetime_is_returned_by_login(self): |
| 127 | + settings.admin_token = hash_password("admin-password") |
| 128 | + settings.jwt_secret = "j" * 48 |
| 129 | + settings.adminSessionExpire = 90 * 24 * 60 * 60 |
| 130 | + original_time = admin_dependencies.time.time |
| 131 | + admin_dependencies.time.time = lambda: 1_800_000_000 |
| 132 | + try: |
| 133 | + response = asyncio.run( |
| 134 | + admin_views.login(LoginData(password="admin-password")) |
| 135 | + ) |
| 136 | + payload = verify_token(response.detail["token"]) |
| 137 | + finally: |
| 138 | + admin_dependencies.time.time = original_time |
| 139 | + |
| 140 | + self.assertEqual(response.detail["expires_in"], 90 * 24 * 60 * 60) |
| 141 | + self.assertEqual( |
| 142 | + response.detail["expires_at"], 1_800_000_000 + 90 * 24 * 60 * 60 |
| 143 | + ) |
| 144 | + self.assertEqual(payload["exp"], response.detail["expires_at"]) |
| 145 | + |
122 | 146 |
|
123 | 147 | class FakeKeyValue: |
124 | 148 | saved_value = None |
@@ -158,6 +182,19 @@ async def update_or_create(cls, key, defaults): |
158 | 182 |
|
159 | 183 |
|
160 | 184 | class ConfigServiceSecurityTests(SettingsOverrideMixin, unittest.TestCase): |
| 185 | + def test_admin_session_lifetime_rejects_out_of_range_values(self): |
| 186 | + settings.user_config = copy.deepcopy(DEFAULT_CONFIG) |
| 187 | + |
| 188 | + for invalid_value in (0, 86399, 90000, 365 * 24 * 60 * 60 + 1): |
| 189 | + with self.subTest(invalid_value=invalid_value): |
| 190 | + with self.assertRaises(HTTPException) as context: |
| 191 | + asyncio.run( |
| 192 | + ConfigService().update_config( |
| 193 | + {"adminSessionExpire": invalid_value} |
| 194 | + ) |
| 195 | + ) |
| 196 | + self.assertEqual(context.exception.status_code, 400) |
| 197 | + |
161 | 198 | def test_admin_password_update_rotates_jwt_secret(self): |
162 | 199 | old_secret = "j" * 48 |
163 | 200 | settings.user_config = { |
|
0 commit comments