rusty_api/core/
auth.rs

1use crate::core::user::{LoginResponse, User};
2use bcrypt::{hash, verify};
3use jsonwebtoken::{encode, Header, EncodingKey};
4use serde::{Deserialize, Serialize};
5use std::env;
6use sqlx::Row;
7
8#[derive(Serialize, Deserialize)]
9pub struct Claims {
10    pub sub: i32,
11    pub exp: usize,
12}
13
14pub fn hash_password(password: &str) -> Result<String, bcrypt::BcryptError> {
15    hash(password, 12)
16}
17
18pub fn verify_password(password: &str, hash: &str) -> bool {
19    verify(password, hash).unwrap_or(false)
20}
21
22pub fn generate_jwt(user: &User) -> String {
23    let claims = Claims {
24        sub: user.id,
25        exp: (chrono::Utc::now() + chrono::Duration::days(7)).timestamp() as usize,
26    };
27    let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
28    encode(&Header::default(), &claims, &EncodingKey::from_secret(secret.as_ref())).unwrap()
29}
30
31pub async fn register_user(
32    pool: &sqlx::SqlitePool,
33    input: crate::core::user::RegisterInput,
34) -> Result<User, String> {
35    // Hash password
36    let password_hash = hash_password(&input.password).map_err(|e| e.to_string())?;
37    
38    // Insert user
39    let user = sqlx::query_as::<_, User>(
40        "INSERT INTO users (username, password_hash) VALUES (?, ?) RETURNING id, username, password_hash"
41    )
42    .bind(&input.username)
43    .bind(&password_hash)
44    .fetch_one(pool)
45    .await
46    .map_err(|e| format!("Database error: {}", e))?;
47    
48    Ok(user)
49}
50
51pub async fn login_user(
52    pool: &sqlx::SqlitePool,
53    input: crate::core::user::LoginInput,
54) -> Result<LoginResponse, String> {
55    // Find user
56    let row = sqlx::query("SELECT id, username, password_hash FROM users WHERE username = ?")
57        .bind(&input.username)
58        .fetch_optional(pool)
59        .await
60        .map_err(|e| format!("Database error: {}", e))?
61        .ok_or("User not found")?;
62
63    let user = User {
64        id: row.get("id"),
65        username: row.get("username"),
66        password_hash: row.get("password_hash"),
67    };
68    
69    // Verify password
70    if !verify_password(&input.password, &user.password_hash) {
71        return Err("Invalid password".to_string());
72    }
73    
74    // Generate JWT
75    let token = generate_jwt(&user);
76    Ok(LoginResponse { token })
77}
78
79/**
80 * Middleware to extract and validate JWT token from the request.
81 */
82pub fn validate_token(token: &str) -> Result<Claims, actix_web::Error> {
83    let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
84
85    match jsonwebtoken::decode::<Claims>(
86        token,
87        &jsonwebtoken::DecodingKey::from_secret(secret.as_ref()),
88        &jsonwebtoken::Validation::default(),
89    ) {
90        Ok(decoded) => Ok(decoded.claims),
91        Err(_) => Err(actix_web::error::ErrorUnauthorized("Invalid token")),
92    }
93}