aboutsummaryrefslogtreecommitdiff
path: root/src/auth/token.rs
blob: 17f75e47533cd8cce162098f98023e96c57fcb8c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// 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<Option<(User, Role)>> {
    // 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<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 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<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 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<Option<(User, Role)>> {
    // 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)
    }
}