use argon2::password_hash::SaltString;
use argon2::{Argon2, PasswordHasher, PasswordVerifier};
use axum::debug_handler;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Redirect};
use chrono::{DateTime, Duration, Utc};
use rand::distributions::{Alphanumeric, DistString};
use rand_core::OsRng;
use sha2::{Digest, Sha256};
use crate::db::TokenSearchRow;
pub fn redirect_to_home() -> Redirect {
Redirect::to("/")
}
pub fn redirect_to_login() -> Redirect {
Redirect::to("/auth/login")
}
pub fn redirect_to_dashboard() -> Redirect {
Redirect::to("/ui")
}
pub fn redirect_to_zones_list() -> Redirect {
Redirect::to("/ui/zones/list")
}
#[derive(Debug, Clone)]
pub struct ApiToken {
pub token_key: String,
pub token_secret: String,
pub token_hash: String,
pub issued: DateTime<Utc>,
pub expiry: Option<DateTime<Utc>>,
}
pub fn create_api_token(api_cookie_secret: &[u8], lifetime: i32, userid: i64) -> ApiToken {
let issued = Utc::now();
let expiry = match lifetime {
-1 => None,
_ => Some(issued + Duration::seconds(lifetime.into())),
};
let api_token_to_hash = format!("{api_cookie_secret:?}-{userid:?}-{issued:?}-{lifetime:?}-");
let api_token = hex::encode(Sha256::digest(api_token_to_hash));
let token_secret = format!("goatns_{api_token}");
log::trace!("Final token: {token_secret}");
let salt = SaltString::generate(&mut OsRng);
log::debug!("generating hash");
let argon2 = Argon2::default();
let password_hash = argon2
.hash_password(token_secret.as_bytes(), &salt)
.unwrap();
let password_hash_string = password_hash.to_string();
log::debug!("done");
let token_key = Alphanumeric.sample_string(&mut rand::thread_rng(), 12);
let token_key = format!("GA{}", token_key);
ApiToken {
token_key,
token_secret,
token_hash: password_hash_string,
issued,
expiry,
}
}
pub fn validate_api_token(token: &TokenSearchRow, payload_token: &str) -> Result<(), String> {
let passwordhash =
match argon2::PasswordHash::parse(&token.tokenhash, argon2::password_hash::Encoding::B64) {
Ok(val) => {
#[cfg(test)]
println!("Hashed payload: {val:?}");
val
}
Err(err) => {
return Err(format!(
"Failed to parse token ({:?}) into hash: {err:?}",
token.tokenhash
));
}
};
Argon2::default()
.verify_password(payload_token.as_bytes(), &passwordhash)
.map_err(|e| format!("validation error: {e:?}"))
}
#[debug_handler]
pub async fn handler_404() -> impl IntoResponse {
axum::response::Response::builder()
.status(StatusCode::NOT_FOUND)
.header("Content-type", "text/html")
.body(
"<h1>Oh no!</h1><p>You've found a 404, try <a href='#' onclick='history.back();'>going back</a> or <a href='/'>home!</a></p>"
.to_string(),
)
.unwrap()
.into_response()
}