fix(security): require a configured session secret in production
session_secret defaulted to a random per-process value, which silently invalidates all sessions on restart and rotates the management client secret. Add _resolve_session_secret(): use the configured secret; allow a generated one only in debug or for a localhost issuer; otherwise fail startup. The management client secret is now tied to the resolved session secret. Refs: porchlight-wvx Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c175633980
commit
cf2754f302
3 changed files with 40 additions and 5 deletions
|
|
@ -1,7 +1,10 @@
|
|||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
|
||||
from porchlight.app import create_app
|
||||
from porchlight.config import Settings
|
||||
from porchlight.dependencies import (
|
||||
get_credential_repo,
|
||||
get_magic_link_repo,
|
||||
|
|
@ -56,3 +59,19 @@ async def test_dependency_functions() -> None:
|
|||
assert get_user_repo(request) == "user_repo_sentinel"
|
||||
assert get_credential_repo(request) == "credential_repo_sentinel"
|
||||
assert get_magic_link_repo(request) == "magic_link_repo_sentinel"
|
||||
|
||||
|
||||
def test_create_app_requires_session_secret_in_production() -> None:
|
||||
settings = Settings(issuer="https://op.example.com", sqlite_path=":memory:", debug=False)
|
||||
with pytest.raises(RuntimeError, match="SESSION_SECRET"):
|
||||
create_app(settings)
|
||||
|
||||
|
||||
def test_create_app_allows_missing_secret_on_localhost() -> None:
|
||||
settings = Settings(issuer="http://localhost:8000", sqlite_path=":memory:")
|
||||
assert create_app(settings) is not None
|
||||
|
||||
|
||||
def test_create_app_allows_missing_secret_in_debug() -> None:
|
||||
settings = Settings(issuer="https://op.example.com", sqlite_path=":memory:", debug=True)
|
||||
assert create_app(settings) is not None
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ scope = ["openid", "profile"]
|
|||
toml_file = tmp_path / "test.toml"
|
||||
toml_file.write_text(toml_content)
|
||||
|
||||
settings = Settings(_toml_file=str(toml_file))
|
||||
settings = Settings(_toml_file=str(toml_file), session_secret="x" * 32)
|
||||
app = create_app(settings)
|
||||
|
||||
async with (
|
||||
|
|
@ -41,7 +41,7 @@ scope = ["openid", "profile"]
|
|||
|
||||
async def test_manage_app_always_registered() -> None:
|
||||
"""The internal manage-app client is always registered, even without config file clients."""
|
||||
settings = Settings(issuer="https://test.example.com")
|
||||
settings = Settings(issuer="https://test.example.com", session_secret="x" * 32)
|
||||
app = create_app(settings)
|
||||
|
||||
async with (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue