1use crate::prelude::*;
25use citadel_io::{error, ErrorCode};
26use citadel_user::auth::pq::client::ClientManagement;
27use citadel_user::auth::pq::messages::{ManagementDone, ManagementMessage, ServerOutcome};
28use futures::StreamExt;
29
30#[async_trait]
31pub trait SignInManagementExt {
32 async fn manage_sign_in(
35 &self,
36 op: SignInManagementOp,
37 step_up: SignInFactors,
38 ) -> Result<SignInManagementOutcome, NetworkError>;
39}
40
41#[async_trait]
42impl<R: Ratchet> SignInManagementExt for CitadelClientServerConnection<R> {
43 async fn manage_sign_in(
44 &self,
45 op: SignInManagementOp,
46 step_up: SignInFactors,
47 ) -> Result<SignInManagementOutcome, NetworkError> {
48 let cid = self.cid;
49 let remote = &self.remote;
50 let username = remote
51 .account_manager()
52 .get_username_by_cid(cid)
53 .await?
54 .ok_or_else(|| error!(ErrorCode::SessionClientNotLoaded))?;
55 let (begin, client) =
56 ClientManagement::begin(&username, op.clone(), step_up.password.as_ref())?;
57 let challenge = match exchange(remote, cid, ManagementMessage::Begin(begin)).await? {
58 ManagementMessage::Challenge(challenge) => challenge,
59 other => return Err(unexpected(other)),
60 };
61 let key = step_up.security_key.as_ref();
62 let ask = |purpose, request| async move {
63 let key = key.ok_or_else(|| {
64 error!(ErrorCode::PqSignInFactorMissing, "a security key channel")
65 })?;
66 key.ask(purpose, request).await
67 };
68 let step_up_key = match ClientManagement::step_up_key_request(&challenge)? {
69 Some(request) => Some(ask(SecurityKeyPurpose::StepUp, request).await?),
70 None => None,
71 };
72 let new_key = match client.new_key_request(&challenge)? {
73 Some(request) => Some(ask(SecurityKeyPurpose::Enrol, request).await?),
74 None => None,
75 };
76 let (commit, committed) = client.commit(cid, &challenge, step_up_key, new_key).await?;
77 let done = match exchange(remote, cid, ManagementMessage::Commit(commit)).await? {
78 ManagementMessage::EnrolChallenge(enrol) => {
79 let proof = committed.enrol_proof(cid, &enrol)?;
80 exchange(remote, cid, ManagementMessage::EnrolProof(proof)).await?
81 }
82 other => other,
83 };
84 let outcome = match done {
85 ManagementMessage::Done(ManagementDone::Done(outcome)) => outcome,
86 other => return Err(unexpected(other)),
87 };
88 Ok(match outcome {
89 ServerOutcome::Credentials {
90 policy,
91 credentials,
92 } => SignInManagementOutcome::Credentials {
93 policy,
94 credentials,
95 },
96 ServerOutcome::Added { id } => SignInManagementOutcome::Added { id },
97 ServerOutcome::Renamed => SignInManagementOutcome::Renamed,
98 ServerOutcome::Removed => SignInManagementOutcome::Removed,
99 ServerOutcome::PolicySet => SignInManagementOutcome::PolicySet,
100 ServerOutcome::RecoveryCodesReplaced => SignInManagementOutcome::RecoveryCodes(
101 committed
102 .into_recovery_codes()
103 .iter()
104 .map(|code| code.display().to_string())
105 .collect(),
106 ),
107 })
108 }
109}
110
111fn unexpected(message: ManagementMessage) -> NetworkError {
112 match message {
113 ManagementMessage::Done(ManagementDone::Refused(reason)) => {
114 error!(ErrorCode::PqSignInPolicy, reason)
115 }
116 _ => error!(
117 ErrorCode::PqSignInMalformed,
118 "an out-of-order management reply"
119 ),
120 }
121}
122
123async fn exchange<R: Ratchet>(
125 remote: &ClientServerRemote<R>,
126 cid: u64,
127 message: ManagementMessage,
128) -> Result<ManagementMessage, NetworkError> {
129 let request = NodeRequest::PeerCommand(PeerCommand {
130 session_cid: cid,
131 command: PeerSignal::SignInManagement {
132 session_cid: cid,
133 message,
134 },
135 });
136 let mut replies = remote.send_callback_subscription(request).await?;
137 while let Some(reply) = replies.next().await {
138 if let NodeResult::PeerEvent(PeerEvent {
139 event: PeerSignal::SignInManagement { message, .. },
140 ..
141 }) = reply.into_result()?
142 {
143 return Ok(message);
144 }
145 }
146 Err(error!(
147 ErrorCode::RemoteKernelStreamDied,
148 "sign-in management"
149 ))
150}