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:
@@ -0,0 +1,49 @@
|
||||
"""The identity-provider interface, its data types and typed errors (AD-1, AD-17)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
class IdpError(Exception):
|
||||
"""Base class of every error the identity-provider adapter raises."""
|
||||
|
||||
|
||||
class IdpUnavailable(IdpError):
|
||||
"""The provider could not be reached or answered with something unusable."""
|
||||
|
||||
|
||||
class LoginRejected(IdpError):
|
||||
"""The sign-in response is not valid: wrong state, bad token, provider error."""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Flow:
|
||||
"""What must survive between the redirect and the callback. Kept in the session."""
|
||||
|
||||
state: str
|
||||
nonce: str
|
||||
code_verifier: str
|
||||
|
||||
def as_dict(self) -> dict[str, str]:
|
||||
return {"state": self.state, "nonce": self.nonce, "code_verifier": self.code_verifier}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Mapping[str, str]) -> Flow:
|
||||
return cls(data["state"], data["nonce"], data["code_verifier"])
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Identity:
|
||||
username: str # the validated preferred_username claim
|
||||
subject: str
|
||||
|
||||
|
||||
class Idp(Protocol):
|
||||
def begin(self) -> tuple[str, Flow]:
|
||||
"""The address to send the browser to, and the flow to remember."""
|
||||
|
||||
def complete(self, params: Mapping[str, str], flow: Flow) -> Identity:
|
||||
"""Validate the callback's query parameters against the flow; return who signed in."""
|
||||
Reference in New Issue
Block a user