feat(auth): implement user registration and login with JWT authentication
- Added `bcrypt`, `jsonwebtoken`, and `once_cell` dependencies to manage password hashing and JWT handling. - Created `Claims` struct for JWT claims and implemented token generation in the login route. - Implemented user registration and authentication logic in the `auth` module. - Updated error handling to include validation errors. - Created new routes for user registration and login, and integrated them into the main router. - Added tests for the authentication flow, including registration and login scenarios. - Updated user model to include a password hash field. - Refactored user creation logic to include password validation. - Adjusted feed and user routes to utilize JWT for authentication.
This commit is contained in:
@@ -9,6 +9,8 @@ name = "app"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
bcrypt = "0.17.1"
|
||||
models = { path = "../models" }
|
||||
validator = "0.20"
|
||||
|
||||
sea-orm = { workspace = true }
|
||||
|
@@ -3,6 +3,7 @@ pub struct Config {
|
||||
pub host: String,
|
||||
pub port: u32,
|
||||
pub prefork: bool,
|
||||
pub auth_secret: String,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -15,6 +16,7 @@ impl Config {
|
||||
.parse()
|
||||
.expect("PORT is not a number"),
|
||||
prefork: std::env::var("PREFORK").is_ok_and(|v| v == "1"),
|
||||
auth_secret: std::env::var("AUTH_SECRET").unwrap_or_else(|_| "secret".into()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,7 @@ pub enum UserError {
|
||||
Forbidden,
|
||||
UsernameTaken,
|
||||
AlreadyFollowing,
|
||||
Validation(String), // Added Validation variant
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
@@ -16,6 +17,7 @@ impl std::fmt::Display for UserError {
|
||||
UserError::Forbidden => write!(f, "You do not have permission to perform this action"),
|
||||
UserError::UsernameTaken => write!(f, "Username is already taken"),
|
||||
UserError::AlreadyFollowing => write!(f, "You are already following this user"),
|
||||
UserError::Validation(msg) => write!(f, "Validation error: {}", msg),
|
||||
UserError::Internal(msg) => write!(f, "Internal server error: {}", msg),
|
||||
}
|
||||
}
|
||||
|
54
thoughts-backend/app/src/persistence/auth.rs
Normal file
54
thoughts-backend/app/src/persistence/auth.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use bcrypt::{hash, verify, BcryptError, DEFAULT_COST};
|
||||
use models::{
|
||||
domains::user,
|
||||
params::auth::{LoginParams, RegisterParams},
|
||||
};
|
||||
use sea_orm::{ActiveModelTrait, ColumnTrait, DbConn, EntityTrait, QueryFilter, Set};
|
||||
use validator::Validate; // Import the Validate trait
|
||||
|
||||
use crate::error::UserError;
|
||||
|
||||
fn hash_password(password: &str) -> Result<String, BcryptError> {
|
||||
hash(password, DEFAULT_COST)
|
||||
}
|
||||
|
||||
pub async fn register_user(db: &DbConn, params: RegisterParams) -> Result<user::Model, UserError> {
|
||||
// Validate the parameters
|
||||
params
|
||||
.validate()
|
||||
.map_err(|e| UserError::Validation(e.to_string()))?;
|
||||
|
||||
let hashed_password =
|
||||
hash_password(¶ms.password).map_err(|e| UserError::Internal(e.to_string()))?;
|
||||
|
||||
let new_user = user::ActiveModel {
|
||||
username: Set(params.username),
|
||||
password_hash: Set(Some(hashed_password)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
new_user.insert(db).await.map_err(|e| {
|
||||
if let Some(sea_orm::SqlErr::UniqueConstraintViolation { .. }) = e.sql_err() {
|
||||
UserError::UsernameTaken
|
||||
} else {
|
||||
UserError::Internal(e.to_string())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn authenticate_user(db: &DbConn, params: LoginParams) -> Result<user::Model, UserError> {
|
||||
let user = user::Entity::find()
|
||||
.filter(user::Column::Username.eq(params.username))
|
||||
.one(db)
|
||||
.await
|
||||
.map_err(|e| UserError::Internal(e.to_string()))?
|
||||
.ok_or(UserError::NotFound)?;
|
||||
|
||||
let password_hash = user.password_hash.as_ref().ok_or(UserError::NotFound)?;
|
||||
|
||||
if verify(params.password, password_hash).map_err(|e| UserError::Internal(e.to_string()))? {
|
||||
Ok(user)
|
||||
} else {
|
||||
Err(UserError::NotFound)
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
pub mod auth;
|
||||
pub mod follow;
|
||||
pub mod thought;
|
||||
pub mod user;
|
||||
|
Reference in New Issue
Block a user