1use std::sync::Arc;
9
10use axum::{
11 Extension, Router,
12 http::StatusCode,
13 middleware,
14 routing::{delete, get, post},
15};
16use tower_http::timeout::TimeoutLayer;
17use utoipa::OpenApi as _;
18use utoipa_swagger_ui::SwaggerUi;
19
20use super::ServerState;
21use super::api_doc::ApiDoc;
22use super::auth_middleware::{
23 AuthRateLimiter, rate_limit, read_only_guard, strip_body_for_logs,
24};
25use super::handlers;
26
27pub fn build_router(state: Arc<ServerState>) -> Router {
50 let api = Router::new()
51 .route("/sessions", get(handlers::get_sessions))
53 .route("/analysis/images", get(handlers::get_image_analysis))
54 .route("/configurations", get(handlers::get_configurations))
55 .route("/nodes", get(handlers::get_nodes))
56 .route("/groups", get(handlers::get_groups))
57 .route("/groups/available", get(handlers::get_available_groups))
58 .route("/images", get(handlers::get_images))
59 .route("/templates", get(handlers::get_templates))
60 .route("/boot-parameters", get(handlers::get_boot_parameters))
61 .route("/kernel-parameters", get(handlers::get_kernel_parameters))
62 .route("/redfish-endpoints", get(handlers::get_redfish_endpoints))
63 .route("/groups/nodes", get(handlers::get_groups_nodes))
65 .route("/groups/hardware", get(handlers::get_groups_hardware))
66 .route("/clusters", get(handlers::get_clusters_deprecated))
69 .route(
70 "/hardware-clusters",
71 get(handlers::get_hardware_clusters_deprecated),
72 )
73 .route(
74 "/hardware-nodes-list",
75 get(handlers::get_hardware_nodes_list),
76 )
77 .route("/nodes", post(handlers::add_node))
80 .route("/nodes/{id}", delete(handlers::delete_node))
81 .route("/groups", post(handlers::create_group))
83 .route("/groups/{label}", delete(handlers::delete_group))
84 .route(
85 "/groups/{name}/members",
86 post(handlers::add_nodes_to_group).delete(handlers::delete_group_members),
87 )
88 .route(
90 "/boot-parameters",
91 post(handlers::add_boot_parameters)
92 .put(handlers::update_boot_parameters)
93 .delete(handlers::delete_boot_parameters),
94 )
95 .route(
97 "/redfish-endpoints",
98 post(handlers::add_redfish_endpoint)
99 .put(handlers::update_redfish_endpoint),
100 )
101 .route(
102 "/redfish-endpoints/{id}",
103 delete(handlers::delete_redfish_endpoint),
104 )
105 .route("/sessions/{name}", delete(handlers::delete_session))
107 .route("/sessions", post(handlers::create_session))
109 .route("/images", delete(handlers::delete_images))
111 .route("/configurations", delete(handlers::delete_configurations))
113 .route("/boot-config", post(handlers::apply_boot_config))
115 .route(
117 "/kernel-parameters/apply",
118 post(handlers::apply_kernel_parameters),
119 )
120 .route(
121 "/kernel-parameters/add",
122 post(handlers::add_kernel_parameters),
123 )
124 .route(
125 "/kernel-parameters",
126 delete(handlers::delete_kernel_parameters),
127 )
128 .route("/migrate/nodes", post(handlers::migrate_nodes))
130 .route("/migrate/backup", post(handlers::migrate_backup))
131 .route("/migrate/restore", post(handlers::migrate_restore))
132 .route("/ephemeral-env", post(handlers::create_ephemeral_env))
134 .route("/power", post(handlers::post_power))
137 .route(
138 "/power/transitions/{id}",
139 get(handlers::get_power_transition),
140 )
141 .route(
143 "/templates/{name}/sessions",
144 post(handlers::post_template_session),
145 )
146 .route("/sessions/{name}/logs", get(handlers::get_session_logs))
148 .route(
153 "/sat-file/configurations",
154 post(handlers::post_sat_configuration),
155 )
156 .route(
157 "/sat-file/images/cfs-session",
158 post(handlers::post_sat_image_cfs_session),
159 )
160 .route(
161 "/sat-file/images/stamp",
162 post(handlers::post_sat_image_stamp),
163 )
164 .route(
165 "/sat-file/session-templates",
166 post(handlers::post_sat_session_template),
167 )
168 .route("/sat-file/validate", post(handlers::post_sat_validate))
169 .route("/health", get(handlers::health))
171 .route(
173 "/hardware-clusters/{target}/members",
174 post(handlers::add_hw_component).delete(handlers::delete_hw_component),
175 )
176 .route(
178 "/hardware-clusters/{target}/configuration",
179 post(handlers::apply_hw_configuration),
180 )
181 .merge(build_ws_routes())
182 .layer(TimeoutLayer::with_status_code(
185 StatusCode::REQUEST_TIMEOUT,
186 state.request_timeout,
187 ))
188 .layer(middleware::from_fn(read_only_guard));
192
193 let limiter = AuthRateLimiter::new();
197 let auth = Router::new()
198 .route("/token", post(handlers::auth_token))
199 .route("/validate", post(handlers::auth_validate))
200 .layer(middleware::from_fn(strip_body_for_logs))
201 .layer(middleware::from_fn_with_state(state.clone(), rate_limit))
202 .layer(Extension(limiter));
203
204 Router::new()
205 .nest("/api/v1", api)
206 .nest("/api/v1/auth", auth)
207 .merge(SwaggerUi::new("/docs").url("/openapi.json", ApiDoc::openapi()))
208 .layer(middleware::from_fn(add_hsts_header))
214 .with_state(state)
215}
216
217async fn add_hsts_header(
220 request: axum::extract::Request,
221 next: middleware::Next,
222) -> axum::response::Response {
223 let mut response = next.run(request).await;
224 response.headers_mut().insert(
225 axum::http::header::STRICT_TRANSPORT_SECURITY,
226 axum::http::HeaderValue::from_static("max-age=31536000; includeSubDomains"),
227 );
228 response
229}
230
231fn build_ws_routes() -> Router<Arc<ServerState>> {
234 Router::new()
235 .route("/nodes/{xname}/console", get(handlers::console_node_ws))
236 .route(
237 "/sessions/{name}/console",
238 get(handlers::console_session_ws),
239 )
240}