feat(service): expand service layer with new domain operations
- Add IM service modules: audit, channel roles, custom emojis, forum tags, integrations, invitations, repo links, slash commands, stages, voice, webhooks - Add PR service modules: review requests, templates - Add repo service modules: contributors, release assets, git extras (archive, branch rename, commit extras, diff/merge, tag, tree) - Add user service: social (follow/block) - Add internal auth service - Update existing service modules with expanded functionality - Remove deleted IM modules: articles, delivery trace, drafts, follows, messages, polls, presence, reactions, threads
This commit is contained in:
+24
-2
@@ -35,6 +35,7 @@ struct PendingEmailChange {
|
||||
impl AuthService {
|
||||
const EMAIL_CHANGE_PREFIX: &'static str = "auth:email_change:";
|
||||
const EMAIL_CHANGE_TTL_SECS: u64 = 60 * 60;
|
||||
const EMAIL_CHANGE_COOLDOWN_SECS: u64 = 60;
|
||||
|
||||
pub async fn auth_get_email(&self, ctx: &Session) -> Result<EmailResponse, AppError> {
|
||||
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
|
||||
@@ -63,11 +64,20 @@ impl AuthService {
|
||||
if new_email.is_empty() {
|
||||
return Err(AppError::BadRequest("email is required".into()));
|
||||
}
|
||||
|
||||
// Rate limiting: check cooldown
|
||||
let cooldown_key = format!("{}cooldown:{}", Self::EMAIL_CHANGE_PREFIX, user_uid);
|
||||
if self.ctx.cache.exists(&cooldown_key).await {
|
||||
return Err(AppError::BadRequest(
|
||||
"email change request was sent recently; please try again later".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let password = self.auth_rsa_decode(ctx, params.password).await?;
|
||||
|
||||
let row = sqlx::query("SELECT password_hash FROM user_password WHERE user_id = $1")
|
||||
.bind(user_uid)
|
||||
.fetch_optional(self.ctx.db.reader())
|
||||
.fetch_optional(self.ctx.db.writer())
|
||||
.await
|
||||
.map_err(AppError::Database)?
|
||||
.ok_or(AppError::UserNotFound)?;
|
||||
@@ -103,6 +113,7 @@ impl AuthService {
|
||||
},
|
||||
Some(Duration::from_secs(Self::EMAIL_CHANGE_TTL_SECS)),
|
||||
)
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
|
||||
|
||||
let domain = self.ctx.config.main_domain()?;
|
||||
@@ -133,6 +144,16 @@ impl AuthService {
|
||||
AppError::InternalServerError(e.to_string())
|
||||
})?;
|
||||
|
||||
// Set cooldown after successful email send
|
||||
let cooldown_key = format!("{}cooldown:{}", Self::EMAIL_CHANGE_PREFIX, user_uid);
|
||||
if let Err(e) = self.ctx.cache.set(
|
||||
&cooldown_key,
|
||||
&true,
|
||||
Some(Duration::from_secs(Self::EMAIL_CHANGE_COOLDOWN_SECS)),
|
||||
).await {
|
||||
tracing::warn!(error = %e, "Failed to set email change cooldown");
|
||||
}
|
||||
|
||||
tracing::info!(new_email = %new_email, user_uid = %user_uid, "Email change verification sent");
|
||||
Ok(())
|
||||
}
|
||||
@@ -148,6 +169,7 @@ impl AuthService {
|
||||
self.ctx
|
||||
.cache
|
||||
.get::<PendingEmailChange>(&cache_key)
|
||||
.await
|
||||
.ok_or(AppError::NotFound(
|
||||
"invalid or expired email verification token".into(),
|
||||
))?;
|
||||
@@ -195,7 +217,7 @@ impl AuthService {
|
||||
|
||||
txn.commit().await.map_err(|_| AppError::TxnError)?;
|
||||
|
||||
let _ = self.ctx.cache.delete(&cache_key);
|
||||
let _ = self.ctx.cache.delete(&cache_key).await;
|
||||
tracing::info!(new_email = %pending.new_email, user_uid = %pending.user_uid, "Email changed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user