Fix OIDC subject collisions by introducing canonical provider-plus-subject principals
- Add canonical_principal(provider, sub) => 'oidc:v1:<provider>:<sub>' - Preserve provider identity across HTTP sessions and WebSockets - Apply principals throughout: game creation/joining/seating, WebSocket views/actions, Redis engine state, match history, leaderboard focus, ratings, /api/me, OpenAPI, and telemetry - Widen identity columns to 1024 chars with schema-only aerich migration (5_20261024221522_canonical_principals.py) - Document provider names as stable security identifiers - Add regression tests: same subject at different providers cannot share games, private hands, join codes, moves, history, or ratings; distinct seat and rating ownership verified - All platform, integration, game, and frontend tests pass; mypy clean
This commit is contained in:
@@ -72,9 +72,10 @@ _DEFAULT_OIDC_CLIENT_ID = "tavolo"
|
||||
class OidcProviderSettings:
|
||||
"""One OIDC provider, parsed from the ``OIDC_<n>_*`` variables.
|
||||
|
||||
``provider`` names the provider in URLs (``/auth/<provider>/login``)
|
||||
and namespaces its session state (``oidc:<provider>:...``); it must
|
||||
match kaya-oidc's ``[A-Za-z0-9_-]+`` pattern.
|
||||
``provider`` names the provider in URLs (``/auth/<provider>/login``),
|
||||
namespaces its session state (``oidc:<provider>:...``), and is part of
|
||||
the canonical user principal. It must match kaya-oidc's
|
||||
``[A-Za-z0-9_-]+`` pattern and remain stable across deployments.
|
||||
"""
|
||||
|
||||
provider: str
|
||||
|
||||
@@ -95,10 +95,11 @@ def session_tag_hook(span: Any, carrier: Any) -> None:
|
||||
The session id is the session cookie, i.e. a credential, so only a
|
||||
truncated SHA-256 of it is exported; session ids are
|
||||
``secrets.token_urlsafe(32)`` (256 bits), which makes the digest
|
||||
irreversible in practice. The OIDC ``sub`` is a pseudonymous
|
||||
identifier already persisted in Postgres and served by the API, so it
|
||||
is exported verbatim as ``user.id`` (OpenTelemetry's current end-user
|
||||
attribute; ``enduser.id`` is PII-flagged and being superseded).
|
||||
irreversible in practice. The canonical provider-qualified principal
|
||||
is a pseudonymous identifier already persisted in Postgres and served
|
||||
by the API, so it is exported verbatim as ``user.id`` (OpenTelemetry's
|
||||
current end-user attribute; ``enduser.id`` is PII-flagged and being
|
||||
superseded).
|
||||
"""
|
||||
session = getattr(carrier, "session", None)
|
||||
if session is None:
|
||||
@@ -111,9 +112,7 @@ def session_tag_hook(span: Any, carrier: Any) -> None:
|
||||
)
|
||||
user = session_user(session)
|
||||
if user is not None:
|
||||
sub = user.get("sub")
|
||||
if sub:
|
||||
span.set_attribute("user.id", str(sub))
|
||||
span.set_attribute("user.id", user.principal)
|
||||
|
||||
|
||||
class _NoOtelLogs(logging.Filter):
|
||||
|
||||
Reference in New Issue
Block a user