kanidmd_core/https/
v1.rs

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