116 lines
3.1 KiB
Rust
116 lines
3.1 KiB
Rust
use std::collections::HashSet;
|
|
|
|
use argon2::{
|
|
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
|
|
Argon2, PasswordHash, PasswordVerifier,
|
|
};
|
|
use axum::{
|
|
extract::FromRequestParts,
|
|
http::{request::Parts, StatusCode},
|
|
RequestPartsExt,
|
|
};
|
|
use axum_extra::{
|
|
extract::cookie::{Cookie, CookieJar},
|
|
headers::{authorization::Bearer, Authorization},
|
|
typed_header::TypedHeaderRejectionReason,
|
|
TypedHeader,
|
|
};
|
|
use bearer::verify_bearer;
|
|
pub use error::AuthError;
|
|
use rand::distr::Alphanumeric;
|
|
use rand::prelude::*;
|
|
use rand_chacha::ChaCha20Rng;
|
|
use tokio::task;
|
|
|
|
use crate::database::model::User;
|
|
|
|
mod bearer;
|
|
mod error;
|
|
mod scopes;
|
|
|
|
#[derive(Debug)]
|
|
pub struct Permissions<'a>(pub HashSet<&'a str>);
|
|
|
|
// Middleware for getting permissions
|
|
impl<S> FromRequestParts<S> for Permissions<'_>
|
|
where
|
|
S: Send + Sync,
|
|
{
|
|
type Rejection = crate::Error;
|
|
|
|
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
|
// First check if the request has a beaerer token to authenticate
|
|
match parts.extract::<TypedHeader<Authorization<Bearer>>>().await {
|
|
Ok(bearer) => {
|
|
verify_bearer(bearer.token().to_string()).map_err(|_| AuthError::InvalidToken)?;
|
|
|
|
let permissions = Permissions {
|
|
0: HashSet::from(["root"]),
|
|
};
|
|
|
|
return Ok(permissions);
|
|
}
|
|
Err(err) => match err.reason() {
|
|
TypedHeaderRejectionReason::Missing => (),
|
|
TypedHeaderRejectionReason::Error(_err) => {
|
|
return Err(AuthError::InvalidToken.into())
|
|
}
|
|
_ => return Err(AuthError::Unexpected.into()),
|
|
},
|
|
};
|
|
|
|
match parts.extract::<CookieJar>().await {
|
|
Ok(jar) => {
|
|
if let Some(session_token) = jar.get("session_token") {
|
|
// TODO: Implement function to retrieve user permissions
|
|
tracing::info!("{session_token:?}")
|
|
}
|
|
}
|
|
Err(_) => (),
|
|
}
|
|
|
|
Err(AuthError::Unauthorized.into())
|
|
}
|
|
}
|
|
|
|
pub async fn generate_password_hash(
|
|
password: String,
|
|
) -> Result<String, argon2::password_hash::Error> {
|
|
let password_hash: Result<String, argon2::password_hash::Error> =
|
|
task::spawn_blocking(move || {
|
|
let salt = SaltString::generate(&mut OsRng);
|
|
|
|
let argon2 = Argon2::default();
|
|
|
|
let password_hash = argon2
|
|
.hash_password(password.as_bytes(), &salt)?
|
|
.to_string();
|
|
|
|
Ok(password_hash)
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
Ok(password_hash?)
|
|
}
|
|
|
|
pub async fn verify_password_hash(
|
|
password: &str,
|
|
hash: &str,
|
|
) -> Result<(), argon2::password_hash::Error> {
|
|
let parsed_hash = PasswordHash::new(hash)?;
|
|
Argon2::default().verify_password(password.as_bytes(), &parsed_hash)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn generate_session_token() -> String {
|
|
let session = ChaCha20Rng::from_os_rng()
|
|
.sample_iter(&Alphanumeric)
|
|
.take(60)
|
|
.map(char::from)
|
|
.collect::<String>();
|
|
|
|
session
|
|
}
|