auth: reject sessions older than user.password_changed_at
load_current_user now treats a session whose pw_changed_at marker is missing, malformed, or older than the user's current password_changed_at as logged-out. Same shape as the existing user.active check. Forced fan-out updates to every test fixture that forges a session via session_transaction(): each now stamps a current pw_changed_at marker. test_deactivated_user_existing_session_invalidated keeps its meaning — the deactivation still flips the user to inactive, and load_current_user rejects the session via the user.active branch before reaching the freshness branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
84dc672180
commit
e75f379dcb
12 changed files with 115 additions and 3 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
from datetime import datetime
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Callable, TypeVar
|
from typing import Callable, TypeVar
|
||||||
from urllib.parse import quote, unquote
|
from urllib.parse import quote, unquote
|
||||||
|
|
@ -39,9 +40,29 @@ def load_current_user() -> None:
|
||||||
return
|
return
|
||||||
with session_scope() as db:
|
with session_scope() as db:
|
||||||
user = db.scalar(select(User).where(User.id == int(user_id)))
|
user = db.scalar(select(User).where(User.id == int(user_id)))
|
||||||
# Treat deactivated users as logged-out so existing sessions stop
|
if user is None or not user.active:
|
||||||
# working as soon as an admin flips active=False.
|
g.user = None
|
||||||
g.user = user if user is not None and user.active else None
|
return
|
||||||
|
|
||||||
|
marker = session.get("pw_changed_at")
|
||||||
|
if marker is None:
|
||||||
|
g.user = None
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
marker_dt = datetime.fromisoformat(marker)
|
||||||
|
except ValueError:
|
||||||
|
g.user = None
|
||||||
|
return
|
||||||
|
# user.password_changed_at comes back naive from SQLite; strip tz from the
|
||||||
|
# marker so an aware-marker session (just stamped from an in-memory user)
|
||||||
|
# compares cleanly with a freshly-loaded user row.
|
||||||
|
if marker_dt.tzinfo is not None:
|
||||||
|
marker_dt = marker_dt.replace(tzinfo=None)
|
||||||
|
if marker_dt < user.password_changed_at:
|
||||||
|
g.user = None
|
||||||
|
return
|
||||||
|
|
||||||
|
g.user = user
|
||||||
|
|
||||||
|
|
||||||
def current_user() -> User | None:
|
def current_user() -> User | None:
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,12 @@ def admin_client(tmp_path, monkeypatch):
|
||||||
db.add_all([admin, second_admin])
|
db.add_all([admin, second_admin])
|
||||||
db.flush()
|
db.flush()
|
||||||
admin_id = admin.id
|
admin_id = admin.id
|
||||||
|
admin_marker = admin.password_changed_at.isoformat()
|
||||||
|
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = admin_id
|
sess["user_id"] = admin_id
|
||||||
|
sess["pw_changed_at"] = admin_marker
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
return client, admin_id
|
return client, admin_id
|
||||||
|
|
||||||
|
|
@ -124,11 +126,15 @@ def test_deactivated_user_existing_session_invalidated(admin_client):
|
||||||
"""An active session at the moment of deactivation stops working."""
|
"""An active session at the moment of deactivation stops working."""
|
||||||
client, _ = admin_client
|
client, _ = admin_client
|
||||||
target = _add_user("bob")
|
target = _add_user("bob")
|
||||||
|
with session_scope() as db:
|
||||||
|
bob = db.scalar(select(User).where(User.id == target))
|
||||||
|
bob_marker = bob.password_changed_at.isoformat()
|
||||||
|
|
||||||
# Forge a session for bob.
|
# Forge a session for bob.
|
||||||
bob_client = client.application.test_client()
|
bob_client = client.application.test_client()
|
||||||
with bob_client.session_transaction() as sess:
|
with bob_client.session_transaction() as sess:
|
||||||
sess["user_id"] = target
|
sess["user_id"] = target
|
||||||
|
sess["pw_changed_at"] = bob_marker
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
|
|
||||||
# Sanity: bob can hit a logged-in route.
|
# Sanity: bob can hit a logged-in route.
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,56 @@ def test_login_sets_session(client) -> None:
|
||||||
assert sess.get("user_id") is not None
|
assert sess.get("user_id") is not None
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_current_user_rejects_missing_marker(client) -> None:
|
||||||
|
with session_scope() as db:
|
||||||
|
u = User(username="alice", password_digest=hash_password("secret"))
|
||||||
|
db.add(u)
|
||||||
|
db.flush()
|
||||||
|
uid = u.id
|
||||||
|
|
||||||
|
with client.session_transaction() as sess:
|
||||||
|
sess["user_id"] = uid
|
||||||
|
|
||||||
|
response = client.get("/dashboard")
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert "/login" in response.headers["Location"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_current_user_rejects_stale_marker(client) -> None:
|
||||||
|
from datetime import UTC, datetime, timedelta
|
||||||
|
|
||||||
|
with session_scope() as db:
|
||||||
|
u = User(username="alice", password_digest=hash_password("secret"))
|
||||||
|
db.add(u)
|
||||||
|
db.flush()
|
||||||
|
uid = u.id
|
||||||
|
|
||||||
|
stale = datetime.now(UTC).replace(tzinfo=None) - timedelta(minutes=5)
|
||||||
|
with client.session_transaction() as sess:
|
||||||
|
sess["user_id"] = uid
|
||||||
|
sess["pw_changed_at"] = stale.isoformat()
|
||||||
|
|
||||||
|
response = client.get("/dashboard")
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert "/login" in response.headers["Location"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_current_user_accepts_current_marker(client) -> None:
|
||||||
|
with session_scope() as db:
|
||||||
|
u = User(username="alice", password_digest=hash_password("secret"))
|
||||||
|
db.add(u)
|
||||||
|
db.flush()
|
||||||
|
uid = u.id
|
||||||
|
marker = u.password_changed_at.isoformat()
|
||||||
|
|
||||||
|
with client.session_transaction() as sess:
|
||||||
|
sess["user_id"] = uid
|
||||||
|
sess["pw_changed_at"] = marker
|
||||||
|
|
||||||
|
response = client.get("/dashboard")
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_login_stamps_password_changed_at_in_session(client) -> None:
|
def test_login_stamps_password_changed_at_in_session(client) -> None:
|
||||||
with session_scope() as session:
|
with session_scope() as session:
|
||||||
session.add(User(username="alice", password_digest=hash_password("secret")))
|
session.add(User(username="alice", password_digest=hash_password("secret")))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import json
|
import json
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
@ -31,6 +32,7 @@ def user_client(tmp_path, monkeypatch):
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = user_id
|
sess["user_id"] = user_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
@ -58,6 +60,7 @@ def linked_blueprint(tmp_path, monkeypatch):
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = user_id
|
sess["user_id"] = user_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
|
|
||||||
return client, blueprint_id
|
return client, blueprint_id
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
|
|
||||||
|
|
@ -66,6 +67,7 @@ def test_sse_resume_from_last_seq(seeded_job_logs) -> None:
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = user_id
|
sess["user_id"] = user_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
response = client.get(f"/jobs/{job_id}/stream?last_seq=5")
|
response = client.get(f"/jobs/{job_id}/stream?last_seq=5")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
@ -76,6 +78,7 @@ def test_sse_replays_custom_job_log_events(seeded_job_logs) -> None:
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = user_id
|
sess["user_id"] = user_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
response = client.get(f"/jobs/{job_id}/stream?last_seq=5")
|
response = client.get(f"/jobs/{job_id}/stream?last_seq=5")
|
||||||
text = response.get_data(as_text=True)
|
text = response.get_data(as_text=True)
|
||||||
|
|
@ -91,6 +94,7 @@ def test_sse_resumes_from_last_event_id_header(seeded_job_logs) -> None:
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = user_id
|
sess["user_id"] = user_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
response = client.get(f"/jobs/{job_id}/stream", headers={"Last-Event-ID": "6"})
|
response = client.get(f"/jobs/{job_id}/stream", headers={"Last-Event-ID": "6"})
|
||||||
text = response.get_data(as_text=True)
|
text = response.get_data(as_text=True)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""HTTP-level tests for the overlay 'Files' tree-fragment + download routes."""
|
"""HTTP-level tests for the overlay 'Files' tree-fragment + download routes."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
|
|
@ -34,6 +35,7 @@ def _client_for(app, user_id: int):
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = user_id
|
sess["user_id"] = user_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ from l4d2web.auth import hash_password
|
||||||
from l4d2web.db import init_db, session_scope
|
from l4d2web.db import init_db, session_scope
|
||||||
from l4d2web.models import Blueprint, BlueprintOverlay, Overlay, User
|
from l4d2web.models import Blueprint, BlueprintOverlay, Overlay, User
|
||||||
from l4d2web.services.security import validate_overlay_ref
|
from l4d2web.services.security import validate_overlay_ref
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
@ -23,6 +24,7 @@ def admin_client(tmp_path, monkeypatch):
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = admin_id
|
sess["user_id"] = admin_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
@ -48,6 +50,7 @@ def user_client_with_overlay(tmp_path, monkeypatch):
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = user_id
|
sess["user_id"] = user_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
@ -146,6 +149,7 @@ def test_two_users_can_have_workshop_overlay_with_same_name(tmp_path, monkeypatc
|
||||||
c = app.test_client()
|
c = app.test_client()
|
||||||
with c.session_transaction() as sess:
|
with c.session_transaction() as sess:
|
||||||
sess["user_id"] = uid
|
sess["user_id"] = uid
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import pytest
|
import pytest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from l4d2web.app import create_app
|
from l4d2web.app import create_app
|
||||||
from l4d2web.auth import hash_password
|
from l4d2web.auth import hash_password
|
||||||
|
|
@ -35,6 +36,7 @@ def auth_client_with_server(tmp_path, monkeypatch):
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = user_id
|
sess["user_id"] = user_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -61,6 +63,7 @@ def user_client_and_other_blueprint(tmp_path, monkeypatch):
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = owner_id
|
sess["user_id"] = owner_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
return client, blueprint_id
|
return client, blueprint_id
|
||||||
|
|
||||||
|
|
@ -345,6 +348,7 @@ def test_admin_can_use_admin_pages(tmp_path, monkeypatch) -> None:
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = admin_id
|
sess["user_id"] = admin_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
admin_page = client.get("/admin")
|
admin_page = client.get("/admin")
|
||||||
assert admin_page.status_code == 200
|
assert admin_page.status_code == 200
|
||||||
|
|
@ -380,6 +384,7 @@ def test_admin_can_view_other_users_job(tmp_path, monkeypatch) -> None:
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = admin_id
|
sess["user_id"] = admin_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
response = client.get(f"/jobs/{job_id}")
|
response = client.get(f"/jobs/{job_id}")
|
||||||
|
|
||||||
|
|
@ -402,6 +407,7 @@ def test_admin_can_enqueue_runtime_install_job(tmp_path, monkeypatch) -> None:
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = admin_id
|
sess["user_id"] = admin_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
|
|
||||||
response = client.post("/admin/install", headers={"X-CSRF-Token": "test-token"})
|
response = client.post("/admin/install", headers={"X-CSRF-Token": "test-token"})
|
||||||
|
|
@ -457,6 +463,7 @@ def test_root_redirects_by_auth_state(tmp_path, monkeypatch) -> None:
|
||||||
|
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = user_id
|
sess["user_id"] = user_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
logged_in_response = client.get("/")
|
logged_in_response = client.get("/")
|
||||||
assert logged_in_response.status_code == 302
|
assert logged_in_response.status_code == 302
|
||||||
|
|
@ -514,6 +521,7 @@ def test_admin_jobs_page_renders_system_job(tmp_path, monkeypatch) -> None:
|
||||||
admin_client = app.test_client()
|
admin_client = app.test_client()
|
||||||
with admin_client.session_transaction() as sess:
|
with admin_client.session_transaction() as sess:
|
||||||
sess["user_id"] = admin_id
|
sess["user_id"] = admin_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
with session_scope() as db:
|
with session_scope() as db:
|
||||||
db.add(Job(user_id=None, server_id=None, operation="refresh_workshop_items", state="queued"))
|
db.add(Job(user_id=None, server_id=None, operation="refresh_workshop_items", state="queued"))
|
||||||
|
|
@ -541,6 +549,7 @@ def test_non_admin_cannot_view_system_job(tmp_path, monkeypatch) -> None:
|
||||||
user_client = app.test_client()
|
user_client = app.test_client()
|
||||||
with user_client.session_transaction() as sess:
|
with user_client.session_transaction() as sess:
|
||||||
sess["user_id"] = user_id
|
sess["user_id"] = user_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
with session_scope() as db:
|
with session_scope() as db:
|
||||||
job = Job(user_id=None, server_id=None, operation="refresh_workshop_items", state="queued")
|
job = Job(user_id=None, server_id=None, operation="refresh_workshop_items", state="queued")
|
||||||
|
|
@ -717,6 +726,7 @@ def test_overlay_jobs_page_403_for_other_users_private_overlay(tmp_path, monkeyp
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = other_id
|
sess["user_id"] = other_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
response = client.get(f"/overlays/{overlay_id}/jobs")
|
response = client.get(f"/overlays/{overlay_id}/jobs")
|
||||||
assert response.status_code == 403
|
assert response.status_code == 403
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"""Routes for type='script' overlays: create, /script (update body),
|
"""Routes for type='script' overlays: create, /script (update body),
|
||||||
/wipe, /build. Permissions mirror workshop overlays (owner or admin)."""
|
/wipe, /build. Permissions mirror workshop overlays (owner or admin)."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
@ -52,6 +53,7 @@ def _client_for(app, user_id: int):
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = user_id
|
sess["user_id"] = user_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import json
|
import json
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -32,6 +33,7 @@ def user_client_with_blueprints(tmp_path, monkeypatch):
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = payload["user_id"]
|
sess["user_id"] = payload["user_id"]
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
|
|
||||||
return client, payload
|
return client, payload
|
||||||
|
|
@ -52,6 +54,7 @@ def test_servers_page_without_blueprints_shows_create_blueprint_cta(tmp_path, mo
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = user_id
|
sess["user_id"] = user_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
|
|
||||||
response = client.get("/servers")
|
response = client.get("/servers")
|
||||||
|
|
@ -235,6 +238,7 @@ def test_create_server_allows_same_name_for_different_users(tmp_path, monkeypatc
|
||||||
|
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = alice_id
|
sess["user_id"] = alice_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
alice_resp = client.post(
|
alice_resp = client.post(
|
||||||
"/servers",
|
"/servers",
|
||||||
|
|
@ -245,6 +249,7 @@ def test_create_server_allows_same_name_for_different_users(tmp_path, monkeypatc
|
||||||
|
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = bob_id
|
sess["user_id"] = bob_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
bob_resp = client.post(
|
bob_resp = client.post(
|
||||||
"/servers",
|
"/servers",
|
||||||
|
|
@ -318,6 +323,7 @@ def test_create_server_returns_409_when_port_range_exhausted(tmp_path, monkeypat
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = user_id
|
sess["user_id"] = user_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
|
|
||||||
first = client.post(
|
first = client.post(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from l4d2web.app import create_app
|
from l4d2web.app import create_app
|
||||||
from l4d2web.auth import hash_password
|
from l4d2web.auth import hash_password
|
||||||
|
|
@ -32,6 +33,7 @@ def owner_client_with_server(tmp_path, monkeypatch):
|
||||||
client = app.test_client()
|
client = app.test_client()
|
||||||
with client.session_transaction() as sess:
|
with client.session_transaction() as sess:
|
||||||
sess["user_id"] = user_id
|
sess["user_id"] = user_id
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
|
|
||||||
return client, server_id
|
return client, server_id
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"""Tests for the workshop overlay routes (add items, remove items, build,
|
"""Tests for the workshop overlay routes (add items, remove items, build,
|
||||||
admin refresh)."""
|
admin refresh)."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
@ -54,6 +55,7 @@ def env_user(tmp_path, monkeypatch):
|
||||||
c = app.test_client()
|
c = app.test_client()
|
||||||
with c.session_transaction() as sess:
|
with c.session_transaction() as sess:
|
||||||
sess["user_id"] = uid
|
sess["user_id"] = uid
|
||||||
|
sess["pw_changed_at"] = datetime.now(UTC).isoformat()
|
||||||
sess["csrf_token"] = "test-token"
|
sess["csrf_token"] = "test-token"
|
||||||
return c
|
return c
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue