feat: add OIDC claims mapping and PorchlightUserInfo source

This commit is contained in:
Johan Lundberg 2026-02-16 12:52:43 +01:00
parent fd098a4eff
commit 02b75a3eca
No known key found for this signature in database
GPG key ID: A6C152738D03C7D1
3 changed files with 122 additions and 0 deletions

View file

@ -35,5 +35,8 @@ class Settings(BaseSettings):
# Magic links
invite_ttl: int = 86400 # seconds
# Signing keys
signing_key_path: str = "data/keys"
# Theme
theme: str = "default"

View file

@ -0,0 +1,54 @@
"""OIDC claims mapping and UserInfo source."""
from idpyoidc.server.user_info import UserInfo
from fastapi_oidc_op.models import User
def user_to_claims(user: User) -> dict:
"""Convert a User model to an OIDC claims dict.
Only includes claims that have non-None values.
The 'sub' claim always uses the userid (proquint).
"""
claims: dict = {"sub": user.userid}
# preferred_username: use explicit field, fall back to username
claims["preferred_username"] = user.preferred_username or user.username
optional_fields = {
"given_name": user.given_name,
"family_name": user.family_name,
"nickname": user.nickname,
"email": user.email,
"email_verified": user.email_verified if user.email else None,
"phone_number": user.phone_number,
"phone_number_verified": user.phone_number_verified if user.phone_number else None,
"picture": user.picture,
"locale": user.locale,
}
for claim_name, value in optional_fields.items():
if value is not None:
claims[claim_name] = value
# updated_at as Unix timestamp (OIDC spec requires number)
if user.updated_at:
claims["updated_at"] = int(user.updated_at.timestamp())
return claims
class PorchlightUserInfo(UserInfo):
"""UserInfo source backed by an in-memory claims cache.
Claims are populated via set_user_claims() after authentication.
idpyoidc calls __call__() synchronously to look up claims.
"""
def __init__(self, **kwargs) -> None:
super().__init__(db={}, **kwargs)
def set_user_claims(self, user_id: str, claims: dict) -> None:
"""Populate claims cache for a user."""
self.db[user_id] = claims