//! Authentication use super::*; use tokio::time; use std::{error, fmt}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Sha256Hash(pub sha256::Sha256Hash); type RsaSignature = rsa::Signature; #[derive(Debug)] pub struct DecodeTokenError; impl str::FromStr for Sha256Hash { type Err = DecodeTokenError; fn from_str(s: &str) -> Result { conv::ModifiedBase64String::try_from_base64(s).map_err(|_| DecodeTokenError).and_then(|md| { let mut output =sha256::Sha256Hash::default(); if md.decode(output.as_mut()) == sha256::SIZE { Ok(Self(output)) } else { Err(DecodeTokenError) } }) } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct AuthRequest { pub id: Uuid, sign_this: [u8; 32], salt: [u8; 16], passwd_is_allowed: bool, ttl_ms: u64 } impl AuthRequest { pub fn hash_password(&self, _state: &State, passwd: &str) -> sha256::Sha256Hash { // NOTE: _state will be used when we have a 2nd global salt as well, for now, ignore it. sha256::compute_slices([passwd.as_bytes(), &self.salt[..]].iter()) } } impl AuthRequest { /// The TTL for this auth request pub fn ttl(&self) -> time::Duration { time::Duration::from_millis(self.ttl_ms) } /// Create a new auth request pub fn new(cfg: &settings::Settings) -> Self { let mut empty = Self { id: Uuid::new_v4(), sign_this: [0; 32], salt: [0;16], passwd_is_allowed: cfg.allow_passwd_auth, ttl_ms: cfg.auth_req_ttl_millis.jitter(), }; getrandom(&mut empty.sign_this[..]).expect("fatal rng"); getrandom(&mut empty.salt[..]).expect("fatal rng"); empty } } pub async fn auth_req(who: source::IpAddr, state: Arc) -> Result { let req = AuthRequest::new(state.cfg()); trace!("{:?} auth req", who); // Add `req` into `state` auth hashmap (`req.id` is key) for verification. // Use `DelayQueue` to remove `req.id` from the hashmap after `ttl` expires. { let mut auth = state.auth_tokens().await; auth.insert_req(req.clone()); } Ok(req) } async fn real_auth_key(state: Arc, req_id: Uuid, sigs: impl IntoIterator) -> Result<(), AuthError> { Ok(()) } pub async fn auth_key(who: source::IpAddr, state: Arc, req_id: Uuid, num: usize, body: Bytes) -> Result<(), warp::Rejection> { trace!("{:?} auth resp key <{}>:{}", who, req_id, num); //TODO: Read keys from body, pass to `real_auth_key`. todo!() } async fn real_auth_pass(state: Arc, req_id: Uuid, passhash: sha256::Sha256Hash) -> Result<(), AuthError> { let req = { state.auth_tokens().await.handle_req(req_id)? }; if !req.passwd_is_allowed { return Err(AuthError::Method); } //TODO: Grab valid password hash from `State` and compare //TODO: Generate real authoriseation token that maps to whichever user was authorised, insert into state with a TTL that gets refreshed when the token is used. Ok(()) } pub async fn auth_pass(who: source::IpAddr, state: Arc, req_id: Uuid, passhash: sha256::Sha256Hash) -> Result<(), warp::Rejection> { trace!("{:?} auth resp pass <{}>: \"{}\"", who, req_id, passhash); real_auth_pass(state, req_id, passhash).await.map_err(warp::reject::custom) } #[derive(Debug)] pub enum AuthError { Id, Hash, Sig, Method, Internal, } impl AuthError { /// A warp recovery filter for auth errors pub async fn recover(err: warp::Rejection) -> Result { use warp::http::StatusCode; if let Some(this) = err.find::() { let code = match this { Self::Id => return Err(warp::reject::not_found()), Self::Hash | Self::Sig => StatusCode::FORBIDDEN, Self::Method => StatusCode::METHOD_NOT_ALLOWED, _ => StatusCode::INTERNAL_SERVER_ERROR, }; Ok(warp::reply::with_status(format!("auth failed: {}", this), code)) } else { Err(err) } } } impl error::Error for AuthError{} impl warp::reject::Reject for AuthError{} impl fmt::Display for AuthError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Id => write!(f, "invalid response id"), Self::Hash => write!(f, "no matching hash"), Self::Sig => write!(f, "no matching signature"), Self::Method => write!(f, "auth method not allowed"), _ => write!(f, "internal error"), } } } impl From for AuthError { fn from(_: state::AuthCacheError) -> Self { Self::Id } }