You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
181 lines
4.5 KiB
181 lines
4.5 KiB
//! 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<Self, Self::Err> {
|
|
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<State>) -> Result<AuthRequest, Infallible>
|
|
{
|
|
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<State>, req_id: Uuid, sigs: impl IntoIterator<Item=RsaSignature>) -> Result<(), AuthError>
|
|
{
|
|
Ok(())
|
|
}
|
|
pub async fn auth_key(who: source::IpAddr, state: Arc<State>, 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<State>, 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<State>, 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<impl warp::Reply, warp::Rejection>
|
|
{
|
|
use warp::http::StatusCode;
|
|
if let Some(this) = err.find::<Self>() {
|
|
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<state::AuthCacheError> for AuthError
|
|
{
|
|
fn from(_: state::AuthCacheError) -> Self
|
|
{
|
|
Self::Id
|
|
}
|
|
}
|
|
|