mirror of
https://github.com/telemt/telemt.git
synced 2026-10-08 18:35:58 +03:00
138 lines
4.5 KiB
Rust
138 lines
4.5 KiB
Rust
use super::*;
|
|
use tracing::warn;
|
|
|
|
pub(in crate::api) async fn rotate_secret(
|
|
user: &str,
|
|
body: RotateSecretRequest,
|
|
expected_revision: Option<String>,
|
|
shared: &ApiShared,
|
|
) -> Result<(CreateUserResponse, String), ApiFailure> {
|
|
let secret = body.secret.unwrap_or_else(random_user_secret);
|
|
if !is_valid_user_secret(&secret) {
|
|
return Err(ApiFailure::bad_request(
|
|
"secret must be exactly 32 hex characters",
|
|
));
|
|
}
|
|
|
|
let _guard = shared.mutation_lock.lock().await;
|
|
let mut cfg = load_config_from_disk(&shared.config_path).await?;
|
|
ensure_expected_revision(&shared.config_path, expected_revision.as_deref()).await?;
|
|
|
|
if !cfg.access.users.contains_key(user) {
|
|
return Err(ApiFailure::new(
|
|
StatusCode::NOT_FOUND,
|
|
"not_found",
|
|
"User not found",
|
|
));
|
|
}
|
|
|
|
cfg.access.users.insert(user.to_string(), secret.clone());
|
|
cfg.validate()
|
|
.map_err(|e| ApiFailure::bad_request(format!("config validation failed: {}", e)))?;
|
|
let revision =
|
|
save_access_sections_to_disk(&shared.config_path, &cfg, &[AccessSection::Users]).await?;
|
|
shared
|
|
.proxy_shared
|
|
.stage_user(user, &secret, cfg.access.is_user_enabled(user))
|
|
.ok_or_else(|| ApiFailure::internal("failed to stage rotated user credential"))?;
|
|
drop(_guard);
|
|
|
|
let (detected_ip_v4, detected_ip_v6) = shared.detected_link_ips();
|
|
let users = users_from_config(
|
|
&cfg,
|
|
&shared.stats,
|
|
&shared.ip_tracker,
|
|
detected_ip_v4,
|
|
detected_ip_v6,
|
|
None,
|
|
)
|
|
.await;
|
|
let user_info = users
|
|
.into_iter()
|
|
.find(|entry| entry.username == user)
|
|
.ok_or_else(|| ApiFailure::internal("failed to build updated user view"))?;
|
|
|
|
Ok((
|
|
CreateUserResponse {
|
|
user: user_info,
|
|
secret,
|
|
},
|
|
revision,
|
|
))
|
|
}
|
|
|
|
pub(in crate::api) async fn delete_user(
|
|
user: &str,
|
|
expected_revision: Option<String>,
|
|
shared: &ApiShared,
|
|
) -> Result<(String, String), ApiFailure> {
|
|
let _guard = shared.mutation_lock.lock().await;
|
|
let mut cfg = load_config_from_disk(&shared.config_path).await?;
|
|
ensure_expected_revision(&shared.config_path, expected_revision.as_deref()).await?;
|
|
|
|
if !cfg.access.users.contains_key(user) {
|
|
return Err(ApiFailure::new(
|
|
StatusCode::NOT_FOUND,
|
|
"not_found",
|
|
"User not found",
|
|
));
|
|
}
|
|
if cfg.access.users.len() <= 1 {
|
|
return Err(ApiFailure::new(
|
|
StatusCode::CONFLICT,
|
|
"last_user_forbidden",
|
|
"Cannot delete the last configured user",
|
|
));
|
|
}
|
|
|
|
let mut touched_sections = vec![AccessSection::Users];
|
|
cfg.access.users.remove(user);
|
|
if cfg.access.user_enabled.remove(user).is_some() {
|
|
touched_sections.push(AccessSection::UserEnabled);
|
|
}
|
|
if cfg.access.user_ad_tags.remove(user).is_some() {
|
|
touched_sections.push(AccessSection::UserAdTags);
|
|
}
|
|
if cfg.access.user_max_tcp_conns.remove(user).is_some() {
|
|
touched_sections.push(AccessSection::UserMaxTcpConns);
|
|
}
|
|
if cfg.access.user_expirations.remove(user).is_some() {
|
|
touched_sections.push(AccessSection::UserExpirations);
|
|
}
|
|
if cfg.access.user_data_quota.remove(user).is_some() {
|
|
touched_sections.push(AccessSection::UserDataQuota);
|
|
}
|
|
if cfg.access.user_rate_limits.remove(user).is_some() {
|
|
touched_sections.push(AccessSection::UserRateLimits);
|
|
}
|
|
if cfg.access.user_max_unique_ips.remove(user).is_some() {
|
|
touched_sections.push(AccessSection::UserMaxUniqueIps);
|
|
}
|
|
|
|
cfg.validate()
|
|
.map_err(|e| ApiFailure::bad_request(format!("config validation failed: {}", e)))?;
|
|
let revision =
|
|
save_access_sections_to_disk(&shared.config_path, &cfg, &touched_sections).await?;
|
|
let deleted_incarnation = shared.proxy_shared.delete_user(user).incarnation;
|
|
let configured_users = cfg.access.users.keys().cloned().collect();
|
|
if let Err(error) = shared
|
|
.quota_state
|
|
.remove_user(&configured_users, user)
|
|
.await
|
|
{
|
|
warn!(
|
|
user,
|
|
error = %error,
|
|
"Deleted user quota checkpoint cleanup will be reconciled on restart"
|
|
);
|
|
}
|
|
shared.ip_tracker.remove_user_limit(user).await;
|
|
shared
|
|
.ip_tracker
|
|
.clear_user_ips_if_not_newer(user, deleted_incarnation)
|
|
.await;
|
|
drop(_guard);
|
|
|
|
Ok((user.to_string(), revision))
|
|
}
|