jwt implementation and login, not working yet
This commit is contained in:
@@ -26,7 +26,8 @@ lazy_static = "1.5.0"
|
||||
regex = "1.11.1"
|
||||
bcrypt = "0.17.0"
|
||||
validator = { version = "0.20.0", features = ["derive"] }
|
||||
uuid = { version = "1.16.0", features = ["v4"] }
|
||||
uuid = { version = "1.16.0", features = ["serde", "v4"] }
|
||||
jsonwebtoken = "9.3.1"
|
||||
|
||||
[lib]
|
||||
name = "server"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// src/auth/handlers.rs
|
||||
|
||||
pub mod register;
|
||||
pub mod login;
|
||||
|
||||
pub use register::*;
|
||||
pub use login::*;
|
||||
|
||||
46
server/src/auth/handlers/login.rs
Normal file
46
server/src/auth/handlers/login.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
// src/auth/handlers/login.rs
|
||||
use bcrypt::verify;
|
||||
use tonic::{Request, Response, Status};
|
||||
use crate::db::PgPool;
|
||||
use crate::auth::{models::AuthError, logic::jwt}; // Fixed import path
|
||||
use common::proto::multieko2::auth::{LoginRequest, LoginResponse};
|
||||
|
||||
pub async fn login(
|
||||
pool: &PgPool,
|
||||
request: LoginRequest,
|
||||
) -> Result<Response<LoginResponse>, Status> {
|
||||
let user = sqlx::query!(
|
||||
r#"
|
||||
SELECT id, password_hash, role
|
||||
FROM users
|
||||
WHERE username = $1 OR email = $1
|
||||
"#,
|
||||
request.identifier
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
.map_err(|e| Status::internal(e.to_string()))?
|
||||
.ok_or_else(|| Status::unauthenticated("Invalid credentials"))?;
|
||||
|
||||
// Handle the optional password_hash
|
||||
let password_hash = user.password_hash
|
||||
.ok_or_else(|| Status::internal("User account has no password set"))?;
|
||||
|
||||
// Verify the password
|
||||
if !verify(&request.password, &password_hash)
|
||||
.map_err(|e| Status::internal(e.to_string()))?
|
||||
{
|
||||
return Err(Status::unauthenticated("Invalid credentials"));
|
||||
}
|
||||
|
||||
let token = jwt::generate_token(user.id, &user.role)
|
||||
.map_err(|e| Status::internal(e.to_string()))?;
|
||||
|
||||
Ok(Response::new(LoginResponse {
|
||||
access_token: token,
|
||||
token_type: "Bearer".to_string(),
|
||||
expires_in: 86400, // 24 hours
|
||||
user_id: user.id.to_string(),
|
||||
role: user.role,
|
||||
}))
|
||||
}
|
||||
7
server/src/auth/logic.rs
Normal file
7
server/src/auth/logic.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
// src/auth/logic.rs
|
||||
|
||||
pub mod jwt;
|
||||
pub mod middleware;
|
||||
|
||||
pub use jwt::*;
|
||||
pub use middleware::*;
|
||||
55
server/src/auth/logic/jwt.rs
Normal file
55
server/src/auth/logic/jwt.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
// src/auth/jwt.rs
|
||||
use jsonwebtoken::{encode, decode, Header, EncodingKey, DecodingKey, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::{Duration, OffsetDateTime};
|
||||
use uuid::Uuid;
|
||||
use std::sync::OnceLock;
|
||||
use crate::auth::models::AuthError;
|
||||
|
||||
static KEYS: OnceLock<Keys> = OnceLock::new();
|
||||
|
||||
struct Keys {
|
||||
encoding: EncodingKey,
|
||||
decoding: DecodingKey,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Claims {
|
||||
pub sub: Uuid, // User ID
|
||||
pub exp: i64, // Expiration time
|
||||
pub role: String, // User role
|
||||
}
|
||||
|
||||
pub fn init_jwt() -> Result<(), AuthError> {
|
||||
let secret = std::env::var("JWT_SECRET")
|
||||
.map_err(|_| AuthError::ConfigError("JWT_SECRET must be set".to_string()))?;
|
||||
|
||||
KEYS.set(Keys {
|
||||
encoding: EncodingKey::from_secret(secret.as_bytes()),
|
||||
decoding: DecodingKey::from_secret(secret.as_bytes()),
|
||||
}).map_err(|_| AuthError::ConfigError("Failed to initialize JWT keys".to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_token(user_id: Uuid, role: &str) -> Result<String, AuthError> {
|
||||
let keys = KEYS.get().ok_or(AuthError::ConfigError("JWT not initialized".to_string()))?;
|
||||
|
||||
let exp = OffsetDateTime::now_utc() + Duration::hours(24);
|
||||
let claims = Claims {
|
||||
sub: user_id,
|
||||
exp: exp.unix_timestamp(),
|
||||
role: role.to_string(),
|
||||
};
|
||||
|
||||
encode(&Header::default(), &claims, &keys.encoding)
|
||||
.map_err(|e| AuthError::JwtError(e.to_string()))
|
||||
}
|
||||
|
||||
pub fn validate_token(token: &str) -> Result<Claims, AuthError> {
|
||||
let keys = KEYS.get().ok_or(AuthError::ConfigError("JWT not initialized".to_string()))?;
|
||||
|
||||
decode::<Claims>(token, &keys.decoding, &Validation::default())
|
||||
.map(|data| data.claims)
|
||||
.map_err(|e| AuthError::JwtError(e.to_string()))
|
||||
}
|
||||
22
server/src/auth/logic/middleware.rs
Normal file
22
server/src/auth/logic/middleware.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
// src/auth/middleware.rs
|
||||
use tonic::{metadata::MetadataValue, service::Interceptor, Status};
|
||||
use crate::auth::{logic::jwt, models::AuthError};
|
||||
|
||||
pub struct AuthInterceptor;
|
||||
|
||||
impl Interceptor for AuthInterceptor {
|
||||
fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, Status> {
|
||||
let metadata = request.metadata();
|
||||
let token = metadata.get("authorization")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|s| s.strip_prefix("Bearer "))
|
||||
.ok_or(Status::unauthenticated("Missing authorization header"))?;
|
||||
|
||||
let claims = jwt::validate_token(token)
|
||||
.map_err(|e| Status::unauthenticated(e.to_string()))?;
|
||||
|
||||
// Store claims in request extensions
|
||||
request.extensions_mut().insert(claims);
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// src/auth/mod.rs
|
||||
|
||||
pub mod models;
|
||||
pub mod logic;
|
||||
pub mod handlers;
|
||||
|
||||
|
||||
@@ -14,6 +14,14 @@ pub struct RegisterRequest {
|
||||
pub password_confirmation: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Validate, Deserialize)]
|
||||
pub struct LoginRequest {
|
||||
#[validate(length(min = 1))]
|
||||
pub identifier: String,
|
||||
#[validate(length(min = 1))]
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AuthError {
|
||||
#[error("Passwords do not match")]
|
||||
@@ -24,4 +32,10 @@ pub enum AuthError {
|
||||
DatabaseError(String),
|
||||
#[error("Hashing error: {0}")]
|
||||
HashingError(String),
|
||||
#[error("Invalid credentials")]
|
||||
InvalidCredentials,
|
||||
#[error("JWT error: {0}")]
|
||||
JwtError(String),
|
||||
#[error("Configuration error: {0}")]
|
||||
ConfigError(String),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user