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 = client_auth_info
50 .pre_validated_uat()
51 .map_err(|op_err| HtmxError::new(&kopid, op_err, domain_info.clone()))?;
52
53 let time = time::OffsetDateTime::now_utc() + time::Duration::new(60, 0);
54 let can_rw = uat.purpose_readwrite_active(time);
55
56 if !can_rw {
58 let display_ctx = LoginDisplayCtx {
59 domain_info,
60 oauth2: None,
61 reauth: Some(Reauth {
62 username: uat.spn.clone(),
63 purpose: ReauthPurpose::ProfileSettings,
64 }),
65 error: None,
66 };
67
68 return Ok(super::login::view_reauth_get(
69 state,
70 client_auth_info,
71 kopid,
72 jar,
73 Urls::EnrolDevice.as_ref(),
74 display_ctx,
75 )
76 .await);
77 }
78
79 let spn = uat.spn.clone();
80
81 let cu_intent = state
82 .qe_w_ref
83 .handle_idmcredentialupdateintent(
84 client_auth_info,
85 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}