Can request account deletion
This commit is contained in:
		@@ -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()),
 | 
				
			||||||
                ))
 | 
					                ))
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user