gitdataai/lib/service/auth/register.rs
2026-06-01 22:04:38 +08:00

102 lines
3.5 KiB
Rust

use argon2::{Argon2, password_hash::PasswordHasher};
use db::sqlx;
use model::users::UserModel;
use serde::{Deserialize, Serialize};
use session::Session;
use crate::{AppService, error::AppError};
#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct RegisterParams {
pub username: String,
pub email: String,
pub password: String,
pub captcha: String,
}
impl AppService {
#[tracing::instrument(skip(self, params, context), fields(username = %params.username))]
pub async fn auth_register(
&self,
params: RegisterParams,
context: &Session,
) -> Result<UserModel, AppError> {
self.auth_check_captcha(context, params.captcha).await?;
let password = self.auth_rsa_decode(context, params.password).await?;
Self::validate_password_strength(&password)?;
let username_exists = self
.auth_find_user_by_username(&params.username)
.await
.is_ok();
let email_exists =
self.auth_find_user_by_email(&params.email).await.is_ok();
if username_exists || email_exists {
self.metrics
.auth_register_total
.with_label_values(&["already_exists"])
.inc();
return Err(AppError::AccountAlreadyExists);
}
let user_id = uuid::Uuid::now_v7();
let now = chrono::Utc::now();
let password_hash = Argon2::default()
.hash_password(password.as_bytes())
.map_err(|e| AppError::PasswordHashError(e.to_string()))?
.to_string();
let mut txn = self.db.begin().await.map_err(|_| AppError::TxnError)?;
let user = sqlx::query_as::<_, UserModel>(
"INSERT INTO \"user\" \
(id, username, display_name, avatar_url, website_url, allow_use, can_search, \
last_sign_in_at, created_at, updated_at) \
VALUES ($1, $2, $3, '', '', true, true, NULL, $4, $4) \
RETURNING id, username, display_name, avatar_url, website_url, allow_use, can_search, \
last_sign_in_at, created_at, updated_at",
)
.bind(user_id)
.bind(&params.username)
.bind(&params.username)
.bind(now)
.fetch_one(&mut **txn.inner_mut())
.await
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
sqlx::query(
"INSERT INTO user_email (\"user\", email, created_at, active, last_use_login, updated_at) \
VALUES ($1, $2, $3, true, NULL, $3)",
)
.bind(user_id)
.bind(&params.email)
.bind(now)
.execute(&mut **txn.inner_mut())
.await
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
sqlx::query(
"INSERT INTO user_password (\"user\", hash, salt, is_active, reason, created_at, updated_at) \
VALUES ($1, $2, '', true, NULL, $3, $3)",
)
.bind(user_id)
.bind(&password_hash)
.bind(now)
.execute(&mut **txn.inner_mut())
.await
.map_err(|e| AppError::DatabaseError(e.to_string()))?;
txn.commit().await.map_err(|_| AppError::TxnError)?;
context.set_user(user_id);
context.remove(Self::RSA_PRIVATE_KEY);
context.remove(Self::RSA_PUBLIC_KEY);
tracing::info!(user_uid = %user_id, username = %user.username, "User registered successfully");
self.metrics
.auth_register_total
.with_label_values(&["success"])
.inc();
Ok(user)
}
}