kanidmd_core/https/views/
enrol.rs
1use askama::Template;
2use askama_axum::IntoResponse;
3
4use axum::extract::State;
5use axum::response::Response;
6use axum::Extension;
7
8use axum_extra::extract::CookieJar;
9use kanidm_proto::internal::UserAuthToken;
10
11use qrcode::render::svg;
12use qrcode::QrCode;
13use url::Url;
14
15use std::time::Duration;
16
17use super::constants::Urls;
18use super::navbar::NavbarCtx;
19use crate::https::extractors::{DomainInfo, VerifiedClientInformation};
20use crate::https::middleware::KOpId;
21use crate::https::views::constants::ProfileMenuItems;
22use crate::https::views::errors::HtmxError;
23use crate::https::views::login::{LoginDisplayCtx, Reauth, ReauthPurpose};
24use crate::https::ServerState;
25
26#[derive(Template)]
27#[template(path = "user_settings.html")]
28struct ProfileView {
29 navbar_ctx: NavbarCtx,
30 profile_partial: EnrolDeviceView,
31}
32
33#[derive(Template)]
34#[template(path = "enrol_device.html")]
35pub(crate) struct EnrolDeviceView {
36 menu_active_item: ProfileMenuItems,
37 secret: String,
38 qr_code_svg: String,
39 uri: Url,
40}
41
42pub(crate) async fn view_enrol_get(
43 State(state): State<ServerState>,
44 Extension(kopid): Extension<KOpId>,
45 VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
46 DomainInfo(domain_info): DomainInfo,
47 jar: CookieJar,
48) -> axum::response::Result<Response> {
49 let uat: UserAuthToken = state
50 .qe_r_ref
51 .handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
52 .await
53 .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
54
55 let time = time::OffsetDateTime::now_utc() + time::Duration::new(60, 0);
56 let can_rw = uat.purpose_readwrite_active(time);
57
58 if !can_rw {
60 let display_ctx = LoginDisplayCtx {
61 domain_info,
62 oauth2: None,
63 reauth: Some(Reauth {
64 username: uat.spn,
65 purpose: ReauthPurpose::ProfileSettings,
66 }),
67 error: None,
68 };
69
70 return Ok(super::login::view_reauth_get(
71 state,
72 client_auth_info,
73 kopid,
74 jar,
75 Urls::EnrolDevice.as_ref(),
76 display_ctx,
77 )
78 .await);
79 }
80
81 let cu_intent = state
82 .qe_w_ref
83 .handle_idmcredentialupdateintent(
84 client_auth_info,
85 uat.spn,
86 Some(Duration::from_secs(900)),
87 kopid.eventid,
88 )
89 .await
90 .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
91
92 let secret = cu_intent.token;
93
94 let mut uri = state.origin.clone();
95 uri.set_path(Urls::CredReset.as_ref());
96 uri.set_query(Some(format!("token={secret}").as_str()));
97
98 let qr_code_svg = match QrCode::new(uri.as_str()) {
99 Ok(qr) => qr.render::<svg::Color>().build(),
100 Err(qr_err) => {
101 error!("Failed to create TOTP QR code: {qr_err}");
102 "QR Code Generation Failed".to_string()
103 }
104 };
105
106 Ok(ProfileView {
107 navbar_ctx: NavbarCtx { domain_info },
108
109 profile_partial: EnrolDeviceView {
110 menu_active_item: ProfileMenuItems::EnrolDevice,
111 qr_code_svg,
112 secret,
113 uri,
114 },
115 }
116 .into_response())
117}