1use std::collections::{BTreeMap, BTreeSet};
2
3use super::errors::WebError;
4use super::middleware::KOpId;
5use super::ServerState;
6use crate::https::extractors::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 struct HTTPOauth2Error(Oauth2Error);
48
49impl IntoResponse for HTTPOauth2Error {
50 fn into_response(self) -> Response {
51 let HTTPOauth2Error(error) = self;
52
53 if let Oauth2Error::AuthenticationRequired = error {
54 (
55 StatusCode::UNAUTHORIZED,
56 [
57 (WWW_AUTHENTICATE, "Bearer"),
58 (ACCESS_CONTROL_ALLOW_ORIGIN, "*"),
59 ],
60 )
61 .into_response()
62 } else {
63 let err = ErrorResponse {
64 error: error.to_string(),
65 ..Default::default()
66 };
67
68 let body = match serde_json::to_string(&err) {
69 Ok(val) => val,
70 Err(e) => {
71 admin_warn!("Failed to serialize error response: original_error=\"{:?}\" serialization_error=\"{:?}\"", err, e);
72 format!("{:?}", err)
73 }
74 };
75
76 (
77 StatusCode::BAD_REQUEST,
78 [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
79 body,
80 )
81 .into_response()
82 }
83 }
84}
85
86pub(crate) fn oauth2_id(rs_name: &str) -> Filter<FilterInvalid> {
90 filter_all!(f_and!([
91 f_eq(Attribute::Class, EntryClass::OAuth2ResourceServer.into()),
92 f_eq(Attribute::Name, PartialValue::new_iname(rs_name))
93 ]))
94}
95
96#[utoipa::path(
97 get,
98 path = "/ui/images/oauth2/{rs_name}",
99 operation_id = "oauth2_image_get",
100 responses(
101 (status = 200, description = "Ok", body=&[u8]),
102 (status = 401, description = "Authorization required"),
103 (status = 403, description = "Not Authorized"),
104 ),
105 security(("token_jwt" = [])),
106 tag = "ui",
107)]
108pub(crate) async fn oauth2_image_get(
111 State(state): State<ServerState>,
112 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
113 Path(rs_name): Path<String>,
114) -> Response {
115 let rs_filter = oauth2_id(&rs_name);
116 let res = state
117 .qe_r_ref
118 .handle_oauth2_rs_image_get_image(client_auth_info, rs_filter)
119 .await;
120
121 match res {
122 Ok(Some(image)) => (
123 StatusCode::OK,
124 [(CONTENT_TYPE, image.filetype.as_content_type_str())],
125 image.contents,
126 )
127 .into_response(),
128 Ok(None) => {
129 warn!(?rs_name, "No image set for OAuth2 client");
130 (StatusCode::NOT_FOUND, "").into_response()
131 }
132 Err(err) => WebError::from(err).into_response(),
133 }
134}
135
136#[instrument(level = "debug", skip(state, kopid))]
198pub async fn oauth2_authorise_post(
199 State(state): State<ServerState>,
200 Extension(kopid): Extension<KOpId>,
201 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
202 Json(auth_req): Json<AuthorisationRequest>,
203) -> impl IntoResponse {
204 let mut res = oauth2_authorise(state, auth_req, kopid, client_auth_info)
205 .await
206 .into_response();
207 if res.status() == StatusCode::FOUND {
208 *res.status_mut() = StatusCode::OK;
210 }
211 res
212}
213
214#[instrument(level = "debug", skip(state, kopid))]
215pub async fn oauth2_authorise_get(
216 State(state): State<ServerState>,
217 Extension(kopid): Extension<KOpId>,
218 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
219 Query(auth_req): Query<AuthorisationRequest>,
220) -> impl IntoResponse {
221 oauth2_authorise(state, auth_req, kopid, client_auth_info).await
223}
224
225async fn oauth2_authorise(
226 state: ServerState,
227 auth_req: AuthorisationRequest,
228 kopid: KOpId,
229 client_auth_info: ClientAuthInfo,
230) -> impl IntoResponse {
231 let res: Result<AuthoriseResponse, Oauth2Error> = state
232 .qe_r_ref
233 .handle_oauth2_authorise(client_auth_info, auth_req, kopid.eventid)
234 .await;
235
236 match res {
237 Ok(AuthoriseResponse::ConsentRequested {
238 client_name,
239 scopes,
240 pii_scopes,
241 consent_token,
242 }) => {
243 #[allow(clippy::unwrap_used)]
247 let body = serde_json::to_string(&AuthorisationResponse::ConsentRequested {
248 client_name,
249 scopes,
250 pii_scopes,
251 consent_token,
252 })
253 .unwrap();
254 #[allow(clippy::unwrap_used)]
255 Response::builder()
256 .status(StatusCode::OK)
257 .body(body.into())
258 .unwrap()
259 }
260 Ok(AuthoriseResponse::Permitted(success)) => {
261 #[allow(clippy::unwrap_used)]
264 let body =
265 Body::from(serde_json::to_string(&AuthorisationResponse::Permitted).unwrap());
266 let redirect_uri = success.build_redirect_uri();
267
268 #[allow(clippy::unwrap_used)]
269 Response::builder()
270 .status(StatusCode::FOUND)
271 .header(
272 LOCATION,
273 HeaderValue::from_str(redirect_uri.as_str()).unwrap(),
274 )
275 .header(
277 ACCESS_CONTROL_ALLOW_ORIGIN,
278 HeaderValue::from_str(&redirect_uri.origin().ascii_serialization()).unwrap(),
279 )
280 .body(body)
281 .unwrap()
282 }
283 Ok(AuthoriseResponse::AuthenticationRequired { .. })
284 | Err(Oauth2Error::AuthenticationRequired) => {
285 #[allow(clippy::unwrap_used)]
287 Response::builder()
288 .status(StatusCode::UNAUTHORIZED)
289 .header(WWW_AUTHENTICATE, HeaderValue::from_static("Bearer"))
290 .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
291 .body(Body::empty())
292 .unwrap()
293 }
294 Err(Oauth2Error::AccessDenied) => {
295 #[allow(clippy::expect_used)]
297 Response::builder()
298 .status(StatusCode::FORBIDDEN)
299 .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
300 .body(Body::empty())
301 .expect("Failed to generate a forbidden response")
302 }
303 Err(e) => {
314 admin_error!(
315 "Unable to authorise - Error ID: {:?} error: {}",
316 kopid.eventid,
317 &e.to_string()
318 );
319 #[allow(clippy::expect_used)]
320 Response::builder()
321 .status(StatusCode::BAD_REQUEST)
322 .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
323 .body(Body::empty())
324 .expect("Failed to generate a bad request response")
325 }
326 }
327}
328
329pub async fn oauth2_authorise_permit_post(
330 State(state): State<ServerState>,
331 Extension(kopid): Extension<KOpId>,
332 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
333 Json(consent_req): Json<String>,
334) -> impl IntoResponse {
335 let mut res = oauth2_authorise_permit(state, consent_req, kopid, client_auth_info)
336 .await
337 .into_response();
338 if res.status() == StatusCode::FOUND {
339 *res.status_mut() = StatusCode::OK;
341 }
342 res
343}
344
345#[derive(Serialize, Deserialize, Debug)]
346pub struct ConsentRequestData {
347 token: String,
348}
349
350pub async fn oauth2_authorise_permit_get(
351 State(state): State<ServerState>,
352 Query(token): Query<ConsentRequestData>,
353 Extension(kopid): Extension<KOpId>,
354 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
355) -> impl IntoResponse {
356 oauth2_authorise_permit(state, token.token, kopid, client_auth_info).await
358}
359
360async fn oauth2_authorise_permit(
361 state: ServerState,
362 consent_req: String,
363 kopid: KOpId,
364 client_auth_info: ClientAuthInfo,
365) -> impl IntoResponse {
366 let res = state
367 .qe_w_ref
368 .handle_oauth2_authorise_permit(client_auth_info, consent_req, kopid.eventid)
369 .await;
370
371 match res {
372 Ok(success) => {
373 let redirect_uri = success.build_redirect_uri();
376
377 #[allow(clippy::expect_used)]
378 Response::builder()
379 .status(StatusCode::FOUND)
380 .header(LOCATION, redirect_uri.as_str())
381 .header(
382 ACCESS_CONTROL_ALLOW_ORIGIN,
383 redirect_uri.origin().ascii_serialization(),
384 )
385 .body(Body::empty())
386 .expect("Failed to generate response")
387 }
388 Err(err) => {
389 match err {
390 OperationError::NotAuthenticated => {
391 WebError::from(err).response_with_access_control_origin_header()
392 }
393 _ => {
394 #[allow(clippy::expect_used)]
403 Response::builder()
404 .status(StatusCode::INTERNAL_SERVER_ERROR)
405 .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
406 .body(Body::empty())
407 .expect("Failed to generate error response")
408 }
409 }
410 }
411 }
412}
413
414pub async fn oauth2_authorise_reject_post(
416 State(state): State<ServerState>,
417 Extension(kopid): Extension<KOpId>,
418 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
419 Form(consent_req): Form<ConsentRequestData>,
420) -> Response<Body> {
421 oauth2_authorise_reject(state, consent_req.token, kopid, client_auth_info).await
422}
423
424pub async fn oauth2_authorise_reject_get(
425 State(state): State<ServerState>,
426 Extension(kopid): Extension<KOpId>,
427 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
428 Query(consent_req): Query<ConsentRequestData>,
429) -> Response<Body> {
430 oauth2_authorise_reject(state, consent_req.token, kopid, client_auth_info).await
431}
432
433async fn oauth2_authorise_reject(
437 state: ServerState,
438 consent_req: String,
439 kopid: KOpId,
440 client_auth_info: ClientAuthInfo,
441) -> Response<Body> {
442 let res = state
446 .qe_r_ref
447 .handle_oauth2_authorise_reject(client_auth_info, consent_req, kopid.eventid)
448 .await;
449
450 match res {
451 Ok(reject) => {
452 let redirect_uri = reject.build_redirect_uri();
453
454 #[allow(clippy::unwrap_used)]
455 Response::builder()
456 .header(LOCATION, redirect_uri.as_str())
457 .header(
458 ACCESS_CONTROL_ALLOW_ORIGIN,
459 redirect_uri.origin().ascii_serialization(),
460 )
461 .body(Body::empty())
462 .unwrap()
463 }
465 Err(err) => {
466 match err {
467 OperationError::NotAuthenticated => {
468 WebError::from(err).response_with_access_control_origin_header()
469 }
470 _ => {
471 #[allow(clippy::expect_used)]
476 Response::builder()
477 .status(StatusCode::INTERNAL_SERVER_ERROR)
478 .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
479 .body(Body::empty())
480 .expect("Failed to generate an error response")
481 }
482 }
483 }
484 }
485}
486
487#[axum_macros::debug_handler]
488#[instrument(skip(state, kopid, client_auth_info), level = "DEBUG")]
489pub async fn oauth2_token_post(
490 State(state): State<ServerState>,
491 Extension(kopid): Extension<KOpId>,
492 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
493 Form(tok_req): Form<AccessTokenRequest>,
494) -> impl IntoResponse {
495 match state
502 .qe_w_ref
503 .handle_oauth2_token_exchange(client_auth_info, tok_req, kopid.eventid)
504 .await
505 {
506 Ok(tok_res) => (
507 StatusCode::OK,
508 [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
509 Json(tok_res),
510 )
511 .into_response(),
512 Err(e) => HTTPOauth2Error(e).into_response(),
513 }
514}
515
516pub async fn oauth2_openid_discovery_get(
518 State(state): State<ServerState>,
519 Path(client_id): Path<String>,
520 Extension(kopid): Extension<KOpId>,
521) -> impl IntoResponse {
522 let res = state
523 .qe_r_ref
524 .handle_oauth2_openid_discovery(client_id, kopid.eventid)
525 .await;
526
527 match res {
528 Ok(dsc) => (
529 StatusCode::OK,
530 [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
531 Json(dsc),
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
541#[derive(Deserialize)]
542pub struct Oauth2OpenIdWebfingerQuery {
543 resource: String,
544}
545
546pub async fn oauth2_openid_webfinger_get(
547 State(state): State<ServerState>,
548 Path(client_id): Path<String>,
549 Query(query): Query<Oauth2OpenIdWebfingerQuery>,
550 Extension(kopid): Extension<KOpId>,
551) -> impl IntoResponse {
552 let Oauth2OpenIdWebfingerQuery { resource } = query;
553
554 let cleaned_resource = resource.strip_prefix("acct:").unwrap_or(&resource);
555
556 let res = state
557 .qe_r_ref
558 .handle_oauth2_webfinger_discovery(&client_id, cleaned_resource, kopid.eventid)
559 .await;
560
561 match res {
562 Ok(mut dsc) => (
563 StatusCode::OK,
564 [
565 (ACCESS_CONTROL_ALLOW_ORIGIN, "*"),
566 (CONTENT_TYPE, "application/jrd+json"),
567 ],
568 Json({
569 dsc.subject = resource;
570 dsc
571 }),
572 )
573 .into_response(),
574 Err(e) => {
575 error!(err = ?e, "Unable to access discovery info");
576 WebError::from(e).response_with_access_control_origin_header()
577 }
578 }
579}
580
581pub async fn oauth2_rfc8414_metadata_get(
582 State(state): State<ServerState>,
583 Path(client_id): Path<String>,
584 Extension(kopid): Extension<KOpId>,
585) -> impl IntoResponse {
586 let res = state
587 .qe_r_ref
588 .handle_oauth2_rfc8414_metadata(client_id, kopid.eventid)
589 .await;
590
591 match res {
592 Ok(dsc) => (
593 StatusCode::OK,
594 [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
595 Json(dsc),
596 )
597 .into_response(),
598 Err(e) => {
599 error!(err = ?e, "Unable to access discovery info");
600 WebError::from(e).response_with_access_control_origin_header()
601 }
602 }
603}
604
605#[debug_handler]
606pub async fn oauth2_openid_userinfo_get(
607 State(state): State<ServerState>,
608 Path(client_id): Path<String>,
609 Extension(kopid): Extension<KOpId>,
610 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
611) -> Response {
612 let client_token = match client_auth_info.bearer_token {
614 Some(val) => val,
615 None => {
616 error!("Bearer Authentication Not Provided");
617 return HTTPOauth2Error(Oauth2Error::AuthenticationRequired).into_response();
618 }
619 };
620
621 let res = state
622 .qe_r_ref
623 .handle_oauth2_openid_userinfo(client_id, client_token, kopid.eventid)
624 .await;
625
626 match res {
627 Ok(uir) => (
628 StatusCode::OK,
629 [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
630 Json(uir),
631 )
632 .into_response(),
633 Err(e) => HTTPOauth2Error(e).into_response(),
634 }
635}
636
637pub async fn oauth2_openid_publickey_get(
638 State(state): State<ServerState>,
639 Path(client_id): Path<String>,
640 Extension(kopid): Extension<KOpId>,
641) -> Response {
642 let res = state
643 .qe_r_ref
644 .handle_oauth2_openid_publickey(client_id, kopid.eventid)
645 .await
646 .map(Json::from)
647 .map_err(WebError::from);
648
649 match res {
650 Ok(jsn) => (StatusCode::OK, [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")], jsn).into_response(),
651 Err(web_err) => web_err.response_with_access_control_origin_header(),
652 }
653}
654
655pub async fn oauth2_token_introspect_post(
658 State(state): State<ServerState>,
659 Extension(kopid): Extension<KOpId>,
660 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
661 Form(intr_req): Form<AccessTokenIntrospectRequest>,
662) -> impl IntoResponse {
663 request_trace!("Introspect Request - {:?}", intr_req);
664 let res = state
665 .qe_r_ref
666 .handle_oauth2_token_introspect(client_auth_info, intr_req, kopid.eventid)
667 .await;
668
669 match res {
670 Ok(atr) => {
671 let body = match serde_json::to_string(&atr) {
672 Ok(val) => val,
673 Err(e) => {
674 admin_warn!("Failed to serialize introspect response: original_data=\"{:?}\" serialization_error=\"{:?}\"", atr, e);
675 format!("{:?}", atr)
676 }
677 };
678 #[allow(clippy::unwrap_used)]
679 Response::builder()
680 .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
681 .header(CONTENT_TYPE, APPLICATION_JSON)
682 .body(Body::from(body))
683 .unwrap()
684 }
685 Err(Oauth2Error::AuthenticationRequired) => {
686 #[allow(clippy::expect_used)]
688 Response::builder()
689 .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
690 .status(StatusCode::UNAUTHORIZED)
691 .body(Body::empty())
692 .expect("Failed to generate an unauthorized response")
693 }
694 Err(e) => {
695 let err = ErrorResponse {
697 error: e.to_string(),
698 ..Default::default()
699 };
700
701 let body = match serde_json::to_string(&err) {
702 Ok(val) => val,
703 Err(e) => {
704 format!("{:?}", e)
705 }
706 };
707 #[allow(clippy::expect_used)]
708 Response::builder()
709 .status(StatusCode::BAD_REQUEST)
710 .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
711 .body(Body::from(body))
712 .expect("Failed to generate an error response")
713 }
714 }
715}
716
717pub async fn oauth2_token_revoke_post(
720 State(state): State<ServerState>,
721 Extension(kopid): Extension<KOpId>,
722 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
723 Form(intr_req): Form<TokenRevokeRequest>,
724) -> impl IntoResponse {
725 request_trace!("Revoke Request - {:?}", intr_req);
726
727 let res = state
728 .qe_w_ref
729 .handle_oauth2_token_revoke(client_auth_info, intr_req, kopid.eventid)
730 .await;
731
732 match res {
733 Ok(()) => (StatusCode::OK, [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")], "").into_response(),
734 Err(Oauth2Error::AuthenticationRequired) => {
735 (
737 StatusCode::UNAUTHORIZED,
738 [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
739 "",
740 )
741 .into_response()
742 }
743 Err(e) => {
744 let err = ErrorResponse {
746 error: e.to_string(),
747 ..Default::default()
748 };
749 (
750 StatusCode::BAD_REQUEST,
751 [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
752 serde_json::to_string(&err).unwrap_or("".to_string()),
753 )
754 .into_response()
755 }
756 }
757}
758
759pub async fn oauth2_preflight_options() -> Response {
761 (
762 StatusCode::OK,
763 [
764 (ACCESS_CONTROL_ALLOW_ORIGIN, "*"),
765 (ACCESS_CONTROL_ALLOW_HEADERS, "Authorization"),
766 ],
767 String::new(),
768 )
769 .into_response()
770}
771
772#[serde_as]
773#[derive(Deserialize, Debug, Serialize)]
774pub(crate) struct DeviceFlowForm {
775 client_id: String,
776 #[serde_as(as = "Option<StringWithSeparator::<CommaSeparator, String>>")]
777 scope: Option<BTreeSet<String>>,
778 #[serde(flatten)]
779 extra: BTreeMap<String, String>, }
781
782#[cfg(feature = "dev-oauth2-device-flow")]
784#[instrument(level = "info", skip(state, kopid, client_auth_info))]
785pub(crate) async fn oauth2_authorise_device_post(
786 State(state): State<ServerState>,
787 Extension(kopid): Extension<KOpId>,
788 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
789 Form(form): Form<DeviceFlowForm>,
790) -> Result<Json<DeviceAuthorizationResponse>, HTTPOauth2Error> {
791 state
792 .qe_w_ref
793 .handle_oauth2_device_flow_start(
794 client_auth_info,
795 &form.client_id,
796 &form.scope,
797 kopid.eventid,
798 )
799 .await
800 .map(Json::from)
801 .map_err(HTTPOauth2Error)
802}
803
804pub fn route_setup(state: ServerState) -> Router<ServerState> {
805 let openid_router = Router::new()
807 .route(
810 "/oauth2/openid/:client_id/.well-known/openid-configuration",
811 get(oauth2_openid_discovery_get).options(oauth2_preflight_options),
812 )
813 .route(
814 "/oauth2/openid/:client_id/.well-known/webfinger",
815 get(oauth2_openid_webfinger_get).options(oauth2_preflight_options),
816 )
817 .route(
820 "/oauth2/openid/:client_id/userinfo",
821 get(oauth2_openid_userinfo_get)
822 .post(oauth2_openid_userinfo_get)
823 .options(oauth2_preflight_options),
824 )
825 .route(
828 "/oauth2/openid/:client_id/public_key.jwk",
829 get(oauth2_openid_publickey_get).options(oauth2_preflight_options),
830 )
831 .route(
834 "/oauth2/openid/:client_id/.well-known/oauth-authorization-server",
835 get(oauth2_rfc8414_metadata_get).options(oauth2_preflight_options),
836 )
837 .with_state(state.clone());
838
839 let mut router = Router::new()
840 .route("/oauth2", get(super::v1_oauth2::oauth2_get))
841 .route(
844 OAUTH2_AUTHORISE,
845 post(oauth2_authorise_post).get(oauth2_authorise_get),
846 )
847 .route(
850 OAUTH2_AUTHORISE_PERMIT,
851 post(oauth2_authorise_permit_post).get(oauth2_authorise_permit_get),
852 )
853 .route(
856 OAUTH2_AUTHORISE_REJECT,
857 post(oauth2_authorise_reject_post).get(oauth2_authorise_reject_get),
858 );
859 #[cfg(feature = "dev-oauth2-device-flow")]
862 {
863 router = router.route(OAUTH2_AUTHORISE_DEVICE, post(oauth2_authorise_device_post))
864 }
865 router = router
868 .route(
869 OAUTH2_TOKEN_ENDPOINT,
870 post(oauth2_token_post).options(oauth2_preflight_options),
871 )
872 .route(
875 OAUTH2_TOKEN_INTROSPECT_ENDPOINT,
876 post(oauth2_token_introspect_post),
877 )
878 .route(OAUTH2_TOKEN_REVOKE_ENDPOINT, post(oauth2_token_revoke_post))
879 .merge(openid_router)
880 .with_state(state)
881 .layer(from_fn(super::middleware::caching::dont_cache_me));
882
883 router
884}