- fix: issue where login failed to return the user if their role was null

- add: register route for api-service
- update: spawn a thread for the rest api in the api service
This commit is contained in:
2024-12-17 04:06:56 -05:00
parent 52455d6ffc
commit c67cdd5b2a
8 changed files with 74 additions and 23 deletions

View File

@@ -8,7 +8,7 @@ use tonic::transport::Channel;
use tower_http::cors::{Any, CorsLayer};
use auth::auth_service_client::AuthServiceClient;
use auth::LoginRequest;
use auth::{LoginRequest, RegisterRequest};
use log::error;
use tokio::sync::Mutex;
@@ -27,6 +27,19 @@ struct RestLoginResponse {
token: String,
}
#[derive(Serialize, Deserialize)]
struct RestRegisterRequest {
username: String,
email: String,
password: String,
}
#[derive(Serialize, Deserialize)]
struct RestRegisterResponse {
success: bool,
message: String,
}
async fn login_handler(
State(grpc_client): State<Arc<Mutex<AuthServiceClient<Channel>>>>,
Json(payload): Json<RestLoginRequest>,
@@ -49,9 +62,34 @@ async fn login_handler(
}
}
async fn register_handler(
State(grpc_client): State<Arc<Mutex<AuthServiceClient<Channel>>>>,
Json(payload): Json<RestRegisterRequest>,
) -> Result<Json<RestRegisterResponse>, axum::http::StatusCode> {
let mut client = grpc_client.lock().await;
let request = tonic::Request::new(RegisterRequest {
username: payload.username,
email: payload.email,
password: payload.password,
});
match client.register(request).await {
Ok(response) => Ok(Json(RestRegisterResponse {
success: response.into_inner().user_id != 0,
message: "Registration successful".to_string(),
})),
Err(e) => {
error!("gRPC Login call failed: {}", e);
Err(axum::http::StatusCode::INTERNAL_SERVER_ERROR)
},
}
}
pub async fn serve_rest_api(
grpc_client: Arc<Mutex<AuthServiceClient<Channel>>>,
) -> Result<(), Box<dyn std::error::Error>> {
) -> Result<(), Box<dyn std::error::Error + Send>> {
let cors = CorsLayer::new()
.allow_origin(Any) // Allow requests from any origin
.allow_methods([Method::GET, Method::POST]) // Allow specific methods
@@ -59,6 +97,7 @@ pub async fn serve_rest_api(
let app = Router::new()
.route("/api/login", post(login_handler))
.route("/api/register", post(register_handler))
.with_state(grpc_client)
.layer(cors);

View File

@@ -65,7 +65,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Start the Axum REST API
info!("Starting REST API on {}:{}", addr, port);
axum_gateway::serve_rest_api(grpc_client).await?;
tokio::spawn(axum_gateway::serve_rest_api(grpc_client));
select! {
_ = signal::ctrl_c() => {},

View File

@@ -6,7 +6,7 @@ use crate::users::{hash_password, verify_user};
use chrono::{Duration, Utc};
use rand::Rng;
use tonic::{Request, Response, Status};
use tracing::{info, warn};
use tracing::{error, info, warn};
pub struct MyAuthService<T: DatabaseClientTrait + Clone> {
pub db_client: T,
@@ -62,11 +62,19 @@ impl<T: DatabaseClientTrait + Send + Sync + Clone + 'static> AuthService for MyA
let hashed_password = hash_password(&req.password);
// Create user in the database
let user = self.db_client.clone().create_user(&req.username, &req.email, &hashed_password)
.await
.map_err(|e| Status::internal(format!("Database error: {}", e)))?;
let result = self.db_client.clone().create_user(&req.username, &req.email, &hashed_password)
.await;
Ok(Response::new(RegisterResponse { user_id: user.user_id }))
match result {
Ok(user) => Ok(Response::new(RegisterResponse {
user_id: user.user_id,
message: "User registered successfully".into(),
})),
Err(e) => {
error!("Failed to register user: {:?}", e);
Err(Status::internal("Failed to register user"))
}
}
}
async fn request_password_reset(

View File

@@ -21,7 +21,6 @@ pub fn verify_password(password: &str, hash: &str) -> bool {
pub async fn verify_user<T: DatabaseClientTrait>(mut db_client: T,
username: &str, password: &str) -> Option<String> {
// Placeholder: Replace with a gRPC call to the Database Service
let user = db_client.get_user_by_username(username).await.ok()?;
if verify_password(password, &user.hashed_password) {

View File

@@ -20,7 +20,7 @@ impl UserService for MyDatabaseService {
username: user.username,
email: user.email,
hashed_password: user.hashed_password,
roles: user.roles,
roles: user.roles.unwrap_or_else(Vec::new),
}))
}
@@ -30,7 +30,7 @@ impl UserService for MyDatabaseService {
) -> Result<Response<CreateUserResponse>, Status> {
let req = request.into_inner();
let user_id = self.db.user_repo.create_user(&req.username, &req.email, &req.hashed_password, &[])
let user_id = self.db.user_repo.create_user(&req.username, &req.email, &req.hashed_password)
.await
.map_err(|_| Status::internal("Failed to create user"))?;
@@ -53,7 +53,7 @@ impl UserService for MyDatabaseService {
username: user.username,
email: user.email,
hashed_password: user.hashed_password,
roles: user.roles,
roles: user.roles.unwrap_or_else(Vec::new),
}))
}
@@ -72,7 +72,7 @@ impl UserService for MyDatabaseService {
username: user.username,
email: user.email,
hashed_password: user.hashed_password,
roles: user.roles,
roles: user.roles.unwrap_or_else(Vec::new),
}))
}
}

View File

@@ -3,6 +3,7 @@ use crate::redis_cache::{RedisCache, Cache}; // Import RedisCache and Cache Trai
use serde::{Serialize, Deserialize};
use std::sync::Arc;
use tokio::sync::Mutex;
use tracing::{debug};
#[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct User {
@@ -10,7 +11,7 @@ pub struct User {
pub username: String,
pub email: String,
pub hashed_password: String,
pub roles: Vec<String>,
pub roles: Option<Vec<String>>,
pub created_at: chrono::NaiveDateTime,
pub updated_at: chrono::NaiveDateTime,
}
@@ -79,19 +80,21 @@ impl UserRepository {
Ok(user)
}
pub async fn create_user(&self, username: &str, email: &str, hashed_password: &str, roles: &[String]) -> Result<i32, sqlx::Error> {
let row = sqlx::query(
"INSERT INTO users (username, email, hashed_password, roles, created_at, updated_at) \
VALUES ($1, $2, $3, $4, NOW(), NOW()) RETURNING id",
pub async fn create_user(&self, username: &str, email: &str, hashed_password: &str) -> Result<i32, sqlx::Error> {
let result = sqlx::query!(
r#"
INSERT INTO users (username, email, hashed_password)
VALUES ($1, $2, $3)
RETURNING id
"#,
username,
email,
hashed_password
)
.bind(username)
.bind(email)
.bind(hashed_password)
.bind(roles)
.fetch_one(&self.pool)
.await?;
Ok(row.get("id"))
Ok(result.id)
}
pub async fn update_user_email(&self, user_id: i32, new_email: &str) -> Result<(), sqlx::Error> {

View File

@@ -20,6 +20,7 @@
dockerfile: ./api-service/Dockerfile
ports:
- "8080:8080"
- "8081:8081"
env_file:
- ./api-service/.env
- .env

View File

@@ -37,6 +37,7 @@ message RegisterRequest {
message RegisterResponse {
int32 user_id = 1;
string message = 2;
}
message PasswordResetRequest {