factoriauth/src/auth.rs

126 lines
3.5 KiB
Rust
Raw Normal View History

2024-02-10 12:06:53 +01:00
use std::sync::Arc;
use base64::{prelude::BASE64_STANDARD, Engine};
use hmac::{Hmac, Mac};
use md5::Md5;
2024-02-10 13:32:49 +01:00
use rand::{
distributions::{Alphanumeric, DistString},
thread_rng,
};
use secrecy::{ExposeSecret, SecretVec};
2024-02-10 13:08:45 +01:00
use thiserror::Error;
use time::{macros::format_description, OffsetDateTime};
2024-02-10 12:06:53 +01:00
use tokio::sync::Mutex;
2024-02-10 13:07:36 +01:00
use tracing::{event, instrument, Level};
2024-02-10 12:32:01 +01:00
use crate::{
2024-02-10 13:07:36 +01:00
db::{/* Database, */ Database, SqliteDatabase},
secrets::{Password, ServerHash, ServerPadlock, UserServerKey, UserToken},
2024-02-10 12:32:01 +01:00
};
2024-02-10 13:08:45 +01:00
#[derive(Debug, Error)]
pub enum AuthenticationError {
#[error("Invalid username or password")]
InvalidUserOrPassword,
#[error("Invalid token")]
InvalidToken,
#[error("Invalid server hash")]
InvalidServerHash,
#[error("Authentication backend error")]
Backend(#[from] sqlx::Error),
}
2024-02-10 11:20:35 +01:00
#[derive(Debug)]
2024-02-10 12:56:35 +01:00
pub struct Authenticator {
db: Arc<Mutex<SqliteDatabase>>,
2024-02-10 10:51:56 +01:00
}
2024-02-10 12:56:35 +01:00
impl Authenticator {
2024-02-10 13:32:49 +01:00
const USER_TOKEN_LEN: usize = 30;
2024-02-10 12:06:53 +01:00
pub fn new(db: Arc<Mutex<SqliteDatabase>>) -> Self {
Self { db }
2024-02-10 10:51:56 +01:00
}
2024-02-10 11:20:35 +01:00
#[instrument]
2024-02-10 12:06:53 +01:00
pub async fn create_user_token(&mut self, username: &str, password: &Password) -> UserToken {
2024-02-10 13:32:49 +01:00
let new_token =
UserToken::from(Alphanumeric.sample_string(&mut thread_rng(), Self::USER_TOKEN_LEN));
2024-02-10 13:07:36 +01:00
let mut db = self.db.lock().await;
if let Err(err) = db.save_token(username, &new_token).await {
event!(Level::ERROR, %err, "Failed to save token in database");
}
new_token
2024-02-10 11:11:46 +01:00
}
2024-02-10 11:20:35 +01:00
#[instrument]
2024-02-10 13:08:45 +01:00
pub async fn verify_user_token(
&self,
username: &str,
token: &UserToken,
) -> Result<(), AuthenticationError> {
let mut db = self.db.lock().await;
// TODO: (in db) distinguish between invalid token and SQLX error
2024-02-10 13:08:45 +01:00
db.get_token(username).await?;
Ok(())
2024-02-10 10:51:56 +01:00
}
}
pub struct ServerPadlockGenerator {
secret: SecretVec<u8>,
}
impl ServerPadlockGenerator {
pub fn new(/* secret */) -> Self {
todo!()
}
pub fn generate_padlock(&self, server_hash: &ServerHash) -> ServerPadlock {
todo!()
}
}
pub struct UserServerKeyGenerator {
user_authenticator: Arc<Authenticator>,
padlock_generator: Arc<ServerPadlockGenerator>,
}
impl UserServerKeyGenerator {
pub fn new(/* Authenticator, ServerPadlockGenerator? */) -> Self {
todo!()
}
pub async fn generate_user_server_key(
&self,
username: &str,
token: &UserToken,
server_hash: &ServerHash,
) -> Result<(UserServerKey, String), AuthenticationError> {
self.user_authenticator
.verify_user_token(username, token)
.await?;
let padlock = self.padlock_generator.generate_padlock(server_hash);
let timestamp = OffsetDateTime::now_utc()
.format(format_description!(
"[year repr:last_two][month][day][hour repr:24][minute][second]"
))
.expect("timestamp format should be validated at compile-time");
event!(Level::DEBUG, timestamp, "Generating user_server_key");
let mut mac: Hmac<Md5> = Hmac::new_from_slice(padlock.0.expose_secret().as_bytes())
.map_err(|_e| AuthenticationError::InvalidServerHash)?;
mac.update(format!("{}_{}_{}", username, padlock.0.expose_secret(), timestamp).as_bytes());
let user_server_key =
UserServerKey(BASE64_STANDARD.encode(mac.finalize().into_bytes()).into());
Ok((user_server_key, timestamp))
}
}