Add sign up

This commit is contained in:
eraden 2024-01-27 21:23:18 +01:00
parent 70cb64a2e0
commit 9805a96510
4 changed files with 158 additions and 11 deletions

View File

@ -15,6 +15,7 @@ use uuid::Uuid;
mod email_check; mod email_check;
mod sign_in; mod sign_in;
mod sign_up;
mod social_auth; mod social_auth;
#[derive(Debug, serde::Serialize)] #[derive(Debug, serde::Serialize)]
@ -27,7 +28,8 @@ pub fn configure(http_client: reqwest::Client, config: &mut ServiceConfig) {
config config
.service(email_check::email_check) .service(email_check::email_check)
.service(oauth) .service(oauth)
.service(sign_in::sign_in); .service(sign_in::sign_in)
.service(sign_up::sign_up);
social_auth::configure(http_client, config); social_auth::configure(http_client, config);
} }

View File

@ -77,25 +77,27 @@ async fn try_sign_in(
tracing::error!("Invalid email address: {e}"); tracing::error!("Invalid email address: {e}");
return Err(JsonError::new("Please provide a valid email address.")); return Err(JsonError::new("Please provide a valid email address."));
} }
if !config.enable_signup && !has_workspace_invites(&email, &mut *db).await? {
return Err(JsonError::new(
"New account creation is disabled. Please contact your site administrator",
));
}
let (user, was_created) = match Users::find() match Users::find()
.filter(entities::users::Column::Email.eq(&email)) .filter(entities::users::Column::Email.eq(&email))
.one(&mut *db) .one(&mut *db)
.await .await
{ {
Ok(Some(user)) => (user, false), Ok(None) => {}
Ok(None) if !config.enable_signup && !has_workspace_invites(&email, &mut *db).await? => { Ok(Some(user)) => return Err(JsonError::new("User with this email already exists")),
return Err(JsonError::new(
"New account creation is disabled. Please contact your site administrator",
));
}
Ok(None) => (create_user(&req, &email, &password, &mut *db).await?, true),
Err(e) => { Err(e) => {
tracing::error!("Failed to load user for sign-in: {e}"); tracing::error!("Failed to load user for sign-in: {e}");
return Ok(Error::DatabaseError.error_response()); return Ok(Error::DatabaseError.error_response());
} }
}; };
let user = create_user(&req, &email, &password, db).await?;
let user_id = user.id; let user_id = user.id;
let mut user: UserModel = user.into(); let mut user: UserModel = user.into();

View File

@ -0,0 +1,144 @@
use actix_jwt_session::SessionStorage;
use actix_web::web::{Data, Json};
use actix_web::ResponseError;
use actix_web::{post, HttpRequest, HttpResponse};
use entities::prelude::Users;
use entities::users::ActiveModel as UserModel;
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;
use crate::extractors::RequireInstanceConfigured;
use crate::http::api::authentication::{auth_http_response, create_user, has_workspace_invites};
use crate::models::{Error, JsonError};
#[post("/sign-up")]
pub async fn sign_up(
_: RequireInstanceConfigured,
req: HttpRequest,
payload: Json<SignUpInput>,
db: Data<DatabaseConnection>,
config: Data<ApplicationConfig>,
event_bus: Data<crate::EventBusClient>,
session: Data<SessionStorage>,
) -> Result<HttpResponse, JsonError> {
let mut t = db.begin().await.map_err(|e| {
tracing::error!("Failed to start transaction for sign-in: {e}");
Error::DatabaseError
})?;
let res = try_sign_in(req, payload, &mut t, config, event_bus, session).await?;
t.commit().await.map_err(|e| {
tracing::error!("Failed to commit transaction for sign-in: {e}");
Error::DatabaseError
})?;
Ok(res)
}
async fn try_sign_in(
req: HttpRequest,
payload: Json<SignUpInput>,
db: &mut DatabaseTransaction,
config: Data<ApplicationConfig>,
event_bus: Data<crate::EventBusClient>,
session: Data<SessionStorage>,
) -> Result<HttpResponse, JsonError> {
if payload.email.trim().is_empty() || payload.password.trim().is_empty() {
return Err(JsonError::new("Both email and password are required"));
}
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}");
return Err(JsonError::new("Please provide a valid email address."));
}
let (user, was_created) = match Users::find()
.filter(entities::users::Column::Email.eq(&email))
.one(&mut *db)
.await
{
Ok(Some(user)) => (user, false),
Ok(None) if !config.enable_signup && !has_workspace_invites(&email, &mut *db).await? => {
return Err(JsonError::new(
"New account creation is disabled. Please contact your site administrator",
));
}
Ok(None) => (create_user(&req, &email, &password, &mut *db).await?, true),
Err(e) => {
tracing::error!("Failed to load user for sign-in: {e}");
return Ok(Error::DatabaseError.error_response());
}
};
let user_id = user.id;
let mut user: UserModel = user.into();
let (ip, user_agent, _current_site) = crate::utils::extract_req_info(&req)?;
user.is_active = 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(ip.clone());
user.last_login_uagent = Set(user_agent.clone());
user.token_updated_at = Set(Some(chrono::Utc::now().fixed_offset()));
let user = Users::update(user).exec(&mut *db).await.map_err(|e| {
tracing::error!("Failed to update account for {email:?}: {e}");
Error::DatabaseError
})?;
crate::utils::invites_to_membership(user_id, &email, None, &mut *db).await?;
if let Err(e) = event_bus
.publish(
Topic::User,
Msg::User(UserMsg::SignIn {
user_id: UserId::new(user_id),
email,
user_agent,
ip,
medium: SignInMedium::Email,
first_time: false,
}),
QoS::AtLeastOnce,
true,
)
.await
{
tracing::warn!("Failed to publish sign-in msg after sign in: {e}");
};
auth_http_response(user, session, StatusCode::OK)
.await
.map_err(JsonError::new)
}
#[derive(serde::Deserialize)]
struct SignUpInput {
email: String,
password: String,
}

View File

@ -1,4 +1,4 @@
use actix_web::{HttpRequest, HttpResponse}; use actix_web::HttpRequest;
use chrono::Utc; use chrono::Utc;
use entities::project_member_invites::{ use entities::project_member_invites::{
Column as ProjectMemberInviteColumn, Model as ProjectMemberInvite, Column as ProjectMemberInviteColumn, Model as ProjectMemberInvite,
@ -21,7 +21,6 @@ use tracing::error;
use uuid::Uuid; use uuid::Uuid;
use crate::http::OAuthError; use crate::http::OAuthError;
use crate::models::JsonError;
use crate::{http::AuthError, models::Error}; use crate::{http::AuthError, models::Error};
pub fn extract_req_info(req: &HttpRequest) -> Result<(String, String, String), Error> { pub fn extract_req_info(req: &HttpRequest) -> Result<(String, String, String), Error> {