Can request account deletion
This commit is contained in:
		@@ -4,9 +4,11 @@ CREATE TABLE users (
 | 
			
		||||
    name VARCHAR(30) NOT NULL,
 | 
			
		||||
    email VARCHAR(255) NOT NULL,
 | 
			
		||||
    password VARCHAR NULL,
 | 
			
		||||
    reset_password_token VARCHAR(150) NULL,
 | 
			
		||||
    time_create BIGINT NOT NULL,
 | 
			
		||||
    reset_password_token VARCHAR(150) NULL,
 | 
			
		||||
    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,
 | 
			
		||||
    active BOOLEAN NOT NULL DEFAULT TRUE,
 | 
			
		||||
    admin BOOLEAN NOT NULL DEFAULT FALSE
 | 
			
		||||
 
 | 
			
		||||
@@ -88,6 +88,14 @@ pub struct AppConfig {
 | 
			
		||||
    )]
 | 
			
		||||
    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
 | 
			
		||||
    #[arg(
 | 
			
		||||
        long,
 | 
			
		||||
@@ -154,6 +162,11 @@ impl AppConfig {
 | 
			
		||||
        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
 | 
			
		||||
    pub fn openid_providers(&self) -> Vec<OIDCProvider<'_>> {
 | 
			
		||||
        if self.disable_oidc {
 | 
			
		||||
 
 | 
			
		||||
@@ -97,3 +97,19 @@ pub async fn replace_password(
 | 
			
		||||
 | 
			
		||||
    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",
 | 
			
		||||
                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())?
 | 
			
		||||
    .run()
 | 
			
		||||
 
 | 
			
		||||
@@ -12,11 +12,15 @@ pub struct User {
 | 
			
		||||
    pub email: String,
 | 
			
		||||
    #[serde(skip_serializing)]
 | 
			
		||||
    pub password: Option<String>,
 | 
			
		||||
    #[serde(skip_serializing)]
 | 
			
		||||
    pub reset_password_token: Option<String>,
 | 
			
		||||
    pub time_create: i64,
 | 
			
		||||
    #[serde(skip_serializing)]
 | 
			
		||||
    pub reset_password_token: Option<String>,
 | 
			
		||||
    #[serde(skip_serializing)]
 | 
			
		||||
    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 active: bool,
 | 
			
		||||
    pub admin: bool,
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,11 @@ diesel::table! {
 | 
			
		||||
        name -> Varchar,
 | 
			
		||||
        email -> Varchar,
 | 
			
		||||
        password -> Nullable<Varchar>,
 | 
			
		||||
        reset_password_token -> Nullable<Varchar>,
 | 
			
		||||
        time_create -> Int8,
 | 
			
		||||
        reset_password_token -> Nullable<Varchar>,
 | 
			
		||||
        time_gen_reset_token -> Int8,
 | 
			
		||||
        delete_account_token -> Nullable<Varchar>,
 | 
			
		||||
        time_gen_delete_account_token -> Int8,
 | 
			
		||||
        time_activate -> Int8,
 | 
			
		||||
        active -> Bool,
 | 
			
		||||
        admin -> Bool,
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ pub enum RatedAction {
 | 
			
		||||
    FailedPasswordLogin,
 | 
			
		||||
    StartOpenIDLogin,
 | 
			
		||||
    RequestReplacePasswordSignedIn,
 | 
			
		||||
    RequestDeleteAccount,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RatedAction {
 | 
			
		||||
@@ -21,7 +22,8 @@ impl RatedAction {
 | 
			
		||||
            RatedAction::RequestNewPasswordResetLink => "req-pwd-reset-lnk",
 | 
			
		||||
            RatedAction::FailedPasswordLogin => "failed-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::StartOpenIDLogin => 30,
 | 
			
		||||
            RatedAction::RequestReplacePasswordSignedIn => 5,
 | 
			
		||||
            RatedAction::RequestDeleteAccount => 5,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -103,6 +103,37 @@ pub async fn request_reset_password(user: &mut User) -> anyhow::Result<()> {
 | 
			
		||||
    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
 | 
			
		||||
pub async fn delete_not_validated_accounts() -> anyhow::Result<()> {
 | 
			
		||||
    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::time_gen_reset_token.eq(user.time_gen_reset_token),
 | 
			
		||||
                    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::password.eq(user.password.clone()),
 | 
			
		||||
                ))
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user