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