magic sign in
This commit is contained in:
parent
6b6fd54292
commit
ba4e6a377f
@ -1,7 +1,8 @@
|
||||
use crate::models::Error;
|
||||
use crate::models::{Error, JsonError};
|
||||
use crate::session::AppClaims;
|
||||
use actix_jwt_session::{
|
||||
Duration, Hashing, JwtTtl, RefreshTtl, SessionStorage, JWT_HEADER_NAME, REFRESH_HEADER_NAME,
|
||||
Duration, Hashing, JwtTtl, Pair, RefreshTtl, SessionStorage, JWT_HEADER_NAME,
|
||||
REFRESH_HEADER_NAME,
|
||||
};
|
||||
use actix_web::web::scope;
|
||||
use actix_web::web::{Data, ServiceConfig};
|
||||
@ -31,6 +32,23 @@ pub struct AuthResponseBody {
|
||||
refresh_token: String,
|
||||
}
|
||||
|
||||
impl AuthResponseBody {
|
||||
pub fn build(pair: Pair<AppClaims>) -> Result<Self, JsonError> {
|
||||
let access_token = pair.jwt.encode().map_err(|e| {
|
||||
tracing::error!("Failed encode JWT: {e}");
|
||||
JsonError::new(AuthError::EncryptPair)
|
||||
})?;
|
||||
let refresh_token = pair.refresh.encode().map_err(|e| {
|
||||
tracing::error!("Failed encode JWT: {e}");
|
||||
JsonError::new(AuthError::EncryptPair)
|
||||
})?;
|
||||
Ok(Self {
|
||||
access_token,
|
||||
refresh_token,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure(http_client: reqwest::Client, config: &mut ServiceConfig) {
|
||||
config.service(
|
||||
scope("")
|
||||
@ -89,6 +107,8 @@ pub enum AuthError {
|
||||
Oauth(OAuthError),
|
||||
#[display(fmt = "Encrypt password failed")]
|
||||
EncryptPass,
|
||||
#[display(fmt = "Encrypt JWT pair failed")]
|
||||
EncryptPair,
|
||||
}
|
||||
|
||||
async fn create_user(
|
||||
@ -210,9 +230,30 @@ pub fn random_password() -> String {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub mod password {
|
||||
const HAS_NUM: u8 = 1;
|
||||
const HAS_UPPER: u8 = 2;
|
||||
const HAS_LOWER: u8 = 4;
|
||||
const HAS_SPECIAL: u8 = 8;
|
||||
|
||||
pub fn is_valid(pass: &str) -> bool {
|
||||
pass.len() >= 8
|
||||
|| pass.chars().fold(0, |memo, c| match c {
|
||||
_ if c.is_numeric() => memo | HAS_NUM,
|
||||
_ if c.is_uppercase() => memo | HAS_UPPER,
|
||||
_ if c.is_lowercase() => memo | HAS_UPPER,
|
||||
_ => memo | HAS_SPECIAL,
|
||||
}) & HAS_NUM
|
||||
& HAS_SPECIAL
|
||||
& HAS_UPPER
|
||||
& HAS_LOWER
|
||||
!= 0
|
||||
}
|
||||
}
|
||||
|
||||
pub mod magic_link {
|
||||
use crate::models::Error;
|
||||
use crate::{http::AuthError, RedisClient};
|
||||
use crate::{http::AuthError, redis_c, RedisClient};
|
||||
use actix_web::web::Data;
|
||||
use jet_contract::*;
|
||||
use rand::prelude::*;
|
||||
@ -234,12 +275,24 @@ pub mod magic_link {
|
||||
format!("magic_{email}")
|
||||
}
|
||||
|
||||
pub async fn clear_magic_link(email: &str, redis: Data<RedisClient>) -> Result<(), Error> {
|
||||
let Ok(mut redis) = redis.get().await else {
|
||||
return Err(AuthError::SerializeMsg.into());
|
||||
};
|
||||
|
||||
let key = magic_link_key(email);
|
||||
let _: () = redis.del(key).await.map_err(|_| Error::RedisConnection)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn create_magic_link(
|
||||
email: &str,
|
||||
redis: Data<RedisClient>,
|
||||
) -> Result<(MagicLinkKey, MagicLinkToken, AttemptValidity), Error> {
|
||||
use rand::distributions::Alphanumeric;
|
||||
|
||||
let mut redis = redis_c!(redis)?;
|
||||
|
||||
let key = magic_link_key(email);
|
||||
let mut rng = rand::thread_rng();
|
||||
let token = format!(
|
||||
@ -261,10 +314,6 @@ pub mod magic_link {
|
||||
.collect::<String>(),
|
||||
);
|
||||
|
||||
let Ok(mut redis) = redis.get().await else {
|
||||
return Err(AuthError::SerializeMsg.into());
|
||||
};
|
||||
|
||||
if redis
|
||||
.exists(&key)
|
||||
.await
|
||||
|
@ -4,7 +4,8 @@ use actix_web::web::{Data, Json};
|
||||
use actix_web::{post, HttpRequest, HttpResponse};
|
||||
use entities::prelude::{Users, WorkspaceMemberInvites};
|
||||
use entities::users::Model as User;
|
||||
use sea_orm::prelude::*;
|
||||
use jet_contract::event_bus::SignInMedium;
|
||||
use sea_orm::{prelude::*, DatabaseTransaction};
|
||||
use sea_orm::{DatabaseConnection, EntityTrait, QueryFilter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_email::Email;
|
||||
@ -12,7 +13,8 @@ use serde_email::Email;
|
||||
use crate::config::ApplicationConfig;
|
||||
use crate::extractors::RequireInstanceConfigured;
|
||||
use crate::http::magic_link::create_magic_link;
|
||||
use crate::models::*;
|
||||
use crate::utils::user_by_email;
|
||||
use crate::{db_commit, db_rollback, db_t, models::*};
|
||||
use crate::{EventBusClient, RedisClient};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@ -33,16 +35,24 @@ pub async fn email_check(
|
||||
if !serde_email::is_valid_email(&payload.email) {
|
||||
return Ok(HttpResponse::BadRequest().json(JsonError::new("Email is not valid")));
|
||||
}
|
||||
let mut t = db_t!(db)?;
|
||||
|
||||
let user = Users::find()
|
||||
.filter(entities::users::Column::Email.eq(payload.email.as_str()))
|
||||
.one(&**db)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError)?;
|
||||
let user = user_by_email(payload.email.as_str(), &mut t).await?;
|
||||
|
||||
match user {
|
||||
let res = match user {
|
||||
Some(user) => handle_existing_user(req, payload, user, app_config, event_bus, redis).await,
|
||||
None => register(req, payload, app_config, event_bus, db, redis).await,
|
||||
None => register(req, payload, app_config, event_bus, &mut t, redis).await,
|
||||
};
|
||||
|
||||
match res {
|
||||
Ok(r) => {
|
||||
db_commit!(t)?;
|
||||
Ok(r)
|
||||
}
|
||||
Err(e) => {
|
||||
db_rollback!(t)?;
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +144,7 @@ async fn register(
|
||||
payload: Json<EmailCheckPayload>,
|
||||
app_config: Data<ApplicationConfig>,
|
||||
event_bus: Data<EventBusClient>,
|
||||
db: Data<DatabaseConnection>,
|
||||
db: &mut DatabaseTransaction,
|
||||
redis: Data<RedisClient>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
use sea_orm::Set;
|
||||
@ -142,7 +152,7 @@ async fn register(
|
||||
if !app_config.enable_signup
|
||||
&& WorkspaceMemberInvites::find()
|
||||
.filter(entities::workspace_member_invites::Column::Email.eq(payload.email.as_str()))
|
||||
.count(&**db)
|
||||
.count(&mut *db)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError)?
|
||||
== 0
|
||||
@ -167,14 +177,17 @@ async fn register(
|
||||
user_timezone: Set("UTC".to_string()),
|
||||
last_login_ip: Set(ip.to_string()),
|
||||
last_logout_ip: Set(ip.to_string()),
|
||||
last_login_medium: Set("MAGIC_LINK".to_string()),
|
||||
last_login_medium: Set(SignInMedium::MagicLink.as_str().to_owned()),
|
||||
last_login_uagent: Set(user_agent.clone()),
|
||||
..Default::default()
|
||||
};
|
||||
if !app_config.enable_magic_link_login {
|
||||
return Err(AuthError::MagicLinkOff.into());
|
||||
}
|
||||
let user = user.save(&**db).await.map_err(|_| Error::DatabaseError)?;
|
||||
let user = user
|
||||
.save(&mut *db)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError)?;
|
||||
{
|
||||
use jet_contract::event_bus::*;
|
||||
let user_id = user.id.clone().unwrap();
|
||||
|
@ -4,14 +4,16 @@ use actix_web::{
|
||||
HttpRequest, HttpResponse,
|
||||
};
|
||||
use jet_contract::event_bus::{EmailMsg, Topic};
|
||||
use sea_orm::prelude::*;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use sea_orm::{prelude::*, DatabaseTransaction};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use tracing::error;
|
||||
|
||||
use super::{create_user, random_password};
|
||||
use crate::{db_commit, models::*, utils::extract_req_current_site};
|
||||
use crate::{
|
||||
db_t, extractors::RequireInstanceConfigured, models::JsonError, EventBusClient, RedisClient,
|
||||
db_commit, db_rollback, db_t, extractors::RequireInstanceConfigured, models::JsonError,
|
||||
models::*, utils::extract_req_current_site, EventBusClient, RedisClient,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@ -29,31 +31,56 @@ pub async fn magic_generate(
|
||||
event_bus: Data<EventBusClient>,
|
||||
) -> Result<HttpResponse, JsonError> {
|
||||
let mut t = db_t!(db)?;
|
||||
match try_create_magic_link(req, payload, &mut t, redis, event_bus).await {
|
||||
Ok(r) => {
|
||||
db_commit!(t)?;
|
||||
Ok(r)
|
||||
}
|
||||
Err(e) => {
|
||||
db_rollback!(t)?;
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_create_magic_link(
|
||||
req: HttpRequest,
|
||||
payload: Json<Input>,
|
||||
t: &mut DatabaseTransaction,
|
||||
redis: Data<RedisClient>,
|
||||
event_bus: Data<EventBusClient>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let email = payload.into_inner().email;
|
||||
|
||||
let user = match entities::prelude::Users::find()
|
||||
let _user = match entities::prelude::Users::find()
|
||||
.filter(entities::users::Column::Email.eq(&email))
|
||||
.one(&mut t)
|
||||
.one(&mut *t)
|
||||
.await
|
||||
{
|
||||
Ok(Some(user)) => user,
|
||||
Ok(None) => create_user(&req, &email, &random_password(), &mut t).await?,
|
||||
Err(e) => return Err(Error::DatabaseError.into()),
|
||||
Ok(None) => create_user(&req, &email, &random_password(), &mut *t).await?,
|
||||
Err(e) => {
|
||||
error!("Failed to connect to database while creating magic link: {e}");
|
||||
return Err(Error::DatabaseError.into());
|
||||
}
|
||||
};
|
||||
|
||||
db_commit!(t)?;
|
||||
|
||||
let (key, token, validity) = super::magic_link::create_magic_link(&email, redis).await?;
|
||||
let (key, token, _validity) = super::magic_link::create_magic_link(&email, redis).await?;
|
||||
let current_site = extract_req_current_site(&req)?;
|
||||
|
||||
event_bus
|
||||
.publish(Topic::Email, jet_contract::event_bus::Msg::Email(EmailMsg::MagicLink {
|
||||
email,
|
||||
key,
|
||||
token,
|
||||
current_site,
|
||||
}), rumqttc::QoS::AtLeastOnce, true)
|
||||
.publish(
|
||||
Topic::Email,
|
||||
jet_contract::event_bus::Msg::Email(EmailMsg::MagicLink {
|
||||
email,
|
||||
key: jet_contract::MagicLinkKey::new(key.clone()),
|
||||
token,
|
||||
current_site,
|
||||
}),
|
||||
rumqttc::QoS::AtLeastOnce,
|
||||
true,
|
||||
)
|
||||
.await;
|
||||
|
||||
Ok(HttpResponse::NotImplemented().finish())
|
||||
Ok(HttpResponse::Ok().json(json!({ "key": key })))
|
||||
}
|
||||
|
@ -1,16 +1,33 @@
|
||||
use actix_jwt_session::{Duration, JwtTtl, RefreshTtl, SessionStorage};
|
||||
use actix_web::{
|
||||
post,
|
||||
web::{Data, Json},
|
||||
HttpRequest, HttpResponse,
|
||||
};
|
||||
/*
|
||||
|
||||
use jet_contract::{
|
||||
event_bus::{Msg, SignInMedium, Topic, UserMsg},
|
||||
redis::AsyncCommands,
|
||||
};
|
||||
use reqwest::StatusCode;
|
||||
use rumqttc::QoS;
|
||||
use sea_orm::prelude::*;
|
||||
use sea_orm::*;
|
||||
use tracing::error;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use tracing::error;
|
||||
|
||||
use crate::{models::{Error, JsonError}, extractors::RequireInstanceConfigured};
|
||||
use crate::{RedisClient, EventBusClient};
|
||||
use crate::{
|
||||
db_commit, db_rollback, db_t,
|
||||
extractors::RequireInstanceConfigured,
|
||||
models::{Error, JsonError},
|
||||
redis_c,
|
||||
session::AppClaims,
|
||||
utils::{extract_req_ip, extract_req_uagent, user_by_email},
|
||||
};
|
||||
use crate::{EventBusClient, RedisClient};
|
||||
|
||||
use super::{AuthResponseBody, auth_http_response};
|
||||
|
||||
#[post("/magic-sign-in")]
|
||||
async fn magic_sign_in(
|
||||
@ -20,32 +37,102 @@ async fn magic_sign_in(
|
||||
db: Data<DatabaseConnection>,
|
||||
redis: Data<RedisClient>,
|
||||
event_bus: Data<EventBusClient>,
|
||||
session: Data<SessionStorage>,
|
||||
) -> Result<HttpResponse, JsonError> {
|
||||
let mut t = db.begin().await.map_err(|e| {
|
||||
error!("Failed to get database connection: {e}");
|
||||
Error::DatabaseError
|
||||
})?;
|
||||
let mut t = db_t!(db)?;
|
||||
|
||||
match try_magic_sign_in(&mut t).await {
|
||||
match try_magic_sign_in(req, payload, &mut t, redis, event_bus, session).await {
|
||||
Ok(r) => {
|
||||
t.commit().await.map_err(|e| {
|
||||
error!("Failed to commit database changes");
|
||||
JsonError::new("Internal server error")
|
||||
})?;
|
||||
db_commit!(t)?;
|
||||
Ok(r)
|
||||
}
|
||||
Err(e) => {
|
||||
t.rollback().await.ok();
|
||||
db_rollback!(t).ok();
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_magic_sign_in() -> Result<HttpResponse, JsonError> {}
|
||||
async fn try_magic_sign_in(
|
||||
req: HttpRequest,
|
||||
payload: Json<Input>,
|
||||
t: &mut DatabaseTransaction,
|
||||
redis: Data<RedisClient>,
|
||||
event_bus: Data<EventBusClient>,
|
||||
session: Data<SessionStorage>,
|
||||
) -> Result<HttpResponse, JsonError> {
|
||||
let payload = payload.into_inner();
|
||||
let (key, user_token) = (payload.key.trim(), payload.token.trim());
|
||||
if key.is_empty() || user_token.is_empty() {
|
||||
return Err(JsonError::new("User token and key are required"));
|
||||
}
|
||||
let mut redis = redis_c!(redis)?;
|
||||
|
||||
let token: String = redis.hget(key, "token").await.map_err(|e| {
|
||||
tracing::error!("Failed to read token from redis: {e}");
|
||||
Error::RedisConnection
|
||||
})?;
|
||||
let email: String = redis.hget(key, "email").await.map_err(|e| {
|
||||
tracing::error!("Failed to read token from redis: {e}");
|
||||
Error::RedisConnection
|
||||
})?;
|
||||
if user_token != token {
|
||||
return Err(JsonError::new(
|
||||
"The magic code/link has expired please try again",
|
||||
));
|
||||
}
|
||||
let user = user_by_email(&email, &mut *t)
|
||||
.await?
|
||||
.ok_or(Error::UserRequired)?;
|
||||
let user_agent = extract_req_uagent(&req)?;
|
||||
let ip = extract_req_ip(&req)?;
|
||||
event_bus
|
||||
.publish(
|
||||
Topic::User,
|
||||
Msg::User(UserMsg::SignIn {
|
||||
user_id: jet_contract::UserId::new(user.id),
|
||||
email,
|
||||
user_agent,
|
||||
ip,
|
||||
medium: SignInMedium::Email,
|
||||
first_time: false,
|
||||
}),
|
||||
QoS::AtLeastOnce,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.map_err(JsonError::new)?;
|
||||
|
||||
let user_clone = user.clone();
|
||||
|
||||
let mut user: entities::users::ActiveModel = user.into();
|
||||
user.is_active = Set(true);
|
||||
user.is_email_verified = Set(true);
|
||||
user.last_active = Set(Some(chrono::Utc::now().fixed_offset()));
|
||||
user.last_login_time = Set(Some(chrono::Utc::now().fixed_offset()));
|
||||
user.last_login_ip = Set(extract_req_ip(&req)?);
|
||||
user.last_login_uagent = Set(extract_req_uagent(&req)?);
|
||||
user.token_updated_at = Set(Some(chrono::Utc::now().fixed_offset()));
|
||||
user.save(&mut *t).await.map_err(|e| {
|
||||
tracing::error!("Failed to update user: {e}");
|
||||
Error::DatabaseError
|
||||
})?;
|
||||
|
||||
crate::utils::invites_to_membership(
|
||||
user_clone.id,
|
||||
&user_clone.email.as_deref().unwrap_or_default(),
|
||||
None,
|
||||
&mut *t,
|
||||
)
|
||||
.await?;
|
||||
|
||||
auth_http_response(user_clone, session, StatusCode::OK)
|
||||
.await
|
||||
.map_err(JsonError::new)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Input {
|
||||
key: String,
|
||||
token: String,
|
||||
}
|
||||
*/
|
||||
|
@ -15,6 +15,7 @@ use crate::config::ApplicationConfig;
|
||||
use crate::extractors::RequireInstanceConfigured;
|
||||
use crate::http::api::authentication::{auth_http_response, create_user, has_workspace_invites};
|
||||
use crate::models::{Error, JsonError};
|
||||
use crate::utils::user_by_email;
|
||||
|
||||
use super::EmailAllowComment;
|
||||
|
||||
@ -65,17 +66,8 @@ async fn try_sign_in(
|
||||
));
|
||||
}
|
||||
|
||||
match Users::find()
|
||||
.filter(entities::users::Column::Email.eq(&email))
|
||||
.one(&mut *db)
|
||||
.await
|
||||
{
|
||||
Ok(None) => {}
|
||||
Ok(Some(_user)) => return Err(JsonError::new("User with this email already exists")),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to load user for sign-in: {e}");
|
||||
return Ok(Error::DatabaseError.error_response());
|
||||
}
|
||||
let None = user_by_email(&email, &mut *db).await? else {
|
||||
return Err(JsonError::new("User with this email already exists"));
|
||||
};
|
||||
|
||||
let user = create_user(&req, &email, &password, db).await?;
|
||||
|
@ -10,14 +10,13 @@ use reqwest::StatusCode;
|
||||
use rumqttc::QoS;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use sea_orm::*;
|
||||
use validators::models::Host;
|
||||
use validators::prelude::*;
|
||||
|
||||
use crate::config::ApplicationConfig;
|
||||
use crate::extractors::RequireInstanceConfigured;
|
||||
use crate::http::api::authentication::{auth_http_response, create_user, has_workspace_invites};
|
||||
use crate::models::{Error, JsonError};
|
||||
use crate::utils::{extract_req_info, extract_req_ip, extract_req_uagent};
|
||||
use crate::utils::{extract_req_ip, extract_req_uagent};
|
||||
|
||||
use super::EmailAllowComment;
|
||||
|
||||
@ -56,8 +55,12 @@ async fn try_sign_in(
|
||||
return Err(JsonError::new("Both email and password are required"));
|
||||
}
|
||||
let email = payload.email.trim().to_lowercase();
|
||||
|
||||
let password = payload.password.clone();
|
||||
|
||||
if !super::password::is_valid(&password) {
|
||||
return Err(JsonError::new("Password is too weak. Password must have at least 8 characters and contains small letter, big letter, number and special character"));
|
||||
}
|
||||
|
||||
if let Err(e) = EmailAllowComment::validate_str(&email) {
|
||||
tracing::error!("Invalid email address: {e}");
|
||||
return Err(JsonError::new("Please provide a valid email address."));
|
||||
|
@ -39,6 +39,7 @@ use crate::{
|
||||
extractors::RequireInstanceConfigured,
|
||||
http::OAuthError,
|
||||
models::{Error, JsonError},
|
||||
utils::user_by_email,
|
||||
};
|
||||
|
||||
macro_rules! oauth_envs {
|
||||
@ -173,7 +174,7 @@ async fn handle_callback(
|
||||
v
|
||||
}
|
||||
Err(e) => {
|
||||
db_rollback!(tx, "Failed to rollback social_auth changes to postgres");
|
||||
db_rollback!(tx, "Failed to rollback social_auth changes to postgres").ok();
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
@ -224,34 +225,14 @@ async fn handle_user_info(
|
||||
raw: _,
|
||||
} = user_info;
|
||||
|
||||
let Some(email) = email else {
|
||||
return Ok(HttpResponse::BadRequest().json(JsonError::new(
|
||||
"Something went wrong. Please try again later or contact the support team.",
|
||||
)));
|
||||
};
|
||||
let email = email.ok_or(Error::ContactSupport)?;
|
||||
if !email.contains('@') {
|
||||
return Ok(HttpResponse::BadRequest().json(JsonError::new(
|
||||
"Something went wrong. Please try again later or contact the support team.",
|
||||
)));
|
||||
return Err(Error::ContactSupport);
|
||||
}
|
||||
|
||||
let user = Users::find()
|
||||
.filter(UserColumn::Email.eq(&email))
|
||||
.one(&mut *db)
|
||||
.await;
|
||||
let user = user_by_email(&email, &mut *db).await?;
|
||||
|
||||
let mut user: UserModel = match user {
|
||||
Ok(Some(user)) => user.into(),
|
||||
Ok(None) => {
|
||||
return Ok(HttpResponse::BadRequest().json(JsonError::new(
|
||||
"Something went wrong. Please try again later or contact the support team.",
|
||||
)));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to find user for oauth {provider} sign-in: {e}");
|
||||
return Ok(HttpResponse::InternalServerError().finish());
|
||||
}
|
||||
};
|
||||
let mut user: UserModel = user.ok_or(Error::ContactSupport)?.into();
|
||||
|
||||
let (ip, user_agent, _current_site) = crate::utils::extract_req_info(&req)?;
|
||||
|
||||
|
@ -48,6 +48,10 @@ pub enum Error {
|
||||
AddToWorkspace,
|
||||
#[display(fmt = "Failed to add user to projects")]
|
||||
AddToProject,
|
||||
#[display(fmt = "Something went wrong. Please try again later or contact the support team.")]
|
||||
ContactSupport,
|
||||
#[display(fmt = "User not found")]
|
||||
UserRequired,
|
||||
}
|
||||
|
||||
impl From<AuthError> for Error {
|
||||
|
@ -1,3 +1,4 @@
|
||||
use actix_web::web::Data;
|
||||
use actix_web::HttpRequest;
|
||||
use chrono::Utc;
|
||||
use entities::project_member_invites::{
|
||||
@ -64,6 +65,29 @@ macro_rules! db_rollback {
|
||||
})
|
||||
}};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! redis_c {
|
||||
($redis: expr) => {
|
||||
$redis.get().await.map_err(|e| {
|
||||
tracing::error!("Failed to obtain redis connection: {e}");
|
||||
Error::RedisConnection
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn user_by_email(
|
||||
email: &str,
|
||||
db: &mut DatabaseTransaction,
|
||||
) -> Result<Option<entities::users::Model>, Error> {
|
||||
entities::prelude::Users::find()
|
||||
.filter(entities::users::Column::Email.eq(email))
|
||||
.one(db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to load users: {e}");
|
||||
Error::DatabaseError
|
||||
})
|
||||
}
|
||||
|
||||
pub fn extract_req_ip(req: &HttpRequest) -> Result<String, Error> {
|
||||
Ok(req
|
||||
|
Loading…
Reference in New Issue
Block a user