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(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 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
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 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
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 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
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 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
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 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
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 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
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 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
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 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
571) -> Response {
572 let client_token = match client_auth_info.bearer_token {
574 Some(val) => val,
575 None => {
576 error!("Bearer Authentication Not Provided");
577 return WebError::OAuth2(Oauth2Error::AuthenticationRequired).into_response();
578 }
579 };
580
581 let res = state
582 .qe_r_ref
583 .handle_oauth2_openid_userinfo(client_id, client_token, kopid.eventid)
584 .await;
585
586 match res {
587 Ok(uir) => (
588 StatusCode::OK,
589 [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
590 Json(uir),
591 )
592 .into_response(),
593 Err(e) => WebError::OAuth2(e).into_response(),
594 }
595}
596
597pub async fn oauth2_openid_publickey_get(
598 State(state): State<ServerState>,
599 Path(client_id): Path<String>,
600 Extension(kopid): Extension<KOpId>,
601) -> Response {
602 let res = state
603 .qe_r_ref
604 .handle_oauth2_openid_publickey(client_id, kopid.eventid)
605 .await
606 .map(Json::from)
607 .map_err(WebError::from);
608
609 match res {
610 Ok(jsn) => (StatusCode::OK, [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")], jsn).into_response(),
611 Err(web_err) => web_err.response_with_access_control_origin_header(),
612 }
613}
614
615pub async fn oauth2_token_introspect_post(
618 State(state): State<ServerState>,
619 Extension(kopid): Extension<KOpId>,
620 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
621 Form(intr_req): Form<AccessTokenIntrospectRequest>,
622) -> impl IntoResponse {
623 request_trace!("Introspect Request - {:?}", intr_req);
624 let res = state
625 .qe_r_ref
626 .handle_oauth2_token_introspect(client_auth_info, intr_req, kopid.eventid)
627 .await;
628
629 match res {
630 Ok(atr) => {
631 let body = match serde_json::to_string(&atr) {
632 Ok(val) => val,
633 Err(e) => {
634 admin_warn!("Failed to serialize introspect response: original_data=\"{:?}\" serialization_error=\"{:?}\"", atr, e);
635 format!("{:?}", atr)
636 }
637 };
638 #[allow(clippy::unwrap_used)]
639 Response::builder()
640 .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
641 .header(CONTENT_TYPE, APPLICATION_JSON)
642 .body(Body::from(body))
643 .unwrap()
644 }
645 Err(Oauth2Error::AuthenticationRequired) => {
646 #[allow(clippy::expect_used)]
648 Response::builder()
649 .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
650 .status(StatusCode::UNAUTHORIZED)
651 .body(Body::empty())
652 .expect("Failed to generate an unauthorized response")
653 }
654 Err(e) => {
655 let err = ErrorResponse {
657 error: e.to_string(),
658 ..Default::default()
659 };
660
661 let body = match serde_json::to_string(&err) {
662 Ok(val) => val,
663 Err(e) => {
664 format!("{:?}", e)
665 }
666 };
667 #[allow(clippy::expect_used)]
668 Response::builder()
669 .status(StatusCode::BAD_REQUEST)
670 .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*")
671 .body(Body::from(body))
672 .expect("Failed to generate an error response")
673 }
674 }
675}
676
677pub async fn oauth2_token_revoke_post(
680 State(state): State<ServerState>,
681 Extension(kopid): Extension<KOpId>,
682 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
683 Form(intr_req): Form<TokenRevokeRequest>,
684) -> impl IntoResponse {
685 request_trace!("Revoke Request - {:?}", intr_req);
686
687 let res = state
688 .qe_w_ref
689 .handle_oauth2_token_revoke(client_auth_info, intr_req, kopid.eventid)
690 .await;
691
692 match res {
693 Ok(()) => (StatusCode::OK, [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")], "").into_response(),
694 Err(Oauth2Error::AuthenticationRequired) => {
695 (
697 StatusCode::UNAUTHORIZED,
698 [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
699 "",
700 )
701 .into_response()
702 }
703 Err(e) => {
704 let err = ErrorResponse {
706 error: e.to_string(),
707 ..Default::default()
708 };
709 (
710 StatusCode::BAD_REQUEST,
711 [(ACCESS_CONTROL_ALLOW_ORIGIN, "*")],
712 serde_json::to_string(&err).unwrap_or("".to_string()),
713 )
714 .into_response()
715 }
716 }
717}
718
719pub async fn oauth2_preflight_options() -> Response {
721 (
722 StatusCode::OK,
723 [
724 (ACCESS_CONTROL_ALLOW_ORIGIN, "*"),
725 (ACCESS_CONTROL_ALLOW_HEADERS, "Authorization"),
726 ],
727 String::new(),
728 )
729 .into_response()
730}
731
732#[serde_as]
733#[derive(Deserialize, Debug, Serialize)]
734pub(crate) struct DeviceFlowForm {
735 client_id: String,
736 #[serde_as(as = "Option<StringWithSeparator::<CommaSeparator, String>>")]
737 scope: Option<BTreeSet<String>>,
738 #[serde(flatten)]
739 extra: BTreeMap<String, String>, }
741
742#[cfg(feature = "dev-oauth2-device-flow")]
744#[instrument(level = "info", skip(state, kopid, client_auth_info))]
745pub(crate) async fn oauth2_authorise_device_post(
746 State(state): State<ServerState>,
747 Extension(kopid): Extension<KOpId>,
748 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
749 Form(form): Form<DeviceFlowForm>,
750) -> Result<Json<DeviceAuthorizationResponse>, WebError> {
751 state
752 .qe_w_ref
753 .handle_oauth2_device_flow_start(
754 client_auth_info,
755 &form.client_id,
756 &form.scope,
757 kopid.eventid,
758 )
759 .await
760 .map(Json::from)
761 .map_err(WebError::OAuth2)
762}
763
764pub fn route_setup(state: ServerState) -> Router<ServerState> {
765 let openid_router = Router::new()
767 .route(
770 "/oauth2/openid/:client_id/.well-known/openid-configuration",
771 get(oauth2_openid_discovery_get).options(oauth2_preflight_options),
772 )
773 .route(
774 "/oauth2/openid/:client_id/.well-known/webfinger",
775 get(oauth2_openid_webfinger_get).options(oauth2_preflight_options),
776 )
777 .route(
780 "/oauth2/openid/:client_id/userinfo",
781 get(oauth2_openid_userinfo_get)
782 .post(oauth2_openid_userinfo_get)
783 .options(oauth2_preflight_options),
784 )
785 .route(
788 "/oauth2/openid/:client_id/public_key.jwk",
789 get(oauth2_openid_publickey_get).options(oauth2_preflight_options),
790 )
791 .route(
794 "/oauth2/openid/:client_id/.well-known/oauth-authorization-server",
795 get(oauth2_rfc8414_metadata_get).options(oauth2_preflight_options),
796 )
797 .with_state(state.clone());
798
799 let mut router = Router::new()
800 .route("/oauth2", get(super::v1_oauth2::oauth2_get))
801 .route(
804 OAUTH2_AUTHORISE,
805 post(oauth2_authorise_post).get(oauth2_authorise_get),
806 )
807 .route(
810 OAUTH2_AUTHORISE_PERMIT,
811 post(oauth2_authorise_permit_post).get(oauth2_authorise_permit_get),
812 )
813 .route(
816 OAUTH2_AUTHORISE_REJECT,
817 post(oauth2_authorise_reject_post).get(oauth2_authorise_reject_get),
818 );
819 #[cfg(feature = "dev-oauth2-device-flow")]
822 {
823 router = router.route(OAUTH2_AUTHORISE_DEVICE, post(oauth2_authorise_device_post))
824 }
825 router = router
828 .route(
829 OAUTH2_TOKEN_ENDPOINT,
830 post(oauth2_token_post).options(oauth2_preflight_options),
831 )
832 .route(
835 OAUTH2_TOKEN_INTROSPECT_ENDPOINT,
836 post(oauth2_token_introspect_post),
837 )
838 .route(OAUTH2_TOKEN_REVOKE_ENDPOINT, post(oauth2_token_revoke_post))
839 .merge(openid_router)
840 .with_state(state)
841 .layer(from_fn(super::middleware::caching::dont_cache_me));
842
843 router
844}