Formatting code
This commit is contained in:
@@ -86,7 +86,10 @@ impl InsertUser {
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub fn update_profile(email: &str, profile_picture: Option<&str>) -> Result<QueryUser, ServiceError> {
|
||||
pub fn update_profile(
|
||||
email: &str,
|
||||
profile_picture: Option<&str>,
|
||||
) -> Result<QueryUser, ServiceError> {
|
||||
let mut conn = connection()?;
|
||||
let user = diesel::update(users::table)
|
||||
.filter(users::email.eq(&email))
|
||||
@@ -120,7 +123,7 @@ impl From<QueryUser> for ResponseUser {
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Auth {
|
||||
pub id: String,
|
||||
pub user: ResponseUser
|
||||
pub user: ResponseUser,
|
||||
}
|
||||
|
||||
impl FromRequest for Auth {
|
||||
@@ -131,21 +134,30 @@ impl FromRequest for Auth {
|
||||
.cookie(SESSION_COOKIE_NAME)
|
||||
.map(|c| c.value().to_string())
|
||||
.or_else(|| {
|
||||
req.headers().get(http::header::AUTHORIZATION)
|
||||
.map(|h| h.to_str().unwrap().split_at(7).1.to_string())
|
||||
req
|
||||
.headers()
|
||||
.get(http::header::AUTHORIZATION)
|
||||
.map(|h| h.to_str().unwrap().split_at(7).1.to_string())
|
||||
}) {
|
||||
Some(token) => token,
|
||||
None => return ready(Err(ActixError::from(ServiceError {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
return ready(Err(ActixError::from(ServiceError {
|
||||
status: 401,
|
||||
message: "Unauthorized".to_string()
|
||||
message: "Unauthorized".to_string(),
|
||||
})))
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
let ip_address = req.peer_addr().unwrap().ip().to_string();
|
||||
|
||||
|
||||
match Session::verify(&session_id, &ip_address) {
|
||||
Ok(v) => return ready(Ok(Auth { id: v.0.id, user: v.1.into() })),
|
||||
Err(err) => return ready(Err(ActixError::from(err)))
|
||||
Ok(v) => {
|
||||
return ready(Ok(Auth {
|
||||
id: v.0.id,
|
||||
user: v.1.into(),
|
||||
}))
|
||||
}
|
||||
Err(err) => return ready(Err(ActixError::from(err))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,7 +168,7 @@ pub fn verify_role(auth: &Auth, role: &str) -> Result<(), ServiceError> {
|
||||
} else {
|
||||
Err(ServiceError {
|
||||
status: 403,
|
||||
message: "Forbidden".to_string()
|
||||
message: "Forbidden".to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
use std::env;
|
||||
|
||||
use actix_web::{get, post, web, HttpResponse, ResponseError, cookie::{Cookie, time::Duration}, HttpRequest};
|
||||
use actix_web::{
|
||||
get, post, web, HttpResponse, ResponseError,
|
||||
cookie::{Cookie, time::Duration},
|
||||
HttpRequest,
|
||||
};
|
||||
use log::error;
|
||||
use redis::AsyncCommands;
|
||||
use siren::ServiceError;
|
||||
|
||||
use crate::{auth::{InsertUser, Auth, LoginRequest, QueryUser, RegisterUser, Session, SESSION_COOKIE_NAME}, storage::{self}};
|
||||
use crate::{
|
||||
auth::{InsertUser, Auth, LoginRequest, QueryUser, RegisterUser, Session, SESSION_COOKIE_NAME},
|
||||
storage::{self},
|
||||
};
|
||||
|
||||
use super::verify_hash;
|
||||
|
||||
@@ -14,12 +21,10 @@ async fn register(user: web::Json<RegisterUser>) -> HttpResponse {
|
||||
let register_user = user.0;
|
||||
let insert_user: InsertUser = match register_user.convert_to_insert() {
|
||||
Ok(user) => user,
|
||||
Err(err) => return ResponseError::error_response(&err)
|
||||
Err(err) => return ResponseError::error_response(&err),
|
||||
};
|
||||
match InsertUser::insert(insert_user) {
|
||||
Ok(_) => {
|
||||
HttpResponse::Created().finish()
|
||||
},
|
||||
Ok(_) => HttpResponse::Created().finish(),
|
||||
Err(err) => {
|
||||
// Obfuscate the service error message to prevent leaking database details
|
||||
if err.status == 409 {
|
||||
@@ -39,10 +44,12 @@ async fn login(request: HttpRequest, login_request: web::Json<LoginRequest>) ->
|
||||
|
||||
let query_user = match QueryUser::get_by_email(&email) {
|
||||
Ok(query_user) => query_user,
|
||||
Err(_) => return ResponseError::error_response(&ServiceError {
|
||||
status: 401,
|
||||
message: "The email or password was incorrect.".to_string()
|
||||
})
|
||||
Err(_) => {
|
||||
return ResponseError::error_response(&ServiceError {
|
||||
status: 401,
|
||||
message: "The email or password was incorrect.".to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
// Verify password
|
||||
if verify_hash(&login_request.password, &query_user.hash) {
|
||||
@@ -52,7 +59,7 @@ async fn login(request: HttpRequest, login_request: web::Json<LoginRequest>) ->
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
error!("Failed to get redis connection: {}", err);
|
||||
return ResponseError::error_response(&err)
|
||||
return ResponseError::error_response(&err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -61,10 +68,16 @@ async fn login(request: HttpRequest, login_request: web::Json<LoginRequest>) ->
|
||||
.parse::<i64>()
|
||||
.expect("SESSION_TTL must be an integer");
|
||||
|
||||
let session_result: redis::RedisResult<()> = conn.set_ex(session.id.to_string(), &serde_json::to_string(&session).unwrap(), (session_ttl * 60) as usize).await;
|
||||
let session_result: redis::RedisResult<()> = conn
|
||||
.set_ex(
|
||||
session.id.to_string(),
|
||||
&serde_json::to_string(&session).unwrap(),
|
||||
(session_ttl * 60) as usize,
|
||||
)
|
||||
.await;
|
||||
if let Err(err) = session_result {
|
||||
error!("Failed to set access token in redis: {}", err);
|
||||
return ResponseError::error_response(&ServiceError::from(err))
|
||||
return ResponseError::error_response(&ServiceError::from(err));
|
||||
};
|
||||
|
||||
let session_cookie = Cookie::build(SESSION_COOKIE_NAME, session.id.clone())
|
||||
@@ -82,12 +95,15 @@ async fn login(request: HttpRequest, login_request: web::Json<LoginRequest>) ->
|
||||
HttpResponse::Ok()
|
||||
.cookie(session_cookie)
|
||||
.cookie(user_id_cookie)
|
||||
.json(Auth { id: session.id, user: query_user.into() })
|
||||
.json(Auth {
|
||||
id: session.id,
|
||||
user: query_user.into(),
|
||||
})
|
||||
} else {
|
||||
return ResponseError::error_response(&ServiceError {
|
||||
status: 401,
|
||||
message: "The email or password was incorrect.".to_string()
|
||||
})
|
||||
message: "The email or password was incorrect.".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +115,7 @@ async fn refresh(req: HttpRequest, auth: Auth) -> HttpResponse {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
error!("Failed to get redis connection: {}", err);
|
||||
return ResponseError::error_response(&err)
|
||||
return ResponseError::error_response(&err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -113,10 +129,16 @@ async fn refresh(req: HttpRequest, auth: Auth) -> HttpResponse {
|
||||
|
||||
// Create new session
|
||||
let session = Session::new(&auth.user.email, &ip_address);
|
||||
let session_result: redis::RedisResult<()> = conn.set_ex(session.id.to_string(), &serde_json::to_string(&session).unwrap(), (session_ttl * 60) as usize).await;
|
||||
let session_result: redis::RedisResult<()> = conn
|
||||
.set_ex(
|
||||
session.id.to_string(),
|
||||
&serde_json::to_string(&session).unwrap(),
|
||||
(session_ttl * 60) as usize,
|
||||
)
|
||||
.await;
|
||||
if let Err(err) = session_result {
|
||||
error!("Failed to set session id in redis: {}", err);
|
||||
return ResponseError::error_response(&ServiceError::from(err))
|
||||
return ResponseError::error_response(&ServiceError::from(err));
|
||||
};
|
||||
|
||||
// Create cookies
|
||||
@@ -130,7 +152,10 @@ async fn refresh(req: HttpRequest, auth: Auth) -> HttpResponse {
|
||||
HttpResponse::Ok()
|
||||
.cookie(session_cookie)
|
||||
.cookie(user_id_cookie)
|
||||
.json(Auth { id: session.id, user: auth.user })
|
||||
.json(Auth {
|
||||
id: session.id,
|
||||
user: auth.user,
|
||||
})
|
||||
}
|
||||
|
||||
#[post("/logout")]
|
||||
@@ -139,14 +164,14 @@ async fn logout(auth: Auth) -> HttpResponse {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
error!("Failed to get redis connection: {}", err);
|
||||
return ResponseError::error_response(&err)
|
||||
return ResponseError::error_response(&err);
|
||||
}
|
||||
};
|
||||
|
||||
let session_result: redis::RedisResult<()> = conn.del(&auth.id.to_string()).await;
|
||||
if let Err(err) = session_result {
|
||||
error!("Failed to remove session id in redis: {}", err);
|
||||
return ResponseError::error_response(&ServiceError::from(err))
|
||||
return ResponseError::error_response(&ServiceError::from(err));
|
||||
};
|
||||
|
||||
let session_cookie = Cookie::build(SESSION_COOKIE_NAME, "")
|
||||
@@ -160,7 +185,7 @@ async fn logout(auth: Auth) -> HttpResponse {
|
||||
.max_age(Duration::new(-1, 0))
|
||||
.http_only(true)
|
||||
.finish();
|
||||
|
||||
|
||||
HttpResponse::Ok()
|
||||
.cookie(session_cookie)
|
||||
.cookie(user_id_cookie)
|
||||
@@ -189,12 +214,13 @@ pub fn init_routes(config: &mut web::ServiceConfig) {
|
||||
u.role = "admin".to_string();
|
||||
u.verified = true;
|
||||
let _ = InsertUser::insert(u);
|
||||
config.service(web::scope("auth")
|
||||
.service(register)
|
||||
.service(login)
|
||||
.service(refresh)
|
||||
.service(logout)
|
||||
.service(me)
|
||||
.service(roles)
|
||||
);
|
||||
}
|
||||
config.service(
|
||||
web::scope("auth")
|
||||
.service(register)
|
||||
.service(login)
|
||||
.service(refresh)
|
||||
.service(logout)
|
||||
.service(me)
|
||||
.service(roles),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::env;
|
||||
|
||||
use actix_web::cookie::{time::Duration, Cookie};
|
||||
use argon2::{password_hash::{rand_core::OsRng, SaltString}, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
|
||||
use argon2::{
|
||||
password_hash::{rand_core::OsRng, SaltString},
|
||||
Argon2, PasswordHash, PasswordHasher, PasswordVerifier,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use redis::Commands;
|
||||
@@ -31,7 +34,7 @@ impl Session {
|
||||
id: csprng_128bit(),
|
||||
user_id: user_id.to_string(),
|
||||
ip_address: hash(&ip_address).unwrap(),
|
||||
expiration: (now + chrono::Duration::minutes(ttl)).timestamp()
|
||||
expiration: (now + chrono::Duration::minutes(ttl)).timestamp(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +43,7 @@ impl Session {
|
||||
// Check if the session exists
|
||||
let session = match conn.get::<_, String>(session_id) {
|
||||
Ok(session) => session,
|
||||
Err(_) => return Err(ServiceError::new(401, "Unauthorized".to_string()))
|
||||
Err(_) => return Err(ServiceError::new(401, "Unauthorized".to_string())),
|
||||
};
|
||||
let session: Self = serde_json::from_str(&session)?;
|
||||
// Check if the IP address matches
|
||||
@@ -51,12 +54,12 @@ impl Session {
|
||||
// Check if the user exists
|
||||
let user = match crate::auth::model::QueryUser::get_by_email(&email) {
|
||||
Ok(user) => user,
|
||||
Err(_) => return Err(ServiceError::new(401, "Unauthorized".to_string()))
|
||||
Err(_) => return Err(ServiceError::new(401, "Unauthorized".to_string())),
|
||||
};
|
||||
// Check if the session has expired
|
||||
let now = chrono::Utc::now().timestamp();
|
||||
if now < session.expiration {
|
||||
return Ok((session, user))
|
||||
return Ok((session, user));
|
||||
}
|
||||
}
|
||||
Err(ServiceError::new(401, "Unauthorized".to_string()))
|
||||
@@ -79,7 +82,11 @@ impl Session {
|
||||
fn csprng_128bit() -> String {
|
||||
// Generate a CSPRNG 128-bit (16 byte) ID using alphanumeric characters (a-z, A-Z, 0-9)
|
||||
let rng = ChaCha20Rng::from_entropy();
|
||||
rng.sample_iter(rand::distributions::Alphanumeric).take(16).map(char::from).collect()
|
||||
rng
|
||||
.sample_iter(rand::distributions::Alphanumeric)
|
||||
.take(16)
|
||||
.map(char::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn hash(str: &str) -> Result<String, ServiceError> {
|
||||
@@ -93,10 +100,10 @@ pub fn verify_hash(str: &str, hash: &str) -> bool {
|
||||
let bytes = str.as_bytes();
|
||||
let parsed_hash = match PasswordHash::new(hash) {
|
||||
Ok(h) => h,
|
||||
Err(_) => return false
|
||||
Err(_) => return false,
|
||||
};
|
||||
match Argon2::default().verify_password(bytes, &parsed_hash) {
|
||||
Ok(_) => true,
|
||||
Err(_) => false
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user