mirror of
https://github.com/kristoferssolo/Axium.git
synced 2026-02-04 13:32:02 +00:00
Added an example docker-compose for loadbalancing added better documentation to the auth mech.
This commit is contained in:
@@ -12,18 +12,12 @@ use utoipa::ToSchema;
|
||||
|
||||
use crate::utils::auth::{encode_jwt, verify_hash};
|
||||
use crate::database::{apikeys::fetch_active_apikeys_by_user_id_from_db, users::fetch_user_by_email_from_db};
|
||||
use crate::models::auth::SignInData;
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct SignInData {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
pub totp: Option<String>,
|
||||
}
|
||||
|
||||
/// User sign-in endpoint
|
||||
/// User sign-in endpoint.
|
||||
///
|
||||
/// This endpoint allows users to sign in using their email, password, and optionally a TOTP code.
|
||||
///
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `State(pool)`: The shared database connection pool.
|
||||
/// - `Json(user_data)`: The user sign-in data (email, password, and optional TOTP code).
|
||||
@@ -48,91 +42,122 @@ pub async fn signin(
|
||||
State(pool): State<PgPool>,
|
||||
Json(user_data): Json<SignInData>,
|
||||
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
|
||||
// Fetch the user from the database based on their email.
|
||||
let user = match fetch_user_by_email_from_db(&pool, &user_data.email).await {
|
||||
Ok(Some(user)) => user,
|
||||
Ok(None) | Err(_) => return Err((
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(json!({ "error": "Incorrect credentials." }))
|
||||
)),
|
||||
Ok(None) | Err(_) => {
|
||||
// If the user is not found or there's an error, return an unauthorized response.
|
||||
return Err((
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(json!({ "error": "Incorrect credentials." }))
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch active API keys for the user.
|
||||
let api_key_hashes = match fetch_active_apikeys_by_user_id_from_db(&pool, user.id).await {
|
||||
Ok(hashes) => hashes,
|
||||
Err(_) => return Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": "Internal server error." }))
|
||||
)),
|
||||
Err(_) => {
|
||||
// If there's an error fetching API keys, return an internal server error.
|
||||
return Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": "Internal server error." }))
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Check API key first (async version)
|
||||
|
||||
// Check if any of the API keys match the provided password.
|
||||
let api_key_futures = api_key_hashes.iter().map(|api_key| {
|
||||
let password = user_data.password.clone();
|
||||
let hash = api_key.key_hash.clone();
|
||||
async move {
|
||||
// Verify the password against each API key hash.
|
||||
verify_hash(&password, &hash)
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Wait for all API key verification futures to complete.
|
||||
let any_api_key_valid = futures::future::join_all(api_key_futures)
|
||||
.await
|
||||
.into_iter()
|
||||
.any(|result| result);
|
||||
|
||||
// Check password (async version)
|
||||
|
||||
// Verify the user's password against their stored password hash.
|
||||
let password_valid = verify_hash(&user_data.password, &user.password_hash)
|
||||
.await
|
||||
.map_err(|_| (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": "Internal server error." }))
|
||||
))?;
|
||||
|
||||
.map_err(|_| {
|
||||
// If there's an error verifying the password, return an internal server error.
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": "Internal server error." }))
|
||||
)
|
||||
})?;
|
||||
|
||||
// Determine if the credentials are valid based on API keys or password.
|
||||
let credentials_valid = any_api_key_valid || password_valid;
|
||||
|
||||
|
||||
if !credentials_valid {
|
||||
// If credentials are not valid, return an unauthorized response.
|
||||
return Err((
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(json!({ "error": "Incorrect credentials." }))
|
||||
));
|
||||
}
|
||||
|
||||
// Check TOTP if it's set up for the user
|
||||
// Check TOTP if it's set up for the user.
|
||||
if let Some(totp_secret) = user.totp_secret {
|
||||
match user_data.totp {
|
||||
Some(totp_code) => {
|
||||
// Create a TOTP instance with the user's secret.
|
||||
let totp = TOTP::new(
|
||||
Algorithm::SHA512,
|
||||
8,
|
||||
1,
|
||||
30,
|
||||
totp_secret.into_bytes(),
|
||||
).map_err(|_| (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": "Internal server error." }))
|
||||
))?;
|
||||
).map_err(|_| {
|
||||
// If there's an error creating the TOTP instance, return an internal server error.
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": "Internal server error." }))
|
||||
)
|
||||
})?;
|
||||
|
||||
// Check if the provided TOTP code is valid.
|
||||
if !totp.check_current(&totp_code).unwrap_or(false) {
|
||||
// If the TOTP code is invalid, return an unauthorized response.
|
||||
return Err((
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(json!({ "error": "Invalid 2FA code." }))
|
||||
));
|
||||
}
|
||||
},
|
||||
None => return Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({ "error": "2FA code required for this account." }))
|
||||
)),
|
||||
None => {
|
||||
// If TOTP is set up but no code is provided, return a bad request.
|
||||
return Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(json!({ "error": "2FA code required for this account." }))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a JWT token for the user.
|
||||
let email = user.email.clone();
|
||||
let token = encode_jwt(user.email)
|
||||
.map_err(|_| (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": "Internal server error." }))
|
||||
))?;
|
||||
.map_err(|_| {
|
||||
// If there's an error generating the JWT, return an internal server error.
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(json!({ "error": "Internal server error." }))
|
||||
)
|
||||
})?;
|
||||
|
||||
// Log the successful sign-in.
|
||||
info!("User signed in: {}", email);
|
||||
|
||||
// Return the JWT token in a JSON response.
|
||||
Ok(Json(json!({ "token": token })))
|
||||
}
|
||||
|
||||
@@ -39,3 +39,17 @@ pub struct AuthError {
|
||||
|
||||
// Implement Error trait for AuthError if needed
|
||||
// impl std::error::Error for AuthError {}
|
||||
|
||||
|
||||
/// Data structure for user sign-in information.
|
||||
///
|
||||
/// This includes the user's email, password, and optionally a TOTP code.
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct SignInData {
|
||||
/// User's email address.
|
||||
pub email: String,
|
||||
/// User's password.
|
||||
pub password: String,
|
||||
/// Optional TOTP code for two-factor authentication.
|
||||
pub totp: Option<String>,
|
||||
}
|
||||
Reference in New Issue
Block a user