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    // The user lacks an elevated session, request a re-auth.
59    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}