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 let password_hash = hash_password(&input.password).map_err(|e| e.to_string())?;
37
38 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 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 if !verify_password(&input.password, &user.password_hash) {
71 return Err("Invalid password".to_string());
72 }
73
74 let token = generate_jwt(&user);
76 Ok(LoginResponse { token })
77}
78
79pub 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}