Information on whether a user with a given handle exists or not could be collected by checking the difference between response times of auth-required endpoints with and without a real handle being passed into Basic auth. This is because the time-expensive password hash would only be computed for users that exist, lengthening the response time. In local testing, this was a difference of 8ms vs. 35-60ms. A hash is now computed even if a user with the requested handle doesn't exist, mitigating the issue and leaving only negligible differences inbetween all response times, from which no information can be obtained.
69 lines
2.0 KiB
Rust
69 lines
2.0 KiB
Rust
use axum::http::HeaderMap;
|
|
use rand08::{RngCore, rngs::OsRng};
|
|
|
|
use crate::users::{User, UserError, sessions::SessionError};
|
|
|
|
mod implementation;
|
|
|
|
pub const COOKIE_NAME: &str = "mnemohash";
|
|
|
|
pub trait UserAuthenticate {
|
|
fn authenticate(headers: &HeaderMap) -> Result<Option<User>, AuthError>;
|
|
}
|
|
pub trait UserAuthRequired {
|
|
fn required(self) -> Result<User, AuthError>;
|
|
}
|
|
|
|
pub trait UserPasswordHashing {
|
|
/// Returns the hashed password as a String
|
|
fn hash_password(passw: &str) -> Result<String, argon2::password_hash::Error>;
|
|
/// Returns whether the password matches the hash
|
|
fn match_hash_password(passw: &str, hash: &str) -> Result<bool, argon2::password_hash::Error>;
|
|
}
|
|
pub trait UserAuthDummyData {
|
|
const DUMMY_PASSWORD_PHC: &str;
|
|
const DUMMY_PASSWORD: &str;
|
|
}
|
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
pub enum AuthError {
|
|
#[error("Invalid credentials")]
|
|
InvalidCredentials,
|
|
#[error("Authentication required")]
|
|
AuthRequired,
|
|
#[error("Session error: {0}")]
|
|
SessionError(#[from] SessionError),
|
|
#[error("User error: {0}")]
|
|
UserError(#[from] UserError),
|
|
#[error("Invalid authorization header format")]
|
|
InvalidFormat,
|
|
#[error("Invalid base64 encoding")]
|
|
InvalidBase64(#[from] base64::DecodeError),
|
|
#[error("Invalid UTF-8 in credentials")]
|
|
InvalidUtf8(#[from] std::string::FromUtf8Error),
|
|
#[error("Database error: {0}")]
|
|
DatabaseError(#[from] rusqlite::Error),
|
|
#[error("Argon2 passhash error: {0}")]
|
|
PassHashError(argon2::password_hash::Error),
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
#[allow(unused)]
|
|
pub enum TokenSize {
|
|
/// 5 bytes = 8 chars
|
|
Char8,
|
|
/// 10 bytes = 16 chars
|
|
Char16,
|
|
/// 20 bytes = 32 chars
|
|
Char32,
|
|
/// 40 bytes = 64 chars
|
|
Char64,
|
|
}
|
|
|
|
pub fn generate_token(len: TokenSize) -> String {
|
|
let mut bytes = vec![0u8; len.bytes()];
|
|
let mut rng = OsRng;
|
|
rng.try_fill_bytes(&mut bytes).unwrap();
|
|
base32::encode(base32::Alphabet::Crockford, &bytes)
|
|
}
|