Can request account deletion

This commit is contained in:
2023-06-06 09:47:52 +02:00
parent 1a8211c13d
commit 4b8baa2416
8 changed files with 83 additions and 5 deletions
geneit_backend

@ -4,9 +4,11 @@ CREATE TABLE users (
name VARCHAR(30) NOT NULL, name VARCHAR(30) NOT NULL,
email VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL,
password VARCHAR NULL, password VARCHAR NULL,
reset_password_token VARCHAR(150) NULL,
time_create BIGINT NOT NULL, time_create BIGINT NOT NULL,
reset_password_token VARCHAR(150) NULL,
time_gen_reset_token BIGINT NOT NULL DEFAULT 0, time_gen_reset_token BIGINT NOT NULL DEFAULT 0,
delete_account_token VARCHAR(150) NULL,
time_gen_delete_account_token BIGINT NOT NULL DEFAULT 0,
time_activate BIGINT NOT NULL DEFAULT 0, time_activate BIGINT NOT NULL DEFAULT 0,
active BOOLEAN NOT NULL DEFAULT TRUE, active BOOLEAN NOT NULL DEFAULT TRUE,
admin BOOLEAN NOT NULL DEFAULT FALSE admin BOOLEAN NOT NULL DEFAULT FALSE

@ -88,6 +88,14 @@ pub struct AppConfig {
)] )]
pub reset_password_url: String, pub reset_password_url: String,
/// Delete account URL
#[clap(
long,
env,
default_value = "http://localhost:3000/delete_account#TOKEN"
)]
pub delete_account_url: String,
/// URL where the OpenID configuration can be found /// URL where the OpenID configuration can be found
#[arg( #[arg(
long, long,
@ -154,6 +162,11 @@ impl AppConfig {
self.reset_password_url.replace("TOKEN", token) self.reset_password_url.replace("TOKEN", token)
} }
/// Get account delete URL
pub fn get_account_delete_url(&self, token: &str) -> String {
self.delete_account_url.replace("TOKEN", token)
}
/// Get OpenID providers configuration /// Get OpenID providers configuration
pub fn openid_providers(&self) -> Vec<OIDCProvider<'_>> { pub fn openid_providers(&self) -> Vec<OIDCProvider<'_>> {
if self.disable_oidc { if self.disable_oidc {

@ -97,3 +97,19 @@ pub async fn replace_password(
Ok(HttpResponse::Accepted().finish()) Ok(HttpResponse::Accepted().finish())
} }
/// Request delete account
pub async fn request_delete_account(remote_ip: RemoteIP, token: LoginToken) -> HttpResult {
// Rate limiting
if rate_limiter_service::should_block_action(remote_ip.0, RatedAction::RequestDeleteAccount)
.await?
{
return Ok(HttpResponse::TooManyRequests().finish());
}
rate_limiter_service::record_action(remote_ip.0, RatedAction::RequestDeleteAccount).await?;
let mut user = users_service::get_by_id(token.user_id).await?;
users_service::request_delete_account(&mut user).await?;
Ok(HttpResponse::Accepted().finish())
}

@ -62,6 +62,10 @@ async fn main() -> std::io::Result<()> {
"/user/replace_password", "/user/replace_password",
web::post().to(user_controller::replace_password), web::post().to(user_controller::replace_password),
) )
.route(
"/user/request_delete",
web::get().to(user_controller::request_delete_account),
)
}) })
.bind(AppConfig::get().listen_address.as_str())? .bind(AppConfig::get().listen_address.as_str())?
.run() .run()

@ -12,11 +12,15 @@ pub struct User {
pub email: String, pub email: String,
#[serde(skip_serializing)] #[serde(skip_serializing)]
pub password: Option<String>, pub password: Option<String>,
#[serde(skip_serializing)]
pub reset_password_token: Option<String>,
pub time_create: i64, pub time_create: i64,
#[serde(skip_serializing)] #[serde(skip_serializing)]
pub reset_password_token: Option<String>,
#[serde(skip_serializing)]
pub time_gen_reset_token: i64, pub time_gen_reset_token: i64,
#[serde(skip_serializing)]
pub delete_account_token: Option<String>,
#[serde(skip_serializing)]
pub time_gen_delete_account_token: i64,
pub time_activate: i64, pub time_activate: i64,
pub active: bool, pub active: bool,
pub admin: bool, pub admin: bool,

@ -6,9 +6,11 @@ diesel::table! {
name -> Varchar, name -> Varchar,
email -> Varchar, email -> Varchar,
password -> Nullable<Varchar>, password -> Nullable<Varchar>,
reset_password_token -> Nullable<Varchar>,
time_create -> Int8, time_create -> Int8,
reset_password_token -> Nullable<Varchar>,
time_gen_reset_token -> Int8, time_gen_reset_token -> Int8,
delete_account_token -> Nullable<Varchar>,
time_gen_delete_account_token -> Int8,
time_activate -> Int8, time_activate -> Int8,
active -> Bool, active -> Bool,
admin -> Bool, admin -> Bool,

@ -11,6 +11,7 @@ pub enum RatedAction {
FailedPasswordLogin, FailedPasswordLogin,
StartOpenIDLogin, StartOpenIDLogin,
RequestReplacePasswordSignedIn, RequestReplacePasswordSignedIn,
RequestDeleteAccount,
} }
impl RatedAction { impl RatedAction {
@ -21,7 +22,8 @@ impl RatedAction {
RatedAction::RequestNewPasswordResetLink => "req-pwd-reset-lnk", RatedAction::RequestNewPasswordResetLink => "req-pwd-reset-lnk",
RatedAction::FailedPasswordLogin => "failed-login", RatedAction::FailedPasswordLogin => "failed-login",
RatedAction::StartOpenIDLogin => "start-oidc-login", RatedAction::StartOpenIDLogin => "start-oidc-login",
RatedAction::RequestReplacePasswordSignedIn => "rep-pwd-signed-in", RatedAction::RequestReplacePasswordSignedIn => "req-pwd-signed-in",
RatedAction::RequestDeleteAccount => "req-del-acct",
} }
} }
@ -33,6 +35,7 @@ impl RatedAction {
RatedAction::FailedPasswordLogin => 15, RatedAction::FailedPasswordLogin => 15,
RatedAction::StartOpenIDLogin => 30, RatedAction::StartOpenIDLogin => 30,
RatedAction::RequestReplacePasswordSignedIn => 5, RatedAction::RequestReplacePasswordSignedIn => 5,
RatedAction::RequestDeleteAccount => 5,
} }
} }

@ -103,6 +103,37 @@ pub async fn request_reset_password(user: &mut User) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
/// Request delete account
pub async fn request_delete_account(user: &mut User) -> anyhow::Result<()> {
// If required, regenerate token
if user.delete_account_token.is_none()
|| user.time_gen_delete_account_token as u64 + 3600 * 2 < time()
{
user.delete_account_token = Some(rand_str(149));
user.time_gen_delete_account_token = time() as i64;
update_account(user).await?;
}
// Send mail
mail_service::send_mail(
&user.email,
"Suppression de votre compte",
format!(
"Bonjour, \n\n\
Vous avez demandé la suppression de votre compte GeneIT. Cette opération peut être effectuée via le lien suivant : {} \n\n\
Ce lien est valide durant 24 heures.\n\n\
Cordialement,\n\n\
L'équipe de GeneIT",
AppConfig::get()
.get_account_delete_url(user.delete_account_token.as_deref().unwrap_or(""))
),
)
.await?;
Ok(())
}
/// Delete not validated accounts whose reset token has expired /// Delete not validated accounts whose reset token has expired
pub async fn delete_not_validated_accounts() -> anyhow::Result<()> { pub async fn delete_not_validated_accounts() -> anyhow::Result<()> {
db_connection::execute(|conn| { db_connection::execute(|conn| {
@ -158,6 +189,9 @@ pub async fn update_account(user: &User) -> anyhow::Result<()> {
users::dsl::email.eq(user.email.clone()), users::dsl::email.eq(user.email.clone()),
users::dsl::time_gen_reset_token.eq(user.time_gen_reset_token), users::dsl::time_gen_reset_token.eq(user.time_gen_reset_token),
users::dsl::reset_password_token.eq(user.reset_password_token.clone()), users::dsl::reset_password_token.eq(user.reset_password_token.clone()),
users::dsl::time_gen_delete_account_token
.eq(user.time_gen_delete_account_token),
users::dsl::delete_account_token.eq(user.delete_account_token.clone()),
users::dsl::time_activate.eq(time() as i64), users::dsl::time_activate.eq(time() as i64),
users::dsl::password.eq(user.password.clone()), users::dsl::password.eq(user.password.clone()),
)) ))