102 lines
3.5 KiB
Rust
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(¶ms.username)
|
|
.await
|
|
.is_ok();
|
|
let email_exists =
|
|
self.auth_find_user_by_email(¶ms.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(¶ms.username)
|
|
.bind(¶ms.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(¶ms.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)
|
|
}
|
|
}
|