broken only push user data
This commit is contained in:
@@ -12,7 +12,7 @@ dotenvy = "0.15.7"
|
||||
prost = "0.13.5"
|
||||
serde = { version = "1.0.218", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
sqlx = { version = "0.8.3", features = ["chrono", "postgres", "runtime-tokio", "runtime-tokio-native-tls", "time"] }
|
||||
sqlx = { version = "0.8.3", features = ["chrono", "postgres", "runtime-tokio", "runtime-tokio-native-tls", "time", "uuid"] }
|
||||
tokio = { version = "1.43.0", features = ["full", "macros"] }
|
||||
tonic = "0.12.3"
|
||||
tonic-reflection = "0.12.3"
|
||||
@@ -24,6 +24,9 @@ thiserror = "2.0.12"
|
||||
dashmap = "6.1.0"
|
||||
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"] }
|
||||
|
||||
[lib]
|
||||
name = "server"
|
||||
|
||||
38
server/migrations/20250324192805_auth.sql
Normal file
38
server/migrations/20250324192805_auth.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
-- Add migration script here
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
username VARCHAR(255) NOT NULL UNIQUE,
|
||||
email VARCHAR(255) UNIQUE,
|
||||
password_hash VARCHAR(255),
|
||||
role VARCHAR(20) NOT NULL DEFAULT 'accountant',
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Add an index for faster lookups
|
||||
CREATE INDEX idx_users_email_username ON users(email, username);
|
||||
|
||||
ALTER TABLE users
|
||||
ADD CONSTRAINT valid_roles CHECK (role IN (
|
||||
'admin',
|
||||
'moderator',
|
||||
'accountant',
|
||||
'viewer'
|
||||
));
|
||||
|
||||
-- Create JWT sessions table
|
||||
CREATE TABLE user_sessions (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
jwt_token TEXT NOT NULL UNIQUE,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
revoked BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Add indexes
|
||||
CREATE INDEX idx_sessions_user ON user_sessions(user_id);
|
||||
CREATE INDEX idx_sessions_expires ON user_sessions(expires_at);
|
||||
|
||||
|
||||
|
||||
5
server/src/auth/handlers.rs
Normal file
5
server/src/auth/handlers.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
// src/auth/handlers.rs
|
||||
|
||||
pub mod register;
|
||||
|
||||
pub use register::*;
|
||||
61
server/src/auth/handlers/register.rs
Normal file
61
server/src/auth/handlers/register.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use bcrypt::{hash, DEFAULT_COST};
|
||||
use tonic::{Request, Response, Status};
|
||||
use uuid::Uuid;
|
||||
use crate::{auth::{models::{RegisterRequest, AuthResponse, AuthError}, db::PgPool}};
|
||||
|
||||
pub struct AuthService {
|
||||
pool: PgPool,
|
||||
}
|
||||
|
||||
impl AuthService {
|
||||
pub fn new(pool: PgPool) -> Self {
|
||||
Self { pool }
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl multieko2::auth::auth_service_server::AuthService for AuthService {
|
||||
async fn register(
|
||||
&self,
|
||||
request: Request<multieko2::auth::RegisterRequest>,
|
||||
) -> Result<Response<multieko2::auth::AuthResponse>, Status> {
|
||||
let payload = request.into_inner();
|
||||
|
||||
// Validate passwords match
|
||||
if payload.password != payload.password_confirmation {
|
||||
return Err(Status::invalid_argument(AuthError::PasswordMismatch.to_string()));
|
||||
}
|
||||
|
||||
// Hash password
|
||||
let password_hash = hash(payload.password, DEFAULT_COST)
|
||||
.map_err(|e| Status::internal(AuthError::HashingError(e.to_string()).to_string()))?;
|
||||
|
||||
// Insert user
|
||||
let user = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO users (username, email, password_hash, role)
|
||||
VALUES ($1, $2, $3, 'accountant')
|
||||
RETURNING id, username, email, role
|
||||
"#,
|
||||
payload.username,
|
||||
payload.email,
|
||||
password_hash
|
||||
)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
if e.to_string().contains("duplicate key") {
|
||||
Status::already_exists(AuthError::UserExists.to_string())
|
||||
} else {
|
||||
Status::internal(AuthError::DatabaseError(e.to_string()).to_string())
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Response::new(multieko2::auth::AuthResponse {
|
||||
id: user.id.to_string(),
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
}))
|
||||
}
|
||||
}
|
||||
5
server/src/auth/mod.rs
Normal file
5
server/src/auth/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
// src/auth/mod.rs
|
||||
|
||||
pub mod models;
|
||||
pub mod handlers;
|
||||
|
||||
34
server/src/auth/models.rs
Normal file
34
server/src/auth/models.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use validator::Validate;
|
||||
|
||||
#[derive(Debug, Validate, Deserialize)]
|
||||
pub struct RegisterRequest {
|
||||
#[validate(length(min = 3, max = 30))]
|
||||
pub username: String,
|
||||
#[validate(email)]
|
||||
pub email: String,
|
||||
#[validate(length(min = 8))]
|
||||
pub password: String,
|
||||
pub password_confirmation: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct AuthResponse {
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AuthError {
|
||||
#[error("Passwords do not match")]
|
||||
PasswordMismatch,
|
||||
#[error("User already exists")]
|
||||
UserExists,
|
||||
#[error("Database error: {0}")]
|
||||
DatabaseError(String),
|
||||
#[error("Hashing error: {0}")]
|
||||
HashingError(String),
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// src/lib.rs
|
||||
pub mod db;
|
||||
pub mod auth;
|
||||
pub mod server;
|
||||
pub mod adresar;
|
||||
pub mod uctovnictvo;
|
||||
|
||||
Reference in New Issue
Block a user