Coverage for src/lumen/api/well_known.py: 100%

26 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-08-31 18:46 +0000

1""" 

2This endpoint serves the well-known SMART-on-FHIR configuration discovery document. 

3It publishes IdP OAuth endpoints and supported security capabilities to client applications. 

4""" 

5 

6from __future__ import annotations 

7 

8import json 

9from typing import Annotated, Any 

10 

11from fastapi import APIRouter, Depends 

12from fastapi.responses import Response 

13 

14from ..auth.jwks import discover_oidc_config 

15from ..config import Settings, get_settings 

16from ..core.exceptions import OperationOutcomeError 

17 

18router = APIRouter() 

19 

20# SMART capabilities advertised for Stage 1-4 (compartment enforcement). 

21# `launch-ehr`/`context-passthrough` are not advertised: the core enforces a 

22# `patient` launch-context claim if one is present, but no launch endpoint 

23# (launch parameter issuance/resolution) exists yet — that's on the IdP to 

24# provide (see docs/architecture/smart-on-fhir.md §7). 

25_SMART_CAPABILITIES = [ 

26 "permission-v1", 

27 "permission-v2", 

28 "permission-patient", 

29 "client-confidential-symmetric", 

30] 

31 

32_SCOPES_SUPPORTED = [ 

33 "openid", 

34 "fhirUser", 

35 "system/*.read", 

36 "system/*.write", 

37 "system/*.*", 

38 "user/*.read", 

39 "user/*.write", 

40 "patient/*.read", 

41 "patient/*.write", 

42 "user/*.*", 

43] 

44 

45 

46@router.get("/.well-known/smart-configuration") 

47async def smart_configuration( 

48 settings: Annotated[Settings, Depends(get_settings)], 

49) -> Response: 

50 if not settings.smart.enabled or not settings.smart.tenant_map: 

51 raise OperationOutcomeError( 

52 status_code=404, 

53 code="not-found", 

54 diagnostics="SMART-on-FHIR is not enabled on this server.", 

55 ) 

56 

57 # Use the first configured issuer as the authoritative IdP for discovery. 

58 issuer = next(iter(settings.smart.tenant_map)) 

59 

60 fetch_base = settings.smart.internal_issuer_map.get(issuer) 

61 try: 

62 oidc: dict[str, Any] = await discover_oidc_config(issuer, fetch_base=fetch_base) 

63 except Exception as exc: 

64 raise OperationOutcomeError( 

65 status_code=502, 

66 code="exception", 

67 diagnostics=f"Failed to fetch OIDC discovery from IdP: {exc}", 

68 ) from exc 

69 

70 doc: dict[str, Any] = { 

71 "issuer": issuer, 

72 "authorization_endpoint": oidc.get("authorization_endpoint", ""), 

73 "token_endpoint": oidc["token_endpoint"], 

74 "jwks_uri": oidc["jwks_uri"], 

75 "scopes_supported": _SCOPES_SUPPORTED, 

76 "response_types_supported": ["code"], 

77 "capabilities": _SMART_CAPABILITIES, 

78 "code_challenge_methods_supported": ["S256"], 

79 } 

80 

81 # Include optional endpoints when the IdP advertises them. 

82 for key in ("userinfo_endpoint", "revocation_endpoint", "introspection_endpoint"): 

83 if key in oidc: 

84 doc[key] = oidc[key] 

85 

86 return Response( 

87 content=json.dumps(doc), 

88 media_type="application/json", 

89 headers={"Cache-Control": "no-store"}, 

90 )