Working on email templating, updating with swagger
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use actix_web::{FromRequest, Error as ActixError, HttpRequest, dev::Payload, http};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use super::{SESSION_COOKIE_NAME, Session};
|
||||
use crate::{error::Error, users::User};
|
||||
use super::{Session, SESSION_COOKIE_NAME};
|
||||
use actix_web::{Error as ActixError, FromRequest, HttpRequest, dev::Payload, http};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Auth {
|
||||
@@ -34,13 +34,13 @@ impl FromRequest for Auth {
|
||||
return Err(Error::new(401, "API Key does not exist".to_string()).into());
|
||||
}
|
||||
};
|
||||
match User::select(&api_key.id).await {
|
||||
match User::select(&api_key.user_id).await {
|
||||
Some(user) => Ok(Auth {
|
||||
session_id: None,
|
||||
api_key: Some(key_id),
|
||||
user,
|
||||
}),
|
||||
None => Err(Error::new(404, format!("User {} not found", api_key.id)).into()),
|
||||
None => Err(Error::new(404, format!("User {} not found", api_key.user_id)).into()),
|
||||
}
|
||||
};
|
||||
return Box::pin(fut);
|
||||
@@ -79,13 +79,13 @@ impl FromRequest for Auth {
|
||||
// Verify the session
|
||||
let fut = async move {
|
||||
match Session::verify(&session_id, &ip_address).await {
|
||||
Ok(session) => match User::select(&session.id).await {
|
||||
Ok(session) => match User::select(&session.user_id).await {
|
||||
Some(user) => Ok(Auth {
|
||||
session_id: Some(session_id),
|
||||
api_key: None,
|
||||
user,
|
||||
}),
|
||||
None => Err(Error::new(404, format!("User {} not found", session.id)).into()),
|
||||
None => Err(Error::new(404, format!("User {} not found", session.user_id)).into()),
|
||||
},
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use argon2::{
|
||||
password_hash::{rand_core::OsRng, SaltString},
|
||||
Argon2, PasswordHash, PasswordHasher, PasswordVerifier,
|
||||
password_hash::{SaltString, rand_core::OsRng},
|
||||
};
|
||||
use rand::distr::Alphanumeric;
|
||||
use rand::prelude::*;
|
||||
@@ -11,10 +11,10 @@ mod routes;
|
||||
mod session;
|
||||
|
||||
pub use auth::*;
|
||||
pub use session::*;
|
||||
pub use routes::init_routes;
|
||||
pub use session::*;
|
||||
|
||||
use crate::error::{Error, ApiResult};
|
||||
use crate::error::{ApiResult, Error};
|
||||
|
||||
pub fn csprng(take: usize) -> String {
|
||||
// Generate a CSPRNG 128-bit (16 byte) ID using alphanumeric characters (a-z, A-Z, 0-9)
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
use actix_web::{post, web, HttpResponse, ResponseError, HttpRequest, put, get};
|
||||
use crate::{
|
||||
account::{verify_hash, Session, SESSION_COOKIE_NAME},
|
||||
account::{SESSION_COOKIE_NAME, Session, verify_hash},
|
||||
error::Error,
|
||||
smtp,
|
||||
users::{LoginRequest, RegisterRequest, User, UserResponse},
|
||||
};
|
||||
use actix_web::{HttpRequest, HttpResponse, ResponseError, get, post, put, web};
|
||||
use utoipa_actix_web::scope;
|
||||
use utoipa_actix_web::service_config::ServiceConfig;
|
||||
|
||||
use crate::account::Auth;
|
||||
use crate::account::{Auth, csprng};
|
||||
use crate::users::UpdateUser;
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Account",
|
||||
request_body(
|
||||
content = RegisterRequest, content_type = "application/json"
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "", body = UserResponse),
|
||||
(status = 409, description = ""),
|
||||
)
|
||||
)]
|
||||
#[post("/register")]
|
||||
async fn register(user: web::Json<RegisterRequest>, req: HttpRequest) -> HttpResponse {
|
||||
let register_user = user.into_inner();
|
||||
@@ -38,13 +51,22 @@ async fn register(user: web::Json<RegisterRequest>, req: HttpRequest) -> HttpRes
|
||||
);
|
||||
HttpResponse::Conflict().finish()
|
||||
} else {
|
||||
log::error!("attemptFailed to register user [Email: {}]: {}", email, err);
|
||||
log::error!("Failed to register user [Email: {}]: {}", email, err);
|
||||
ResponseError::error_response(&err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Account",
|
||||
request_body(
|
||||
content = LoginRequest, content_type = "application/json"
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "", body = UserResponse),
|
||||
),
|
||||
)]
|
||||
#[post("/login")]
|
||||
async fn login(request: web::Json<LoginRequest>, req: HttpRequest) -> HttpResponse {
|
||||
let email = &request.email;
|
||||
@@ -93,6 +115,17 @@ async fn login(request: web::Json<LoginRequest>, req: HttpRequest) -> HttpRespon
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Account",
|
||||
responses(
|
||||
(status = 200, description = ""),
|
||||
(status = 401, description = ""),
|
||||
(status = 500, description = ""),
|
||||
),
|
||||
security(
|
||||
("session_auth" = [])
|
||||
)
|
||||
)]
|
||||
#[post("/logout")]
|
||||
async fn logout(req: HttpRequest, auth: Auth) -> HttpResponse {
|
||||
let email = auth.user.email;
|
||||
@@ -132,6 +165,16 @@ async fn logout(req: HttpRequest, auth: Auth) -> HttpResponse {
|
||||
.finish()
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Account",
|
||||
responses(
|
||||
(status = 200, description = "", body = UserResponse),
|
||||
(status = 401, description = ""),
|
||||
),
|
||||
security(
|
||||
("session_auth" = [])
|
||||
)
|
||||
)]
|
||||
#[get("/profile")]
|
||||
async fn get_profile(req: HttpRequest) -> HttpResponse {
|
||||
let ip_address = req.peer_addr().unwrap().ip().to_string();
|
||||
@@ -154,7 +197,7 @@ async fn get_profile(req: HttpRequest) -> HttpResponse {
|
||||
.finish();
|
||||
}
|
||||
};
|
||||
let id = &session.id;
|
||||
let id = &session.user_id;
|
||||
let query_user = match User::select(&id).await {
|
||||
Some(query_user) => query_user,
|
||||
None => {
|
||||
@@ -186,6 +229,16 @@ async fn get_profile(req: HttpRequest) -> HttpResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Account",
|
||||
responses(
|
||||
(status = 200, description = ""),
|
||||
(status = 401, description = ""),
|
||||
),
|
||||
security(
|
||||
("session_auth" = [])
|
||||
)
|
||||
)]
|
||||
#[get("/session")]
|
||||
async fn session_refresh(req: HttpRequest) -> HttpResponse {
|
||||
let ip_address = req.peer_addr().unwrap().ip().to_string();
|
||||
@@ -208,7 +261,7 @@ async fn session_refresh(req: HttpRequest) -> HttpResponse {
|
||||
.finish();
|
||||
}
|
||||
};
|
||||
let id = &session.id;
|
||||
let id = &session.user_id;
|
||||
let session_cookie = session.cookie();
|
||||
let session_exp_cookie = session.expiration_cookie();
|
||||
|
||||
@@ -229,6 +282,19 @@ async fn session_refresh(req: HttpRequest) -> HttpResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Account",
|
||||
request_body(
|
||||
content = String, content_type = "application/json"
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "", body = UserResponse),
|
||||
(status = 401, description = ""),
|
||||
),
|
||||
security(
|
||||
("session_auth" = [])
|
||||
)
|
||||
)]
|
||||
#[put("/password")]
|
||||
async fn change_password(
|
||||
password: web::Json<String>,
|
||||
@@ -269,25 +335,52 @@ async fn change_password(
|
||||
ip_address,
|
||||
err
|
||||
);
|
||||
ResponseError::error_response(&Error::new(500, err.to_string()))
|
||||
ResponseError::error_response(&err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/password-reset")]
|
||||
async fn password_reset(req: HttpRequest, _auth: Auth) -> HttpResponse {
|
||||
let _ip_address = req.peer_addr().unwrap().ip().to_string();
|
||||
#[utoipa::path(
|
||||
tag = "Account",
|
||||
responses(
|
||||
(status = 200, description = ""),
|
||||
(status = 401, description = ""),
|
||||
),
|
||||
security(
|
||||
("session_auth" = [])
|
||||
)
|
||||
)]
|
||||
#[post("/password/reset")]
|
||||
async fn reset_password(req: HttpRequest, auth: Auth) -> HttpResponse {
|
||||
let ip_address = req.peer_addr().unwrap().ip().to_string();
|
||||
let id = auth.user.id;
|
||||
let email = auth.user.email;
|
||||
let token = csprng(128);
|
||||
|
||||
match smtp::send_password_reset(&email, &token) {
|
||||
Ok(_) => HttpResponse::Ok().finish(),
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Invalid password reset attempt [ID: {}] [IP Address: {}]: {}",
|
||||
&id,
|
||||
ip_address,
|
||||
err
|
||||
);
|
||||
ResponseError::error_response(&err)
|
||||
}
|
||||
};
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
|
||||
pub fn init_routes(config: &mut web::ServiceConfig) {
|
||||
pub fn init_routes(config: &mut ServiceConfig) {
|
||||
config.service(
|
||||
web::scope("account")
|
||||
scope::scope("/account")
|
||||
.service(register)
|
||||
.service(login)
|
||||
.service(logout)
|
||||
.service(change_password)
|
||||
.service(get_profile)
|
||||
.service(session_refresh),
|
||||
.service(session_refresh)
|
||||
.service(change_password)
|
||||
.service(reset_password),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use actix_web::cookie::{time::Duration, Cookie};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use redis::{AsyncCommands, RedisResult};
|
||||
use tokio::task;
|
||||
use uuid::Uuid;
|
||||
use super::{csprng, hash, verify_hash};
|
||||
use crate::{
|
||||
db::redis_async_connection,
|
||||
error::{Error, ApiResult},
|
||||
error::{ApiResult, Error},
|
||||
};
|
||||
use super::{csprng, hash, verify_hash};
|
||||
use actix_web::cookie::{Cookie, time::Duration};
|
||||
use chrono::{DateTime, Utc};
|
||||
use redis::{AsyncCommands, RedisResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::task;
|
||||
use uuid::Uuid;
|
||||
|
||||
const DEFAULT_SESSION_TTL: i64 = 86400; // (In seconds) 24 hours
|
||||
pub const SESSION_COOKIE_NAME: &str = "session";
|
||||
@@ -17,22 +17,22 @@ pub const SESSION_EXPIRATION_COOKIE_NAME: &str = "session_expiration";
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Session {
|
||||
pub session_id: String,
|
||||
pub id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub ip_address: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub expires_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn default(id: &Uuid, ip_address: &str) -> Self {
|
||||
Self::new(64, id, ip_address, Some(DEFAULT_SESSION_TTL))
|
||||
pub fn default(user_id: &Uuid, ip_address: &str) -> Self {
|
||||
Self::new(64, user_id, ip_address, Some(DEFAULT_SESSION_TTL))
|
||||
}
|
||||
|
||||
pub fn new(take: usize, id: &Uuid, ip_address: &str, ttl: Option<i64>) -> Self {
|
||||
pub fn new(take: usize, user_id: &Uuid, ip_address: &str, ttl: Option<i64>) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
session_id: csprng(take),
|
||||
id: id.clone(),
|
||||
user_id: user_id.clone(),
|
||||
ip_address: hash(&ip_address).unwrap(),
|
||||
expires_at: match ttl {
|
||||
Some(ttl) => Some(now + chrono::Duration::seconds(ttl)),
|
||||
@@ -79,7 +79,7 @@ impl Session {
|
||||
);
|
||||
};
|
||||
});
|
||||
session = Session::default(&session.id, ip_address);
|
||||
session = Session::default(&session.user_id, ip_address);
|
||||
session.store().await?;
|
||||
Ok(session)
|
||||
}
|
||||
@@ -120,8 +120,8 @@ impl Session {
|
||||
if let Ok(environment) = std::env::var("ENVIRONMENT") {
|
||||
if environment == "development" || environment == "dev" {
|
||||
log::trace!(
|
||||
"Session cookie [ID: {}]: {}",
|
||||
self.id,
|
||||
"Session cookie [User ID: {}]: {}",
|
||||
self.user_id,
|
||||
self.session_id
|
||||
);
|
||||
cookie.set_secure(false);
|
||||
@@ -148,8 +148,8 @@ impl Session {
|
||||
if let Ok(environment) = std::env::var("ENVIRONMENT") {
|
||||
if environment == "development" || environment == "dev" {
|
||||
log::trace!(
|
||||
"Session expiration cookie [ID: {}]: {}",
|
||||
self.id,
|
||||
"Session expiration cookie [User ID: {}]: {}",
|
||||
self.user_id,
|
||||
self.session_id
|
||||
);
|
||||
cookie.set_secure(false);
|
||||
|
||||
Reference in New Issue
Block a user