gitdataai/libs/service/auth/register.rs
2026-04-15 09:08:09 +08:00

172 lines
5.9 KiB
Rust

use crate::AppService;
use crate::error::AppError;
use argon2::password_hash::{Salt, SaltString};
use argon2::{Argon2, PasswordHasher};
use models::users::{user, user_activity_log, user_email, user_password};
use models::workspaces::{WorkspaceRole, workspace, workspace_membership};
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use uuid::Uuid;
#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct RegisterParams {
pub username: String,
pub email: String,
pub password: String,
pub captcha: String,
}
impl AppService {
pub async fn auth_register(
&self,
params: RegisterParams,
context: &Session,
) -> Result<user::Model, AppError> {
self.auth_check_captcha(&context, params.captcha).await?;
let password = self.auth_rsa_decode(&context, params.password).await?;
if self
.utils_find_user_by_username(params.username.clone())
.await
.is_ok()
{
slog::warn!(self.logs, "Registration failed: username already exists"; "username" => &params.username);
return Err(AppError::UserNameExists);
}
if self
.utils_find_user_by_email(params.email.clone())
.await
.is_ok()
{
slog::warn!(self.logs, "Registration failed: email already exists"; "email" => &params.email);
return Err(AppError::EmailExists);
}
let user_uid = Uuid::now_v7();
let now = chrono::Utc::now();
let txn = self.db.begin().await.map_err(|_| AppError::TxnError)?;
let user_model = user::ActiveModel {
uid: Set(user_uid),
username: Set(params.username.clone()),
display_name: Set(Some(params.username.clone())),
avatar_url: Set(None),
website_url: Set(None),
organization: Set(None),
last_sign_in_at: Set(None),
created_at: Set(now),
updated_at: Set(now),
};
let user = user_model.insert(&txn).await.map_err(|e| {
slog::error!(self.logs, "{}", format!("Failed to insert user: {:?}", e));
AppError::UserNotFound
})?;
let user_email_model = user_email::ActiveModel {
user: Set(user_uid),
email: Set(params.email),
created_at: Set(now),
};
user_email_model.insert(&txn).await.map_err(|e| {
slog::error!(
self.logs,
"{}",
format!("Failed to insert user email: {:?}", e)
);
AppError::UserNotFound
})?;
let salt = SaltString::generate(&mut rsa::rand_core::OsRng::default());
let password_hash = Argon2::default()
.hash_password(password.as_bytes(), Salt::from_b64(&*salt.to_string())?)
.map_err(|e| {
slog::error!(self.logs, "{}", format!("Failed to hash password: {:?}", e));
AppError::UserNotFound
})?
.to_string();
let user_password_model = user_password::ActiveModel {
user: Set(user_uid),
password_hash: Set(password_hash),
password_salt: Set(Some(salt.to_string())),
is_active: Set(true),
created_at: Set(now),
updated_at: Set(now),
};
user_password_model.insert(&txn).await.map_err(|e| {
slog::error!(
self.logs,
"{}",
format!("Failed to insert user password: {:?}", e)
);
AppError::UserNotFound
})?;
let _ = user_activity_log::ActiveModel {
user_uid: Set(Option::from(user_uid)),
action: Set("register".to_string()),
ip_address: Set(context.ip_address()),
user_agent: Set(context.user_agent()),
details: Set(serde_json::json!({
"username": user.username.clone(),
"method": "password"
})
.into()),
created_at: Set(now),
..Default::default()
}
.insert(&txn)
.await;
// Auto-create personal workspace for the new user
let personal_slug = format!("~{}", params.username);
let ws = workspace::ActiveModel {
id: Set(Uuid::now_v7()),
slug: Set(personal_slug),
name: Set(format!("{} 的工作空间", params.username)),
description: Set(None),
avatar_url: Set(None),
plan: Set("free".to_string()),
billing_email: Set(None),
stripe_customer_id: Set(None),
stripe_subscription_id: Set(None),
plan_expires_at: Set(None),
deleted_at: Set(None),
created_at: Set(now),
updated_at: Set(now),
};
let ws = ws.insert(&txn).await.map_err(|e| {
slog::error!(
self.logs,
"{}",
format!("Failed to insert personal workspace: {:?}", e)
);
AppError::UserNotFound
})?;
let _ = workspace_membership::ActiveModel {
id: Default::default(),
workspace_id: Set(ws.id),
user_id: Set(user_uid),
role: Set(WorkspaceRole::Owner.to_string()),
status: Set("active".to_string()),
invited_by: Set(None),
joined_at: Set(now),
invite_token: Set(None),
invite_expires_at: Set(None),
}
.insert(&txn)
.await;
txn.commit().await.map_err(|_| AppError::TxnError)?;
context.set_user(user_uid);
context.set_current_workspace_id(ws.id);
context.remove(Self::RSA_PRIVATE_KEY);
context.remove(Self::RSA_PUBLIC_KEY);
slog::info!(self.logs, "User registered successfully"; "user_uid" => %user_uid, "username" => &user.username);
Ok(user)
}
}