mirror of https://github.com/chipsenkbeil/distant
Unfinished authentication support -- and we're pegging cpu again probably still from our loops on read and write frame
parent
6dcb943422
commit
fd684185cb
@ -1,76 +1,7 @@
|
||||
use super::{FramedTransport, HeapSecretKey, Reconnectable, Transport};
|
||||
use async_trait::async_trait;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
mod authenticator;
|
||||
mod data;
|
||||
pub use data::*;
|
||||
|
||||
/// Internal state for a singular transport
|
||||
#[derive(Clone, Debug)]
|
||||
enum State {
|
||||
/// Transport is not authenticated and has not begun the process of authenticating
|
||||
NotAuthenticated,
|
||||
|
||||
/// Transport is in the state of currently authenticating, either by issuing challenges or
|
||||
/// responding with answers to challenges
|
||||
Authenticating,
|
||||
|
||||
/// Transport has finished authenticating successfully
|
||||
Authenticated {
|
||||
/// Unique key that marks the transport as authenticated for use in shortcutting
|
||||
/// authentication when a transport needs to reconnect. This is NOT the key used for
|
||||
/// encryption and is instead meant to be shared (secretly) between client-server that are
|
||||
/// aware of a previously-successful authentication.
|
||||
key: HeapSecretKey,
|
||||
},
|
||||
}
|
||||
|
||||
/// Represents a stateful authenticator that is capable of performing authentication with
|
||||
/// another [`Authenticator`] by communicating through a [`FramedTransport`].
|
||||
///
|
||||
/// ### Details
|
||||
///
|
||||
/// The authenticator manages a mapping of `ClientId` -> `Key` upon successful authentication which
|
||||
/// can be used to verify re-authentication without needing to perform full authentication again.
|
||||
/// This is particularly useful in re-connecting a `FramedTransport` post-handshake after a network
|
||||
/// disruption.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Authenticator {
|
||||
authenticated: HashMap<String, State>,
|
||||
}
|
||||
mod handler;
|
||||
|
||||
impl Authenticator {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
authenticated: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs authentication with the other side, moving the state to be authenticated.
|
||||
pub async fn authenticate<T, const CAPACITY: usize>(
|
||||
&mut self,
|
||||
transport: &mut FramedTransport<T, CAPACITY>,
|
||||
authentication: Authentication,
|
||||
) -> io::Result<()> {
|
||||
if self.is_authenticated() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
todo!();
|
||||
}
|
||||
|
||||
/// Clears out any tracked clients
|
||||
pub fn clear(&mut self) {
|
||||
self.authenticated.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Authenticator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
pub use authenticator::*;
|
||||
pub use data::*;
|
||||
pub use handler::*;
|
||||
|
@ -0,0 +1,186 @@
|
||||
use super::{data::*, AuthHandler};
|
||||
use crate::{utils, FramedTransport, Transport};
|
||||
use async_trait::async_trait;
|
||||
use log::*;
|
||||
use std::{collections::HashMap, io};
|
||||
|
||||
/// Represents an interface for authenticating or submitting challenges for authentication.
|
||||
#[async_trait]
|
||||
pub trait Authenticator: Send {
|
||||
/// Performs authentication by leveraging the `handler` for any received challenge.
|
||||
async fn authenticate(&mut self, mut handler: impl AuthHandler + Send) -> io::Result<()>;
|
||||
|
||||
/// Issues a challenge and returns the answers to the `questions` asked.
|
||||
async fn challenge(
|
||||
&mut self,
|
||||
questions: Vec<AuthQuestion>,
|
||||
options: HashMap<String, String>,
|
||||
) -> io::Result<Vec<String>>;
|
||||
|
||||
/// Requests verification of some `kind` and `text`, returning true if passed verification.
|
||||
async fn verify(&mut self, kind: AuthVerifyKind, text: String) -> io::Result<bool>;
|
||||
|
||||
/// Reports information with no response expected.
|
||||
async fn info(&mut self, text: String) -> io::Result<()>;
|
||||
|
||||
/// Reports an error occurred during authentication, consuming the authenticator since no more
|
||||
/// challenges should be issued.
|
||||
async fn error(self, kind: AuthErrorKind, text: String) -> io::Result<()>;
|
||||
|
||||
/// Reports that the authentication has finished successfully, consuming the authenticator
|
||||
/// since no more challenges should be issued.
|
||||
async fn finished(self) -> io::Result<()>;
|
||||
}
|
||||
|
||||
/// Wraps a [`FramedTransport`] in order to perform challenge-based communication through the
|
||||
/// transport to authenticate it. The authenticator is capable of conducting challenges or
|
||||
/// leveraging an [`AuthHandler`] to process challenges.
|
||||
pub struct FramedAuthenticator<'a, T: Send, const CAPACITY: usize> {
|
||||
transport: &'a mut FramedTransport<T, CAPACITY>,
|
||||
}
|
||||
|
||||
impl<'a, T: Send, const CAPACITY: usize> FramedAuthenticator<'a, T, CAPACITY> {
|
||||
pub fn new(transport: &'a mut FramedTransport<T, CAPACITY>) -> Self {
|
||||
Self { transport }
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! write_frame {
|
||||
($transport:expr, $data:expr) => {{
|
||||
$transport
|
||||
.write_frame(utils::serialize_to_vec(&$data)?)
|
||||
.await?
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! next_frame_as {
|
||||
($transport:expr, $type:ident, $variant:ident) => {{
|
||||
match { next_frame_as!($transport, $type) } {
|
||||
$type::$variant(x) => x,
|
||||
x => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("Unexpected frame: {x:?}"),
|
||||
))
|
||||
}
|
||||
}
|
||||
}};
|
||||
($transport:expr, $type:ident) => {{
|
||||
let frame = $transport.read_frame().await?.ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::UnexpectedEof, "Transport closed early")
|
||||
})?;
|
||||
|
||||
utils::deserialize_from_slice::<$type>(frame.as_item())?
|
||||
}};
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a, T, const CAPACITY: usize> Authenticator for FramedAuthenticator<'a, T, CAPACITY>
|
||||
where
|
||||
T: Transport + Send + Sync,
|
||||
{
|
||||
/// Performs authentication by leveraging the `handler` for any received challenge.
|
||||
async fn authenticate(&mut self, mut handler: impl AuthHandler + Send) -> io::Result<()> {
|
||||
loop {
|
||||
match next_frame_as!(self.transport, AuthRequest) {
|
||||
AuthRequest::Challenge(x) => {
|
||||
let answers = handler.on_challenge(x.questions, x.options).await?;
|
||||
write_frame!(
|
||||
self.transport,
|
||||
AuthResponse::Challenge(AuthChallengeResponse { answers })
|
||||
);
|
||||
}
|
||||
AuthRequest::Verify(x) => {
|
||||
let valid = handler.on_verify(x.kind, x.text).await?;
|
||||
write_frame!(
|
||||
self.transport,
|
||||
AuthResponse::Verify(AuthVerifyResponse { valid })
|
||||
);
|
||||
}
|
||||
AuthRequest::Info(x) => {
|
||||
handler.on_info(x.text).await?;
|
||||
}
|
||||
AuthRequest::Error(x) => {
|
||||
let kind = x.kind;
|
||||
let text = x.text;
|
||||
|
||||
handler.on_error(kind, &text).await?;
|
||||
|
||||
return Err(match kind {
|
||||
AuthErrorKind::FailedChallenge => io::Error::new(
|
||||
io::ErrorKind::PermissionDenied,
|
||||
format!("Failed challenge: {text}"),
|
||||
),
|
||||
AuthErrorKind::FailedVerification => io::Error::new(
|
||||
io::ErrorKind::PermissionDenied,
|
||||
format!("Failed verification: {text}"),
|
||||
),
|
||||
AuthErrorKind::Unknown => {
|
||||
io::Error::new(io::ErrorKind::Other, format!("Unknown error: {text}"))
|
||||
}
|
||||
});
|
||||
}
|
||||
AuthRequest::Finished => return Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Issues a challenge and returns the answers to the `questions` asked.
|
||||
async fn challenge(
|
||||
&mut self,
|
||||
questions: Vec<AuthQuestion>,
|
||||
options: HashMap<String, String>,
|
||||
) -> io::Result<Vec<String>> {
|
||||
trace!(
|
||||
"Authenticator::challenge(questions = {:?}, options = {:?})",
|
||||
questions,
|
||||
options
|
||||
);
|
||||
|
||||
write_frame!(
|
||||
self.transport,
|
||||
AuthRequest::from(AuthChallengeRequest { questions, options })
|
||||
);
|
||||
let response = next_frame_as!(self.transport, AuthResponse, Challenge);
|
||||
Ok(response.answers)
|
||||
}
|
||||
|
||||
/// Requests verification of some `kind` and `text`, returning true if passed verification.
|
||||
async fn verify(&mut self, kind: AuthVerifyKind, text: String) -> io::Result<bool> {
|
||||
trace!(
|
||||
"Authenticator::verify(kind = {:?}, text = {:?})",
|
||||
kind,
|
||||
text
|
||||
);
|
||||
|
||||
write_frame!(
|
||||
self.transport,
|
||||
AuthRequest::from(AuthVerifyRequest { kind, text })
|
||||
);
|
||||
let response = next_frame_as!(self.transport, AuthResponse, Verify);
|
||||
Ok(response.valid)
|
||||
}
|
||||
|
||||
/// Reports information with no response expected.
|
||||
async fn info(&mut self, text: String) -> io::Result<()> {
|
||||
trace!("Authenticator::info(text = {:?})", text);
|
||||
write_frame!(self.transport, AuthRequest::from(AuthInfo { text }));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reports an error occurred during authentication, consuming the authenticator since no more
|
||||
/// challenges should be issued.
|
||||
async fn error(self, kind: AuthErrorKind, text: String) -> io::Result<()> {
|
||||
trace!("Authenticator::error(kind = {:?}, text = {:?})", kind, text);
|
||||
write_frame!(self.transport, AuthRequest::from(AuthError { kind, text }));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reports that the authentication has finished successfully, consuming the authenticator
|
||||
/// since no more challenges should be issued.
|
||||
async fn finished(self) -> io::Result<()> {
|
||||
trace!("Authenticator::finished()");
|
||||
write_frame!(self.transport, AuthRequest::Finished);
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
use super::data::*;
|
||||
use async_trait::async_trait;
|
||||
use std::{collections::HashMap, io};
|
||||
|
||||
/// Interface for a handler of authentication requests
|
||||
#[async_trait]
|
||||
pub trait AuthHandler {
|
||||
/// Callback when a challenge is received, returning answers to the given questions.
|
||||
async fn on_challenge(
|
||||
&mut self,
|
||||
questions: Vec<AuthQuestion>,
|
||||
options: HashMap<String, String>,
|
||||
) -> io::Result<Vec<String>>;
|
||||
|
||||
/// Callback when a verification request is received, returning true if approvided or false if
|
||||
/// unapproved.
|
||||
async fn on_verify(&mut self, kind: AuthVerifyKind, text: String) -> io::Result<bool>;
|
||||
|
||||
/// Callback when authentication is finished and no more requests will be received
|
||||
async fn on_finished(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Callback when information is received. To fail, return an error from this function.
|
||||
#[allow(unused_variables)]
|
||||
async fn on_info(&mut self, text: String) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Callback when an error is received. Regardless of the result returned, this will terminate
|
||||
/// the authenticator. In the situation where a custom error would be preferred, have this
|
||||
/// callback return an error.
|
||||
#[allow(unused_variables)]
|
||||
async fn on_error(&mut self, kind: AuthErrorKind, text: &str) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<H: AuthHandler + Send> AuthHandler for &mut H {
|
||||
async fn on_challenge(
|
||||
&mut self,
|
||||
questions: Vec<AuthQuestion>,
|
||||
options: HashMap<String, String>,
|
||||
) -> io::Result<Vec<String>> {
|
||||
AuthHandler::on_challenge(self, questions, options).await
|
||||
}
|
||||
|
||||
async fn on_verify(&mut self, kind: AuthVerifyKind, text: String) -> io::Result<bool> {
|
||||
AuthHandler::on_verify(self, kind, text).await
|
||||
}
|
||||
|
||||
async fn on_finished(&mut self) -> io::Result<()> {
|
||||
AuthHandler::on_finished(self).await
|
||||
}
|
||||
|
||||
async fn on_info(&mut self, text: String) -> io::Result<()> {
|
||||
AuthHandler::on_info(self, text).await
|
||||
}
|
||||
|
||||
async fn on_error(&mut self, kind: AuthErrorKind, text: &str) -> io::Result<()> {
|
||||
AuthHandler::on_error(self, kind, text).await
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue