1use std::collections::{BTreeMap, BTreeSet};
2
3use super::errors::WebError;
4use super::middleware::KOpId;
5use super::ServerState;
6use crate::https::extractors::{AuthorisationHeaders, VerifiedClientInformation};
7use axum::{
8    body::Body,
9    extract::{Path, Query, State},
10    http::{
11        header::{
12            ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_ORIGIN, CONTENT_TYPE, LOCATION,
13            WWW_AUTHENTICATE,
14        },
15        HeaderValue, StatusCode,
16    },
17    middleware::from_fn,
18    response::{IntoResponse, Response},
19    routing::{get, post},
20    Extension, Form, Json, Router,
21};
22use axum_macros::debug_handler;
23use kanidm_proto::constants::uri::{
24    OAUTH2_AUTHORISE, OAUTH2_AUTHORISE_PERMIT, OAUTH2_AUTHORISE_REJECT,
25};
26use kanidm_proto::constants::APPLICATION_JSON;
27use kanidm_proto::oauth2::AuthorisationResponse;
28
29#[cfg(feature = "dev-oauth2-device-flow")]
30use kanidm_proto::oauth2::DeviceAuthorizationResponse;
31use kanidmd_lib::idm::oauth2::{
32    AccessTokenIntrospectRequest, AccessTokenRequest, AuthorisationRequest, AuthoriseResponse,
33    ErrorResponse, Oauth2Error, TokenRevokeRequest,
34};
35use kanidmd_lib::prelude::f_eq;
36use kanidmd_lib::prelude::*;
37use kanidmd_lib::value::PartialValue;
38use serde::{Deserialize, Serialize};
39use serde_with::formats::CommaSeparator;
40use serde_with::{serde_as, StringWithSeparator};
41
42#[cfg(feature = "dev-oauth2-device-flow")]
43use uri::OAUTH2_AUTHORISE_DEVICE;
44use uri::{OAUTH2_TOKEN_ENDPOINT, OAUTH2_TOKEN_INTROSPECT_ENDPOINT, OAUTH2_TOKEN_REVOKE_ENDPOINT};
45
46pub(crate) fn oauth2_id(rs_name: &str) -> Filter<FilterInvalid> {
50    filter_all!(f_and!([
51        f_eq(Attribute::Class, EntryClass::OAuth2ResourceServer.into()),
52        f_eq(Attribute::Name, PartialValue::new_iname(rs_name))
53    ]))
54}
55
56#[utoipa::path(
57    get,
58    path = "/ui/images/oauth2/{rs_name}",
59    operation_id = "oauth2_image_get",
60    responses(
61        (status = 200, description = "Ok", body=&[u8]),
62        (status = 401, description = "Authorization required"),
63        (status = 403, description = "Not Authorized"),
64    ),
65    security(("token_jwt" = [])),
66    tag = "ui",
67)]
68pub(crate) async fn oauth2_image_get(
71    State(state): State<ServerState>,
72    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
73    Path(rs_name): Path<String>,
74) -> Response {
75    let rs_filter = oauth2_id(&rs_name);
76    let res = state
77        .qe_r_ref
78        .handle_oauth2_rs_image_get_image(client_auth_info, rs_filter)
79        .await;
80
81    match res {
82        Ok(Some(image)) => (
83            StatusCode::OK,
84            [(CONTENT_TYPE, image.filetype.as_content_type_str())],
85            image.contents,
86        )
87            .into_response(),
88        Ok(None) => {
89            warn!(?rs_name, "No image set for OAuth2 client");
90            (StatusCode::NOT_FOUND, "").into_response()
91        }
92        Err(err) => WebError::from(err).into_response(),
93    }
94}
95
96#[instrument(level = "debug", skip(state, kopid))]
158pub async fn oauth2_authorise_post(
159    State(state): State<ServerState>,
160    Extension(kopid): Extension<KOpId>,
161    AuthorisationHeaders(client_auth_info): AuthorisationHeaders,
162    Json(auth_req): Json<AuthorisationRequest>,
163) -> impl IntoResponse {
164    let mut res = oauth2_authorise(state, auth_req, kopid, client_auth_info)
165        .await
166        .into_response();
167    if res.status() == StatusCode::FOUND {
168        *res.status_mut() = StatusCode::OK;
170    }
171    res
172}
173
174#[instrument(level = "debug", skip(state, kopid))]
175pub async fn oauth2_authorise_get(
176    State(state): State<ServerState>,
177    Extension(kopid): Extension<KOpId>,
178    AuthorisationHeaders(client_auth_info): AuthorisationHeaders,
179    Query(auth_req): Query<AuthorisationRequest>,
180) -> impl IntoResponse {
181    oauth2_authorise(state, auth_req, kopid, client_auth_info).await
183}
184
185async fn oauth2_authorise(
186    state: ServerState,
187    auth_req: AuthorisationRequest,
188    kopid: KOpId,
189    client_auth_info: ClientAuthInfo,
190) -> impl IntoResponse {
191    let res: Result<AuthoriseResponse, Oauth2Error> = state
192        .qe_r_ref
193        .handle_oauth2_authorise(client_auth_info, auth_req, kopid.eventid)
194        .await;
195
196    match res {
197        Ok(AuthoriseResponse::ConsentRequested {
198            client_name,
199            scopes,
200            pii_scopes,
201            consent_token,
202        }) => {
203            #[allow(clippy::unwrap_used)]
207            let body = serde_json::to_string(&AuthorisationResponse::ConsentRequested {
208                client_name,
209                scopes,
210                pii_scopes,
211                consent_token,
212            })
213            .unwrap();
214            #[allow(clippy::unwrap_used)]
215            Response::builder()
216                .status(StatusCode::OK)
217                .body(body.into())
218                .unwrap()
219        }
220        Ok(AuthoriseResponse::Permitted(success)) => {
221            #[allow(clippy::unwrap_used)]
224            let body =
225                Body::from(serde_json::to_string(&AuthorisationResponse::Permitted).unwrap());
226            let redirect_uri = success.build_redirect_uri();
227
228            #[allow(clippy::unwrap_used)]
229            Response::builder()
230                .status(StatusCode::FOUND)
231                .header(
232                    LOCATION,
233                    HeaderValue::from_str(redirect_uri.as_str()).unwrap(),
234                )
235                .header(
237                    ACCESS_CONTROL_ALLOW_ORIGIN,
238                    HeaderValue::from_str(&redirect_uri.origin().ascii_serialization()).unwrap(),
239                )
240                .body(body)
241                .unwrap()
242        }
243        Ok(AuthoriseResponse::AuthenticationRequired { .. })
244        | Err(Oauth2Error::AuthenticationRequired) => {
245            #[allow(clippy::unwrap_used)]
247            Response::builder()
248                .status(StatusCode::UNAUTHORIZED)
249                .header(WWW_AUTHENTICATE, HeaderValue::from_static("Bearer"))
250                .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
251                .body(Body::empty())
252                .unwrap()
253        }
254        Err(Oauth2Error::AccessDenied) => {
255            #[allow(clippy::expect_used)]
257            Response::builder()
258                .status(StatusCode::FORBIDDEN)
259                .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
260                .body(Body::empty())
261                .expect("Failed to generate a forbidden response")
262        }
263        Err(e) => {
274            admin_error!(
275                "Unable to authorise - Error ID: {:?} error: {}",
276                kopid.eventid,
277                &e.to_string()
278            );
279            #[allow(clippy::expect_used)]
280            Response::builder()
281                .status(StatusCode::BAD_REQUEST)
282                .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
283                .body(Body::empty())
284                .expect("Failed to generate a bad request response")
285        }
286    }
287}
288
289pub async fn oauth2_authorise_permit_post(
290    State(state): State<ServerState>,
291    Extension(kopid): Extension<KOpId>,
292    AuthorisationHeaders(client_auth_info): AuthorisationHeaders,
293    Json(consent_req): Json<String>,
294) -> impl IntoResponse {
295    let mut res = oauth2_authorise_permit(state, consent_req, kopid, client_auth_info)
296        .await
297        .into_response();
298    if res.status() == StatusCode::FOUND {
299        *res.status_mut() = StatusCode::OK;
301    }
302    res
303}
304
305#[derive(Serialize, Deserialize, Debug)]
306pub struct ConsentRequestData {
307    token: String,
308}
309
310pub async fn oauth2_authorise_permit_get(
311    State(state): State<ServerState>,
312    Query(token): Query<ConsentRequestData>,
313    Extension(kopid): Extension<KOpId>,
314    AuthorisationHeaders(client_auth_info): AuthorisationHeaders,
315) -> impl IntoResponse {
316    oauth2_authorise_permit(state, token.token, kopid, client_auth_info).await
318}
319
320async fn oauth2_authorise_permit(
321    state: ServerState,
322    consent_req: String,
323    kopid: KOpId,
324    client_auth_info: ClientAuthInfo,
325) -> impl IntoResponse {
326    let res = state
327        .qe_w_ref
328        .handle_oauth2_authorise_permit(client_auth_info, consent_req, kopid.eventid)
329        .await;
330
331    match res {
332        Ok(success) => {
333            let redirect_uri = success.build_redirect_uri();
336
337            #[allow(clippy::expect_used)]
338            Response::builder()
339                .status(StatusCode::FOUND)
340                .header(LOCATION, redirect_uri.as_str())
341                .header(
342                    ACCESS_CONTROL_ALLOW_ORIGIN,
343                    redirect_uri.origin().ascii_serialization(),
344                )
345                .body(Body::empty())
346                .expect("Failed to generate response")
347        }
348        Err(err) => {
349            match err {
350                OperationError::NotAuthenticated => {
351                    WebError::from(err).response_with_access_control_origin_header()
352                }
353                _ => {
354                    #[allow(clippy::expect_used)]
363                    Response::builder()
364                        .status(StatusCode::INTERNAL_SERVER_ERROR)
365                        .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
366                        .body(Body::empty())
367                        .expect("Failed to generate error response")
368                }
369            }
370        }
371    }
372}
373
374pub async fn oauth2_authorise_reject_post(
376    State(state): State<ServerState>,
377    Extension(kopid): Extension<KOpId>,
378    AuthorisationHeaders(client_auth_info): AuthorisationHeaders,
379    Form(consent_req): Form<ConsentRequestData>,
380) -> Response<Body> {
381    oauth2_authorise_reject(state, consent_req.token, kopid, client_auth_info).await
382}
383
384pub async fn oauth2_authorise_reject_get(
385    State(state): State<ServerState>,
386    Extension(kopid): Extension<KOpId>,
387    AuthorisationHeaders(client_auth_info): AuthorisationHeaders,
388    Query(consent_req): Query<ConsentRequestData>,
389) -> Response<Body> {
390    oauth2_authorise_reject(state, consent_req.token, kopid, client_auth_info).await
391}
392
393async fn oauth2_authorise_reject(
397    state: ServerState,
398    consent_req: String,
399    kopid: KOpId,
400    client_auth_info: ClientAuthInfo,
401) -> Response<Body> {
402    let res = state
406        .qe_r_ref
407        .handle_oauth2_authorise_reject(client_auth_info, consent_req, kopid.eventid)
408        .await;
409
410    match res {
411        Ok(reject) => {
412            let redirect_uri = reject.build_redirect_uri();
413
414            #[allow(clippy::unwrap_used)]
415            Response::builder()
416                .header(LOCATION, redirect_uri.as_str())
417                .header(
418                    ACCESS_CONTROL_ALLOW_ORIGIN,
419                    redirect_uri.origin().ascii_serialization(),
420                )
421                .body(Body::empty())
422                .unwrap()
423            }
425        Err(err) => {
426            match err {
427                OperationError::NotAuthenticated => {
428                    WebError::from(err).response_with_access_control_origin_header()
429                }
430                _ => {
431                    #[allow(clippy::expect_used)]
436                    Response::builder()
437                        .status(StatusCode::INTERNAL_SERVER_ERROR)
438                        .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
439                        .body(Body::empty())
440                        .expect("Failed to generate an error response")
441                }
442            }
443        }
444    }
445}
446
447#[axum_macros::debug_handler]
448#[instrument(skip(state, kopid, client_auth_info), level = "DEBUG")]
449pub async fn oauth2_token_post(
450    State(state): State<ServerState>,
451    Extension(kopid): Extension<KOpId>,
452    AuthorisationHeaders(client_auth_info): AuthorisationHeaders,
453    Form(tok_req): Form<AccessTokenRequest>,
454) -> impl IntoResponse {
455    match state
462        .qe_w_ref
463        .handle_oauth2_token_exchange(client_auth_info, tok_req, kopid.eventid)
464        .await
465    {
466        Ok(tok_res) => (
467            StatusCode::OK,
468            [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
469            Json(tok_res),
470        )
471            .into_response(),
472        Err(e) => WebError::OAuth2(e).into_response(),
473    }
474}
475
476pub async fn oauth2_openid_discovery_get(
478    State(state): State<ServerState>,
479    Path(client_id): Path<String>,
480    Extension(kopid): Extension<KOpId>,
481) -> impl IntoResponse {
482    let res = state
483        .qe_r_ref
484        .handle_oauth2_openid_discovery(client_id, kopid.eventid)
485        .await;
486
487    match res {
488        Ok(dsc) => (
489            StatusCode::OK,
490            [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
491            Json(dsc),
492        )
493            .into_response(),
494        Err(e) => {
495            error!(err = ?e, "Unable to access discovery info");
496            WebError::from(e).response_with_access_control_origin_header()
497        }
498    }
499}
500
501#[derive(Deserialize)]
502pub struct Oauth2OpenIdWebfingerQuery {
503    resource: String,
504}
505
506pub async fn oauth2_openid_webfinger_get(
507    State(state): State<ServerState>,
508    Path(client_id): Path<String>,
509    Query(query): Query<Oauth2OpenIdWebfingerQuery>,
510    Extension(kopid): Extension<KOpId>,
511) -> impl IntoResponse {
512    let Oauth2OpenIdWebfingerQuery { resource } = query;
513
514    let cleaned_resource = resource.strip_prefix("acct:").unwrap_or(&resource);
515
516    let res = state
517        .qe_r_ref
518        .handle_oauth2_webfinger_discovery(&client_id, cleaned_resource, kopid.eventid)
519        .await;
520
521    match res {
522        Ok(mut dsc) => (
523            StatusCode::OK,
524            [
525                (ACCESS_CONTROL_ALLOW_ORIGIN, "*"),
526                (CONTENT_TYPE, "application/jrd+json"),
527            ],
528            Json({
529                dsc.subject = resource;
530                dsc
531            }),
532        )
533            .into_response(),
534        Err(e) => {
535            error!(err = ?e, "Unable to access discovery info");
536            WebError::from(e).response_with_access_control_origin_header()
537        }
538    }
539}
540
541pub async fn oauth2_rfc8414_metadata_get(
542    State(state): State<ServerState>,
543    Path(client_id): Path<String>,
544    Extension(kopid): Extension<KOpId>,
545) -> impl IntoResponse {
546    let res = state
547        .qe_r_ref
548        .handle_oauth2_rfc8414_metadata(client_id, kopid.eventid)
549        .await;
550
551    match res {
552        Ok(dsc) => (
553            StatusCode::OK,
554            [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
555            Json(dsc),
556        )
557            .into_response(),
558        Err(e) => {
559            error!(err = ?e, "Unable to access discovery info");
560            WebError::from(e).response_with_access_control_origin_header()
561        }
562    }
563}
564
565#[debug_handler]
566pub async fn oauth2_openid_userinfo_get(
567    State(state): State<ServerState>,
568    Path(client_id): Path<String>,
569    Extension(kopid): Extension<KOpId>,
570    AuthorisationHeaders(client_auth_info): AuthorisationHeaders,
571) -> Response {
572    let Some(client_token) = client_auth_info.bearer_token() else {
574        error!("Bearer Authentication Not Provided");
575        return WebError::OAuth2(Oauth2Error::AuthenticationRequired).into_response();
576    };
577
578    let res = state
579        .qe_r_ref
580        .handle_oauth2_openid_userinfo(client_id, client_token, kopid.eventid)
581        .await;
582
583    match res {
584        Ok(uir) => (
585            StatusCode::OK,
586            [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
587            Json(uir),
588        )
589            .into_response(),
590        Err(e) => WebError::OAuth2(e).into_response(),
591    }
592}
593
594pub async fn oauth2_openid_publickey_get(
595    State(state): State<ServerState>,
596    Path(client_id): Path<String>,
597    Extension(kopid): Extension<KOpId>,
598) -> Response {
599    let res = state
600        .qe_r_ref
601        .handle_oauth2_openid_publickey(client_id, kopid.eventid)
602        .await
603        .map(Json::from)
604        .map_err(WebError::from);
605
606    match res {
607        Ok(jsn) => (StatusCode::OK, [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")], jsn).into_response(),
608        Err(web_err) => web_err.response_with_access_control_origin_header(),
609    }
610}
611
612pub async fn oauth2_token_introspect_post(
615    State(state): State<ServerState>,
616    Extension(kopid): Extension<KOpId>,
617    AuthorisationHeaders(client_auth_info): AuthorisationHeaders,
618    Form(intr_req): Form<AccessTokenIntrospectRequest>,
619) -> impl IntoResponse {
620    request_trace!("Introspect Request - {:?}", intr_req);
621    let res = state
622        .qe_r_ref
623        .handle_oauth2_token_introspect(client_auth_info, intr_req, kopid.eventid)
624        .await;
625
626    match res {
627        Ok(atr) => {
628            let body = match serde_json::to_string(&atr) {
629                Ok(val) => val,
630                Err(e) => {
631                    admin_warn!("Failed to serialize introspect response: original_data=\"{:?}\" serialization_error=\"{:?}\"", atr, e);
632                    format!("{atr:?}")
633                }
634            };
635            #[allow(clippy::unwrap_used)]
636            Response::builder()
637                .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
638                .header(CONTENT_TYPE, APPLICATION_JSON)
639                .body(Body::from(body))
640                .unwrap()
641        }
642        Err(Oauth2Error::AuthenticationRequired) => {
643            #[allow(clippy::expect_used)]
645            Response::builder()
646                .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
647                .status(StatusCode::UNAUTHORIZED)
648                .body(Body::empty())
649                .expect("Failed to generate an unauthorized response")
650        }
651        Err(e) => {
652            let err = ErrorResponse {
654                error: e.to_string(),
655                ..Default::default()
656            };
657
658            let body = match serde_json::to_string(&err) {
659                Ok(val) => val,
660                Err(e) => {
661                    format!("{e:?}")
662                }
663            };
664            #[allow(clippy::expect_used)]
665            Response::builder()
666                .status(StatusCode::BAD_REQUEST)
667                .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
668                .body(Body::from(body))
669                .expect("Failed to generate an error response")
670        }
671    }
672}
673
674pub async fn oauth2_token_revoke_post(
677    State(state): State<ServerState>,
678    Extension(kopid): Extension<KOpId>,
679    AuthorisationHeaders(client_auth_info): AuthorisationHeaders,
680    Form(intr_req): Form<TokenRevokeRequest>,
681) -> impl IntoResponse {
682    request_trace!("Revoke Request - {:?}", intr_req);
683
684    let res = state
685        .qe_w_ref
686        .handle_oauth2_token_revoke(client_auth_info, intr_req, kopid.eventid)
687        .await;
688
689    match res {
690        Ok(()) => (StatusCode::OK, [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")], "").into_response(),
691        Err(Oauth2Error::AuthenticationRequired) => {
692            (
694                StatusCode::UNAUTHORIZED,
695                [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
696                "",
697            )
698                .into_response()
699        }
700        Err(e) => {
701            let err = ErrorResponse {
703                error: e.to_string(),
704                ..Default::default()
705            };
706            (
707                StatusCode::BAD_REQUEST,
708                [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
709                serde_json::to_string(&err).unwrap_or("".to_string()),
710            )
711                .into_response()
712        }
713    }
714}
715
716pub async fn oauth2_preflight_options() -> Response {
718    (
719        StatusCode::OK,
720        [
721            (ACCESS_CONTROL_ALLOW_ORIGIN, "*"),
722            (ACCESS_CONTROL_ALLOW_HEADERS, "Authorization"),
723        ],
724        String::new(),
725    )
726        .into_response()
727}
728
729#[allow(dead_code)]
731#[serde_as]
732#[derive(Deserialize, Debug, Serialize)]
733pub(crate) struct DeviceFlowForm {
734    client_id: String,
735    #[serde_as(as = "Option<StringWithSeparator::<CommaSeparator, String>>")]
736    scope: Option<BTreeSet<String>>,
737    #[serde(flatten)]
738    extra: BTreeMap<String, String>, }
740
741#[cfg(feature = "dev-oauth2-device-flow")]
743#[instrument(level = "info", skip(state, kopid, client_auth_info))]
744pub(crate) async fn oauth2_authorise_device_post(
745    State(state): State<ServerState>,
746    Extension(kopid): Extension<KOpId>,
747    AuthorisationHeaders(client_auth_info): AuthorisationHeaders,
748    Form(form): Form<DeviceFlowForm>,
749) -> Result<Json<DeviceAuthorizationResponse>, WebError> {
750    state
751        .qe_w_ref
752        .handle_oauth2_device_flow_start(
753            client_auth_info,
754            &form.client_id,
755            &form.scope,
756            kopid.eventid,
757        )
758        .await
759        .map(Json::from)
760        .map_err(WebError::OAuth2)
761}
762
763pub fn route_setup(state: ServerState) -> Router<ServerState> {
764    let openid_router = Router::new()
766        .route(
769            "/oauth2/openid/{client_id}/.well-known/openid-configuration",
770            get(oauth2_openid_discovery_get).options(oauth2_preflight_options),
771        )
772        .route(
773            "/oauth2/openid/{client_id}/.well-known/webfinger",
774            get(oauth2_openid_webfinger_get).options(oauth2_preflight_options),
775        )
776        .route(
779            "/oauth2/openid/{client_id}/userinfo",
780            get(oauth2_openid_userinfo_get)
781                .post(oauth2_openid_userinfo_get)
782                .options(oauth2_preflight_options),
783        )
784        .route(
787            "/oauth2/openid/{client_id}/public_key.jwk",
788            get(oauth2_openid_publickey_get).options(oauth2_preflight_options),
789        )
790        .route(
793            "/oauth2/openid/{client_id}/.well-known/oauth-authorization-server",
794            get(oauth2_rfc8414_metadata_get).options(oauth2_preflight_options),
795        )
796        .with_state(state.clone());
797
798    let mut router = Router::new()
799        .route("/oauth2", get(super::v1_oauth2::oauth2_get))
800        .route(
803            OAUTH2_AUTHORISE,
804            post(oauth2_authorise_post).get(oauth2_authorise_get),
805        )
806        .route(
809            OAUTH2_AUTHORISE_PERMIT,
810            post(oauth2_authorise_permit_post).get(oauth2_authorise_permit_get),
811        )
812        .route(
815            OAUTH2_AUTHORISE_REJECT,
816            post(oauth2_authorise_reject_post).get(oauth2_authorise_reject_get),
817        );
818    #[cfg(feature = "dev-oauth2-device-flow")]
821    {
822        router = router.route(OAUTH2_AUTHORISE_DEVICE, post(oauth2_authorise_device_post))
823    }
824    router = router
827        .route(
828            OAUTH2_TOKEN_ENDPOINT,
829            post(oauth2_token_post).options(oauth2_preflight_options),
830        )
831        .route(
834            OAUTH2_TOKEN_INTROSPECT_ENDPOINT,
835            post(oauth2_token_introspect_post),
836        )
837        .route(OAUTH2_TOKEN_REVOKE_ENDPOINT, post(oauth2_token_revoke_post))
838        .merge(openid_router)
839        .with_state(state)
840        .layer(from_fn(super::middleware::caching::dont_cache_me));
841
842    router
843}