kanidmd_core/https/views/
enrol.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
use askama::Template;
use askama_axum::IntoResponse;

use axum::extract::State;
use axum::response::Response;
use axum::Extension;

use axum_extra::extract::CookieJar;
use kanidm_proto::internal::UserAuthToken;

use qrcode::render::svg;
use qrcode::QrCode;
use url::Url;

use std::time::Duration;

use super::constants::Urls;
use super::navbar::NavbarCtx;
use crate::https::extractors::{DomainInfo, VerifiedClientInformation};
use crate::https::middleware::KOpId;
use crate::https::views::constants::ProfileMenuItems;
use crate::https::views::errors::HtmxError;
use crate::https::views::login::{LoginDisplayCtx, Reauth, ReauthPurpose};
use crate::https::ServerState;

#[derive(Template)]
#[template(path = "user_settings.html")]
struct ProfileView {
    navbar_ctx: NavbarCtx,
    profile_partial: EnrolDeviceView,
}

#[derive(Template)]
#[template(path = "enrol_device.html")]
pub(crate) struct EnrolDeviceView {
    menu_active_item: ProfileMenuItems,
    secret: String,
    qr_code_svg: String,
    uri: Url,
}

pub(crate) async fn view_enrol_get(
    State(state): State<ServerState>,
    Extension(kopid): Extension<KOpId>,
    VerifiedClientInformation(client_auth_info): VerifiedClientInformation,
    DomainInfo(domain_info): DomainInfo,
    jar: CookieJar,
) -> axum::response::Result<Response> {
    let uat: UserAuthToken = state
        .qe_r_ref
        .handle_whoami_uat(client_auth_info.clone(), kopid.eventid)
        .await
        .map_err(|op_err| HtmxError::new(&kopid, op_err))?;

    let time = time::OffsetDateTime::now_utc() + time::Duration::new(60, 0);
    let can_rw = uat.purpose_readwrite_active(time);

    // The user lacks an elevated session, request a re-auth.
    if !can_rw {
        let display_ctx = LoginDisplayCtx {
            domain_info,
            oauth2: None,
            reauth: Some(Reauth {
                username: uat.spn,
                purpose: ReauthPurpose::ProfileSettings,
            }),
            error: None,
        };

        return Ok(super::login::view_reauth_get(
            state,
            client_auth_info,
            kopid,
            jar,
            Urls::EnrolDevice.as_ref(),
            display_ctx,
        )
        .await);
    }

    let cu_intent = state
        .qe_w_ref
        .handle_idmcredentialupdateintent(
            client_auth_info,
            uat.spn,
            Some(Duration::from_secs(900)),
            kopid.eventid,
        )
        .await
        .map_err(|op_err| HtmxError::new(&kopid, op_err))?;

    let secret = cu_intent.token;

    let mut uri = state.origin.clone();
    uri.set_path(Urls::CredReset.as_ref());
    uri.set_query(Some(format!("token={secret}").as_str()));

    let qr_code_svg = match QrCode::new(uri.as_str()) {
        Ok(qr) => qr.render::<svg::Color>().build(),
        Err(qr_err) => {
            error!("Failed to create TOTP QR code: {qr_err}");
            "QR Code Generation Failed".to_string()
        }
    };

    Ok(ProfileView {
        navbar_ctx: NavbarCtx { domain_info },
        profile_partial: EnrolDeviceView {
            menu_active_item: ProfileMenuItems::EnrolDevice,
            qr_code_svg,
            secret,
            uri,
        },
    }
    .into_response())
}