Can request account deletion

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

View File

@ -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

View File

@ -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 {

View File

@ -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())
}

View File

@ -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()

View File

@ -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,

View File

@ -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,

View File

@ -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,
} }
} }

View File

@ -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()),
)) ))