kanidmd_core/https/
v1.rs

1//! The V1 API things!
2
3use super::errors::WebError;
4use super::middleware::caching::{cache_me_short, dont_cache_me};
5use super::middleware::KOpId;
6use super::ServerState;
7use crate::https::apidocs::response_schema::{ApiResponseWithout200, DefaultApiResponse};
8use crate::https::extractors::{ClientConnInfo, VerifiedClientInformation};
9use axum::extract::{Path, State};
10use axum::http::{HeaderMap, HeaderValue};
11use axum::middleware::from_fn;
12use axum::response::{IntoResponse, Response};
13use axum::routing::{delete, get, post, put};
14use axum::{Extension, Json, Router};
15use axum_extra::extract::cookie::{Cookie, CookieJar, SameSite};
16use compact_jwt::{Jwk, Jws, JwsSigner};
17use kanidm_proto::constants::uri::V1_AUTH_VALID;
18use kanidm_proto::internal::{
19    ApiToken, AppLink, CUIntentToken, CURequest, CUSessionToken, CUStatus, CreateRequest,
20    CredentialStatus, DeleteRequest, IdentifyUserRequest, IdentifyUserResponse, ModifyRequest,
21    RadiusAuthToken, SearchRequest, SearchResponse, UserAuthToken, COOKIE_AUTH_SESSION_ID,
22    COOKIE_BEARER_TOKEN,
23};
24use kanidm_proto::v1::{
25    AccountUnixExtend, ApiTokenGenerate, AuthIssueSession, AuthRequest, AuthResponse,
26    AuthState as ProtoAuthState, Entry as ProtoEntry, GroupUnixExtend, SingleStringRequest,
27    UatStatus, UnixGroupToken, UnixUserToken, WhoamiResponse,
28};
29use kanidmd_lib::idm::event::AuthResult;
30use kanidmd_lib::idm::AuthState;
31use kanidmd_lib::prelude::*;
32use kanidmd_lib::value::PartialValue;
33use std::net::IpAddr;
34use uuid::Uuid;
35
36#[utoipa::path(
37    post,
38    path = "/v1/raw/create",
39    responses(
40        DefaultApiResponse,
41    ),
42    request_body=CreateRequest,
43    security(("token_jwt" = [])),
44    tag = "v1/raw",
45    operation_id="raw_create"
46)]
47/// Raw request to the system, be warned this can be dangerous!
48pub async fn raw_create(
49    State(state): State<ServerState>,
50    Extension(kopid): Extension<KOpId>,
51    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
52    Json(msg): Json<CreateRequest>,
53) -> Result<Json<()>, WebError> {
54    state
55        .qe_w_ref
56        .handle_create(client_auth_info, msg, kopid.eventid)
57        .await
58        .map(Json::from)
59        .map_err(WebError::from)
60}
61
62#[utoipa::path(
63    post,
64    path = "/v1/raw/modify",
65    responses(
66        DefaultApiResponse,
67    ),
68    request_body=ModifyRequest,
69    security(("token_jwt" = [])),
70    tag = "v1/raw",
71    operation_id="raw_modify"
72)]
73/// Raw request to the system, be warned this can be dangerous!
74pub async fn raw_modify(
75    State(state): State<ServerState>,
76    Extension(kopid): Extension<KOpId>,
77    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
78    Json(msg): Json<ModifyRequest>,
79) -> Result<Json<()>, WebError> {
80    state
81        .qe_w_ref
82        .handle_modify(client_auth_info, msg, kopid.eventid)
83        .await
84        .map(Json::from)
85        .map_err(WebError::from)
86}
87
88#[utoipa::path(
89    post,
90    path = "/v1/raw/delete",
91    responses(
92        DefaultApiResponse,
93    ),
94    request_body=DeleteRequest,
95    security(("token_jwt" = [])),
96    tag = "v1/raw",
97    operation_id = "raw_delete"
98)]
99/// Raw request to the system, be warned this can be dangerous!
100pub async fn raw_delete(
101    State(state): State<ServerState>,
102    Extension(kopid): Extension<KOpId>,
103    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
104    Json(msg): Json<DeleteRequest>,
105) -> Result<Json<()>, WebError> {
106    state
107        .qe_w_ref
108        .handle_delete(client_auth_info, msg, kopid.eventid)
109        .await
110        .map(Json::from)
111        .map_err(WebError::from)
112}
113
114#[utoipa::path(
115    post,
116    path = "/v1/raw/search",
117    responses(
118        (status = 200, body=SearchResponse, content_type="application/json"),
119        ApiResponseWithout200,
120    ),
121    request_body=SearchRequest,
122    security(("token_jwt" = [])),
123    tag = "v1/raw",
124    operation_id="raw_search"
125)]
126/// Raw request to the system, be warned this can be dangerous!
127pub async fn raw_search(
128    State(state): State<ServerState>,
129    Extension(kopid): Extension<KOpId>,
130    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
131    Json(msg): Json<SearchRequest>,
132) -> Result<Json<SearchResponse>, WebError> {
133    state
134        .qe_r_ref
135        .handle_search(client_auth_info, msg, kopid.eventid)
136        .await
137        .map(Json::from)
138        .map_err(WebError::from)
139}
140
141#[utoipa::path(
142    get,
143    path = "/v1/self",
144    responses(
145        (status = 200, body=WhoamiResponse, content_type="application/json"),
146        ApiResponseWithout200,
147    ),
148    security(("token_jwt" = [])),
149    tag = "v1/self",
150    operation_id="whoami"
151)]
152// Whoami?
153pub async fn whoami(
154    State(state): State<ServerState>,
155    Extension(kopid): Extension<KOpId>,
156    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
157) -> Result<Json<WhoamiResponse>, WebError> {
158    // New event, feed current auth data from the token to it.
159    state
160        .qe_r_ref
161        .handle_whoami(client_auth_info, kopid.eventid)
162        .await
163        .map(Json::from)
164        .map_err(WebError::from)
165}
166
167#[utoipa::path(
168    get,
169    path = "/v1/self/_uat",
170    responses(
171        (status = 200, description = "Ok", body=UserAuthToken, content_type="application/json"),
172        ApiResponseWithout200,
173    ),
174    security(("token_jwt" = [])),
175    tag = "v1/self",
176    operation_id="whoami_uat"
177)]
178pub async fn whoami_uat(
179    State(state): State<ServerState>,
180    Extension(kopid): Extension<KOpId>,
181    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
182) -> Result<Json<UserAuthToken>, WebError> {
183    state
184        .qe_r_ref
185        .handle_whoami_uat(&client_auth_info, kopid.eventid)
186        .await
187        .map(Json::from)
188        .map_err(WebError::from)
189}
190
191#[utoipa::path(
192    get,
193    path = "/v1/logout",
194    responses(
195        DefaultApiResponse,
196    ),
197    security(("token_jwt" = [])),
198    tag = "v1/auth",
199    operation_id="logout"
200)]
201pub async fn logout(
202    State(state): State<ServerState>,
203    Extension(kopid): Extension<KOpId>,
204    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
205    jar: CookieJar,
206) -> Result<Response, WebError> {
207    state
208        .qe_w_ref
209        .handle_logout(client_auth_info, kopid.eventid)
210        .await
211        .map(Json::from)
212        .map(|json| (jar, json).into_response())
213        .map_err(WebError::from)
214}
215
216// // =============== REST generics ========================
217
218#[instrument(level = "trace", skip(state, kopid))]
219pub async fn json_rest_event_get(
220    state: ServerState,
221    attrs: Option<Vec<String>>,
222    filter: Filter<FilterInvalid>,
223    kopid: KOpId,
224    client_auth_info: ClientAuthInfo,
225) -> Result<Json<Vec<ProtoEntry>>, WebError> {
226    state
227        .qe_r_ref
228        .handle_internalsearch(client_auth_info, filter, attrs, kopid.eventid)
229        .await
230        .map(Json::from)
231        .map_err(WebError::from)
232}
233
234/// Common event handler to search and retrieve entries with a name or id
235/// and return the result as json proto entries
236pub async fn json_rest_event_get_id(
237    state: ServerState,
238    id: String,
239    filter: Filter<FilterInvalid>,
240    attrs: Option<Vec<String>>,
241    kopid: KOpId,
242    client_auth_info: ClientAuthInfo,
243) -> Result<Json<Option<ProtoEntry>>, WebError> {
244    let filter = Filter::join_parts_and(filter, filter_all!(f_id(id.as_str())));
245
246    state
247        .qe_r_ref
248        .handle_internalsearch(client_auth_info, filter, attrs, kopid.eventid)
249        .await
250        .map(|mut r| r.pop())
251        .map(Json::from)
252        .map_err(WebError::from)
253}
254
255/// Common event handler to search and retrieve entries that reference another
256/// entry by the value of name or id and return the result as json proto entries
257pub async fn json_rest_event_get_refers_id(
258    state: ServerState,
259    refers_id: String,
260    filter: Filter<FilterInvalid>,
261    attrs: Option<Vec<String>>,
262    kopid: KOpId,
263    client_auth_info: ClientAuthInfo,
264) -> Result<Json<Vec<ProtoEntry>>, WebError> {
265    state
266        .qe_r_ref
267        .handle_search_refers(client_auth_info, filter, refers_id, attrs, kopid.eventid)
268        .await
269        .map(Json::from)
270        .map_err(WebError::from)
271}
272
273pub async fn json_rest_event_delete_id(
274    state: ServerState,
275    id: String,
276    filter: Filter<FilterInvalid>,
277    kopid: KOpId,
278    client_auth_info: ClientAuthInfo,
279) -> Result<Json<()>, WebError> {
280    let filter = Filter::join_parts_and(filter, filter_all!(f_id(id.as_str())));
281    state
282        .qe_w_ref
283        .handle_internaldelete(client_auth_info, filter, kopid.eventid)
284        .await
285        .map(Json::from)
286        .map_err(WebError::from)
287}
288
289pub async fn json_rest_event_get_attr(
290    state: ServerState,
291    id: &str,
292    attr: String,
293    filter: Filter<FilterInvalid>,
294    kopid: KOpId,
295    client_auth_info: ClientAuthInfo,
296) -> Result<Json<Option<Vec<String>>>, WebError> {
297    let filter = Filter::join_parts_and(filter, filter_all!(f_id(id)));
298    let attrs = Some(vec![attr.clone()]);
299    state
300        .qe_r_ref
301        .handle_internalsearch(client_auth_info, filter, attrs, kopid.eventid)
302        .await
303        .map(|mut event_result| event_result.pop().and_then(|mut e| e.attrs.remove(&attr)))
304        .map(Json::from)
305        .map_err(WebError::from)
306}
307
308pub async fn json_rest_event_get_id_attr(
309    state: ServerState,
310    id: String,
311    attr: String,
312    filter: Filter<FilterInvalid>,
313    kopid: KOpId,
314    client_auth_info: ClientAuthInfo,
315) -> Result<Json<Option<Vec<String>>>, WebError> {
316    json_rest_event_get_attr(state, id.as_str(), attr, filter, kopid, client_auth_info).await
317}
318
319pub async fn json_rest_event_post(
320    state: ServerState,
321    classes: Vec<String>,
322    obj: ProtoEntry,
323    kopid: KOpId,
324    client_auth_info: ClientAuthInfo,
325) -> Result<Json<()>, WebError> {
326    debug_assert!(!classes.is_empty());
327
328    let mut obj = obj;
329    obj.attrs.insert(Attribute::Class.to_string(), classes);
330    let msg = CreateRequest {
331        entries: vec![obj.to_owned()],
332    };
333
334    state
335        .qe_w_ref
336        .handle_create(client_auth_info, msg, kopid.eventid)
337        .await
338        .map(Json::from)
339        .map_err(WebError::from)
340}
341
342pub async fn json_rest_event_post_id_attr(
343    state: ServerState,
344    id: String,
345    attr: String,
346    filter: Filter<FilterInvalid>,
347    values: Vec<String>,
348    kopid: KOpId,
349    client_auth_info: ClientAuthInfo,
350) -> Result<Json<()>, WebError> {
351    state
352        .qe_w_ref
353        .handle_appendattribute(client_auth_info, id, attr, values, filter, kopid.eventid)
354        .await
355        .map(Json::from)
356        .map_err(WebError::from)
357}
358
359// Okay, so a put normally needs
360///  * filter of what we are working on (id + class)
361///  * a `Map<String, Vec<String>>` that we turn into a modlist.
362///
363/// OR
364///  * filter of what we are working on (id + class)
365///  * a `Vec<String>` that we are changing
366///  * the attr name  (as a param to this in path)
367///
368pub async fn json_rest_event_put_attr(
369    state: ServerState,
370    id: String,
371    attr: String,
372    filter: Filter<FilterInvalid>,
373    values: Vec<String>,
374    kopid: KOpId,
375    client_auth_info: ClientAuthInfo,
376) -> Result<Json<()>, WebError> {
377    state
378        .qe_w_ref
379        .handle_setattribute(client_auth_info, id, attr, values, filter, kopid.eventid)
380        .await
381        .map_err(WebError::from)
382        .map(Json::from)
383}
384
385pub async fn json_rest_event_post_attr(
386    state: ServerState,
387    id: String,
388    attr: String,
389    filter: Filter<FilterInvalid>,
390    values: Vec<String>,
391    kopid: KOpId,
392    client_auth_info: ClientAuthInfo,
393) -> Result<Json<()>, WebError> {
394    state
395        .qe_w_ref
396        .handle_appendattribute(client_auth_info, id, attr, values, filter, kopid.eventid)
397        .await
398        .map(Json::from)
399        .map_err(WebError::from)
400}
401
402pub async fn json_rest_event_delete_id_attr(
403    state: ServerState,
404    id: String,
405    attr: String,
406    filter: Filter<FilterInvalid>,
407    values: Option<Vec<String>>,
408    kopid: KOpId,
409    client_auth_info: ClientAuthInfo,
410) -> Result<Json<()>, WebError> {
411    json_rest_event_delete_attr(state, id, attr, filter, values, kopid, client_auth_info).await
412}
413
414pub async fn json_rest_event_delete_attr(
415    state: ServerState,
416    uuid_or_name: String,
417    attr: String,
418    filter: Filter<FilterInvalid>,
419    values: Option<Vec<String>>,
420    kopid: KOpId,
421    client_auth_info: ClientAuthInfo,
422) -> Result<Json<()>, WebError> {
423    let values = values.unwrap_or_default();
424
425    if values.is_empty() {
426        state
427            .qe_w_ref
428            .handle_purgeattribute(client_auth_info, uuid_or_name, attr, filter, kopid.eventid)
429            .await
430    } else {
431        state
432            .qe_w_ref
433            .handle_removeattributevalues(
434                client_auth_info,
435                uuid_or_name,
436                attr,
437                values,
438                filter,
439                kopid.eventid,
440            )
441            .await
442    }
443    .map(Json::from)
444    .map_err(WebError::from)
445}
446
447#[utoipa::path(
448    get,
449    path = "/v1/schema",
450    responses(
451        (status=200, content_type="application/json", body=Vec<ProtoEntry>),
452        ApiResponseWithout200,
453    ),
454    security(("token_jwt" = [])),
455    tag = "v1/schema",
456    operation_id = "schema_get",
457)]
458// Whoami?
459pub async fn schema_get(
460    State(state): State<ServerState>,
461    Extension(kopid): Extension<KOpId>,
462    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
463) -> Result<Json<Vec<ProtoEntry>>, WebError> {
464    // NOTE: This is filter_all, because from_internal_message will still do the alterations
465    // needed to make it safe. This is needed because there may be aci's that block access
466    // to the recycle/ts types in the filter, and we need the aci to only eval on this
467    // part of the filter!
468    let filter = filter_all!(f_or!([
469        f_eq(Attribute::Class, EntryClass::AttributeType.into()),
470        f_eq(Attribute::Class, EntryClass::ClassType.into())
471    ]));
472    json_rest_event_get(state, None, filter, kopid, client_auth_info).await
473}
474
475#[utoipa::path(
476    get,
477    path = "/v1/schema/attributetype",
478    responses(
479        (status=200, content_type="application/json", body=Vec<ProtoEntry>),
480        ApiResponseWithout200,
481    ),
482    security(("token_jwt" = [])),
483    tag = "v1/schema",
484    operation_id = "schema_attributetype_get",
485)]
486pub async fn schema_attributetype_get(
487    State(state): State<ServerState>,
488    Extension(kopid): Extension<KOpId>,
489    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
490) -> Result<Json<Vec<ProtoEntry>>, WebError> {
491    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::AttributeType.into()));
492    json_rest_event_get(state, None, filter, kopid, client_auth_info).await
493}
494
495#[utoipa::path(
496    get,
497    path = "/v1/schema/attributetype/{id}",
498    responses(
499        (status=200, body=Option<ProtoEntry>, content_type="application/json"),
500        ApiResponseWithout200,
501    ),
502    security(("token_jwt" = [])),
503    tag = "v1/schema",
504    operation_id = "schema_attributetype_get_id",
505)]
506pub async fn schema_attributetype_get_id(
507    State(state): State<ServerState>,
508    Path(id): Path<String>,
509    Extension(kopid): Extension<KOpId>,
510    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
511) -> Result<Json<Option<ProtoEntry>>, WebError> {
512    // These can't use get_id because the attribute name and class name aren't ... well name.
513    let filter = filter_all!(f_and!([
514        f_eq(Attribute::Class, EntryClass::AttributeType.into()),
515        f_eq(
516            Attribute::AttributeName,
517            PartialValue::new_iutf8(id.as_str())
518        )
519    ]));
520
521    state
522        .qe_r_ref
523        .handle_internalsearch(client_auth_info, filter, None, kopid.eventid)
524        .await
525        .map(|mut r| r.pop())
526        .map(Json::from)
527        .map_err(WebError::from)
528}
529
530#[utoipa::path(
531    get,
532    path = "/v1/schema/classtype",
533    responses(
534        (status=200, body=Vec<ProtoEntry>, content_type="application/json"),
535        ApiResponseWithout200,
536    ),
537    security(("token_jwt" = [])),
538    tag = "v1/schema",
539    operation_id="schema_classtype_get",
540)]
541pub async fn schema_classtype_get(
542    State(state): State<ServerState>,
543    Extension(kopid): Extension<KOpId>,
544    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
545) -> Result<Json<Vec<ProtoEntry>>, WebError> {
546    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::ClassType.into()));
547    json_rest_event_get(state, None, filter, kopid, client_auth_info).await
548}
549
550#[utoipa::path(
551    get,
552    path = "/v1/schema/classtype/{id}",
553    responses(
554        (status=200, body=Option<ProtoEntry>, content_type="application/json"),
555        ApiResponseWithout200,
556    ),
557    security(("token_jwt" = [])),
558    tag = "v1/schema",
559    operation_id="schema_classtype_get_id",
560)]
561pub async fn schema_classtype_get_id(
562    State(state): State<ServerState>,
563    Extension(kopid): Extension<KOpId>,
564    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
565    Path(id): Path<String>,
566) -> Result<Json<Option<ProtoEntry>>, WebError> {
567    // These can't use get_id because they attribute name and class name aren't ... well name.
568    let filter = filter_all!(f_and!([
569        f_eq(Attribute::Class, EntryClass::ClassType.into()),
570        f_eq(Attribute::ClassName, PartialValue::new_iutf8(id.as_str()))
571    ]));
572    state
573        .qe_r_ref
574        .handle_internalsearch(client_auth_info, filter, None, kopid.eventid)
575        .await
576        .map(|mut r| r.pop())
577        .map(Json::from)
578        .map_err(WebError::from)
579}
580
581#[utoipa::path(
582    get,
583    path = "/v1/person",
584    responses(
585        (status=200, body=Vec<ProtoEntry>, content_type="application/json"),
586        ApiResponseWithout200,
587    ),
588    security(("token_jwt" = [])),
589    tag = "v1/person",
590    operation_id = "person_get",
591)]
592pub async fn person_get(
593    State(state): State<ServerState>,
594    Extension(kopid): Extension<KOpId>,
595    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
596) -> Result<Json<Vec<ProtoEntry>>, WebError> {
597    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Person.into()));
598    json_rest_event_get(state, None, filter, kopid, client_auth_info).await
599}
600
601#[utoipa::path(
602    post,
603    path = "/v1/person",
604    responses(
605        DefaultApiResponse,
606    ),
607    request_body=ProtoEntry,
608    security(("token_jwt" = [])),
609    tag = "v1/person",
610    operation_id = "person_post",
611)]
612/// Expects the following fields in the attrs field of the req: [name, displayname]
613pub async fn person_post(
614    State(state): State<ServerState>,
615    Extension(kopid): Extension<KOpId>,
616    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
617    Json(obj): Json<ProtoEntry>,
618) -> Result<Json<()>, WebError> {
619    let classes: Vec<String> = vec![
620        EntryClass::Person.into(),
621        EntryClass::Account.into(),
622        EntryClass::Object.into(),
623    ];
624    json_rest_event_post(state, classes, obj, kopid, client_auth_info).await
625}
626
627#[utoipa::path(
628    get,
629    path = "/v1/person/_search/{id}",
630    responses(
631        (status=200, body=Option<ProtoEntry>, content_type="application/json"),
632        ApiResponseWithout200,
633    ),
634    security(("token_jwt" = [])),
635    tag = "v1/person",
636    operation_id = "person_search_id",
637)]
638pub async fn person_search_id(
639    State(state): State<ServerState>,
640    Path(id): Path<String>,
641    Extension(kopid): Extension<KOpId>,
642    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
643) -> Result<Json<Vec<ProtoEntry>>, WebError> {
644    let filter = filter_all!(f_and!([
645        f_eq(Attribute::Class, EntryClass::Person.into()),
646        f_sub(Attribute::Name, PartialValue::new_iname(&id))
647    ]));
648    json_rest_event_get(state, None, filter, kopid, client_auth_info).await
649}
650
651#[utoipa::path(
652    get,
653    path = "/v1/person/{id}",
654    responses(
655        (status=200, body=Option<ProtoEntry>, content_type="application/json"),
656        ApiResponseWithout200,
657    ),
658    security(("token_jwt" = [])),
659    tag = "v1/person",
660    operation_id = "person_id_get",
661)]
662pub async fn person_id_get(
663    State(state): State<ServerState>,
664    Path(id): Path<String>,
665    Extension(kopid): Extension<KOpId>,
666    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
667) -> Result<Json<Option<ProtoEntry>>, WebError> {
668    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Person.into()));
669    json_rest_event_get_id(state, id, filter, None, kopid, client_auth_info).await
670}
671
672#[utoipa::path(
673    delete,
674    path = "/v1/person/{id}",
675    responses(
676        DefaultApiResponse,
677    ),
678    security(("token_jwt" = [])),
679    tag = "v1/person",
680    operation_id = "person_id_delete",
681)]
682pub async fn person_id_delete(
683    State(state): State<ServerState>,
684    Path(id): Path<String>,
685    Extension(kopid): Extension<KOpId>,
686    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
687) -> Result<Json<()>, WebError> {
688    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Person.into()));
689    json_rest_event_delete_id(state, id, filter, kopid, client_auth_info).await
690}
691
692// == person -> certificates
693
694#[utoipa::path(
695    get,
696    path = "/v1/person/{id}/_certificate",
697    responses(
698        (status=200, body=Option<ProtoEntry>, content_type="application/json"),
699        ApiResponseWithout200,
700    ),
701    security(("token_jwt" = [])),
702    tag = "v1/person/certificate",
703    operation_id = "person_get_id_certificate",
704)]
705pub async fn person_get_id_certificate(
706    State(state): State<ServerState>,
707    Path(id): Path<String>,
708    Extension(kopid): Extension<KOpId>,
709    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
710) -> Result<Json<Vec<ProtoEntry>>, WebError> {
711    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::ClientCertificate.into()));
712    json_rest_event_get_refers_id(state, id, filter, None, kopid, client_auth_info).await
713}
714
715#[utoipa::path(
716    post,
717    path = "/v1/person/{id}/_certificate",
718    responses(
719        DefaultApiResponse,
720    ),
721    request_body=ProtoEntry,
722    security(("token_jwt" = [])),
723    tag = "v1/person/certificate",
724    operation_id = "person_post_id_certificate",
725)]
726/// Expects the following fields in the attrs field of the req: [certificate]
727///
728/// The person's id will be added implicitly as a reference.
729pub async fn person_post_id_certificate(
730    State(state): State<ServerState>,
731    Path(id): Path<String>,
732    Extension(kopid): Extension<KOpId>,
733    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
734    Json(mut obj): Json<ProtoEntry>,
735) -> Result<Json<()>, WebError> {
736    let classes: Vec<String> = vec![
737        EntryClass::ClientCertificate.into(),
738        EntryClass::Object.into(),
739    ];
740    obj.attrs.insert(Attribute::Refers.to_string(), vec![id]);
741
742    json_rest_event_post(state, classes, obj, kopid, client_auth_info).await
743}
744
745// // == account ==
746
747#[utoipa::path(
748    get,
749    path = "/v1/service_account",
750    responses(
751        (status=200, body=Vec<ProtoEntry>, content_type="application/json"),
752        ApiResponseWithout200,
753    ),
754    security(("token_jwt" = [])),
755    tag = "v1/service_account",
756    operation_id = "service_account_get",
757)]
758pub async fn service_account_get(
759    State(state): State<ServerState>,
760    Extension(kopid): Extension<KOpId>,
761    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
762) -> Result<Json<Vec<ProtoEntry>>, WebError> {
763    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::ServiceAccount.into()));
764    json_rest_event_get(state, None, filter, kopid, client_auth_info).await
765}
766
767#[utoipa::path(
768    post,
769    path = "/v1/service_account",
770    request_body=ProtoEntry,
771    responses(
772        DefaultApiResponse,
773    ),
774    security(("token_jwt" = [])),
775    tag = "v1/service_account",
776    operation_id = "service_account_post",
777)]
778pub async fn service_account_post(
779    State(state): State<ServerState>,
780    Extension(kopid): Extension<KOpId>,
781    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
782    Json(obj): Json<ProtoEntry>,
783) -> Result<Json<()>, WebError> {
784    let classes: Vec<String> = vec![
785        EntryClass::ServiceAccount.into(),
786        EntryClass::Account.into(),
787        EntryClass::Object.into(),
788    ];
789    json_rest_event_post(state, classes, obj, kopid, client_auth_info).await
790}
791
792#[utoipa::path(
793    patch,
794    path = "/v1/service_account/{id}",
795    responses(
796        DefaultApiResponse,
797    ),
798    request_body=ProtoEntry,
799    security(("token_jwt" = [])),
800    tag = "v1/service_account",
801    operation_id = "service_account_id_patch",
802)]
803pub async fn service_account_id_patch(
804    State(state): State<ServerState>,
805    Extension(kopid): Extension<KOpId>,
806    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
807    Path(id): Path<String>,
808    Json(obj): Json<ProtoEntry>,
809) -> Result<Json<()>, WebError> {
810    // Update a value / attrs
811    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
812    let filter = Filter::join_parts_and(filter, filter_all!(f_id(id.as_str())));
813    state
814        .qe_w_ref
815        .handle_internalpatch(client_auth_info, filter, obj, kopid.eventid)
816        .await
817        .map(Json::from)
818        .map_err(WebError::from)
819}
820
821#[utoipa::path(
822    get,
823    path = "/v1/service_account/{id}",
824    responses(
825        (status=200, body=Option<ProtoEntry>, content_type="application/json"),
826        ApiResponseWithout200,
827    ),
828    security(("token_jwt" = [])),
829    tag = "v1/service_account",
830    operation_id = "service_account_id_get",
831)]
832pub async fn service_account_id_get(
833    State(state): State<ServerState>,
834    Path(id): Path<String>,
835    Extension(kopid): Extension<KOpId>,
836    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
837) -> Result<Json<Option<ProtoEntry>>, WebError> {
838    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::ServiceAccount.into()));
839    json_rest_event_get_id(state, id, filter, None, kopid, client_auth_info).await
840}
841
842#[utoipa::path(
843    delete,
844    path = "/v1/service_account/{id}",
845    responses(
846        DefaultApiResponse,
847    ),
848    security(("token_jwt" = [])),
849    tag = "v1/service_account",
850)]
851pub async fn service_account_id_delete(
852    State(state): State<ServerState>,
853    Path(id): Path<String>,
854    Extension(kopid): Extension<KOpId>,
855    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
856) -> Result<Json<()>, WebError> {
857    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::ServiceAccount.into()));
858    json_rest_event_delete_id(state, id, filter, kopid, client_auth_info).await
859}
860
861#[utoipa::path(
862    get,
863    path = "/v1/service_account/{id}/_credential/_generate",
864    responses(
865        (status=200), // TODO: define response
866        ApiResponseWithout200,
867    ),
868    security(("token_jwt" = [])),
869    tag = "v1/service_account",
870)]
871pub async fn service_account_credential_generate(
872    State(state): State<ServerState>,
873    Path(id): Path<String>,
874    Extension(kopid): Extension<KOpId>,
875    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
876) -> Result<Json<String>, WebError> {
877    state
878        .qe_w_ref
879        .handle_service_account_credential_generate(client_auth_info, id, kopid.eventid)
880        .await
881        .map(Json::from)
882        .map_err(WebError::from)
883}
884
885#[utoipa::path(
886    post,
887    path = "/v1/service_account/{id}/_into_person",
888    responses(
889        DefaultApiResponse,
890    ),
891    security(("token_jwt" = [])),
892    tag = "v1/service_account",
893)]
894/// Due to how the migrations work in 6 -> 7, we can accidentally
895/// mark "accounts" as service accounts when they are persons. This
896/// allows migrating them to the person type due to its similarities.
897///
898/// In the future this will be REMOVED!
899#[deprecated]
900pub async fn service_account_into_person(
901    State(state): State<ServerState>,
902    Extension(kopid): Extension<KOpId>,
903    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
904    Path(id): Path<String>,
905) -> Result<Json<()>, WebError> {
906    state
907        .qe_w_ref
908        .handle_service_account_into_person(client_auth_info, id, kopid.eventid)
909        .await
910        .map(Json::from)
911        .map_err(WebError::from)
912}
913
914#[utoipa::path(
915    get,
916    path = "/v1/service_account/{id}/_api_token",
917    responses(
918        (status=200, body=Vec<ApiToken>, content_type="application/json"),
919        ApiResponseWithout200,
920    ),
921    security(("token_jwt" = [])),
922    tag = "v1/service_account",
923    operation_id = "service_account_api_token_get",
924)]
925pub async fn service_account_api_token_get(
926    State(state): State<ServerState>,
927    Extension(kopid): Extension<KOpId>,
928    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
929    Path(id): Path<String>,
930) -> Result<Json<Vec<ApiToken>>, WebError> {
931    state
932        .qe_r_ref
933        .handle_service_account_api_token_get(client_auth_info, id, kopid.eventid)
934        .await
935        .map(Json::from)
936        .map_err(WebError::from)
937}
938
939#[utoipa::path(
940    post,
941    path = "/v1/service_account/{id}/_api_token",
942    request_body = ApiTokenGenerate,
943    responses(
944        (status=200, body=String, content_type="application/json"),
945        ApiResponseWithout200,
946    ),
947    security(("token_jwt" = [])),
948    tag = "v1/service_account",
949    operation_id = "service_account_api_token_post",
950)]
951pub async fn service_account_api_token_post(
952    State(state): State<ServerState>,
953    Extension(kopid): Extension<KOpId>,
954    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
955    Path(id): Path<String>,
956    Json(obj): Json<ApiTokenGenerate>,
957) -> Result<Json<String>, WebError> {
958    state
959        .qe_w_ref
960        .handle_service_account_api_token_generate(
961            client_auth_info,
962            id,
963            obj.label,
964            obj.expiry,
965            obj.read_write,
966            kopid.eventid,
967        )
968        .await
969        .map(Json::from)
970        .map_err(WebError::from)
971}
972
973#[utoipa::path(
974    delete,
975    path = "/v1/service_account/{id}/_api_token/{token_id}",
976    responses(
977        DefaultApiResponse,
978    ),
979    security(("token_jwt" = [])),
980    tag = "v1/service_account",
981    operation_id = "service_account_api_token_delete",
982)]
983pub async fn service_account_api_token_delete(
984    State(state): State<ServerState>,
985    Path((id, token_id)): Path<(String, Uuid)>,
986    Extension(kopid): Extension<KOpId>,
987    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
988) -> Result<Json<()>, WebError> {
989    state
990        .qe_w_ref
991        .handle_service_account_api_token_destroy(client_auth_info, id, token_id, kopid.eventid)
992        .await
993        .map(Json::from)
994        .map_err(WebError::from)
995}
996
997#[utoipa::path(
998    get,
999    path = "/v1/person/{id}/_attr/{attr}",
1000    responses(
1001        (status=200, body=Option<Vec<String>>, content_type="application/json"),
1002        ApiResponseWithout200,
1003    ),
1004    security(("token_jwt" = [])),
1005    tag = "v1/person/attr",
1006    operation_id = "person_id_get_attr",
1007)]
1008pub async fn person_id_get_attr(
1009    State(state): State<ServerState>,
1010    Path((id, attr)): Path<(String, String)>,
1011    Extension(kopid): Extension<KOpId>,
1012    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1013) -> Result<Json<Option<Vec<String>>>, WebError> {
1014    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1015    json_rest_event_get_attr(state, id.as_str(), attr, filter, kopid, client_auth_info).await
1016}
1017
1018#[utoipa::path(
1019    get,
1020    path = "/v1/service_account/{id}/_attr/{attr}",
1021    responses(
1022        (status=200, body=Option<Vec<String>>, content_type="application/json"),
1023        ApiResponseWithout200,
1024    ),
1025    security(("token_jwt" = [])),
1026    tag = "v1/service_account",
1027    operation_id = "service_account_id_get_attr",
1028)]
1029pub async fn service_account_id_get_attr(
1030    State(state): State<ServerState>,
1031    Path((id, attr)): Path<(String, String)>,
1032    Extension(kopid): Extension<KOpId>,
1033    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1034) -> Result<Json<Option<Vec<String>>>, WebError> {
1035    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1036    json_rest_event_get_attr(state, id.as_str(), attr, filter, kopid, client_auth_info).await
1037}
1038
1039#[utoipa::path(
1040    post,
1041    path = "/v1/person/{id}/_attr/{attr}",
1042    request_body= Vec<String>,
1043    responses(
1044        DefaultApiResponse,
1045    ),
1046    security(("token_jwt" = [])),
1047    tag = "v1/person/attr",
1048    operation_id = "person_id_post_attr",
1049)]
1050pub async fn person_id_post_attr(
1051    State(state): State<ServerState>,
1052    Path((id, attr)): Path<(String, String)>,
1053    Extension(kopid): Extension<KOpId>,
1054    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1055    Json(values): Json<Vec<String>>,
1056) -> Result<Json<()>, WebError> {
1057    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1058    json_rest_event_post_id_attr(state, id, attr, filter, values, kopid, client_auth_info).await
1059}
1060
1061#[utoipa::path(
1062    post,
1063    path = "/v1/service_account/{id}/_attr/{attr}",
1064    request_body=Vec<String>,
1065    responses(
1066        DefaultApiResponse,
1067    ),
1068    security(("token_jwt" = [])),
1069    tag = "v1/service_account",
1070    operation_id = "service_account_id_post_attr",
1071)]
1072pub async fn service_account_id_post_attr(
1073    State(state): State<ServerState>,
1074    Path((id, attr)): Path<(String, String)>,
1075    Extension(kopid): Extension<KOpId>,
1076    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1077    Json(values): Json<Vec<String>>,
1078) -> Result<Json<()>, WebError> {
1079    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1080    json_rest_event_post_id_attr(state, id, attr, filter, values, kopid, client_auth_info).await
1081}
1082
1083#[utoipa::path(
1084    delete,
1085    path = "/v1/person/{id}/_attr/{attr}",
1086    responses(
1087        DefaultApiResponse,
1088    ),
1089    security(("token_jwt" = [])),
1090    tag = "v1/person/attr",
1091    operation_id = "person_id_delete_attr",
1092)]
1093pub async fn person_id_delete_attr(
1094    State(state): State<ServerState>,
1095    Path((id, attr)): Path<(String, String)>,
1096    Extension(kopid): Extension<KOpId>,
1097    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1098) -> Result<Json<()>, WebError> {
1099    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1100    json_rest_event_delete_id_attr(state, id, attr, filter, None, kopid, client_auth_info).await
1101}
1102
1103#[utoipa::path(
1104    delete,
1105    path = "/v1/service_account/{id}/_attr/{attr}",
1106    responses(
1107        DefaultApiResponse,
1108    ),
1109    security(("token_jwt" = [])),
1110    tag = "v1/service_account",
1111    operation_id = "service_account_id_delete_attr",
1112)]
1113pub async fn service_account_id_delete_attr(
1114    State(state): State<ServerState>,
1115    Path((id, attr)): Path<(String, String)>,
1116    Extension(kopid): Extension<KOpId>,
1117    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1118) -> Result<Json<()>, WebError> {
1119    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1120    json_rest_event_delete_id_attr(state, id, attr, filter, None, kopid, client_auth_info).await
1121}
1122
1123#[utoipa::path(
1124    put,
1125    path = "/v1/person/{id}/_attr/{attr}",
1126    responses(
1127        DefaultApiResponse,
1128    ),
1129    security(("token_jwt" = [])),
1130    tag = "v1/person/attr",
1131    operation_id = "person_id_put_attr",
1132)]
1133pub async fn person_id_put_attr(
1134    State(state): State<ServerState>,
1135    Path((id, attr)): Path<(String, String)>,
1136    Extension(kopid): Extension<KOpId>,
1137    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1138    Json(values): Json<Vec<String>>,
1139) -> Result<Json<()>, WebError> {
1140    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1141    json_rest_event_put_attr(state, id, attr, filter, values, kopid, client_auth_info).await
1142}
1143
1144#[utoipa::path(
1145    put,
1146    path = "/v1/service_account/{id}/_attr/{attr}",
1147    request_body=Vec<String>,
1148    responses(
1149        DefaultApiResponse,
1150    ),
1151    security(("token_jwt" = [])),
1152    tag = "v1/service_account",
1153    operation_id = "service_account_id_put_attr",
1154)]
1155pub async fn service_account_id_put_attr(
1156    State(state): State<ServerState>,
1157    Path((id, attr)): Path<(String, String)>,
1158    Extension(kopid): Extension<KOpId>,
1159    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1160    Json(values): Json<Vec<String>>,
1161) -> Result<Json<()>, WebError> {
1162    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1163    json_rest_event_put_attr(state, id, attr, filter, values, kopid, client_auth_info).await
1164}
1165
1166#[utoipa::path(
1167    patch,
1168    path = "/v1/person/{id}",
1169    responses(
1170        DefaultApiResponse,
1171    ),
1172    request_body=ProtoEntry,
1173    security(("token_jwt" = [])),
1174    tag = "v1/person",
1175    operation_id = "person_id_patch",
1176)]
1177pub async fn person_id_patch(
1178    State(state): State<ServerState>,
1179    Extension(kopid): Extension<KOpId>,
1180    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1181    Path(id): Path<String>,
1182    Json(obj): Json<ProtoEntry>,
1183) -> Result<Json<()>, WebError> {
1184    // Update a value / attrs
1185    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1186    let filter = Filter::join_parts_and(filter, filter_all!(f_id(id.as_str())));
1187    state
1188        .qe_w_ref
1189        .handle_internalpatch(client_auth_info, filter, obj, kopid.eventid)
1190        .await
1191        .map(Json::from)
1192        .map_err(WebError::from)
1193}
1194
1195#[utoipa::path(
1196    get,
1197    path = "/v1/person/{id}/_credential/_update",
1198    responses(
1199        (status=200), // TODO: define response
1200        ApiResponseWithout200,
1201    ),
1202    security(("token_jwt" = [])),
1203    tag = "v1/person/credential",
1204)]
1205pub async fn person_id_credential_update_get(
1206    State(state): State<ServerState>,
1207    Extension(kopid): Extension<KOpId>,
1208    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1209    Path(id): Path<String>,
1210) -> Result<Json<(CUSessionToken, CUStatus)>, WebError> {
1211    state
1212        .qe_w_ref
1213        .handle_idmcredentialupdate(client_auth_info, id, kopid.eventid)
1214        .await
1215        .map(Json::from)
1216        .map_err(WebError::from)
1217}
1218
1219#[utoipa::path(
1220    get,
1221    path = "/v1/person/{id}/_credential/_update_intent/{ttl}",
1222    params(
1223        ("ttl" = u64, description="The new TTL for the credential?")
1224    ),
1225    responses(
1226        (status=200), // TODO: define response
1227        ApiResponseWithout200,
1228    ),
1229    security(("token_jwt" = [])),
1230    tag = "v1/person/credential",
1231)]
1232// TODO: this shouldn't be a get, we're making changes!
1233#[instrument(level = "trace", skip(state, kopid))]
1234pub async fn person_id_credential_update_intent_ttl_get(
1235    State(state): State<ServerState>,
1236    Extension(kopid): Extension<KOpId>,
1237    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1238    Path((id, ttl)): Path<(String, u64)>,
1239) -> Result<Json<CUIntentToken>, WebError> {
1240    state
1241        .qe_w_ref
1242        .handle_idmcredentialupdateintent(
1243            client_auth_info,
1244            id,
1245            Some(Duration::from_secs(ttl)),
1246            kopid.eventid,
1247        )
1248        .await
1249        .map(Json::from)
1250        .map_err(WebError::from)
1251}
1252
1253#[utoipa::path(
1254    get,
1255    path = "/v1/person/{id}/_credential/_update_intent",
1256    responses(
1257        (status=200), // TODO: define response
1258        ApiResponseWithout200,
1259    ),
1260    security(("token_jwt" = [])),
1261    tag = "v1/person/credential",
1262)]
1263#[instrument(level = "trace", skip(state, kopid))]
1264pub async fn person_id_credential_update_intent_get(
1265    State(state): State<ServerState>,
1266    Extension(kopid): Extension<KOpId>,
1267    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1268    Path(id): Path<String>,
1269) -> Result<Json<CUIntentToken>, WebError> {
1270    state
1271        .qe_w_ref
1272        .handle_idmcredentialupdateintent(client_auth_info, id, None, kopid.eventid)
1273        .await
1274        .map(Json::from)
1275        .map_err(WebError::from)
1276}
1277
1278#[utoipa::path(
1279    get,
1280    path = "/v1/account/{id}/_user_auth_token",
1281    responses(
1282        (status=200), // TODO: define response
1283        ApiResponseWithout200,
1284    ),
1285    security(("token_jwt" = [])),
1286    tag = "v1/account",
1287)]
1288pub async fn account_id_user_auth_token_get(
1289    State(state): State<ServerState>,
1290    Path(id): Path<String>,
1291    Extension(kopid): Extension<KOpId>,
1292    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1293) -> Result<Json<Vec<UatStatus>>, WebError> {
1294    state
1295        .qe_r_ref
1296        .handle_account_user_auth_token_get(client_auth_info, id, kopid.eventid)
1297        .await
1298        .map(Json::from)
1299        .map_err(WebError::from)
1300}
1301
1302#[utoipa::path(
1303    get,
1304    path = "/v1/account/{id}/_user_auth_token/{token_id}",
1305    responses(
1306        DefaultApiResponse,
1307    ),
1308    security(("token_jwt" = [])),
1309    tag = "v1/account",
1310)]
1311pub async fn account_user_auth_token_delete(
1312    State(state): State<ServerState>,
1313    Path((id, token_id)): Path<(String, Uuid)>,
1314    Extension(kopid): Extension<KOpId>,
1315    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1316) -> Result<Json<()>, WebError> {
1317    state
1318        .qe_w_ref
1319        .handle_account_user_auth_token_destroy(client_auth_info, id, token_id, kopid.eventid)
1320        .await
1321        .map(Json::from)
1322        .map_err(WebError::from)
1323}
1324
1325#[utoipa::path(
1326    post,
1327    path = "/v1/credential/_exchange_intent",
1328    params(
1329    ),
1330    responses(
1331        (status=200), // TODO: define response
1332        ApiResponseWithout200,
1333    ),
1334    security(("token_jwt" = [])),
1335    tag = "v1/credential",
1336)] // TODO: post body
1337pub async fn credential_update_exchange_intent(
1338    State(state): State<ServerState>,
1339    Extension(kopid): Extension<KOpId>,
1340    Json(intent_token): Json<String>,
1341) -> Result<Json<(CUSessionToken, CUStatus)>, WebError> {
1342    state
1343        .qe_w_ref
1344        .handle_idmcredentialexchangeintent(intent_token, kopid.eventid)
1345        .await
1346        .map(Json::from)
1347        .map_err(WebError::from)
1348}
1349
1350#[utoipa::path(
1351    post,
1352    path = "/v1/credential/_status",
1353    responses(
1354        (status=200), // TODO: define response
1355        ApiResponseWithout200,
1356    ),
1357    security(("token_jwt" = [])),
1358    tag = "v1/credential",
1359)] // TODO: post body
1360pub async fn credential_update_status(
1361    State(state): State<ServerState>,
1362    Extension(kopid): Extension<KOpId>,
1363    Json(session_token): Json<CUSessionToken>,
1364) -> Result<Json<CUStatus>, WebError> {
1365    state
1366        .qe_r_ref
1367        .handle_idmcredentialupdatestatus(session_token, kopid.eventid)
1368        .await
1369        .map(Json::from)
1370        .map_err(WebError::from)
1371}
1372
1373#[utoipa::path(
1374    post,
1375    path = "/v1/credential/_update",
1376    responses(
1377        (status=200, body=CUStatus), // TODO: define response
1378        ApiResponseWithout200,
1379    ),
1380    security(("token_jwt" = [])),
1381    tag = "v1/credential",
1382)] // TODO: post body
1383#[instrument(level = "debug", skip(state, kopid))]
1384pub async fn credential_update_update(
1385    State(state): State<ServerState>,
1386    Extension(kopid): Extension<KOpId>,
1387    Json(cubody): Json<Vec<serde_json::Value>>,
1388) -> Result<Json<CUStatus>, WebError> {
1389    let scr: CURequest = match serde_json::from_value(cubody[0].clone()) {
1390        Ok(val) => val,
1391        Err(err) => {
1392            let errmsg = format!("Failed to deserialize CURequest: {err:?}");
1393            error!("{}", errmsg);
1394            return Err(WebError::InternalServerError(errmsg));
1395        }
1396    };
1397
1398    let session_token = match serde_json::from_value(cubody[1].clone()) {
1399        Ok(val) => val,
1400        Err(err) => {
1401            let errmsg = format!("Failed to deserialize session token: {err:?}");
1402            error!("{}", errmsg);
1403            return Err(WebError::InternalServerError(errmsg));
1404        }
1405    };
1406    debug!("session_token: {:?}", session_token);
1407    debug!("scr: {:?}", scr);
1408
1409    state
1410        .qe_r_ref
1411        .handle_idmcredentialupdate(session_token, scr, kopid.eventid)
1412        .await
1413        .map(Json::from)
1414        .map_err(WebError::from)
1415}
1416
1417#[utoipa::path(
1418    post,
1419    path = "/v1/credential/_commit",
1420    responses(
1421        DefaultApiResponse,
1422    ),
1423    security(("token_jwt" = [])),
1424    tag = "v1/credential",
1425)] // TODO: post body
1426pub async fn credential_update_commit(
1427    State(state): State<ServerState>,
1428    Extension(kopid): Extension<KOpId>,
1429    Json(session_token): Json<CUSessionToken>,
1430) -> Result<Json<()>, WebError> {
1431    state
1432        .qe_w_ref
1433        .handle_idmcredentialupdatecommit(session_token, kopid.eventid)
1434        .await
1435        .map(Json::from)
1436        .map_err(WebError::from)
1437}
1438
1439#[utoipa::path(
1440    post,
1441    path = "/v1/credential/_cancel",
1442    request_body=CUSessionToken,
1443    responses(
1444        DefaultApiResponse,
1445    ),
1446    security(("token_jwt" = [])),
1447    tag = "v1/credential",
1448)]
1449pub async fn credential_update_cancel(
1450    State(state): State<ServerState>,
1451    Extension(kopid): Extension<KOpId>,
1452    Json(session_token): Json<CUSessionToken>,
1453) -> Result<Json<()>, WebError> {
1454    state
1455        .qe_w_ref
1456        .handle_idmcredentialupdatecancel(session_token, kopid.eventid)
1457        .await
1458        .map(Json::from)
1459        .map_err(WebError::from)
1460}
1461
1462#[utoipa::path(
1463    get,
1464    path = "/v1/service_account/{id}/_credential/_status",
1465    responses(
1466        (status=200), // TODO: define response
1467        ApiResponseWithout200,
1468    ),
1469    security(("token_jwt" = [])),
1470    tag = "v1/service_account",
1471)]
1472pub async fn service_account_id_credential_status_get(
1473    State(state): State<ServerState>,
1474    Extension(kopid): Extension<KOpId>,
1475    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1476    Path(id): Path<String>,
1477) -> Result<Json<CredentialStatus>, WebError> {
1478    match state
1479        .qe_r_ref
1480        .handle_idmcredentialstatus(client_auth_info, id.clone(), kopid.eventid)
1481        .await
1482        .map(Json::from)
1483    {
1484        Ok(val) => Ok(val),
1485        Err(err) => {
1486            if let OperationError::NoMatchingAttributes = err {
1487                debug!("No credentials set on account {}, returning empty list", id);
1488                Ok(Json(CredentialStatus { creds: Vec::new() }))
1489            } else {
1490                Err(WebError::from(err))
1491            }
1492        }
1493    }
1494}
1495
1496#[utoipa::path(
1497    get,
1498    path = "/v1/person/{id}/_credential/_status",
1499    responses(
1500        (status=200), // TODO: define response
1501        ApiResponseWithout200,
1502    ),
1503    security(("token_jwt" = [])),
1504    tag = "v1/person/credential",
1505)]
1506pub async fn person_get_id_credential_status(
1507    State(state): State<ServerState>,
1508    Extension(kopid): Extension<KOpId>,
1509    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1510    Path(id): Path<String>,
1511) -> Result<Json<CredentialStatus>, WebError> {
1512    match state
1513        .qe_r_ref
1514        .handle_idmcredentialstatus(client_auth_info, id.clone(), kopid.eventid)
1515        .await
1516        .map(Json::from)
1517    {
1518        Ok(val) => Ok(val),
1519        Err(err) => {
1520            if let OperationError::NoMatchingAttributes = err {
1521                debug!("No credentials set on person {}, returning empty list", id);
1522                Ok(Json(CredentialStatus { creds: Vec::new() }))
1523            } else {
1524                Err(WebError::from(err))
1525            }
1526        }
1527    }
1528}
1529
1530#[utoipa::path(
1531    get,
1532    path = "/v1/person/{id}/_ssh_pubkeys",
1533    responses(
1534        (status=200, body=Vec<String>, content_type="application/json"),
1535        ApiResponseWithout200,
1536    ),
1537    security(("token_jwt" = [])),
1538    tag = "v1/person/ssh_pubkeys",
1539    operation_id = "person_id_ssh_pubkeys_get",
1540)]
1541pub async fn person_id_ssh_pubkeys_get(
1542    State(state): State<ServerState>,
1543    Extension(kopid): Extension<KOpId>,
1544    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1545    Path(id): Path<String>,
1546) -> Result<Json<Vec<String>>, WebError> {
1547    state
1548        .qe_r_ref
1549        .handle_internalsshkeyread(client_auth_info, id, kopid.eventid)
1550        .await
1551        .map(Json::from)
1552        .map_err(WebError::from)
1553}
1554
1555#[utoipa::path(
1556    get,
1557    path = "/v1/account/{id}/_ssh_pubkeys",
1558    responses(
1559        (status=200, body=Vec<String>, content_type="application/json"),
1560        ApiResponseWithout200,
1561    ),
1562    security(("token_jwt" = [])),
1563    tag = "v1/account",
1564    operation_id = "account_id_ssh_pubkeys_get",
1565)]
1566#[deprecated]
1567pub async fn account_id_ssh_pubkeys_get(
1568    State(state): State<ServerState>,
1569    Extension(kopid): Extension<KOpId>,
1570    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1571    Path(id): Path<String>,
1572) -> Result<Json<Vec<String>>, WebError> {
1573    state
1574        .qe_r_ref
1575        .handle_internalsshkeyread(client_auth_info, id, kopid.eventid)
1576        .await
1577        .map(Json::from)
1578        .map_err(WebError::from)
1579}
1580
1581#[utoipa::path(
1582    get,
1583    path = "/v1/service_account/{id}/_ssh_pubkeys",
1584    responses(
1585        (status=200, body=Vec<String>, content_type="application/json"),
1586        ApiResponseWithout200,
1587    ),
1588    security(("token_jwt" = [])),
1589    tag = "v1/service_account",
1590    operation_id = "service_account_id_ssh_pubkeys_get",
1591)]
1592pub async fn service_account_id_ssh_pubkeys_get(
1593    State(state): State<ServerState>,
1594    Extension(kopid): Extension<KOpId>,
1595    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1596    Path(id): Path<String>,
1597) -> Result<Json<Vec<String>>, WebError> {
1598    state
1599        .qe_r_ref
1600        .handle_internalsshkeyread(client_auth_info, id, kopid.eventid)
1601        .await
1602        .map(Json::from)
1603        .map_err(WebError::from)
1604}
1605
1606#[utoipa::path(
1607    post,
1608    path = "/v1/person/{id}/_ssh_pubkeys",
1609    responses(
1610        DefaultApiResponse,
1611        (status=422, description="Unprocessable Entity", body=String, content_type="text/plain"),
1612    ),
1613    security(("token_jwt" = [])),
1614    tag = "v1/person/ssh_pubkeys",
1615    operation_id = "person_id_ssh_pubkeys_post",
1616)]
1617pub async fn person_id_ssh_pubkeys_post(
1618    State(state): State<ServerState>,
1619    Extension(kopid): Extension<KOpId>,
1620    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1621    Path(id): Path<String>,
1622    Json((tag, key)): Json<(String, String)>,
1623) -> Result<Json<()>, WebError> {
1624    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1625    // Add a msg here
1626    state
1627        .qe_w_ref
1628        .handle_sshkeycreate(client_auth_info, id, &tag, &key, filter, kopid.eventid)
1629        .await
1630        .map(Json::from)
1631        .map_err(WebError::from)
1632}
1633
1634#[utoipa::path(
1635    post,
1636    path = "/v1/service_account/{id}/_ssh_pubkeys",
1637    request_body = (String, String),
1638    responses(
1639        DefaultApiResponse,
1640        (status=422, description="Unprocessable Entity", body=String, content_type="text/plain"),
1641    ),
1642    security(("token_jwt" = [])),
1643    tag = "v1/service_account",
1644    operation_id = "service_account_id_ssh_pubkeys_post",
1645)]
1646pub async fn service_account_id_ssh_pubkeys_post(
1647    State(state): State<ServerState>,
1648    Extension(kopid): Extension<KOpId>,
1649    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1650    Path(id): Path<String>,
1651    Json((tag, key)): Json<(String, String)>,
1652) -> Result<Json<()>, WebError> {
1653    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1654    // Add a msg here
1655    state
1656        .qe_w_ref
1657        .handle_sshkeycreate(client_auth_info, id, &tag, &key, filter, kopid.eventid)
1658        .await
1659        .map(Json::from)
1660        .map_err(WebError::from)
1661}
1662
1663#[utoipa::path(
1664    get,
1665    path = "/v1/person/{id}/_ssh_pubkeys/{tag}",
1666    responses(
1667        (status=200, body=String, content_type="application/json"),
1668        ApiResponseWithout200,
1669    ),
1670    security(("token_jwt" = [])),
1671    tag = "v1/person/ssh_pubkeys",
1672    operation_id = "person_id_ssh_pubkeys_tag_get",
1673)]
1674pub async fn person_id_ssh_pubkeys_tag_get(
1675    State(state): State<ServerState>,
1676    Extension(kopid): Extension<KOpId>,
1677    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1678    Path((id, tag)): Path<(String, String)>,
1679) -> Result<Json<Option<String>>, WebError> {
1680    state
1681        .qe_r_ref
1682        .handle_internalsshkeytagread(client_auth_info, id, tag, kopid.eventid)
1683        .await
1684        .map(Json::from)
1685        .map_err(WebError::from)
1686}
1687#[utoipa::path(
1688    get,
1689    path = "/v1/account/{id}/_ssh_pubkeys/{tag}",
1690    responses(
1691        (status=200, body=String, content_type="application/json"),
1692        ApiResponseWithout200,
1693    ),
1694    security(("token_jwt" = [])),
1695    tag = "v1/account",
1696    operation_id = "account_id_ssh_pubkeys_tag_get",
1697)]
1698pub async fn account_id_ssh_pubkeys_tag_get(
1699    State(state): State<ServerState>,
1700    Extension(kopid): Extension<KOpId>,
1701    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1702    Path((id, tag)): Path<(String, String)>,
1703) -> Result<Json<Option<String>>, WebError> {
1704    state
1705        .qe_r_ref
1706        .handle_internalsshkeytagread(client_auth_info, id, tag, kopid.eventid)
1707        .await
1708        .map(Json::from)
1709        .map_err(WebError::from)
1710}
1711
1712#[utoipa::path(
1713    get,
1714    path = "/v1/service_account/{id}/_ssh_pubkeys/{tag}",
1715    responses(
1716        (status=200, body=String, content_type="application/json"),
1717        ApiResponseWithout200,
1718    ),
1719    security(("token_jwt" = [])),
1720    tag = "v1/service_account",
1721    operation_id = "service_account_id_ssh_pubkeys_tag_get",
1722)]
1723pub async fn service_account_id_ssh_pubkeys_tag_get(
1724    State(state): State<ServerState>,
1725    Extension(kopid): Extension<KOpId>,
1726    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1727    Path((id, tag)): Path<(String, String)>,
1728) -> Result<Json<Option<String>>, WebError> {
1729    state
1730        .qe_r_ref
1731        .handle_internalsshkeytagread(client_auth_info, id, tag, kopid.eventid)
1732        .await
1733        .map(Json::from)
1734        .map_err(WebError::from)
1735}
1736
1737#[utoipa::path(
1738    delete,
1739    path = "/v1/person/{id}/_ssh_pubkeys/{tag}",
1740    params(
1741        ("tag" = String, description="The tag of the SSH key"),
1742    ),
1743    responses(
1744        DefaultApiResponse,
1745    ),
1746    security(("token_jwt" = [])),
1747    tag = "v1/person/ssh_pubkeys",
1748    operation_id = "person_id_ssh_pubkeys_tag_delete",
1749)]
1750pub async fn person_id_ssh_pubkeys_tag_delete(
1751    State(state): State<ServerState>,
1752    Extension(kopid): Extension<KOpId>,
1753    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1754    Path((id, tag)): Path<(String, String)>,
1755) -> Result<Json<()>, WebError> {
1756    let values = vec![tag];
1757    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1758    state
1759        .qe_w_ref
1760        .handle_removeattributevalues(
1761            client_auth_info,
1762            id,
1763            Attribute::SshPublicKey.to_string(),
1764            values,
1765            filter,
1766            kopid.eventid,
1767        )
1768        .await
1769        .map(Json::from)
1770        .map_err(WebError::from)
1771}
1772
1773#[utoipa::path(
1774    delete,
1775    path = "/v1/service_account/{id}/_ssh_pubkeys/{tag}",
1776    params(
1777        ("tag" = String, description="The tag of the SSH key"),
1778    ),
1779    responses(
1780        DefaultApiResponse,
1781    ),
1782    security(("token_jwt" = [])),
1783    tag = "v1/service_account",
1784    operation_id = "service_account_id_ssh_pubkeys_tag_delete",
1785)]
1786pub async fn service_account_id_ssh_pubkeys_tag_delete(
1787    State(state): State<ServerState>,
1788    Extension(kopid): Extension<KOpId>,
1789    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1790    Path((id, tag)): Path<(String, String)>,
1791) -> Result<Json<()>, WebError> {
1792    let values = vec![tag];
1793    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1794    state
1795        .qe_w_ref
1796        .handle_removeattributevalues(
1797            client_auth_info,
1798            id,
1799            Attribute::SshPublicKey.to_string(),
1800            values,
1801            filter,
1802            kopid.eventid,
1803        )
1804        .await
1805        .map(Json::from)
1806        .map_err(WebError::from)
1807}
1808
1809#[utoipa::path(
1810    get,
1811    path = "/v1/person/{id}/_radius",
1812    responses(
1813        (status=200), // TODO: define response
1814        ApiResponseWithout200,
1815    ),
1816    security(("token_jwt" = [])),
1817    tag = "v1/person/radius",
1818    operation_id = "person_id_radius_get"
1819)]
1820/// Get and return a single str
1821pub async fn person_id_radius_get(
1822    State(state): State<ServerState>,
1823    Extension(kopid): Extension<KOpId>,
1824    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1825    Path(id): Path<String>,
1826) -> Result<Json<Option<String>>, WebError> {
1827    // TODO: string
1828    state
1829        .qe_r_ref
1830        .handle_internalradiusread(client_auth_info, id, kopid.eventid)
1831        .await
1832        .map(Json::from)
1833        .map_err(WebError::from)
1834}
1835
1836#[utoipa::path(
1837    post,
1838    path = "/v1/person/{id}/_radius",
1839    responses(
1840        (status=200), // TODO: define response
1841        ApiResponseWithout200,
1842    ),
1843    security(("token_jwt" = [])),
1844    tag = "v1/person/radius",
1845    operation_id = "person_id_radius_post"
1846)]
1847pub async fn person_id_radius_post(
1848    State(state): State<ServerState>,
1849    Extension(kopid): Extension<KOpId>,
1850    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1851    Path(id): Path<String>,
1852) -> Result<Json<String>, WebError> {
1853    // Need to to send the regen msg
1854    state
1855        .qe_w_ref
1856        .handle_regenerateradius(client_auth_info, id, kopid.eventid)
1857        .await
1858        .map(Json::from)
1859        .map_err(WebError::from)
1860}
1861
1862#[utoipa::path(
1863    delete,
1864    path = "/v1/person/{id}/_radius",
1865    responses(
1866        DefaultApiResponse,
1867    ),
1868    security(("token_jwt" = [])),
1869    tag = "v1/person/radius",
1870    operation_id = "person_id_radius_delete"
1871)]
1872pub async fn person_id_radius_delete(
1873    State(state): State<ServerState>,
1874    Path(id): Path<String>,
1875    Extension(kopid): Extension<KOpId>,
1876    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1877) -> Result<Json<()>, WebError> {
1878    let attr = "radius_secret".to_string();
1879    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Account.into()));
1880    json_rest_event_delete_id_attr(state, id, attr, filter, None, kopid, client_auth_info).await
1881}
1882
1883#[utoipa::path(
1884    get,
1885    path = "/v1/person/{id}/_radius/_token",
1886    responses(
1887        (status=200, body=RadiusAuthToken, content_type="application/json"),
1888        ApiResponseWithout200,
1889    ),
1890    security(("token_jwt" = [])),
1891    tag = "v1/person/radius",
1892    operation_id = "person_id_radius_token_get"
1893)]
1894pub async fn person_id_radius_token_get(
1895    State(state): State<ServerState>,
1896    Path(id): Path<String>,
1897    Extension(kopid): Extension<KOpId>,
1898    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1899) -> Result<Json<RadiusAuthToken>, WebError> {
1900    person_id_radius_handler(state, id, kopid, client_auth_info).await
1901}
1902
1903// /v1/account/:id/_radius/_token
1904#[utoipa::path(
1905    get,
1906    path = "/v1/account/{id}/_radius/_token",
1907    responses(
1908        (status=200, body=RadiusAuthToken, content_type="application/json"),
1909        ApiResponseWithout200,
1910    ),
1911    security(("token_jwt" = [])),
1912    tag = "v1/account",
1913    operation_id = "account_id_radius_token_get"
1914)]
1915pub async fn account_id_radius_token_get(
1916    State(state): State<ServerState>,
1917    Path(id): Path<String>,
1918    Extension(kopid): Extension<KOpId>,
1919    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1920) -> Result<Json<RadiusAuthToken>, WebError> {
1921    person_id_radius_handler(state, id, kopid, client_auth_info).await
1922}
1923
1924#[utoipa::path(
1925    post,
1926    path = "/v1/account/{id}/_radius/_token",
1927    responses(
1928        (status=200, body=RadiusAuthToken, content_type="application/json"),
1929        ApiResponseWithout200,
1930    ),
1931    security(("token_jwt" = [])),
1932    tag = "v1/account",
1933    operation_id = "account_id_radius_token_post"
1934)]
1935pub async fn account_id_radius_token_post(
1936    State(state): State<ServerState>,
1937    Path(id): Path<String>,
1938    Extension(kopid): Extension<KOpId>,
1939    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1940) -> Result<Json<RadiusAuthToken>, WebError> {
1941    person_id_radius_handler(state, id, kopid, client_auth_info).await
1942}
1943
1944async fn person_id_radius_handler(
1945    state: ServerState,
1946    id: String,
1947    kopid: KOpId,
1948    client_auth_info: ClientAuthInfo,
1949) -> Result<Json<RadiusAuthToken>, WebError> {
1950    state
1951        .qe_r_ref
1952        .handle_internalradiustokenread(client_auth_info, id, kopid.eventid)
1953        .await
1954        .map(Json::from)
1955        .map_err(WebError::from)
1956}
1957
1958#[utoipa::path(
1959    post,
1960    path = "/v1/person/{id}/_unix",
1961    request_body=AccountUnixExtend,
1962    responses(
1963        DefaultApiResponse,
1964    ),
1965    security(("token_jwt" = [])),
1966    tag = "v1/person/unix",
1967)]
1968#[instrument(name = "account_post_id_unix", level = "INFO", skip(id, state, kopid))]
1969pub async fn person_id_unix_post(
1970    State(state): State<ServerState>,
1971    Path(id): Path<String>,
1972    Extension(kopid): Extension<KOpId>,
1973    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
1974    Json(obj): Json<AccountUnixExtend>,
1975) -> Result<Json<()>, WebError> {
1976    state
1977        .qe_w_ref
1978        .handle_idmaccountunixextend(client_auth_info, id, obj, kopid.eventid)
1979        .await
1980        .map(Json::from)
1981        .map_err(WebError::from)
1982}
1983
1984#[utoipa::path(
1985    post,
1986    path = "/v1/service_account/{id}/_unix",
1987    request_body = AccountUnixExtend,
1988    responses(
1989        DefaultApiResponse,
1990    ),
1991    security(("token_jwt" = [])),
1992    tag = "v1/service_account",
1993)]
1994#[instrument(, level = "INFO", skip(id, state, kopid))]
1995pub async fn service_account_id_unix_post(
1996    State(state): State<ServerState>,
1997    Path(id): Path<String>,
1998    Extension(kopid): Extension<KOpId>,
1999    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2000    Json(obj): Json<AccountUnixExtend>,
2001) -> Result<Json<()>, WebError> {
2002    state
2003        .qe_w_ref
2004        .handle_idmaccountunixextend(client_auth_info, id, obj, kopid.eventid)
2005        .await
2006        .map(Json::from)
2007        .map_err(WebError::from)
2008}
2009
2010#[utoipa::path(
2011    get,post,
2012    path = "/v1/account/{id}/_unix/_token",
2013    responses(
2014        (status=200, body=UnixUserToken, content_type="application/json"),
2015        ApiResponseWithout200,
2016    ),
2017    security(("token_jwt" = [])),
2018    tag = "v1/account",
2019    operation_id = "account_id_unix_token"
2020)]
2021#[instrument(level = "INFO", skip_all)]
2022pub async fn account_id_unix_token(
2023    State(state): State<ServerState>,
2024    Extension(kopid): Extension<KOpId>,
2025    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2026    Path(id): Path<String>,
2027) -> Result<Json<UnixUserToken>, WebError> {
2028    // no point asking for an empty id
2029    if id.is_empty() {
2030        return Err(OperationError::EmptyRequest.into());
2031    }
2032
2033    let res = state
2034        .qe_r_ref
2035        .handle_internalunixusertokenread(client_auth_info, id, kopid.eventid)
2036        .await
2037        .map(Json::from);
2038
2039    // if they're not a posix user we should just hide them
2040    if let Err(OperationError::MissingClass(class)) = &res {
2041        if class == ENTRYCLASS_POSIX_ACCOUNT {
2042            return Err(OperationError::NoMatchingEntries.into());
2043        }
2044    };
2045    // the was returning a 500 error which wasn't right
2046    if let Err(OperationError::InvalidValueState) = &res {
2047        return Err(OperationError::NoMatchingEntries.into());
2048    };
2049    res.map_err(WebError::from)
2050}
2051
2052#[utoipa::path(
2053    post,
2054    path = "/v1/account/{id}/_unix/_auth",
2055    responses(
2056        (status=200, body=Option<UnixUserToken>, content_type="application/json"),
2057        ApiResponseWithout200,
2058    ),
2059    security(("token_jwt" = [])),
2060    tag = "v1/account",
2061    operation_id = "account_id_unix_auth_post"
2062)]
2063pub async fn account_id_unix_auth_post(
2064    State(state): State<ServerState>,
2065    Extension(kopid): Extension<KOpId>,
2066    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2067    Path(id): Path<String>,
2068    Json(obj): Json<SingleStringRequest>,
2069) -> Result<Json<Option<UnixUserToken>>, WebError> {
2070    state
2071        .qe_r_ref
2072        .handle_idmaccountunixauth(client_auth_info, id, obj.value, kopid.eventid)
2073        .await
2074        .map(Json::from)
2075        .map_err(WebError::from)
2076}
2077
2078#[utoipa::path(
2079    put,
2080    path = "/v1/person/{id}/_unix/_credential",
2081    request_body = SingleStringRequest,
2082    responses(
2083        DefaultApiResponse,
2084    ),
2085    security(("token_jwt" = [])),
2086    tag = "v1/person/unix",
2087    operation_id = "person_id_unix_credential_put"
2088)]
2089pub async fn person_id_unix_credential_put(
2090    State(state): State<ServerState>,
2091    Extension(kopid): Extension<KOpId>,
2092    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2093    Path(id): Path<String>,
2094    Json(obj): Json<SingleStringRequest>,
2095) -> Result<Json<()>, WebError> {
2096    state
2097        .qe_w_ref
2098        .handle_idmaccountunixsetcred(client_auth_info, id, obj.value, kopid.eventid)
2099        .await
2100        .map(Json::from)
2101        .map_err(WebError::from)
2102}
2103
2104#[utoipa::path(
2105    delete,
2106    path = "/v1/person/{id}/_unix/_credential",
2107    responses(
2108        DefaultApiResponse,
2109    ),
2110    security(("token_jwt" = [])),
2111    tag = "v1/person/unix",
2112    operation_id = "person_id_unix_credential_delete"
2113)]
2114pub async fn person_id_unix_credential_delete(
2115    State(state): State<ServerState>,
2116    Extension(kopid): Extension<KOpId>,
2117    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2118    Path(id): Path<String>,
2119) -> Result<Json<()>, WebError> {
2120    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::PosixAccount.into()));
2121    state
2122        .qe_w_ref
2123        .handle_purgeattribute(
2124            client_auth_info,
2125            id,
2126            "unix_password".to_string(),
2127            filter,
2128            kopid.eventid,
2129        )
2130        .await
2131        .map(Json::from)
2132        .map_err(WebError::from)
2133}
2134
2135#[utoipa::path(
2136    post,
2137    path = "/v1/person/{id}/_identify/_user",
2138    responses(
2139        (status=200, body=IdentifyUserResponse, content_type="application/json"),
2140        ApiResponseWithout200,
2141    ),
2142    security(("token_jwt" = [])),
2143    tag = "v1/person",
2144    operation_id = "person_identify_user_post"
2145)]
2146pub async fn person_identify_user_post(
2147    State(state): State<ServerState>,
2148    Extension(kopid): Extension<KOpId>,
2149    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2150    Path(id): Path<String>,
2151    Json(user_request): Json<IdentifyUserRequest>,
2152) -> Result<Json<IdentifyUserResponse>, WebError> {
2153    state
2154        .qe_r_ref
2155        .handle_user_identity_verification(client_auth_info, kopid.eventid, user_request, id)
2156        .await
2157        .map(Json::from)
2158        .map_err(WebError::from)
2159}
2160
2161#[utoipa::path(
2162    get,
2163    path = "/v1/group",
2164    responses(
2165        (status=200,body=Vec<ProtoEntry>, content_type="application/json"),
2166        ApiResponseWithout200,
2167    ),
2168    security(("token_jwt" = [])),
2169    tag = "v1/group",
2170    operation_id = "group_get",
2171)]
2172/// Returns all groups visible  to the user
2173pub async fn group_get(
2174    State(state): State<ServerState>,
2175    Extension(kopid): Extension<KOpId>,
2176    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2177) -> Result<Json<Vec<ProtoEntry>>, WebError> {
2178    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Group.into()));
2179    json_rest_event_get(state, None, filter, kopid, client_auth_info).await
2180}
2181
2182#[utoipa::path(
2183    get,
2184    path = "/v1/group/_search/{id}",
2185    responses(
2186        (status=200, body=Option<ProtoEntry>, content_type="application/json"),
2187        ApiResponseWithout200,
2188    ),
2189    security(("token_jwt" = [])),
2190    tag = "v1/group",
2191    operation_id = "group_search_id",
2192)]
2193pub async fn group_search_id(
2194    State(state): State<ServerState>,
2195    Extension(kopid): Extension<KOpId>,
2196    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2197    Path(id): Path<String>,
2198) -> Result<Json<Vec<ProtoEntry>>, WebError> {
2199    let filter = filter_all!(f_and!([
2200        f_eq(Attribute::Class, EntryClass::Group.into()),
2201        f_sub(Attribute::Name, PartialValue::new_iname(&id))
2202    ]));
2203    json_rest_event_get(state, None, filter, kopid, client_auth_info).await
2204}
2205
2206#[utoipa::path(
2207    post,
2208    path = "/v1/group",
2209    responses(
2210        DefaultApiResponse,
2211    ),
2212    security(("token_jwt" = [])),
2213    tag = "v1/group",
2214    operation_id = "group_post",
2215)]
2216pub async fn group_post(
2217    State(state): State<ServerState>,
2218    Extension(kopid): Extension<KOpId>,
2219    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2220    Json(obj): Json<ProtoEntry>,
2221) -> Result<Json<()>, WebError> {
2222    let classes = vec!["group".to_string(), "object".to_string()];
2223    json_rest_event_post(state, classes, obj, kopid, client_auth_info).await
2224}
2225
2226#[utoipa::path(
2227    get,
2228    path = "/v1/group/{id}",
2229    responses(
2230        (status=200, body=Option<ProtoEntry>, content_type="application/json"),
2231        ApiResponseWithout200,
2232    ),
2233    security(("token_jwt" = [])),
2234    tag = "v1/group",
2235    operation_id = "group_id_get",
2236)]
2237pub async fn group_id_get(
2238    State(state): State<ServerState>,
2239    Extension(kopid): Extension<KOpId>,
2240    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2241    Path(id): Path<String>,
2242) -> Result<Json<Option<ProtoEntry>>, WebError> {
2243    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Group.into()));
2244    json_rest_event_get_id(state, id, filter, None, kopid, client_auth_info).await
2245}
2246
2247#[utoipa::path(
2248    patch,
2249    path = "/v1/group/{id}",
2250    responses(
2251        DefaultApiResponse,
2252    ),
2253    request_body=ProtoEntry,
2254    security(("token_jwt" = [])),
2255    tag = "v1/group",
2256    operation_id = "group_id_patch",
2257)]
2258pub async fn group_id_patch(
2259    State(state): State<ServerState>,
2260    Extension(kopid): Extension<KOpId>,
2261    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2262    Path(id): Path<String>,
2263    Json(obj): Json<ProtoEntry>,
2264) -> Result<Json<()>, WebError> {
2265    // Update a value / attrs
2266    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Group.into()));
2267    let filter = Filter::join_parts_and(filter, filter_all!(f_id(id.as_str())));
2268    state
2269        .qe_w_ref
2270        .handle_internalpatch(client_auth_info, filter, obj, kopid.eventid)
2271        .await
2272        .map(Json::from)
2273        .map_err(WebError::from)
2274}
2275
2276#[utoipa::path(
2277    delete,
2278    path = "/v1/group/{id}",
2279    responses(
2280        DefaultApiResponse,
2281    ),
2282    security(("token_jwt" = [])),
2283    tag = "v1/group",
2284    operation_id = "group_id_delete",
2285)]
2286pub async fn group_id_delete(
2287    State(state): State<ServerState>,
2288    Extension(kopid): Extension<KOpId>,
2289    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2290    Path(id): Path<String>,
2291) -> Result<Json<()>, WebError> {
2292    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Group.into()));
2293    json_rest_event_delete_id(state, id, filter, kopid, client_auth_info).await
2294}
2295
2296#[utoipa::path(
2297    get,
2298    path = "/v1/group/{id}/_attr/{attr}",
2299    responses(
2300        (status=200, body=Vec<String>, content_type="application/json"),
2301        ApiResponseWithout200,
2302    ),
2303    security(("token_jwt" = [])),
2304    tag = "v1/group/attr",
2305    operation_id = "group_id_attr_get",
2306)]
2307pub async fn group_id_attr_get(
2308    State(state): State<ServerState>,
2309    Path((id, attr)): Path<(String, String)>,
2310    Extension(kopid): Extension<KOpId>,
2311    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2312) -> Result<Json<Option<Vec<String>>>, WebError> {
2313    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Group.into()));
2314    json_rest_event_get_id_attr(state, id, attr, filter, kopid, client_auth_info).await
2315}
2316
2317#[utoipa::path(
2318    post,
2319    path = "/v1/group/{id}/_attr/{attr}",
2320    request_body=Vec<String>,
2321    responses(
2322        DefaultApiResponse,
2323    ),
2324    security(("token_jwt" = [])),
2325    tag = "v1/group/attr",
2326    operation_id = "group_id_attr_post",
2327)]
2328pub async fn group_id_attr_post(
2329    Path((id, attr)): Path<(String, String)>,
2330    State(state): State<ServerState>,
2331    Extension(kopid): Extension<KOpId>,
2332    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2333    Json(values): Json<Vec<String>>,
2334) -> Result<Json<()>, WebError> {
2335    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Group.into()));
2336    json_rest_event_post_id_attr(state, id, attr, filter, values, kopid, client_auth_info).await
2337}
2338
2339#[utoipa::path(
2340    delete,
2341    path = "/v1/group/{id}/_attr/{attr}",
2342    request_body=Option<Vec<String>>,
2343    responses(
2344        DefaultApiResponse,
2345    ),
2346    security(("token_jwt" = [])),
2347    tag = "v1/group/attr",
2348    operation_id = "group_id_attr_delete",
2349)]
2350pub async fn group_id_attr_delete(
2351    Path((id, attr)): Path<(String, String)>,
2352    State(state): State<ServerState>,
2353    Extension(kopid): Extension<KOpId>,
2354    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2355    values: Option<Json<Vec<String>>>,
2356) -> Result<Json<()>, WebError> {
2357    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Group.into()));
2358    let values = values.map(|v| v.0);
2359    json_rest_event_delete_id_attr(state, id, attr, filter, values, kopid, client_auth_info).await
2360}
2361
2362#[utoipa::path(
2363    put,
2364    path = "/v1/group/{id}/_attr/{attr}",
2365    request_body=Vec<String>,
2366    responses(
2367        DefaultApiResponse,
2368    ),
2369    security(("token_jwt" = [])),
2370    tag = "v1/group/attr",
2371    operation_id = "group_id_attr_put",
2372)]
2373pub async fn group_id_attr_put(
2374    Path((id, attr)): Path<(String, String)>,
2375    State(state): State<ServerState>,
2376    Extension(kopid): Extension<KOpId>,
2377    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2378    Json(values): Json<Vec<String>>,
2379) -> Result<Json<()>, WebError> {
2380    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::Group.into()));
2381    json_rest_event_put_attr(state, id, attr, filter, values, kopid, client_auth_info).await
2382}
2383
2384#[utoipa::path(
2385    post,
2386    path = "/v1/group/{id}/_unix",
2387    request_body = GroupUnixExtend,
2388    responses(
2389        DefaultApiResponse,
2390    ),
2391    security(("token_jwt" = [])),
2392    tag = "v1/group/unix",
2393    operation_id = "group_id_unix_post",
2394)]
2395pub async fn group_id_unix_post(
2396    State(state): State<ServerState>,
2397    Path(id): Path<String>,
2398    Extension(kopid): Extension<KOpId>,
2399    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2400    Json(obj): Json<GroupUnixExtend>,
2401) -> Result<Json<()>, WebError> {
2402    state
2403        .qe_w_ref
2404        .handle_idmgroupunixextend(client_auth_info, id, obj, kopid.eventid)
2405        .await
2406        .map(Json::from)
2407        .map_err(WebError::from)
2408}
2409
2410#[utoipa::path(
2411    get,
2412    path = "/v1/group/{id}/_unix/_token",
2413    responses(
2414        (status=200, body=UnixGroupToken, content_type="application/json"),
2415        ApiResponseWithout200,
2416    ),
2417    security(("token_jwt" = [])),
2418    tag = "v1/group/unix",
2419    operation_id = "group_id_unix_token_get",
2420)]
2421pub async fn group_id_unix_token_get(
2422    State(state): State<ServerState>,
2423    Extension(kopid): Extension<KOpId>,
2424    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2425    Path(id): Path<String>,
2426) -> Result<Json<UnixGroupToken>, WebError> {
2427    state
2428        .qe_r_ref
2429        .handle_internalunixgrouptokenread(client_auth_info, id, kopid.eventid)
2430        .await
2431        .map(Json::from)
2432        .map_err(WebError::from)
2433}
2434
2435#[utoipa::path(
2436    get,
2437    path = "/v1/domain",
2438    responses(
2439        (status=200, body=Vec<ProtoEntry>, content_type="application/json"),
2440        ApiResponseWithout200,
2441    ),
2442    security(("token_jwt" = [])),
2443    tag = "v1/domain",
2444    operation_id = "domain_get",
2445)]
2446pub async fn domain_get(
2447    State(state): State<ServerState>,
2448    Extension(kopid): Extension<KOpId>,
2449    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2450) -> Result<Json<Vec<ProtoEntry>>, WebError> {
2451    let filter = filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO)));
2452    json_rest_event_get(state, None, filter, kopid, client_auth_info).await
2453}
2454
2455#[utoipa::path(
2456    get,
2457    path = "/v1/domain/_attr/{attr}",
2458    responses(
2459        (status=200, body=Option<Vec<String>>, content_type="application/json"),
2460        ApiResponseWithout200,
2461    ),
2462    security(("token_jwt" = [])),
2463    tag = "v1/domain",
2464    operation_id = "domain_attr_get",
2465)]
2466pub async fn domain_attr_get(
2467    State(state): State<ServerState>,
2468    Extension(kopid): Extension<KOpId>,
2469    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2470    Path(attr): Path<String>,
2471) -> Result<Json<Option<Vec<String>>>, WebError> {
2472    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::DomainInfo.into()));
2473    json_rest_event_get_attr(
2474        state,
2475        STR_UUID_DOMAIN_INFO,
2476        attr,
2477        filter,
2478        kopid,
2479        client_auth_info,
2480    )
2481    .await
2482}
2483
2484#[utoipa::path(
2485    put,
2486    path = "/v1/domain/_attr/{attr}",
2487    request_body=Vec<String>,
2488    responses(
2489        DefaultApiResponse,
2490    ),
2491    security(("token_jwt" = [])),
2492    tag = "v1/domain",
2493    operation_id = "domain_attr_put",
2494)]
2495pub async fn domain_attr_put(
2496    State(state): State<ServerState>,
2497    Extension(kopid): Extension<KOpId>,
2498    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2499    Path(attr): Path<String>,
2500    Json(values): Json<Vec<String>>,
2501) -> Result<Json<()>, WebError> {
2502    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::DomainInfo.into()));
2503
2504    json_rest_event_put_attr(
2505        state,
2506        STR_UUID_DOMAIN_INFO.to_string(),
2507        attr,
2508        filter,
2509        values,
2510        kopid,
2511        client_auth_info,
2512    )
2513    .await
2514}
2515
2516#[utoipa::path(
2517    delete,
2518    path = "/v1/domain/_attr/{attr}",
2519    request_body=Option<Vec<String>>,
2520    responses(
2521        DefaultApiResponse,
2522    ),
2523    security(("token_jwt" = [])),
2524    tag = "v1/domain",
2525    operation_id = "domain_attr_delete",
2526)]
2527pub async fn domain_attr_delete(
2528    State(state): State<ServerState>,
2529    Path(attr): Path<String>,
2530    Extension(kopid): Extension<KOpId>,
2531    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2532    Json(values): Json<Option<Vec<String>>>,
2533) -> Result<Json<()>, WebError> {
2534    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::DomainInfo.into()));
2535    json_rest_event_delete_attr(
2536        state,
2537        STR_UUID_DOMAIN_INFO.to_string(),
2538        attr,
2539        filter,
2540        values,
2541        kopid,
2542        client_auth_info,
2543    )
2544    .await
2545}
2546
2547#[utoipa::path(
2548    get,
2549    path = "/v1/system",
2550    responses(
2551        (status=200,body=Vec<ProtoEntry>, content_type="application/json"),
2552        ApiResponseWithout200,
2553    ),
2554    security(("token_jwt" = [])),
2555    tag = "v1/system",
2556    operation_id = "system_get",
2557)]
2558pub async fn system_get(
2559    State(state): State<ServerState>,
2560    Extension(kopid): Extension<KOpId>,
2561    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2562) -> Result<Json<Vec<ProtoEntry>>, WebError> {
2563    let filter = filter_all!(f_eq(
2564        Attribute::Uuid,
2565        PartialValue::Uuid(UUID_SYSTEM_CONFIG)
2566    ));
2567    json_rest_event_get(state, None, filter, kopid, client_auth_info).await
2568}
2569
2570#[utoipa::path(
2571    get,
2572    path = "/v1/system/_attr/{attr}",
2573    responses(
2574        (status=200, body=Option<Vec<String>>, content_type="application/json"),
2575        ApiResponseWithout200,
2576    ),
2577    security(("token_jwt" = [])),
2578    tag = "v1/system",
2579    operation_id = "system_attr_get",
2580)]
2581pub async fn system_attr_get(
2582    State(state): State<ServerState>,
2583    Path(attr): Path<String>,
2584    Extension(kopid): Extension<KOpId>,
2585    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2586) -> Result<Json<Option<Vec<String>>>, WebError> {
2587    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SystemConfig.into()));
2588    json_rest_event_get_attr(
2589        state,
2590        STR_UUID_SYSTEM_CONFIG,
2591        attr,
2592        filter,
2593        kopid,
2594        client_auth_info,
2595    )
2596    .await
2597}
2598
2599#[utoipa::path(
2600    post,
2601    path = "/v1/system/_attr/{attr}",
2602    request_body=Vec<String>,
2603    responses(
2604        DefaultApiResponse,
2605    ),
2606    security(("token_jwt" = [])),
2607    tag = "v1/system",
2608    operation_id = "system_attr_post",
2609)]
2610pub async fn system_attr_post(
2611    State(state): State<ServerState>,
2612    Path(attr): Path<String>,
2613    Extension(kopid): Extension<KOpId>,
2614    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2615    Json(values): Json<Vec<String>>,
2616) -> Result<Json<()>, WebError> {
2617    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SystemConfig.into()));
2618    json_rest_event_post_attr(
2619        state,
2620        STR_UUID_SYSTEM_CONFIG.to_string(),
2621        attr,
2622        filter,
2623        values,
2624        kopid,
2625        client_auth_info,
2626    )
2627    .await
2628}
2629
2630#[utoipa::path(
2631    delete,
2632    path = "/v1/system/_attr/{attr}",
2633    request_body=Option<Vec<String>>,
2634    responses(
2635        DefaultApiResponse,
2636    ),
2637    security(("token_jwt" = [])),
2638    tag = "v1/system",
2639    operation_id = "system_attr_delete",
2640)]
2641pub async fn system_attr_delete(
2642    State(state): State<ServerState>,
2643    Path(attr): Path<String>,
2644    Extension(kopid): Extension<KOpId>,
2645    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2646    Json(values): Json<Option<Vec<String>>>,
2647) -> Result<Json<()>, WebError> {
2648    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SystemConfig.into()));
2649    json_rest_event_delete_attr(
2650        state,
2651        STR_UUID_SYSTEM_CONFIG.to_string(),
2652        attr,
2653        filter,
2654        values,
2655        kopid,
2656        client_auth_info,
2657    )
2658    .await
2659}
2660
2661#[utoipa::path(
2662    put,
2663    path = "/v1/system/_attr/{attr}",
2664    request_body=Vec<String>,
2665    responses(
2666        DefaultApiResponse,
2667    ),
2668    security(("token_jwt" = [])),
2669    tag = "v1/system",
2670    operation_id = "system_attr_put",
2671)]
2672pub async fn system_attr_put(
2673    State(state): State<ServerState>,
2674    Path(attr): Path<String>,
2675    Extension(kopid): Extension<KOpId>,
2676    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2677    Json(values): Json<Vec<String>>,
2678) -> Result<Json<()>, WebError> {
2679    let filter = filter_all!(f_eq(Attribute::Class, EntryClass::SystemConfig.into()));
2680    json_rest_event_put_attr(
2681        state,
2682        STR_UUID_SYSTEM_CONFIG.to_string(),
2683        attr,
2684        filter,
2685        values,
2686        kopid,
2687        client_auth_info,
2688    )
2689    .await
2690}
2691
2692#[utoipa::path(
2693    post,
2694    path = "/v1/recycle_bin",
2695    responses(
2696        (status=200,body=Vec<ProtoEntry>, content_type="application/json"),
2697        ApiResponseWithout200,
2698    ),
2699    security(("token_jwt" = [])),
2700    tag = "v1/recycle_bin",
2701    operation_id="recycle_bin_get",
2702)]
2703pub async fn recycle_bin_get(
2704    State(state): State<ServerState>,
2705    Extension(kopid): Extension<KOpId>,
2706    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2707) -> Result<Json<Vec<ProtoEntry>>, WebError> {
2708    let filter = filter_all!(f_pres(Attribute::Class));
2709    let attrs = None;
2710    state
2711        .qe_r_ref
2712        .handle_internalsearchrecycled(client_auth_info, filter, attrs, kopid.eventid)
2713        .await
2714        .map(Json::from)
2715        .map_err(WebError::from)
2716}
2717
2718#[utoipa::path(
2719    get,
2720    path = "/v1/recycle_bin/{id}",
2721    responses(
2722        (status=200, body=Option<ProtoEntry>, content_type="application/json"),
2723        ApiResponseWithout200,
2724    ),
2725    security(("token_jwt" = [])),
2726    tag = "v1/recycle_bin",
2727    operation_id = "recycle_bin_id_get",
2728)]
2729pub async fn recycle_bin_id_get(
2730    State(state): State<ServerState>,
2731    Path(id): Path<String>,
2732    Extension(kopid): Extension<KOpId>,
2733    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2734) -> Result<Json<Option<ProtoEntry>>, WebError> {
2735    let filter = filter_all!(f_id(id.as_str()));
2736    let attrs = None;
2737
2738    state
2739        .qe_r_ref
2740        .handle_internalsearchrecycled(client_auth_info, filter, attrs, kopid.eventid)
2741        .await
2742        .map(|mut r| r.pop())
2743        .map(Json::from)
2744        .map_err(WebError::from)
2745}
2746
2747#[utoipa::path(
2748    post,
2749    path = "/v1/recycle_bin/{id}/_revive",
2750    responses(
2751        DefaultApiResponse,
2752    ),
2753    security(("token_jwt" = [])),
2754    tag = "v1/recycle_bin",
2755    operation_id = "recycle_bin_revive_id_post",
2756)]
2757pub async fn recycle_bin_revive_id_post(
2758    State(state): State<ServerState>,
2759    Path(id): Path<String>,
2760    Extension(kopid): Extension<KOpId>,
2761    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2762) -> Result<Json<()>, WebError> {
2763    let filter = filter_all!(f_id(id.as_str()));
2764    state
2765        .qe_w_ref
2766        .handle_reviverecycled(client_auth_info, filter, kopid.eventid)
2767        .await
2768        .map(Json::from)
2769        .map_err(WebError::from)
2770}
2771
2772#[utoipa::path(
2773    get,
2774    path = "/v1/self/_applinks",
2775    responses(
2776        (status=200, body=Vec<AppLink>, content_type="application/json"),
2777        ApiResponseWithout200,
2778    ),
2779    security(("token_jwt" = [])),
2780    tag = "v1/self",
2781    operation_id = "self_applinks_get",
2782)]
2783/// Returns your OAuth2 app links for the Web UI
2784pub async fn applinks_get(
2785    State(state): State<ServerState>,
2786    Extension(kopid): Extension<KOpId>,
2787    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2788) -> Result<Json<Vec<AppLink>>, WebError> {
2789    state
2790        .qe_r_ref
2791        .handle_list_applinks(client_auth_info, kopid.eventid)
2792        .await
2793        .map(Json::from)
2794        .map_err(WebError::from)
2795}
2796
2797#[utoipa::path(
2798    post,
2799    path = "/v1/reauth",
2800    responses(
2801        (status=200, content_type="application/json"), // TODO: define response
2802        ApiResponseWithout200,
2803    ),
2804    request_body = AuthIssueSession,
2805    security(("token_jwt" = [])),
2806    tag = "v1/auth",
2807    operation_id = "reauth_post",
2808)] // TODO: post body stuff
2809pub async fn reauth(
2810    State(state): State<ServerState>,
2811    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2812    jar: CookieJar,
2813    Extension(kopid): Extension<KOpId>,
2814    Json(obj): Json<AuthIssueSession>,
2815) -> Result<Response, WebError> {
2816    // This may change in the future ...
2817    let inter = state
2818        .qe_r_ref
2819        .handle_reauth(client_auth_info, obj, kopid.eventid)
2820        .await;
2821    debug!("ReAuth result: {:?}", inter);
2822    auth_session_state_management(&state, jar, inter)
2823}
2824
2825#[utoipa::path(
2826    post,
2827    path = "/v1/auth",
2828    responses(
2829        (status=200, content_type="application/json"), // TODO: define response
2830        ApiResponseWithout200,
2831    ),
2832    request_body = AuthRequest,
2833    security(("token_jwt" = [])),
2834    tag = "v1/auth",
2835    operation_id = "auth_post",
2836)]
2837pub async fn auth(
2838    State(state): State<ServerState>,
2839    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
2840    jar: CookieJar,
2841    headers: HeaderMap,
2842    Extension(kopid): Extension<KOpId>,
2843    Json(obj): Json<AuthRequest>,
2844) -> Result<Response, WebError> {
2845    // First, deal with some state management.
2846    // Do anything here first that's needed like getting the session details
2847    // out of the req cookie.
2848
2849    let maybe_sessionid = state.get_current_auth_session_id(&headers, &jar);
2850    debug!("Session ID: {:?}", maybe_sessionid);
2851
2852    // We probably need to know if we allocate the cookie, that this is a
2853    // new session, and in that case, anything *except* authrequest init is
2854    // invalid.
2855    let inter = state // This may change in the future ...
2856        .qe_r_ref
2857        .handle_auth(maybe_sessionid, obj, kopid.eventid, client_auth_info)
2858        .await;
2859    debug!("Auth result: {:?}", inter);
2860    auth_session_state_management(&state, jar, inter)
2861}
2862
2863// Disable on any level except trace to stop leaking tokens
2864#[instrument(level = "trace", skip_all)]
2865fn auth_session_state_management(
2866    state: &ServerState,
2867    mut jar: CookieJar,
2868    inter: Result<AuthResult, OperationError>,
2869) -> Result<Response, WebError> {
2870    let mut auth_session_id_tok = None;
2871
2872    let res: Result<AuthResponse, _> = match inter {
2873        Ok(AuthResult {
2874            state: auth_state,
2875            sessionid,
2876        }) => {
2877            // Do some response/state management.
2878            match auth_state {
2879                AuthState::Choose(allowed) => {
2880                    debug!("🧩 -> AuthState::Choose");
2881                    let kref = &state.jws_signer;
2882                    let jws = Jws::into_json(&sessionid).map_err(|e| {
2883                        error!(?e);
2884                        OperationError::InvalidSessionState
2885                    })?;
2886
2887                    // Get the header token ready.
2888                    kref.sign(&jws)
2889                        .map(|jwss| {
2890                            auth_session_id_tok = Some(jwss.to_string());
2891                        })
2892                        .map_err(|e| {
2893                            error!(?e);
2894                            OperationError::InvalidSessionState
2895                        })
2896                        .map(|_| ProtoAuthState::Choose(allowed))
2897                }
2898                AuthState::Continue(allowed) => {
2899                    debug!("🧩 -> AuthState::Continue");
2900                    let kref = &state.jws_signer;
2901                    // Get the header token ready.
2902                    let jws = Jws::into_json(&sessionid).map_err(|e| {
2903                        error!(?e);
2904                        OperationError::InvalidSessionState
2905                    })?;
2906                    kref.sign(&jws)
2907                        .map(|jwss| {
2908                            auth_session_id_tok = Some(jwss.to_string());
2909                        })
2910                        .map_err(|e| {
2911                            error!(?e);
2912                            OperationError::InvalidSessionState
2913                        })
2914                        .map(|_| ProtoAuthState::Continue(allowed))
2915                }
2916                AuthState::Success(token, issue) => {
2917                    debug!("🧩 -> AuthState::Success");
2918
2919                    match issue {
2920                        AuthIssueSession::Token => Ok(ProtoAuthState::Success(token.to_string())),
2921                        AuthIssueSession::Cookie => {
2922                            // Update jar
2923                            let token_str = token.to_string();
2924                            let mut bearer_cookie =
2925                                Cookie::new(COOKIE_BEARER_TOKEN, token_str.clone());
2926                            bearer_cookie.set_secure(state.secure_cookies);
2927                            bearer_cookie.set_same_site(SameSite::Lax);
2928                            bearer_cookie.set_http_only(true);
2929                            // We set a domain here because it allows subdomains
2930                            // of the idm to share the cookie. If domain was incorrect
2931                            // then webauthn won't work anyway!
2932                            bearer_cookie.set_domain(state.domain.clone());
2933                            bearer_cookie.set_path("/");
2934                            jar = jar
2935                                .add(bearer_cookie)
2936                                .remove(Cookie::from(COOKIE_AUTH_SESSION_ID));
2937                            Ok(ProtoAuthState::Success(token_str))
2938                        }
2939                    }
2940                }
2941                AuthState::Denied(reason) => {
2942                    debug!("🧩 -> AuthState::Denied");
2943                    Ok(ProtoAuthState::Denied(reason))
2944                }
2945            }
2946            .map(|state| AuthResponse { sessionid, state })
2947        }
2948        Err(e) => Err(e),
2949    };
2950
2951    // if the sessionid was injected into our cookie, set it in the header too.
2952    res.map(|response| {
2953        jar = if let Some(token) = auth_session_id_tok.clone() {
2954            let mut token_cookie = Cookie::new(COOKIE_AUTH_SESSION_ID, token);
2955            token_cookie.set_secure(state.secure_cookies);
2956            token_cookie.set_same_site(SameSite::Strict);
2957            token_cookie.set_http_only(true);
2958            // Not setting domains limits the cookie to precisely this
2959            // url that was used.
2960            // token_cookie.set_domain(state.domain.clone());
2961            jar.add(token_cookie)
2962        } else {
2963            jar
2964        };
2965
2966        trace!(?jar);
2967
2968        let mut res = (jar, Json::from(response)).into_response();
2969
2970        match auth_session_id_tok {
2971            Some(tok) => {
2972                match HeaderValue::from_str(&tok) {
2973                    Ok(val) => {
2974                        res.headers_mut().insert(KSESSIONID, val);
2975                    }
2976                    Err(err) => {
2977                        admin_error!(?err, "Failed to add sessionid {} to header", tok);
2978                    }
2979                }
2980                res
2981            }
2982            None => res,
2983        }
2984    })
2985    .map_err(WebError::from)
2986}
2987
2988#[utoipa::path(
2989    get,
2990    path = "/v1/auth/valid",
2991    responses(
2992        DefaultApiResponse,
2993    ),
2994    security(("token_jwt" = [])),
2995    tag = "v1/auth",
2996    operation_id = "auth_valid",
2997)]
2998pub async fn auth_valid(
2999    State(state): State<ServerState>,
3000    Extension(kopid): Extension<KOpId>,
3001    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
3002) -> Result<Json<()>, WebError> {
3003    state
3004        .qe_r_ref
3005        .handle_auth_valid(client_auth_info, kopid.eventid)
3006        .await
3007        .map(Json::from)
3008        .map_err(WebError::from)
3009}
3010
3011#[utoipa::path(
3012    get,
3013    path = "/v1/debug/ipinfo",
3014    responses(
3015        (status = 200, description = "Ok", body=String, content_type="application/json"),
3016    ),
3017    security(("token_jwt" = [])),
3018    tag = "v1/debug",
3019    operation_id = "debug_ipinfo",
3020)]
3021pub async fn debug_ipinfo(
3022    State(_state): State<ServerState>,
3023    Extension(trusted_client_ip): Extension<ClientConnInfo>,
3024) -> Result<Json<IpAddr>, ()> {
3025    Ok(Json::from(trusted_client_ip.client_ip_addr))
3026}
3027
3028#[utoipa::path(
3029    get,
3030    path = "/v1/jwk/{key_id}",
3031    responses(
3032        (status=200, body=Jwk, content_type="application/json"),
3033        ApiResponseWithout200,
3034    ),
3035    security(("token_jwt" = [])),
3036    tag = "v1/jwk",
3037    operation_id = "public_jwk_key_id_get"
3038)]
3039pub async fn public_jwk_key_id_get(
3040    State(state): State<ServerState>,
3041    Path(key_id): Path<String>,
3042    Extension(kopid): Extension<KOpId>,
3043) -> Result<Json<Jwk>, WebError> {
3044    if key_id.len() > 64 {
3045        // Fast path to reject long KeyIDs
3046        return Err(WebError::from(OperationError::NoMatchingEntries));
3047    }
3048    state
3049        .qe_r_ref
3050        .handle_public_jwk_get(key_id, kopid.eventid)
3051        .await
3052        .map(Json::from)
3053        .map_err(WebError::from)
3054}
3055
3056fn cacheable_routes(state: ServerState) -> Router<ServerState> {
3057    Router::new()
3058        .route("/v1/jwk/:key_id", get(public_jwk_key_id_get))
3059        .route(
3060            "/v1/person/:id/_radius/_token",
3061            get(person_id_radius_token_get),
3062        )
3063        .route("/v1/account/:id/_unix/_token", get(account_id_unix_token))
3064        .route(
3065            "/v1/account/:id/_radius/_token",
3066            get(account_id_radius_token_get),
3067        )
3068        .layer(from_fn(cache_me_short))
3069        .with_state(state)
3070}
3071
3072#[instrument(skip(state))]
3073pub(crate) fn route_setup(state: ServerState) -> Router<ServerState> {
3074    Router::new()
3075        .route("/v1/oauth2", get(super::v1_oauth2::oauth2_get))
3076        .route(
3077            "/v1/oauth2/_basic",
3078            post(super::v1_oauth2::oauth2_basic_post),
3079        )
3080        .route(
3081            "/v1/oauth2/_public",
3082            post(super::v1_oauth2::oauth2_public_post),
3083        )
3084        .route(
3085            "/v1/oauth2/:rs_name",
3086            get(super::v1_oauth2::oauth2_id_get)
3087                .patch(super::v1_oauth2::oauth2_id_patch)
3088                .delete(super::v1_oauth2::oauth2_id_delete),
3089        )
3090        .route(
3091            "/v1/oauth2/:rs_name/_attr/:attr",
3092            post(super::v1_oauth2::oauth2_id_attr_post)
3093                .delete(super::v1_oauth2::oauth2_id_attr_delete),
3094        )
3095        .route(
3096            "/v1/oauth2/:rs_name/_image",
3097            post(super::v1_oauth2::oauth2_id_image_post)
3098                .delete(super::v1_oauth2::oauth2_id_image_delete),
3099        )
3100        .route(
3101            "/v1/oauth2/:rs_name/_basic_secret",
3102            get(super::v1_oauth2::oauth2_id_get_basic_secret),
3103        )
3104        .route(
3105            "/v1/oauth2/:rs_name/_scopemap/:group",
3106            post(super::v1_oauth2::oauth2_id_scopemap_post)
3107                .delete(super::v1_oauth2::oauth2_id_scopemap_delete),
3108        )
3109        .route(
3110            "/v1/oauth2/:rs_name/_sup_scopemap/:group",
3111            post(super::v1_oauth2::oauth2_id_sup_scopemap_post)
3112                .delete(super::v1_oauth2::oauth2_id_sup_scopemap_delete),
3113        )
3114        .route(
3115            "/v1/oauth2/:rs_name/_claimmap/:claim_name/:group",
3116            post(super::v1_oauth2::oauth2_id_claimmap_post)
3117                .delete(super::v1_oauth2::oauth2_id_claimmap_delete),
3118        )
3119        .route(
3120            "/v1/oauth2/:rs_name/_claimmap/:claim_name",
3121            post(super::v1_oauth2::oauth2_id_claimmap_join_post),
3122        )
3123        .route("/v1/raw/create", post(raw_create))
3124        .route("/v1/raw/modify", post(raw_modify))
3125        .route("/v1/raw/delete", post(raw_delete))
3126        .route("/v1/raw/search", post(raw_search))
3127        .route("/v1/schema", get(schema_get))
3128        .route(
3129            "/v1/schema/attributetype",
3130            get(schema_attributetype_get), // post(|| async { "TODO" })
3131        )
3132        .route(
3133            "/v1/schema/attributetype/:id",
3134            get(schema_attributetype_get_id),
3135        )
3136        // .route("/schema/attributetype/:id", put(|| async { "TODO" }).patch(|| async { "TODO" }))
3137        .route(
3138            "/v1/schema/classtype",
3139            get(schema_classtype_get), // .post(|| async { "TODO" })
3140        )
3141        .route(
3142            "/v1/schema/classtype/:id",
3143            get(schema_classtype_get_id), //         .put(|| async { "TODO" })
3144                                          //         .patch(|| async { "TODO" }),
3145        )
3146        .route("/v1/self", get(whoami))
3147        .route("/v1/self/_uat", get(whoami_uat))
3148        // .route("/v1/self/_attr/:attr", get(|| async { "TODO" }))
3149        // .route("/v1/self/_credential", get(|| async { "TODO" }))
3150        // .route("/v1/self/_credential/:cid/_lock", get(|| async { "TODO" }))
3151        // .route(
3152        //     "/v1/self/_radius",
3153        //     get(|| async { "TODO" })
3154        //         .delete(|| async { "TODO" })
3155        //         .post(|| async { "TODO" }),
3156        // )
3157        // .route("/v1/self/_radius/_config", post(|| async { "TODO" }))
3158        // .route("/v1/self/_radius/_config/:token", get(|| async { "TODO" }))
3159        // .route(
3160        //     "/v1/self/_radius/_config/:token/apple",
3161        //     get(|| async { "TODO" }),
3162        // )
3163        // Applinks are the list of apps this account can access.
3164        .route("/v1/self/_applinks", get(applinks_get))
3165        // Person routes
3166        .route("/v1/person", get(person_get).post(person_post))
3167        .route("/v1/person/_search/:id", get(person_search_id))
3168        .route(
3169            "/v1/person/:id",
3170            get(person_id_get)
3171                .patch(person_id_patch)
3172                .delete(person_id_delete),
3173        )
3174        .route(
3175            "/v1/person/:id/_attr/:attr",
3176            get(person_id_get_attr)
3177                .put(person_id_put_attr)
3178                .post(person_id_post_attr)
3179                .delete(person_id_delete_attr),
3180        )
3181        .route(
3182            "/v1/person/:id/_certificate",
3183            get(person_get_id_certificate).post(person_post_id_certificate),
3184        )
3185        .route(
3186            "/v1/person/:id/_credential/_status",
3187            get(person_get_id_credential_status),
3188        )
3189        .route(
3190            "/v1/person/:id/_credential/_update",
3191            get(person_id_credential_update_get),
3192        )
3193        .route(
3194            "/v1/person/:id/_credential/_update_intent/:ttl",
3195            get(person_id_credential_update_intent_ttl_get),
3196        )
3197        .route(
3198            "/v1/person/:id/_credential/_update_intent",
3199            get(person_id_credential_update_intent_get),
3200        )
3201        .route(
3202            "/v1/person/:id/_ssh_pubkeys",
3203            get(person_id_ssh_pubkeys_get).post(person_id_ssh_pubkeys_post),
3204        )
3205        .route(
3206            "/v1/person/:id/_ssh_pubkeys/:tag",
3207            get(person_id_ssh_pubkeys_tag_get).delete(person_id_ssh_pubkeys_tag_delete),
3208        )
3209        .route(
3210            "/v1/person/:id/_radius",
3211            get(person_id_radius_get)
3212                .post(person_id_radius_post)
3213                .delete(person_id_radius_delete),
3214        )
3215        .route("/v1/person/:id/_unix", post(person_id_unix_post))
3216        .route(
3217            "/v1/person/:id/_unix/_credential",
3218            put(person_id_unix_credential_put).delete(person_id_unix_credential_delete),
3219        )
3220        .route(
3221            "/v1/person/:id/_identify_user",
3222            post(person_identify_user_post),
3223        )
3224        // Service accounts
3225        .route(
3226            "/v1/service_account",
3227            get(service_account_get).post(service_account_post),
3228        )
3229        .route(
3230            "/v1/service_account/",
3231            get(service_account_get).post(service_account_post),
3232        )
3233        .route(
3234            "/v1/service_account/:id",
3235            get(service_account_id_get)
3236                .delete(service_account_id_delete)
3237                .patch(service_account_id_patch),
3238        )
3239        .route(
3240            "/v1/service_account/:id/_attr/:attr",
3241            get(service_account_id_get_attr)
3242                .put(service_account_id_put_attr)
3243                .post(service_account_id_post_attr)
3244                .delete(service_account_id_delete_attr),
3245        )
3246        // .route("/v1/service_account/:id/_lock", get(|| async { "TODO" }))
3247        .route(
3248            "/v1/service_account/:id/_into_person",
3249            #[allow(deprecated)]
3250            post(service_account_into_person),
3251        )
3252        .route(
3253            "/v1/service_account/:id/_api_token",
3254            post(service_account_api_token_post).get(service_account_api_token_get),
3255        )
3256        .route(
3257            "/v1/service_account/:id/_api_token/:token_id",
3258            delete(service_account_api_token_delete),
3259        )
3260        // .route(
3261        //     "/v1/service_account/:id/_credential",
3262        //     get(|| async { "TODO" }),
3263        // )
3264        .route(
3265            "/v1/service_account/:id/_credential/_generate",
3266            get(service_account_credential_generate),
3267        )
3268        .route(
3269            "/v1/service_account/:id/_credential/_status",
3270            get(service_account_id_credential_status_get),
3271        )
3272        // .route(
3273        //     "/v1/service_account/:id/_credential/:cid/_lock",
3274        //     get(|| async { "TODO" }),
3275        // )
3276        .route(
3277            "/v1/service_account/:id/_ssh_pubkeys",
3278            get(service_account_id_ssh_pubkeys_get).post(service_account_id_ssh_pubkeys_post),
3279        )
3280        .route(
3281            "/v1/service_account/:id/_ssh_pubkeys/:tag",
3282            get(service_account_id_ssh_pubkeys_tag_get)
3283                .delete(service_account_id_ssh_pubkeys_tag_delete),
3284        )
3285        .route(
3286            "/v1/service_account/:id/_unix",
3287            post(service_account_id_unix_post),
3288        )
3289        .route(
3290            "/v1/account/:id/_unix/_auth",
3291            post(account_id_unix_auth_post),
3292        )
3293        .route("/v1/account/:id/_unix/_token", post(account_id_unix_token))
3294        .route(
3295            "/v1/account/:id/_radius/_token",
3296            post(account_id_radius_token_post),
3297        )
3298        .route(
3299            "/v1/account/:id/_ssh_pubkeys",
3300            #[allow(deprecated)]
3301            get(account_id_ssh_pubkeys_get),
3302        )
3303        .route(
3304            "/v1/account/:id/_ssh_pubkeys/:tag",
3305            get(account_id_ssh_pubkeys_tag_get),
3306        )
3307        .route(
3308            "/v1/account/:id/_user_auth_token",
3309            get(account_id_user_auth_token_get),
3310        )
3311        .route(
3312            "/v1/account/:id/_user_auth_token/:token_id",
3313            delete(account_user_auth_token_delete),
3314        )
3315        .route(
3316            "/v1/credential/_exchange_intent",
3317            post(credential_update_exchange_intent),
3318        )
3319        .route("/v1/credential/_status", post(credential_update_status))
3320        .route("/v1/credential/_update", post(credential_update_update))
3321        .route("/v1/credential/_commit", post(credential_update_commit))
3322        .route("/v1/credential/_cancel", post(credential_update_cancel))
3323        // domain-things
3324        .route("/v1/domain", get(domain_get))
3325        .route(
3326            "/v1/domain/_image",
3327            post(super::v1_domain::image_post).delete(super::v1_domain::image_delete),
3328        )
3329        .route(
3330            "/v1/domain/_attr/:attr",
3331            get(domain_attr_get)
3332                .put(domain_attr_put)
3333                .delete(domain_attr_delete),
3334        )
3335        .route("/v1/group/:id/_unix/_token", get(group_id_unix_token_get))
3336        .route("/v1/group/:id/_unix", post(group_id_unix_post))
3337        .route("/v1/group", get(group_get).post(group_post))
3338        .route("/v1/group/_search/:id", get(group_search_id))
3339        .route(
3340            "/v1/group/:id",
3341            get(group_id_get)
3342                .patch(group_id_patch)
3343                .delete(group_id_delete),
3344        )
3345        .route(
3346            "/v1/group/:id/_attr/:attr",
3347            delete(group_id_attr_delete)
3348                .get(group_id_attr_get)
3349                .put(group_id_attr_put)
3350                .post(group_id_attr_post),
3351        )
3352        .with_state(state.clone())
3353        .route("/v1/system", get(system_get))
3354        .route(
3355            "/v1/system/_attr/:attr",
3356            get(system_attr_get)
3357                .post(system_attr_post)
3358                .put(system_attr_put)
3359                .delete(system_attr_delete),
3360        )
3361        .route("/v1/recycle_bin", get(recycle_bin_get))
3362        .route("/v1/recycle_bin/:id", get(recycle_bin_id_get))
3363        .route(
3364            "/v1/recycle_bin/:id/_revive",
3365            post(recycle_bin_revive_id_post),
3366        )
3367        // .route("/v1/access_profile", get(|| async { "TODO" }))
3368        // .route("/v1/access_profile/:id", get(|| async { "TODO" }))
3369        // .route(
3370        //     "/v1/access_profile/:id/_attr/:attr",
3371        //     get(|| async { "TODO" }),
3372        // )
3373        .route("/v1/auth", post(auth))
3374        .route(V1_AUTH_VALID, get(auth_valid))
3375        .route("/v1/logout", get(logout))
3376        .route("/v1/reauth", post(reauth))
3377        .with_state(state.clone())
3378        .layer(from_fn(dont_cache_me))
3379        .merge(cacheable_routes(state))
3380        .route("/v1/debug/ipinfo", get(debug_ipinfo))
3381}