Generate magic link
This commit is contained in:
parent
9617bf08b0
commit
6b6fd54292
@ -8,12 +8,18 @@ use actix_web::web::{Data, ServiceConfig};
|
||||
use actix_web::{HttpRequest, HttpResponse};
|
||||
use entities::prelude::Users;
|
||||
use entities::users::Model as User;
|
||||
use rand::Rng;
|
||||
use reqwest::StatusCode;
|
||||
use sea_orm::prelude::*;
|
||||
use sea_orm::*;
|
||||
use uuid::Uuid;
|
||||
use validators::prelude::*;
|
||||
use validators::Validator;
|
||||
use validators_prelude::Host;
|
||||
|
||||
mod email_check;
|
||||
mod magic_generate;
|
||||
mod magic_sign_in;
|
||||
mod sign_in;
|
||||
mod sign_out;
|
||||
mod sign_up;
|
||||
@ -32,6 +38,7 @@ pub fn configure(http_client: reqwest::Client, config: &mut ServiceConfig) {
|
||||
.service(sign_in::sign_in)
|
||||
.service(sign_up::sign_up)
|
||||
.service(sign_out::sign_out)
|
||||
.service(magic_sign_in::magic_sign_in)
|
||||
.configure(|c| {
|
||||
social_auth::configure(http_client, c);
|
||||
}),
|
||||
@ -176,3 +183,228 @@ async fn auth_http_response(
|
||||
refresh_token,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Validator)]
|
||||
#[validator(email(
|
||||
comment(Allow),
|
||||
ip(Allow),
|
||||
local(Allow),
|
||||
at_least_two_labels(Allow),
|
||||
non_ascii(Allow)
|
||||
))]
|
||||
pub struct EmailAllowComment {
|
||||
pub local_part: String,
|
||||
pub need_quoted: bool,
|
||||
pub domain_part: Host,
|
||||
pub comment_before_local_part: Option<String>,
|
||||
pub comment_after_local_part: Option<String>,
|
||||
pub comment_before_domain_part: Option<String>,
|
||||
pub comment_after_domain_part: Option<String>,
|
||||
}
|
||||
|
||||
pub fn random_password() -> String {
|
||||
rand::thread_rng()
|
||||
.sample_iter(rand::distributions::Alphanumeric)
|
||||
.take(30)
|
||||
.map(char::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub mod magic_link {
|
||||
use crate::models::Error;
|
||||
use crate::{http::AuthError, RedisClient};
|
||||
use actix_web::web::Data;
|
||||
use jet_contract::*;
|
||||
use rand::prelude::*;
|
||||
use redis::AsyncCommands;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum AttemptValidity {
|
||||
Allowed,
|
||||
Exhausted,
|
||||
}
|
||||
impl AttemptValidity {
|
||||
pub fn is_exhausted(self) -> bool {
|
||||
matches!(self, Self::Exhausted)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn magic_link_key(email: &str) -> String {
|
||||
format!("magic_{email}")
|
||||
}
|
||||
|
||||
pub async fn create_magic_link(
|
||||
email: &str,
|
||||
redis: Data<RedisClient>,
|
||||
) -> Result<(MagicLinkKey, MagicLinkToken, AttemptValidity), Error> {
|
||||
use rand::distributions::Alphanumeric;
|
||||
|
||||
let key = magic_link_key(email);
|
||||
let mut rng = rand::thread_rng();
|
||||
let token = format!(
|
||||
"{}-{}-{}",
|
||||
(&mut rng)
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(4)
|
||||
.map(char::from)
|
||||
.collect::<String>(),
|
||||
(&mut rng)
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(4)
|
||||
.map(char::from)
|
||||
.collect::<String>(),
|
||||
(&mut rng)
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(4)
|
||||
.map(char::from)
|
||||
.collect::<String>(),
|
||||
);
|
||||
|
||||
let Ok(mut redis) = redis.get().await else {
|
||||
return Err(AuthError::SerializeMsg.into());
|
||||
};
|
||||
|
||||
if redis
|
||||
.exists(&key)
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?
|
||||
{
|
||||
let attempt: u8 = redis
|
||||
.hget(&key, "current_attempt")
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?;
|
||||
if attempt + 1 > 2 {
|
||||
let token: String = redis
|
||||
.hget(&key, "token")
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?;
|
||||
return Ok((
|
||||
MagicLinkKey::new(key),
|
||||
MagicLinkToken::new(token),
|
||||
AttemptValidity::Exhausted,
|
||||
));
|
||||
}
|
||||
let _: () = redis
|
||||
.hincr(&key, "current_attempt", 1)
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?;
|
||||
let _: () = redis
|
||||
.hset(&key, "token", &token)
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?;
|
||||
} else {
|
||||
let _: () = redis
|
||||
.hset(&key, "current_attempt", 0)
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?;
|
||||
let _: () = redis
|
||||
.hset_multiple(&key, &[("email", email), ("token", &token)])
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?;
|
||||
let _: () = redis
|
||||
.expire(&key, 600)
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?;
|
||||
}
|
||||
|
||||
Ok((
|
||||
MagicLinkKey::new(key),
|
||||
MagicLinkToken::new(token),
|
||||
AttemptValidity::Allowed,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod create_magic_link_tests {
|
||||
use super::*;
|
||||
use actix_web::web::Data;
|
||||
use jet_contract::deadpool_redis;
|
||||
|
||||
#[tokio::test]
|
||||
async fn full() {
|
||||
let email = "foo@bar.com".to_string();
|
||||
let redis = deadpool_redis::Config::default()
|
||||
.create_pool(Some(deadpool_redis::Runtime::Tokio1))
|
||||
.unwrap();
|
||||
let mut conn = redis.get().await.unwrap();
|
||||
let _: () = conn.del(magic_link_key(&email)).await.unwrap();
|
||||
|
||||
let (key, token, validity) = create_magic_link(&email, Data::new(redis.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
let r: Vec<String> = conn.hgetall(&*key).await.unwrap();
|
||||
assert_eq!(
|
||||
(validity, r),
|
||||
(
|
||||
AttemptValidity::Allowed,
|
||||
vec![
|
||||
"current_attempt".into(),
|
||||
"0".into(),
|
||||
"email".into(),
|
||||
email.clone(),
|
||||
"token".into(),
|
||||
(&*token).clone()
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
let (key, token, validity) = create_magic_link(&email, Data::new(redis.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
let r: Vec<String> = conn.hgetall(&*key).await.unwrap();
|
||||
assert_eq!(
|
||||
(validity, r),
|
||||
(
|
||||
AttemptValidity::Allowed,
|
||||
vec![
|
||||
"current_attempt".into(),
|
||||
"1".into(),
|
||||
"email".into(),
|
||||
email.clone(),
|
||||
"token".into(),
|
||||
token.to_string()
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
let (key, token, validity) = create_magic_link(&email, Data::new(redis.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
let r: Vec<String> = conn.hgetall(&*key).await.unwrap();
|
||||
assert_eq!(
|
||||
(validity, r),
|
||||
(
|
||||
AttemptValidity::Allowed,
|
||||
vec![
|
||||
"current_attempt".into(),
|
||||
"2".into(),
|
||||
"email".into(),
|
||||
email.clone(),
|
||||
"token".into(),
|
||||
token.to_string()
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
let (key, token, validity) = create_magic_link(&email, Data::new(redis.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
let r: Vec<String> = conn.hgetall(&*key).await.unwrap();
|
||||
assert_eq!(
|
||||
(validity, r),
|
||||
(
|
||||
AttemptValidity::Exhausted,
|
||||
vec![
|
||||
"current_attempt".into(),
|
||||
"2".into(),
|
||||
"email".into(),
|
||||
email.clone(),
|
||||
"token".into(),
|
||||
token.to_string()
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,6 @@ use actix_web::web::{Data, Json};
|
||||
use actix_web::{post, HttpRequest, HttpResponse};
|
||||
use entities::prelude::{Users, WorkspaceMemberInvites};
|
||||
use entities::users::Model as User;
|
||||
use jet_contract::redis::AsyncCommands;
|
||||
use jet_contract::{MagicLinkKey, MagicLinkToken};
|
||||
use rand::Rng;
|
||||
use sea_orm::prelude::*;
|
||||
use sea_orm::{DatabaseConnection, EntityTrait, QueryFilter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -14,6 +11,7 @@ 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::{EventBusClient, RedisClient};
|
||||
|
||||
@ -230,193 +228,3 @@ async fn register(
|
||||
is_existing: false,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
enum AttemptValidity {
|
||||
Allowed,
|
||||
Exhausted,
|
||||
}
|
||||
impl AttemptValidity {
|
||||
fn is_exhausted(self) -> bool {
|
||||
matches!(self, Self::Exhausted)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn magic_link_key(email: &str) -> String {
|
||||
format!("magic_{email}")
|
||||
}
|
||||
|
||||
async fn create_magic_link(
|
||||
email: &str,
|
||||
redis: Data<RedisClient>,
|
||||
) -> Result<(MagicLinkKey, MagicLinkToken, AttemptValidity), Error> {
|
||||
use rand::distributions::Alphanumeric;
|
||||
|
||||
let key = magic_link_key(email);
|
||||
let mut rng = rand::thread_rng();
|
||||
let token = format!(
|
||||
"{}-{}-{}",
|
||||
(&mut rng)
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(4)
|
||||
.map(char::from)
|
||||
.collect::<String>(),
|
||||
(&mut rng)
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(4)
|
||||
.map(char::from)
|
||||
.collect::<String>(),
|
||||
(&mut rng)
|
||||
.sample_iter(Alphanumeric)
|
||||
.take(4)
|
||||
.map(char::from)
|
||||
.collect::<String>(),
|
||||
);
|
||||
|
||||
let Ok(mut redis) = redis.get().await else {
|
||||
return Err(AuthError::SerializeMsg.into());
|
||||
};
|
||||
|
||||
if redis
|
||||
.exists(&key)
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?
|
||||
{
|
||||
let attempt: u8 = redis
|
||||
.hget(&key, "current_attempt")
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?;
|
||||
if attempt + 1 > 2 {
|
||||
let token: String = redis
|
||||
.hget(&key, "token")
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?;
|
||||
return Ok((
|
||||
MagicLinkKey::new(key),
|
||||
MagicLinkToken::new(token),
|
||||
AttemptValidity::Exhausted,
|
||||
));
|
||||
}
|
||||
let _: () = redis
|
||||
.hincr(&key, "current_attempt", 1)
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?;
|
||||
let _: () = redis
|
||||
.hset(&key, "token", &token)
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?;
|
||||
} else {
|
||||
let _: () = redis
|
||||
.hset(&key, "current_attempt", 0)
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?;
|
||||
let _: () = redis
|
||||
.hset_multiple(&key, &[("email", email), ("token", &token)])
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?;
|
||||
let _: () = redis
|
||||
.expire(&key, 600)
|
||||
.await
|
||||
.map_err(|_| Error::RedisConnection)?;
|
||||
}
|
||||
|
||||
Ok((
|
||||
MagicLinkKey::new(key),
|
||||
MagicLinkToken::new(token),
|
||||
AttemptValidity::Allowed,
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod create_magic_link_tests {
|
||||
use super::*;
|
||||
use actix_web::web::Data;
|
||||
use jet_contract::deadpool_redis;
|
||||
|
||||
#[tokio::test]
|
||||
async fn full() {
|
||||
let email = "foo@bar.com".to_string();
|
||||
let redis = deadpool_redis::Config::default()
|
||||
.create_pool(Some(deadpool_redis::Runtime::Tokio1))
|
||||
.unwrap();
|
||||
let mut conn = redis.get().await.unwrap();
|
||||
let _: () = conn.del(magic_link_key(&email)).await.unwrap();
|
||||
|
||||
let (key, token, validity) = create_magic_link(&email, Data::new(redis.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
let r: Vec<String> = conn.hgetall(&*key).await.unwrap();
|
||||
assert_eq!(
|
||||
(validity, r),
|
||||
(
|
||||
AttemptValidity::Allowed,
|
||||
vec![
|
||||
"current_attempt".into(),
|
||||
"0".into(),
|
||||
"email".into(),
|
||||
email.clone(),
|
||||
"token".into(),
|
||||
(&*token).clone()
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
let (key, token, validity) = create_magic_link(&email, Data::new(redis.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
let r: Vec<String> = conn.hgetall(&*key).await.unwrap();
|
||||
assert_eq!(
|
||||
(validity, r),
|
||||
(
|
||||
AttemptValidity::Allowed,
|
||||
vec![
|
||||
"current_attempt".into(),
|
||||
"1".into(),
|
||||
"email".into(),
|
||||
email.clone(),
|
||||
"token".into(),
|
||||
token.to_string()
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
let (key, token, validity) = create_magic_link(&email, Data::new(redis.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
let r: Vec<String> = conn.hgetall(&*key).await.unwrap();
|
||||
assert_eq!(
|
||||
(validity, r),
|
||||
(
|
||||
AttemptValidity::Allowed,
|
||||
vec![
|
||||
"current_attempt".into(),
|
||||
"2".into(),
|
||||
"email".into(),
|
||||
email.clone(),
|
||||
"token".into(),
|
||||
token.to_string()
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
let (key, token, validity) = create_magic_link(&email, Data::new(redis.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
let r: Vec<String> = conn.hgetall(&*key).await.unwrap();
|
||||
assert_eq!(
|
||||
(validity, r),
|
||||
(
|
||||
AttemptValidity::Exhausted,
|
||||
vec![
|
||||
"current_attempt".into(),
|
||||
"2".into(),
|
||||
"email".into(),
|
||||
email.clone(),
|
||||
"token".into(),
|
||||
token.to_string()
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
59
crates/jet-api/src/http/api/authentication/magic_generate.rs
Normal file
59
crates/jet-api/src/http/api/authentication/magic_generate.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use actix_web::{
|
||||
post,
|
||||
web::{Data, Json},
|
||||
HttpRequest, HttpResponse,
|
||||
};
|
||||
use jet_contract::event_bus::{EmailMsg, Topic};
|
||||
use sea_orm::prelude::*;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use serde::Deserialize;
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Input {
|
||||
email: String,
|
||||
}
|
||||
|
||||
#[post("/magic-generate")]
|
||||
pub async fn magic_generate(
|
||||
req: HttpRequest,
|
||||
_: RequireInstanceConfigured,
|
||||
payload: Json<Input>,
|
||||
db: Data<DatabaseConnection>,
|
||||
redis: Data<RedisClient>,
|
||||
event_bus: Data<EventBusClient>,
|
||||
) -> Result<HttpResponse, JsonError> {
|
||||
let mut t = db_t!(db)?;
|
||||
let email = payload.into_inner().email;
|
||||
|
||||
let user = match entities::prelude::Users::find()
|
||||
.filter(entities::users::Column::Email.eq(&email))
|
||||
.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()),
|
||||
};
|
||||
|
||||
db_commit!(t)?;
|
||||
|
||||
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)
|
||||
.await;
|
||||
|
||||
Ok(HttpResponse::NotImplemented().finish())
|
||||
}
|
51
crates/jet-api/src/http/api/authentication/magic_sign_in.rs
Normal file
51
crates/jet-api/src/http/api/authentication/magic_sign_in.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use actix_web::{
|
||||
post,
|
||||
web::{Data, Json},
|
||||
HttpRequest, HttpResponse,
|
||||
};
|
||||
/*
|
||||
use sea_orm::prelude::*;
|
||||
use sea_orm::*;
|
||||
use tracing::error;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{models::{Error, JsonError}, extractors::RequireInstanceConfigured};
|
||||
use crate::{RedisClient, EventBusClient};
|
||||
|
||||
#[post("/magic-sign-in")]
|
||||
async fn magic_sign_in(
|
||||
_: RequireInstanceConfigured,
|
||||
req: HttpRequest,
|
||||
payload: Json<Input>,
|
||||
db: Data<DatabaseConnection>,
|
||||
redis: Data<RedisClient>,
|
||||
event_bus: Data<EventBusClient>,
|
||||
) -> Result<HttpResponse, JsonError> {
|
||||
let mut t = db.begin().await.map_err(|e| {
|
||||
error!("Failed to get database connection: {e}");
|
||||
Error::DatabaseError
|
||||
})?;
|
||||
|
||||
match try_magic_sign_in(&mut t).await {
|
||||
Ok(r) => {
|
||||
t.commit().await.map_err(|e| {
|
||||
error!("Failed to commit database changes");
|
||||
JsonError::new("Internal server error")
|
||||
})?;
|
||||
Ok(r)
|
||||
}
|
||||
Err(e) => {
|
||||
t.rollback().await.ok();
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_magic_sign_in() -> Result<HttpResponse, JsonError> {}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Input {
|
||||
key: String,
|
||||
token: String,
|
||||
}
|
||||
*/
|
@ -8,9 +8,7 @@ use jet_contract::event_bus::{Msg, SignInMedium, Topic, UserMsg};
|
||||
use jet_contract::UserId;
|
||||
use reqwest::StatusCode;
|
||||
use rumqttc::QoS;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use sea_orm::*;
|
||||
use validators::models::Host;
|
||||
use validators::prelude::*;
|
||||
|
||||
use crate::config::ApplicationConfig;
|
||||
@ -18,6 +16,8 @@ use crate::extractors::RequireInstanceConfigured;
|
||||
use crate::http::api::authentication::{auth_http_response, create_user, has_workspace_invites};
|
||||
use crate::models::{Error, JsonError};
|
||||
|
||||
use super::EmailAllowComment;
|
||||
|
||||
#[post("/sign-in")]
|
||||
pub async fn sign_in(
|
||||
_: RequireInstanceConfigured,
|
||||
@ -54,24 +54,6 @@ async fn try_sign_in(
|
||||
}
|
||||
let email = payload.email.trim().to_lowercase();
|
||||
|
||||
#[derive(Validator)]
|
||||
#[validator(email(
|
||||
comment(Allow),
|
||||
ip(Allow),
|
||||
local(Allow),
|
||||
at_least_two_labels(Allow),
|
||||
non_ascii(Allow)
|
||||
))]
|
||||
pub struct EmailAllowComment {
|
||||
pub local_part: String,
|
||||
pub need_quoted: bool,
|
||||
pub domain_part: Host,
|
||||
pub comment_before_local_part: Option<String>,
|
||||
pub comment_after_local_part: Option<String>,
|
||||
pub comment_before_domain_part: Option<String>,
|
||||
pub comment_after_domain_part: Option<String>,
|
||||
}
|
||||
|
||||
let password = payload.password.clone();
|
||||
if let Err(e) = EmailAllowComment::validate_str(&email) {
|
||||
tracing::error!("Invalid email address: {e}");
|
||||
|
@ -17,6 +17,9 @@ 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 super::EmailAllowComment;
|
||||
|
||||
#[post("/sign-up")]
|
||||
pub async fn sign_up(
|
||||
@ -54,24 +57,6 @@ async fn try_sign_in(
|
||||
}
|
||||
let email = payload.email.trim().to_lowercase();
|
||||
|
||||
#[derive(Validator)]
|
||||
#[validator(email(
|
||||
comment(Allow),
|
||||
ip(Allow),
|
||||
local(Allow),
|
||||
at_least_two_labels(Allow),
|
||||
non_ascii(Allow)
|
||||
))]
|
||||
pub struct EmailAllowComment {
|
||||
pub local_part: String,
|
||||
pub need_quoted: bool,
|
||||
pub domain_part: Host,
|
||||
pub comment_before_local_part: Option<String>,
|
||||
pub comment_after_local_part: Option<String>,
|
||||
pub comment_before_domain_part: Option<String>,
|
||||
pub comment_after_domain_part: Option<String>,
|
||||
}
|
||||
|
||||
let password = payload.password.clone();
|
||||
if let Err(e) = EmailAllowComment::validate_str(&email) {
|
||||
tracing::error!("Invalid email address: {e}");
|
||||
@ -99,7 +84,8 @@ async fn try_sign_in(
|
||||
let user_id = user.id;
|
||||
let mut user: UserModel = user.into();
|
||||
|
||||
let (ip, user_agent, _current_site) = crate::utils::extract_req_info(&req)?;
|
||||
let ip = extract_req_ip(&req)?;
|
||||
let user_agent = extract_req_uagent(&req)?;
|
||||
|
||||
user.is_active = Set(true);
|
||||
user.last_active = Set(Some(chrono::Utc::now().fixed_offset()));
|
||||
|
@ -35,6 +35,7 @@ use sea_orm::{
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
use crate::{
|
||||
db_commit, db_rollback, db_t,
|
||||
extractors::RequireInstanceConfigured,
|
||||
http::OAuthError,
|
||||
models::{Error, JsonError},
|
||||
@ -155,7 +156,7 @@ async fn handle_callback(
|
||||
use oauth2_signin::web_app::SigninFlowHandleCallbackRet as R;
|
||||
let response = match ret {
|
||||
R::Ok((access_token_body, user_info)) => {
|
||||
let mut tx = db.begin().await.map_err(|_| Error::DatabaseError)?;
|
||||
let mut tx = db_t!(db)?;
|
||||
match handle_user_info(
|
||||
provider,
|
||||
req,
|
||||
@ -168,17 +169,11 @@ async fn handle_callback(
|
||||
.await
|
||||
{
|
||||
Ok(v) => {
|
||||
tx.commit().await.map_err(|e| {
|
||||
error!("Failed to commit social_auth changes to postgres: {e}");
|
||||
Error::DatabaseError
|
||||
})?;
|
||||
db_commit!(tx, "Failed to commit social_auth changes to postgres")?;
|
||||
v
|
||||
}
|
||||
Err(e) => {
|
||||
tx.rollback().await.map_err(|e| {
|
||||
error!("Failed to rollback social_auth changes to postgres: {e}");
|
||||
Error::DatabaseError
|
||||
})?;
|
||||
db_rollback!(tx, "Failed to rollback social_auth changes to postgres");
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,48 @@ use uuid::Uuid;
|
||||
use crate::http::OAuthError;
|
||||
use crate::{http::AuthError, models::Error};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! db_t {
|
||||
($db: expr) => {{
|
||||
use sea_orm::*;
|
||||
|
||||
$db.begin().await.map_err(|e| {
|
||||
tracing::error!("Failed to start db tracation: {e}");
|
||||
crate::models::Error::DatabaseError
|
||||
})
|
||||
}};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! db_commit {
|
||||
($db: expr) => {{
|
||||
$db.commit().await.map_err(|e| {
|
||||
tracing::error!("Failed to commit db tracation: {e}");
|
||||
crate::models::Error::DatabaseError
|
||||
})
|
||||
}};
|
||||
($db: expr, $msg: expr) => {{
|
||||
$db.commit().await.map_err(|e| {
|
||||
tracing::error!(std::concat!($msg, ": {}"), e);
|
||||
crate::models::Error::DatabaseError
|
||||
})
|
||||
}};
|
||||
}
|
||||
#[macro_export]
|
||||
macro_rules! db_rollback {
|
||||
($db: expr) => {{
|
||||
$db.rollback().await.map_err(|e| {
|
||||
tracing::error!("Failed to rollback db tracation: {e}");
|
||||
crate::models::Error::DatabaseError
|
||||
})
|
||||
}};
|
||||
($db: expr, $msg: expr) => {{
|
||||
$db.rollback().await.map_err(|e| {
|
||||
tracing::error!(std::concat!($msg, ": {}"), e);
|
||||
crate::models::Error::DatabaseError
|
||||
})
|
||||
}};
|
||||
}
|
||||
|
||||
pub fn extract_req_ip(req: &HttpRequest) -> Result<String, Error> {
|
||||
Ok(req
|
||||
.peer_addr()
|
||||
|
Loading…
Reference in New Issue
Block a user