Callers never see stores: session_ctx/verify/user-info resolve the store from the request host via satellite.store_for_host; session refresh and logout eviction are dispatch functions too (satellite.refresh_session / evict_session). API handlers keep one code path plus forward_request one-liners; proxy.py folds into satellite.py; Domain.store and the store parameters are gone; 503 comes from the dispatch point as a plain HTTPException. The sync protocol drops replay/generation/seq: snapshots are small, so every connect starts from a full snapshot and a single ordered WebSocket cannot gap; a slow subscriber is dropped and resyncs. The satellite reconnects every refresh_interval to reconcile drift.
61 lines
1.6 KiB
Python
61 lines
1.6 KiB
Python
"""
|
|
Core session management for WebAuthn authentication.
|
|
|
|
This module provides generic session management functionality that is
|
|
independent of any web framework:
|
|
- Session creation and validation
|
|
- Token handling and refresh
|
|
- Credential management
|
|
"""
|
|
|
|
from datetime import UTC, datetime
|
|
from typing import TYPE_CHECKING
|
|
from uuid import UUID
|
|
|
|
from paskia import db
|
|
from paskia.config import RESET_LIFETIME, SESSION_LIFETIME
|
|
from paskia.db.structs import ResetToken
|
|
from paskia.util import hostutil
|
|
|
|
if TYPE_CHECKING:
|
|
from paskia.db import ResetToken
|
|
|
|
EXPIRES = SESSION_LIFETIME
|
|
|
|
|
|
def session_ctx(auth: str, host: str | None = None):
|
|
"""Get session context with normalized host.
|
|
|
|
The store is dispatched by host: remote domains read their replica.
|
|
"""
|
|
from paskia import satellite # noqa: PLC0415 (import cycle)
|
|
|
|
return satellite.store_for_host(host).session_ctx(
|
|
auth, hostutil.normalize_host(host)
|
|
)
|
|
|
|
|
|
def expires() -> datetime:
|
|
return datetime.now(UTC) + EXPIRES
|
|
|
|
|
|
def reset_expires() -> datetime:
|
|
return datetime.now(UTC) + RESET_LIFETIME
|
|
|
|
|
|
def get_reset(token: str) -> ResetToken:
|
|
"""Validate a credential reset token."""
|
|
|
|
record = ResetToken.by_passphrase(token)
|
|
if record:
|
|
return record
|
|
raise ValueError("This authentication link is no longer valid.")
|
|
|
|
|
|
def delete_credential(credential_uuid: UUID, auth: str, host: str | None = None):
|
|
"""Delete a specific credential for the current user."""
|
|
ctx = session_ctx(auth, host)
|
|
if not ctx:
|
|
raise ValueError("Session expired")
|
|
db.delete_credential(credential_uuid, ctx.user.uuid)
|