// PIN authentication module use crate::config::SecurityConfig; use crate::models::{Role, User}; use anyhow::{Context, Result}; use sqlx::MySqlPool; pub async fn authenticate_pin( pool: &MySqlPool, username: &str, pin: &str, security_config: &SecurityConfig, ) -> Result> { // Fetch user from database 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 username = ? AND active = TRUE AND pin_code IS NOT NULL "#, ) .bind(username) .fetch_optional(pool) .await .context("Failed to fetch user from database")?; if let Some(user) = user { // Check if user has a PIN set if let Some(user_pin) = &user.pin_code { // Verify PIN - either bcrypt hash or plaintext depending on config let pin_valid = if security_config.hash_pins { bcrypt::verify(pin, user_pin).unwrap_or(false) } else { user_pin == pin }; if pin_valid { // 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) } } else { // User doesn't have a PIN set Ok(None) } } else { Ok(None) } }