1use crate::{
2 auth_n::{self, certificates::get_certification_provider, middleware::jwt::User},
3 config::ServerConfig,
4 fhir_client::ServerCTX,
5 fhir_http::{HTTPBody, HTTPRequest, http_request_to_fhir_request},
6 mcp,
7 middleware::{
8 errors::{log_operationoutcome_errors, operation_outcome_error_handle},
9 security_headers::SecurityHeaderLayer,
10 },
11 openapi,
12 services::{ConfigError, ServerState, create_services, get_pool},
13 static_assets::{create_static_server, root_asset_route},
14};
15use axum::{
16 Extension, Router, ServiceExt,
17 body::Body,
18 extract::{DefaultBodyLimit, OriginalUri, Path, State},
19 http::Request,
20 http::{HeaderName, HeaderValue, Method, Uri},
21 middleware::from_fn,
22 response::{IntoResponse, Response},
23 routing::{any, get, post},
24};
25use axum_client_ip::ClientIpSource;
26use haste_fhir_client::{
27 FHIRClient,
28 request::{FHIRCapabilitiesResponse, FHIRResponse},
29};
30use haste_fhir_operation_error::OperationOutcomeError;
31use haste_fhir_search::SearchEngine;
32use haste_fhir_terminology::FHIRTerminology;
33use haste_jwt::{ProjectId, TenantId};
34use haste_repository::{Repository, types::SupportedFHIRVersions, utilities::generate_id};
35use sentry::integrations::tower::NewSentryLayer;
36use serde::Deserialize;
37use std::net::SocketAddr;
38use std::sync::Arc;
39use tower::{Layer, ServiceBuilder};
40use tower_http::{catch_panic::CatchPanicLayer, normalize_path::NormalizePath};
41use tower_http::{
42 compression::CompressionLayer,
43 cors::{Any, CorsLayer},
44 normalize_path::NormalizePathLayer,
45 set_header::SetResponseHeaderLayer,
46 trace::TraceLayer,
47};
48use tower_sessions::{
49 Expiry, SessionManagerLayer,
50 cookie::{SameSite, time::Duration},
51};
52use tower_sessions_sqlx_store::PostgresStore;
53
54const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION");
55
56#[derive(Deserialize)]
57struct FHIRHandlerPath {
58 tenant: TenantId,
59 project: ProjectId,
60 fhir_version: SupportedFHIRVersions,
61 fhir_location: Option<String>,
62}
63
64#[derive(Deserialize)]
65struct FHIRRootHandlerPath {
66 tenant: TenantId,
67 project: ProjectId,
68 fhir_version: SupportedFHIRVersions,
69}
70
71async fn fhir_handler<
72 Repo: Repository + Send + Sync + 'static,
73 Search: SearchEngine + Send + Sync + 'static,
74 Terminology: FHIRTerminology + Send + Sync + 'static,
75>(
76 user: Arc<User>,
77 method: Method,
78 uri: Uri,
79 path: FHIRHandlerPath,
80 state: Arc<ServerState<Repo, Search, Terminology>>,
81 body: String,
82) -> Result<Response, OperationOutcomeError> {
83 let fhir_location = path.fhir_location.unwrap_or_default();
84
85 async {
86 let http_req = HTTPRequest::new(
87 method,
88 fhir_location,
89 HTTPBody::String(body),
90 uri.query()
91 .map(|q| {
92 url::form_urlencoded::parse(q.as_bytes())
93 .into_owned()
94 .collect()
95 })
96 .unwrap_or_default(),
97 );
98
99 let fhir_request = http_request_to_fhir_request(SupportedFHIRVersions::R4, http_req)?;
100
101 let ctx = ServerCTX::new(
102 path.tenant,
103 path.project,
104 path.fhir_version,
105 user.clone(),
106 state.fhir_client.clone(),
107 state.rate_limit.clone(),
108 )
109 .with_tracing_id(Some(format!("rest-{}", generate_id(Some(8)))));
110
111 let ctx = Arc::new(ctx);
112
113 let response = state.fhir_client.request(ctx, fhir_request).await?;
114
115 let http_response = response.into_response();
116 Ok(http_response)
117 }
118 .await
119}
120
121async fn fhir_root_handler<
122 Repo: Repository + Send + Sync + 'static,
123 Search: SearchEngine + Send + Sync + 'static,
124 Terminology: FHIRTerminology + Send + Sync + 'static,
125>(
126 method: Method,
127 Extension(user): Extension<Arc<User>>,
128 OriginalUri(uri): OriginalUri,
129 Path(path): Path<FHIRRootHandlerPath>,
130 State(state): State<Arc<ServerState<Repo, Search, Terminology>>>,
131 body: String,
132) -> Result<Response, OperationOutcomeError> {
133 fhir_handler(
134 user,
135 method,
136 uri,
137 FHIRHandlerPath {
138 tenant: path.tenant,
139 project: path.project,
140 fhir_version: path.fhir_version,
141 fhir_location: None,
142 },
143 state,
144 body,
145 )
146 .await
147}
148
149async fn fhir_type_handler<
150 Repo: Repository + Send + Sync + 'static,
151 Search: SearchEngine + Send + Sync + 'static,
152 Terminology: FHIRTerminology + Send + Sync + 'static,
153>(
154 method: Method,
155 Extension(user): Extension<Arc<User>>,
156 OriginalUri(uri): OriginalUri,
157 Path(path): Path<FHIRHandlerPath>,
158 State(state): State<Arc<ServerState<Repo, Search, Terminology>>>,
159 body: String,
160) -> Result<Response, OperationOutcomeError> {
161 fhir_handler(user, method, uri, path, state, body).await
162}
163
164async fn public_metadata_handler<
165 Repo: Repository + Send + Sync + 'static,
166 Search: SearchEngine + Send + Sync + 'static,
167 Terminology: FHIRTerminology + Send + Sync + 'static,
168>(
169 Path(path): Path<FHIRRootHandlerPath>,
170 State(state): State<Arc<ServerState<Repo, Search, Terminology>>>,
171) -> Result<Response, OperationOutcomeError> {
172 let ctx = Arc::new(ServerCTX::system(
173 path.tenant,
174 path.project,
175 state.fhir_client.clone(),
176 state.rate_limit.clone(),
177 ));
178
179 state
180 .fhir_client
181 .capabilities(ctx)
182 .await
183 .map(|capabilities| FHIRResponse::Capabilities(FHIRCapabilitiesResponse { capabilities }))
184 .map(|fhir_response| fhir_response.into_response())
185}
186
187pub async fn server(
188 config: Arc<ServerConfig>,
189) -> Result<NormalizePath<Router>, OperationOutcomeError> {
190 let ip_source = match &config.monitoring.ip_source {
191 crate::config::IpSource::ConnectInfo => ClientIpSource::ConnectInfo,
192 crate::config::IpSource::CfConnectingIp => ClientIpSource::CfConnectingIp,
193 crate::config::IpSource::XRealIp => ClientIpSource::XRealIp,
194 };
195
196 get_certification_provider(config.as_ref());
197
198 let pool = get_pool(config.as_ref()).await;
199 let session_store = PostgresStore::new(pool.clone());
200 session_store.migrate().await.map_err(ConfigError::from)?;
201
202 let shared_state = create_services(config.clone()).await?;
203
204 let fhir_router = Router::new()
205 .route("/{fhir_version}", any(fhir_root_handler))
206 .route("/{fhir_version}/{*fhir_location}", any(fhir_type_handler));
207
208 let protected_resources_router = Router::new()
209 .nest("/fhir", fhir_router)
210 .route("/mcp", post(mcp::route::mcp_handler))
211 .layer(
212 ServiceBuilder::new()
213 .layer(axum::middleware::from_fn_with_state(
214 shared_state.clone(),
215 auth_n::middleware::basic_auth::basic_auth_middleware,
216 ))
217 .layer(axum::middleware::from_fn_with_state(
218 shared_state.clone(),
219 auth_n::middleware::jwt::token_verifcation,
220 ))
221 .layer(axum::middleware::from_fn(
222 auth_n::middleware::project_access::project_access,
223 )),
224 );
225
226 let smart_configuration_router = Router::new()
227 .route(
230 "/fhir/{fhir_version}/.well-known/smart-configuration",
231 get(auth_n::oidc::routes::discovery::smart_configuration),
232 )
233 .route_layer(
234 ServiceBuilder::new().layer(axum::middleware::from_fn_with_state(
235 shared_state.clone(),
236 auth_n::oidc::middleware::project_exists,
237 )),
238 );
239
240 let mut project_router = Router::new()
241 .merge(protected_resources_router)
242 .merge(smart_configuration_router)
243 .nest(
244 "/oidc",
245 auth_n::oidc::routes::create_router(shared_state.clone()),
246 );
247
248 if config.security.publicize_fhir_metadata {
249 project_router = project_router.route(
250 "/fhir/{fhir_version}/metadata",
251 get(public_metadata_handler),
252 );
253 }
254
255 let tenant_router = Router::new()
256 .nest("/auth", auth_n::tenant::routes::create_router())
257 .nest("/{project}/api/v1", project_router)
258 .nest(
259 "/mfa",
260 auth_n::mfa::routes::create_router(shared_state.clone()),
261 )
262 .layer(
263 ServiceBuilder::new()
265 .layer(from_fn(operation_outcome_error_handle))
266 .layer(from_fn(log_operationoutcome_errors)),
267 );
268
269 let discovery_2_0_document_router = Router::new()
270 .route(
271 "/openid-configuration/w/{tenant}/{project}/{*resource}",
272 get(auth_n::oidc::routes::discovery::openid_configuration),
273 )
274 .route(
275 "/openid-configuration/w/{tenant}/{project}",
276 get(auth_n::oidc::routes::discovery::openid_configuration),
277 )
278 .route(
279 "/oauth-protected-resource/w/{tenant}/{project}/{*resource}",
280 get(auth_n::oidc::routes::discovery::oauth_protected_resource),
281 );
282
283 let app = Router::new()
284 .nest("/.well-known", discovery_2_0_document_router)
285 .nest(
286 "/auth",
287 auth_n::global::routes::create_router(shared_state.clone()),
288 )
289 .route("/openapi.json", get(openapi::openapi_document_handler))
290 .nest("/w/{tenant}", tenant_router)
291 .layer(
292 ServiceBuilder::new()
293 .layer(CatchPanicLayer::new())
294 .layer(ip_source.into_extension())
295 .layer(NewSentryLayer::<Request<Body>>::new_from_top())
296 .layer(TraceLayer::new_for_http())
297 .layer(DefaultBodyLimit::max(config.max_request_body_size))
299 .layer(CompressionLayer::new())
300 .layer(SecurityHeaderLayer::new())
301 .layer(SetResponseHeaderLayer::overriding(
302 HeaderName::from_static("x-api-version"),
303 HeaderValue::from_static(SERVER_VERSION),
304 ))
305 .layer(
306 SessionManagerLayer::new(session_store)
307 .with_secure(true)
308 .with_same_site(SameSite::None)
309 .with_expiry(Expiry::OnInactivity(Duration::days(3))),
310 )
311 .layer(
312 CorsLayer::new()
313 .allow_methods(Any)
314 .allow_origin(Any)
315 .allow_headers(Any),
316 ),
317 )
318 .with_state(shared_state)
319 .nest(root_asset_route().to_str().unwrap(), create_static_server());
320
321 Ok(NormalizePathLayer::trim_trailing_slash().layer(app))
322}
323
324pub async fn serve(config: Arc<ServerConfig>, port: u16) -> Result<(), OperationOutcomeError> {
325 let server = server(config).await?;
326
327 let addr = format!("0.0.0.0:{}", port);
328 let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
329
330 tracing::info!("Server started");
331 axum::serve(
332 listener,
333 <tower_http::normalize_path::NormalizePath<Router> as ServiceExt<
334 axum::http::Request<Body>,
335 >>::into_make_service_with_connect_info::<SocketAddr>(server),
336 )
337 .await
338 .unwrap();
339
340 Ok(())
341}