- update: auth system to work with the website auth

This commit is contained in:
2025-03-16 01:35:44 -04:00
parent cbd71d1ab1
commit cf9efc9866
11 changed files with 93 additions and 219 deletions

View File

@@ -1,6 +1,5 @@
use crate::database::{ use crate::database::{
user_service_client::UserServiceClient, CreateUserRequest, CreateUserResponse, user_service_client::UserServiceClient, GetUserByEmailRequest, GetUserByUsernameRequest, GetUserRequest, GetUserResponse,
GetUserByEmailRequest, GetUserByUsernameRequest, GetUserRequest, GetUserResponse,
}; };
use async_trait::async_trait; use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@@ -22,12 +21,6 @@ pub trait DatabaseClientTrait: Sized {
&mut self, &mut self,
email: &str, email: &str,
) -> Result<GetUserResponse, Box<dyn std::error::Error>>; ) -> Result<GetUserResponse, Box<dyn std::error::Error>>;
async fn create_user(
&mut self,
username: &str,
email: &str,
password: &str,
) -> Result<CreateUserResponse, Box<dyn std::error::Error>>;
async fn store_password_reset( async fn store_password_reset(
&mut self, &mut self,
email: &str, email: &str,
@@ -95,21 +88,6 @@ impl DatabaseClientTrait for DatabaseClient {
Ok(response.into_inner()) Ok(response.into_inner())
} }
async fn create_user(
&mut self,
username: &str,
email: &str,
password: &str,
) -> Result<CreateUserResponse, Box<dyn Error>> {
let request = tonic::Request::new(CreateUserRequest {
username: username.to_string(),
email: email.to_string(),
hashed_password: password.to_string(),
});
let response = self.client.create_user(request).await?;
Ok(response.into_inner())
}
async fn store_password_reset( async fn store_password_reset(
&mut self, &mut self,
_email: &str, _email: &str,

View File

@@ -27,51 +27,7 @@ impl AuthService for MyAuthService {
&self, &self,
request: Request<LoginRequest>, request: Request<LoginRequest>,
) -> Result<Response<LoginResponse>, Status> { ) -> Result<Response<LoginResponse>, Status> {
let req = request.into_inner(); Err(Status::unimplemented("login not implemented due to changes"))
info!("Login attempt for username: {}", req.username);
if let Some(user) = verify_user(
self.db_client.as_ref().clone(),
&req.username,
&req.password,
)
.await
{
let user_id = user.user_id.to_string();
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.user_id,
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;
let token = generate_token(&user_id, &&session_id.clone(), user.roles)
.map_err(|_| Status::internal("Token generation failed"))?;
info!("Login successful for username: {}", req.username);
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> { async fn logout(&self, request: Request<LogoutRequest>) -> Result<Response<Empty>, Status> {
@@ -188,29 +144,30 @@ impl AuthService for MyAuthService {
&self, &self,
request: Request<RegisterRequest>, request: Request<RegisterRequest>,
) -> Result<Response<RegisterResponse>, Status> { ) -> Result<Response<RegisterResponse>, Status> {
let req = request.into_inner(); // let req = request.into_inner();
//
// Hash the password // // Hash the password
let hashed_password = hash_password(&req.password); // let hashed_password = hash_password(&req.password);
//
// Create user in the database // // Create user in the database
let result = self // let result = self
.db_client // .db_client
.as_ref() // .as_ref()
.clone() // .clone()
.create_user(&req.username, &req.email, &hashed_password) // .create_user(&req.username, &req.email, &hashed_password)
.await; // .await;
//
match result { // match result {
Ok(user) => Ok(Response::new(RegisterResponse { // Ok(user) => Ok(Response::new(RegisterResponse {
user_id: user.user_id, // user_id: user.user_id,
message: "User registered successfully".into(), // message: "User registered successfully".into(),
})), // })),
Err(e) => { // Err(e) => {
error!("Failed to register user: {:?}", e); // error!("Failed to register user: {:?}", e);
Err(Status::internal("Failed to register user")) // Err(Status::internal("Failed to register user"))
} // }
} // }
Err(Status::unimplemented("register not implemented"))
} }
async fn request_password_reset( async fn request_password_reset(

View File

@@ -1,4 +1,4 @@
use crate::database::{CreateUserResponse, GetUserResponse}; use crate::database::GetUserResponse;
use crate::database_client::{DatabaseClientTrait, PasswordReset}; use crate::database_client::{DatabaseClientTrait, PasswordReset};
use async_trait::async_trait; use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@@ -15,7 +15,6 @@ mock! {
async fn get_user_by_userid(&mut self, user_id: i32) -> Result<GetUserResponse, Box<dyn std::error::Error>>; async fn get_user_by_userid(&mut self, user_id: i32) -> Result<GetUserResponse, Box<dyn std::error::Error>>;
async fn get_user_by_username(&mut self, user_id: &str) -> Result<GetUserResponse, Box<dyn std::error::Error>>; async fn get_user_by_username(&mut self, user_id: &str) -> Result<GetUserResponse, Box<dyn std::error::Error>>;
async fn get_user_by_email(&mut self, email: &str) -> Result<GetUserResponse, Box<dyn Error>>; async fn get_user_by_email(&mut self, email: &str) -> Result<GetUserResponse, Box<dyn Error>>;
async fn create_user(&mut self, username: &str, email: &str, password: &str) -> Result<CreateUserResponse, Box<dyn std::error::Error>>;
async fn store_password_reset(&mut self, email: &str, reset_token: &str, expires_at: DateTime<Utc>) -> Result<(), Box<dyn Error>>; async fn store_password_reset(&mut self, email: &str, reset_token: &str, expires_at: DateTime<Utc>) -> Result<(), Box<dyn Error>>;
async fn get_password_reset(&self, reset_token: &str) -> Result<Option<PasswordReset>, Box<dyn Error>>; async fn get_password_reset(&self, reset_token: &str) -> Result<Option<PasswordReset>, Box<dyn Error>>;
async fn delete_password_reset(&self, reset_token: &str) -> Result<(), Box<dyn Error>>; async fn delete_password_reset(&self, reset_token: &str) -> Result<(), Box<dyn Error>>;

View File

@@ -27,11 +27,12 @@ pub async fn verify_user<T: DatabaseClientTrait>(
username: &str, username: &str,
password: &str, password: &str,
) -> Option<GetUserResponse> { ) -> Option<GetUserResponse> {
let user = db_client.get_user_by_username(username).await.ok()?; // let user = db_client.get_user_by_username(username).await.ok()?;
//
if verify_password(password, &user.hashed_password) { // if verify_password(password, &user.hashed_password) {
Some(user) // Some(user)
} else { // } else {
None // None
} // }
None
} }

View File

@@ -8,7 +8,7 @@ use utils::redis_cache::{Cache, RedisCache};
#[derive(Debug, FromRow, Serialize, Deserialize)] #[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct Character { pub struct Character {
pub id: i32, pub id: i32,
pub user_id: i32, pub user_id: String,
pub name: String, pub name: String,
pub money: i64, pub money: i64,
pub inventory: serde_json::Value, pub inventory: serde_json::Value,
@@ -49,9 +49,9 @@ impl CharacterRepository {
// Fetch from database // Fetch from database
let character = sqlx::query_as::<_, Character>( let character = sqlx::query_as::<_, Character>(
"SELECT id, user_id, name, money, inventory, stats, skills, looks, position, \ "SELECT id, userId as user_id, name, money, inventory, stats, skills, looks, position, \
created_at, updated_at, extract(epoch from (deleted_at - now()))::BIGINT as deleted_at, is_active \ createdAt as created_at, updatedAt as updated_at, extract(epoch from (deletedAt - now()))::BIGINT as deleted_at, isActive as is_active \
FROM characters WHERE id = $1 AND is_active = true", FROM character WHERE id = $1 AND isActive = true",
) )
.bind(character_id) .bind(character_id)
.fetch_one(&self.pool) .fetch_one(&self.pool)
@@ -69,7 +69,7 @@ impl CharacterRepository {
pub async fn create_character( pub async fn create_character(
&self, &self,
user_id: i32, user_id: String,
name: &str, name: &str,
inventory: serde_json::Value, inventory: serde_json::Value,
skills: serde_json::Value, skills: serde_json::Value,
@@ -78,10 +78,10 @@ impl CharacterRepository {
position: serde_json::Value, position: serde_json::Value,
) -> Result<i32, sqlx::Error> { ) -> Result<i32, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
"INSERT INTO characters (user_id, name, inventory, stats, skills, looks, position, created_at, updated_at, is_active) \ "INSERT INTO character (userId, name, inventory, stats, skills, looks, position, createdAt, updatedAt, isActive) \
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW(), true) RETURNING id", VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW(), true) RETURNING id",
) )
.bind(user_id) .bind(&user_id)
.bind(name) .bind(name)
.bind(inventory) .bind(inventory)
.bind(stats) .bind(stats)
@@ -107,9 +107,9 @@ impl CharacterRepository {
character_id: i32, character_id: i32,
delete_type: i32, delete_type: i32,
) -> Result<i64, sqlx::Error> { ) -> Result<i64, sqlx::Error> {
let mut query = "UPDATE characters SET updated_at = NOW(), deleted_at = NOW() + '24 hours' WHERE id = $1 RETURNING user_id, extract(epoch from (deleted_at - now()))::BIGINT as deleted_at"; let mut query = "UPDATE character SET updatedAt = NOW(), deletedAt = NOW() + '24 hours' WHERE id = $1 RETURNING userId, extract(epoch from (deletedAt - now()))::BIGINT as deletedAt";
if 0 == delete_type { if 0 == delete_type {
query = "UPDATE characters SET updated_at = NOW(), deleted_at = null WHERE id = $1 RETURNING user_id, 0::BIGINT as deleted_at"; query = "UPDATE character SET updatedAt = NOW(), deletedAt = null WHERE id = $1 RETURNING userId, 0::BIGINT as deletedAt";
} }
let result = sqlx::query(query) let result = sqlx::query(query)
.bind(character_id) .bind(character_id)
@@ -154,7 +154,7 @@ impl CharacterRepository {
// Fetch from database // Fetch from database
let characters = sqlx::query_as::<_, Character>( let characters = sqlx::query_as::<_, Character>(
"SELECT id, user_id, name, money, inventory, stats, skills, looks, position, created_at, updated_at, extract(epoch from (deleted_at - now()))::BIGINT as deleted_at, is_active FROM characters WHERE user_id = $1 AND is_active = true", "SELECT id, userId as user_id, name, money, inventory, stats, skills, looks, position, createdAt as created_at, updatedAt as updated_at, extract(epoch from (deletedAt - now()))::BIGINT as deleted_at, isActive as is_active FROM character WHERE userId = $1 AND isActive = true",
) )
.bind(user_id) .bind(user_id)
.fetch_all(&self.pool) .fetch_all(&self.pool)

View File

@@ -1,7 +1,7 @@
use crate::grpc::database_service::MyDatabaseService; use crate::grpc::database_service::MyDatabaseService;
use crate::grpc::user_service_server::UserService; use crate::grpc::user_service_server::UserService;
use crate::grpc::{ use crate::grpc::{
CreateUserRequest, CreateUserResponse, GetUserByEmailRequest, GetUserByUsernameRequest, GetUserByEmailRequest, GetUserByUsernameRequest,
GetUserRequest, GetUserResponse, GetUserRequest, GetUserResponse,
}; };
use tonic::{Request, Response, Status}; use tonic::{Request, Response, Status};
@@ -23,30 +23,12 @@ impl UserService for MyDatabaseService {
Ok(Response::new(GetUserResponse { Ok(Response::new(GetUserResponse {
user_id: user.id, user_id: user.id,
username: user.username, username: user.name,
email: user.email, email: user.email,
hashed_password: user.hashed_password, role: user.role,
roles: user.roles.unwrap_or_else(Vec::new),
})) }))
} }
async fn create_user(
&self,
request: Request<CreateUserRequest>,
) -> 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)
.await
.map_err(|_| Status::internal("Failed to create user"))?;
// Return the newly created user ID
Ok(Response::new(CreateUserResponse { user_id }))
}
async fn get_user_by_username( async fn get_user_by_username(
&self, &self,
request: Request<GetUserByUsernameRequest>, request: Request<GetUserByUsernameRequest>,
@@ -62,10 +44,9 @@ impl UserService for MyDatabaseService {
Ok(Response::new(GetUserResponse { Ok(Response::new(GetUserResponse {
user_id: user.id, user_id: user.id,
username: user.username, username: user.name,
email: user.email, email: user.email,
hashed_password: user.hashed_password, role: user.role,
roles: user.roles.unwrap_or_else(Vec::new),
})) }))
} }
@@ -84,10 +65,9 @@ impl UserService for MyDatabaseService {
Ok(Response::new(GetUserResponse { Ok(Response::new(GetUserResponse {
user_id: user.id, user_id: user.id,
username: user.username, username: user.name,
email: user.email, email: user.email,
hashed_password: user.hashed_password, role: user.role,
roles: user.roles.unwrap_or_else(Vec::new),
})) }))
} }
} }

View File

@@ -2,15 +2,14 @@ use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Row}; use sqlx::{FromRow, Row};
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use utils::redis_cache::{Cache, RedisCache}; // Import RedisCache and Cache Trait use utils::redis_cache::{Cache, RedisCache};
#[derive(Debug, FromRow, Serialize, Deserialize)] #[derive(Debug, FromRow, Serialize, Deserialize)]
pub struct User { pub struct User {
pub id: i32, pub id: i32,
pub username: String, pub name: String,
pub email: String, pub email: String,
pub hashed_password: String, pub role: String,
pub roles: Option<Vec<String>>,
pub created_at: chrono::NaiveDateTime, pub created_at: chrono::NaiveDateTime,
pub updated_at: chrono::NaiveDateTime, pub updated_at: chrono::NaiveDateTime,
} }
@@ -40,7 +39,7 @@ impl UserRepository {
} }
let user = sqlx::query_as::<_, User>( let user = sqlx::query_as::<_, User>(
"SELECT id, username, email, hashed_password, roles, created_at, updated_at FROM users WHERE id = $1", "SELECT id, name, email, role, createdAt, updatedAt FROM user WHERE id = $1",
) )
.bind(user_id) .bind(user_id)
.fetch_one(&self.pool) .fetch_one(&self.pool)
@@ -70,7 +69,7 @@ impl UserRepository {
} }
let user = sqlx::query_as::<_, User>( let user = sqlx::query_as::<_, User>(
"SELECT id, username, email, hashed_password, roles, created_at, updated_at FROM users WHERE username = $1", "SELECT id, name, email, role, createdAt, updatedAt FROM user WHERE name = $1",
) )
.bind(username) .bind(username)
.fetch_one(&self.pool) .fetch_one(&self.pool)
@@ -100,7 +99,7 @@ impl UserRepository {
} }
let user = sqlx::query_as::<_, User>( let user = sqlx::query_as::<_, User>(
"SELECT id, username, email, hashed_password, roles, created_at, updated_at FROM users WHERE email = $1", "SELECT id, name, email, role, createdAt, updatedAt FROM user WHERE email = $1",
) )
.bind(email) .bind(email)
.fetch_one(&self.pool) .fetch_one(&self.pool)
@@ -115,62 +114,33 @@ impl UserRepository {
Ok(user) Ok(user)
} }
pub async fn create_user( pub async fn get_user_by_session(&self, session: &str) -> Result<User, sqlx::Error> {
&self, let cache_key = format!("user:session:{}", session);
username: &str,
email: &str, if let Some(user) = self
hashed_password: &str, .cache
) -> Result<i32, sqlx::Error> { .lock()
let row = sqlx::query( .await
r#" .get::<User>(&cache_key)
INSERT INTO users (username, email, hashed_password) .await
VALUES ($1, $2, $3) .map_err(|_| sqlx::Error::RowNotFound)?
RETURNING id {
"#, return Ok(user);
}
let user = sqlx::query_as::<_, User>(
"SELECT id, name, email, role, createdAt, updatedAt FROM user WHERE email = $1",
) )
.bind(username) .bind(session)
.bind(email) .fetch_one(&self.pool)
.bind(hashed_password)
.fetch_one(&self.pool)
.await?;
Ok(row.get(0))
}
pub async fn update_user_email(
&self,
user_id: i32,
new_email: &str,
) -> Result<(), sqlx::Error> {
sqlx::query("UPDATE users SET email = $1, updated_at = NOW() WHERE id = $2")
.bind(new_email)
.bind(user_id)
.execute(&self.pool)
.await?; .await?;
let cache_key = format!("user:{}", user_id);
self.cache self.cache
.lock() .lock()
.await .await
.delete(&cache_key) .set(&cache_key, &user, 300)
.await .await
.map_err(|_| sqlx::Error::RowNotFound)?; .map_err(|_| sqlx::Error::RowNotFound)?;
Ok(()) Ok(user)
}
pub async fn delete_user(&self, user_id: i32) -> Result<(), sqlx::Error> {
sqlx::query("DELETE FROM users WHERE id = $1")
.bind(user_id)
.execute(&self.pool)
.await?;
let cache_key = format!("user:{}", user_id);
self.cache
.lock()
.await
.delete(&cache_key)
.await
.map_err(|_| sqlx::Error::RowNotFound)?;
Ok(())
} }
} }

View File

@@ -172,7 +172,7 @@
- "5432:5432" - "5432:5432"
volumes: volumes:
- db_data:/var/lib/postgresql/data - db_data:/var/lib/postgresql/data
- ./sql/schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro # - ./sql/schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro
consul: consul:
image: hashicorp/consul:latest image: hashicorp/consul:latest

View File

@@ -23,7 +23,7 @@ message CharacterListResponse {
} }
message CreateCharacterRequest { message CreateCharacterRequest {
int32 user_id = 1; string user_id = 1;
string name = 2; string name = 2;
string inventory = 3; // JSON serialized string inventory = 3; // JSON serialized
string skills = 4; // JSON serialized string skills = 4; // JSON serialized
@@ -50,7 +50,7 @@ message DeleteCharacterResponse {
message Character { message Character {
int32 id = 1; int32 id = 1;
int32 user_id = 2; string user_id = 2;
string name = 3; string name = 3;
int64 money = 4; int64 money = 4;
string inventory = 6; string inventory = 6;

View File

@@ -3,38 +3,26 @@ syntax = "proto3";
package user_db_api; package user_db_api;
service UserService { service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse); rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse); rpc GetUserByUsername(GetUserByUsernameRequest) returns (GetUserResponse);
rpc GetUserByUsername(GetUserByUsernameRequest) returns (GetUserResponse); rpc GetUserByEmail(GetUserByEmailRequest) returns (GetUserResponse);
rpc GetUserByEmail(GetUserByEmailRequest) returns (GetUserResponse);
} }
message GetUserRequest { message GetUserRequest {
int32 user_id = 1; int32 user_id = 1;
} }
message GetUserByUsernameRequest { message GetUserByUsernameRequest {
string username = 1; string username = 1;
} }
message GetUserByEmailRequest { message GetUserByEmailRequest {
string email = 1; string email = 1;
} }
message GetUserResponse { message GetUserResponse {
int32 user_id = 1; int32 user_id = 1;
string username = 2; string username = 2;
string email = 3; string email = 3;
string hashed_password = 4; string role = 4;
repeated string roles = 5;
}
message CreateUserRequest {
string username = 1;
string email = 2;
string hashed_password = 3;
}
message CreateUserResponse {
int32 user_id = 1;
} }

View File

@@ -84,6 +84,7 @@ impl SessionService for SessionServiceImpl {
Ok(Response::new(response)) Ok(Response::new(response))
} else { } else {
debug!("Session not found in redis: {}", req.session_id);
Err(Status::not_found("Session not found")) Err(Status::not_found("Session not found"))
} }
} }