- add: logout grpc function

- add: logout packet handler
- add: connection state and service for storing connection data
- add: session service calls to auth-service
- fix: compile error on database service due to moved redis cache
This commit is contained in:
2024-12-20 14:46:00 -05:00
parent 3c1f8c40d6
commit 18afa71d74
22 changed files with 265 additions and 46 deletions

View File

@@ -25,6 +25,7 @@ rand = "0.8.5"
warp = "0.3.7"
reqwest = { version = "0.12.9", features = ["json"] }
utils = { path = "../utils" }
uuid = "1.11.0"
[build-dependencies]
tonic-build = "0.12.3"

View File

@@ -11,6 +11,6 @@ fn main() {
tonic_build::configure()
.build_server(false) // Generate gRPC client code
.compile_well_known_types(true)
.compile_protos(&["../proto/user_db_api.proto"], &["../proto"])
.compile_protos(&["../proto/user_db_api.proto", "../proto/session_service_api.proto"], &["../proto"])
.unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
}

View File

@@ -1,6 +1,9 @@
use std::sync::Arc;
use crate::auth::auth_service_server::AuthService;
use crate::auth::{LoginRequest, LoginResponse, PasswordResetRequest, PasswordResetResponse, RegisterRequest, RegisterResponse, ResetPasswordRequest, ResetPasswordResponse, ValidateTokenRequest, ValidateTokenResponse};
use crate::database_client::{DatabaseClientTrait};
use crate::auth::{LoginRequest, LoginResponse, PasswordResetRequest, PasswordResetResponse, RegisterRequest, RegisterResponse, ResetPasswordRequest, ResetPasswordResponse, ValidateTokenRequest, ValidateTokenResponse, ValidateSessionRequest, ValidateSessionResponse, Empty, LogoutRequest};
use crate::database_client::{DatabaseClient, DatabaseClientTrait};
use crate::session::session_service_client::SessionServiceClient;
use crate::session::{CreateSessionRequest, GetSessionRequest, DeleteSessionRequest};
use crate::jwt::{generate_token, validate_token};
use crate::users::{hash_password, verify_user};
use chrono::{Duration, Utc};
@@ -8,12 +11,13 @@ use rand::Rng;
use tonic::{Request, Response, Status};
use tracing::{error, info, warn};
pub struct MyAuthService<T: DatabaseClientTrait + Clone> {
pub db_client: T,
pub struct MyAuthService {
pub db_client: Arc<DatabaseClient>,
pub session_client: Arc<SessionServiceClient<tonic::transport::Channel>>,
}
#[tonic::async_trait]
impl<T: DatabaseClientTrait + Send + Sync + Clone + 'static> AuthService for MyAuthService<T> {
impl AuthService for MyAuthService {
async fn login(
&self,
request: Request<LoginRequest>,
@@ -22,17 +26,49 @@ impl<T: DatabaseClientTrait + Send + Sync + Clone + 'static> AuthService for MyA
info!("Login attempt for username: {}", req.username);
if let Some(user_id) = verify_user(self.db_client.clone(), &req.username, &req.password).await {
if let Some(user_id) = verify_user(self.db_client.as_ref().clone(), &req.username, &req.password).await {
let token = generate_token(&user_id, vec!["user".to_string()])
.map_err(|_| Status::internal("Token generation failed"))?;
let session_id = uuid::Uuid::new_v4().to_string();
let response = self
.session_client.as_ref().clone()
.create_session(CreateSessionRequest {
session_id: session_id.clone(),
user_id: user_id.parse().unwrap(),
username: req.username.to_string(),
character_id: 0,
ip_address: req.ip_address.to_string(),
})
.await;
let session = match response {
Ok(session) => session,
Err(_) => return Err(Status::internal("Session creation failed")),
};
let session_id = session.into_inner().session_id;
info!("Login successful for username: {}", req.username);
Ok(Response::new(LoginResponse { token, user_id }))
Ok(Response::new(LoginResponse { token, user_id, session_id }))
} else {
warn!("Invalid login attempt for username: {}", req.username);
Err(Status::unauthenticated("Invalid credentials"))
}
}
async fn logout(
&self,
request: Request<LogoutRequest>,
) -> Result<Response<Empty>, Status> {
let req = request.into_inner();
self.session_client.as_ref().clone()
.delete_session(DeleteSessionRequest {
session_id: req.session_id.clone(),
})
.await?;
Ok(Response::new(Empty {}))
}
async fn validate_token(
&self,
@@ -52,6 +88,30 @@ impl<T: DatabaseClientTrait + Send + Sync + Clone + 'static> AuthService for MyA
}
}
async fn validate_session(
&self,
request: Request<ValidateSessionRequest>,
) -> Result<Response<ValidateSessionResponse>, Status> {
let req = request.into_inner();
let response = self
.session_client.as_ref().clone()
.get_session(GetSessionRequest {
session_id: req.session_id,
})
.await;
match response {
Ok(res) => {
println!("Session valid: {:?}", res.into_inner());
Ok(Response::new(ValidateSessionResponse { valid: true }))
}
Err(_) => {
println!("Session invalid or not found");
Ok(Response::new(ValidateSessionResponse { valid: false }))
}
}
}
async fn register(
&self,
request: Request<RegisterRequest>,
@@ -62,7 +122,7 @@ impl<T: DatabaseClientTrait + Send + Sync + Clone + 'static> AuthService for MyA
let hashed_password = hash_password(&req.password);
// Create user in the database
let result = self.db_client.clone().create_user(&req.username, &req.email, &hashed_password)
let result = self.db_client.as_ref().clone().create_user(&req.username, &req.email, &hashed_password)
.await;
match result {
@@ -83,7 +143,7 @@ impl<T: DatabaseClientTrait + Send + Sync + Clone + 'static> AuthService for MyA
) -> Result<Response<PasswordResetResponse>, Status> {
let email = request.into_inner().email;
let user = self.db_client.clone().get_user_by_email(&email).await;
let user = self.db_client.as_ref().clone().get_user_by_email(&email).await;
// Check if the email exists
if user.ok().is_some() {
@@ -98,14 +158,14 @@ impl<T: DatabaseClientTrait + Send + Sync + Clone + 'static> AuthService for MyA
let expires_at = Utc::now() + Duration::hours(1);
// Store the reset token in the database
self.db_client.clone()
self.db_client.as_ref().clone()
.store_password_reset(&email, &reset_token, expires_at)
.await
.map_err(|e| Status::internal(format!("Database error: {}", e)))?;
// Send the reset email
// send_email(&email, "Password Reset Request", &format!(
// "Click the link to reset your password: https://example.com/reset?token={}",
// "Click the link to reset your password: https://azgstudio.com/reset?token={}",
// reset_token
// ))
// .map_err(|e| Status::internal(format!("Email error: {}", e)))?;

View File

@@ -4,10 +4,14 @@ pub mod database_client;
pub mod users;
pub mod auth {
tonic::include_proto!("auth"); // Path matches the package name in auth.proto
tonic::include_proto!("auth");
}
pub mod database {
tonic::include_proto!("user_db_api"); // Matches package name in user_db_api.proto
tonic::include_proto!("user_db_api");
}
pub mod session {
tonic::include_proto!("session_service_api");
}
#[cfg(test)]

View File

@@ -1,19 +1,18 @@
use auth_service::auth::auth_service_server::AuthServiceServer;
use auth_service::database_client::DatabaseClient;
use auth_service::database_client::DatabaseClientTrait;
use auth_service::session::session_service_client::SessionServiceClient;
use auth_service::grpc::MyAuthService;
use dotenv::dotenv;
use std::collections::HashMap;
use std::env;
use std::net::ToSocketAddrs;
use std::str::FromStr;
use std::sync::Arc;
use tokio::{select, signal};
use tonic::transport::Server;
use tracing::log::debug;
use tracing::{info, Level};
use utils::consul_registration;
use utils::service_discovery::get_service_address;
use warp::Filter;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -36,6 +35,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let health_check_url = format!("http://{}:{}/health", service_address, health_port);
let health_check_endpoint_addr = format!("{}:{}", service_address, health_port);
let db_nodes = get_service_address(&consul_url, "database-service").await?;
let session_nodes = get_service_address(&consul_url, "session-service").await?;
// Register service with Consul
let service_id = consul_registration::get_or_generate_service_id();
@@ -58,12 +58,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let db_address = db_nodes.get(0).unwrap();
let db_url = format!("http://{}:{}", db_address.ServiceAddress, db_address.ServicePort);
let database_client = DatabaseClient::connect(&db_url).await?;
let db_client = Arc::new(DatabaseClient::connect(&db_url).await?);
let session_address = session_nodes.get(0).unwrap();
let session_address = format!("http://{}:{}", session_address.ServiceAddress, session_address.ServicePort);
let session_client = Arc::new(SessionServiceClient::connect(session_address).await?);
let full_addr = format!("{}:{}", &addr, port);
let address = full_addr.parse().expect("Invalid address");
let auth_service = MyAuthService {
db_client: database_client,
db_client,
session_client
};
println!("Authentication Service running on {}", addr);