feat: add OIDC sign-in, roles, sessions and client address rule

Authorization-code flow with PKCE through an idp adapter (with a fake), ID
token validation, identity and roles resolved from the directory with a short
cache, refusal of unknown, disabled and locked accounts with one generic page,
sessions that end when the account becomes unavailable, sign-out that ends the
server-side session, role decorators and the trusted-proxy client address rule.
Adds the OIDC_* and PUBLIC_BASE_URL configuration.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3t4WL7bd9bdHHNjEsxzqC
This commit is contained in:
2026-10-17 19:19:51 +02:00
co-authored by Claude Sonnet 5.5
parent 7458a2a815
commit 3c69c2a156
32 changed files with 1692 additions and 14 deletions
+35
View File
@@ -0,0 +1,35 @@
"""The client address, trusting X-Forwarded-For only from configured proxies (AD-7)."""
from __future__ import annotations
import ipaddress
from collections.abc import Iterable
Network = ipaddress.IPv4Network | ipaddress.IPv6Network
def parse_networks(items: Iterable[str]) -> list[Network]:
return [ipaddress.ip_network(item, strict=False) for item in items]
def _address(value: str) -> ipaddress.IPv4Address | ipaddress.IPv6Address | None:
try:
return ipaddress.ip_address(value.strip())
except ValueError:
return None
def client_ip(peer: str, forwarded_for: str, trusted: list[Network]) -> str:
"""The TCP peer, or the last X-Forwarded-For entry when the peer is a trusted proxy.
The last entry is the one the proxy appended itself; earlier ones come from the client and
can be forged. X-Real-IP is never used.
"""
peer_address = _address(peer)
if peer_address is None:
return ""
if not any(peer_address in network for network in trusted):
return str(peer_address)
last = forwarded_for.rsplit(",", 1)[-1] if forwarded_for else ""
forwarded = _address(last)
return str(forwarded) if forwarded is not None else str(peer_address)