feat: add idpyoidc server initialization

This commit is contained in:
Johan Lundberg 2026-02-16 13:24:54 +01:00
parent 02b75a3eca
commit 2426e0675c
No known key found for this signature in database
GPG key ID: A6C152738D03C7D1
2 changed files with 192 additions and 0 deletions

View file

@ -0,0 +1,136 @@
"""idpyoidc Server initialization."""
from pathlib import Path
from idpyoidc.server import Server
from fastapi_oidc_op.config import Settings
from fastapi_oidc_op.oidc.claims import PorchlightUserInfo
def _build_server_config(settings: Settings) -> dict:
"""Build the idpyoidc configuration dict from application settings."""
key_path = Path(settings.signing_key_path)
key_path.mkdir(parents=True, exist_ok=True)
return {
"issuer": settings.issuer,
"key_conf": {
"key_defs": [
{"type": "RSA", "use": ["sig"]},
{"type": "EC", "crv": "P-256", "use": ["sig"]},
],
"private_path": str(key_path / "private_jwks.json"),
"public_path": str(key_path / "public_jwks.json"),
"uri_path": "jwks",
"read_only": False,
},
"endpoint": {
"provider_config": {
"path": ".well-known/openid-configuration",
"class": "idpyoidc.server.oidc.provider_config.ProviderConfiguration",
"kwargs": {},
},
"authorization": {
"path": "authorization",
"class": "idpyoidc.server.oidc.authorization.Authorization",
"kwargs": {},
},
"token": {
"path": "token",
"class": "idpyoidc.server.oidc.token.Token",
"kwargs": {},
},
"userinfo": {
"path": "userinfo",
"class": "idpyoidc.server.oidc.userinfo.UserInfo",
"kwargs": {},
},
},
"userinfo": {
"class": PorchlightUserInfo,
"kwargs": {},
},
"authz": {
"class": "idpyoidc.server.authz.AuthzHandling",
"kwargs": {
"grant_config": {
"usage_rules": {
"authorization_code": {
"supports_minting": ["access_token", "refresh_token", "id_token"],
"max_usage": 1,
"expires_in": 120,
},
"access_token": {
"expires_in": 3600,
},
"refresh_token": {
"supports_minting": ["access_token", "refresh_token", "id_token"],
"expires_in": 86400,
},
},
"expires_in": 2592000,
},
},
},
"token_handler_args": {
"jwks_def": {
"private_path": str(key_path / "token_jwks.json"),
"read_only": False,
"key_defs": [{"type": "oct", "bytes": "24", "use": ["enc"], "kid": "code"}],
},
"code": {
"kwargs": {"lifetime": 600},
},
"token": {
"class": "idpyoidc.server.token.jwt_token.JWTToken",
"kwargs": {
"lifetime": 3600,
"add_claims_by_scope": True,
"aud": [settings.issuer],
},
},
"refresh": {
"class": "idpyoidc.server.token.jwt_token.JWTToken",
"kwargs": {
"lifetime": 86400,
"aud": [settings.issuer],
},
},
"id_token": {
"class": "idpyoidc.server.token.id_token.IDToken",
"kwargs": {
"lifetime": 3600,
},
},
},
"scopes_to_claims": {
"openid": ["sub"],
"profile": [
"name",
"given_name",
"family_name",
"middle_name",
"nickname",
"profile",
"picture",
"website",
"gender",
"birthdate",
"zoneinfo",
"locale",
"updated_at",
"preferred_username",
],
"email": ["email", "email_verified"],
"phone": ["phone_number", "phone_number_verified"],
},
"authentication": {},
}
def create_oidc_server(settings: Settings) -> Server:
"""Create and configure an idpyoidc Server instance."""
config = _build_server_config(settings)
server = Server(conf=config)
return server

View file

@ -0,0 +1,56 @@
import shutil
from pathlib import Path
from fastapi_oidc_op.config import Settings
from fastapi_oidc_op.oidc.provider import create_oidc_server
def test_create_server_has_endpoints() -> None:
key_path = Path("test_keys_provider")
key_path.mkdir(exist_ok=True)
try:
settings = Settings(issuer="http://localhost:8000", sqlite_path=":memory:", signing_key_path=str(key_path))
server = create_oidc_server(settings)
assert "authorization" in server.endpoint
assert "token" in server.endpoint
assert "userinfo" in server.endpoint
assert "provider_config" in server.endpoint
finally:
shutil.rmtree(key_path, ignore_errors=True)
def test_create_server_has_issuer() -> None:
key_path = Path("test_keys_issuer")
key_path.mkdir(exist_ok=True)
try:
settings = Settings(issuer="http://localhost:8000", sqlite_path=":memory:", signing_key_path=str(key_path))
server = create_oidc_server(settings)
assert server.context.issuer == "http://localhost:8000"
finally:
shutil.rmtree(key_path, ignore_errors=True)
def test_create_server_jwks_available() -> None:
key_path = Path("test_keys_jwks")
key_path.mkdir(exist_ok=True)
try:
settings = Settings(issuer="http://localhost:8000", sqlite_path=":memory:", signing_key_path=str(key_path))
server = create_oidc_server(settings)
keys = server.keyjar.export_jwks()
assert "keys" in keys
assert len(keys["keys"]) > 0
finally:
shutil.rmtree(key_path, ignore_errors=True)
def test_create_server_userinfo_is_porchlight() -> None:
key_path = Path("test_keys_userinfo")
key_path.mkdir(exist_ok=True)
try:
settings = Settings(issuer="http://localhost:8000", sqlite_path=":memory:", signing_key_path=str(key_path))
server = create_oidc_server(settings)
from fastapi_oidc_op.oidc.claims import PorchlightUserInfo
assert isinstance(server.context.userinfo, PorchlightUserInfo)
finally:
shutil.rmtree(key_path, ignore_errors=True)