// Token/RFID authentication module use crate::config::SecurityConfig; use crate::models::{Role, User}; use anyhow::{Context, Result}; use sqlx::MySqlPool; pub async fn authenticate_token( pool: &MySqlPool, login_string: &str, security_config: &SecurityConfig, ) -> Result> { // If hashing is enabled, we can't use WHERE login_string = ? directly // Need to fetch all users and verify hashes if security_config.hash_tokens { // Fetch all active users with login_string set let users: Vec = sqlx::query_as::<_, User>( r#" SELECT id, name, username, password, pin_code, login_string, role_id, email, phone, notes, active, last_login_date, created_date, password_reset_token, password_reset_expiry FROM users WHERE login_string IS NOT NULL AND active = TRUE "#, ) .fetch_all(pool) .await .context("Failed to fetch users from database")?; // Find matching user by verifying bcrypt hash for user in users { if let Some(ref stored_hash) = user.login_string { if bcrypt::verify(login_string, stored_hash).unwrap_or(false) { // Found matching user return authenticate_user_by_id(pool, user.id).await; } } } Ok(None) } else { // Plaintext comparison - direct database query let user: Option = sqlx::query_as::<_, User>( r#" SELECT id, name, username, password, pin_code, login_string, role_id, email, phone, notes, active, last_login_date, created_date, password_reset_token, password_reset_expiry FROM users WHERE login_string = ? AND active = TRUE "#, ) .bind(login_string) .fetch_optional(pool) .await .context("Failed to fetch user from database")?; if let Some(user) = user { authenticate_user_by_id(pool, user.id).await } else { Ok(None) } } } async fn authenticate_user_by_id(pool: &MySqlPool, user_id: i32) -> Result> { // Fetch user let user: User = sqlx::query_as::<_, User>( r#" SELECT id, name, username, password, pin_code, login_string, role_id, email, phone, notes, active, last_login_date, created_date, password_reset_token, password_reset_expiry FROM users WHERE id = ? "#, ) .bind(user_id) .fetch_one(pool) .await .context("Failed to fetch user")?; if user.active { // Fetch user's role let role: Role = sqlx::query_as::<_, Role>("SELECT id, name, power, created_at FROM roles WHERE id = ?") .bind(user.role_id) .fetch_one(pool) .await .context("Failed to fetch user role")?; // Update last login date sqlx::query("UPDATE users SET last_login_date = NOW() WHERE id = ?") .bind(user.id) .execute(pool) .await .context("Failed to update last login date")?; Ok(Some((user, role))) } else { Ok(None) } }