Generate reset password URL

This commit is contained in:
Pierre HUBERT 2023-05-30 15:12:58 +02:00
parent c84c2ef3c5
commit 62a52b385e
10 changed files with 418 additions and 16 deletions

View File

@ -467,6 +467,22 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.7" version = "0.2.7"
@ -543,6 +559,22 @@ dependencies = [
"crypto-common", "crypto-common",
] ]
[[package]]
name = "email-encoding"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbfb21b9878cf7a348dcb8559109aabc0ec40d69924bd706fa5149846c4fef75"
dependencies = [
"base64",
"memchr",
]
[[package]]
name = "email_address"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.32" version = "0.8.32"
@ -595,6 +627,15 @@ dependencies = [
"ascii_utils", "ascii_utils",
] ]
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.26" version = "1.0.26"
@ -611,6 +652,21 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.1.0" version = "1.1.0"
@ -626,6 +682,12 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-io"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.28" version = "0.3.28"
@ -656,8 +718,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-io",
"futures-macro", "futures-macro",
"futures-task", "futures-task",
"memchr",
"pin-project-lite", "pin-project-lite",
"pin-utils", "pin-utils",
"slab", "slab",
@ -674,8 +738,10 @@ dependencies = [
"diesel", "diesel",
"env_logger", "env_logger",
"lazy_static", "lazy_static",
"lettre",
"log", "log",
"mailchecker", "mailchecker",
"rand",
"redis", "redis",
"serde", "serde",
"serde_json", "serde_json",
@ -748,6 +814,17 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "hostname"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
dependencies = [
"libc",
"match_cfg",
"winapi",
]
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.9" version = "0.2.9"
@ -797,6 +874,15 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "io-lifetimes" name = "io-lifetimes"
version = "1.0.10" version = "1.0.10"
@ -847,6 +933,29 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lettre"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76bd09637ae3ec7bd605b8e135e757980b3968430ff2b1a4a94fb7769e50166d"
dependencies = [
"base64",
"email-encoding",
"email_address",
"fastrand",
"futures-util",
"hostname",
"httpdate",
"idna",
"mime",
"native-tls",
"nom",
"once_cell",
"quoted_printable",
"socket2",
"tokio",
]
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.144" version = "0.2.144"
@ -906,6 +1015,12 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "match_cfg"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.5.0"
@ -918,6 +1033,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.7.1" version = "0.7.1"
@ -939,6 +1060,34 @@ dependencies = [
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
[[package]]
name = "native-tls"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]] [[package]]
name = "num_cpus" name = "num_cpus"
version = "1.15.0" version = "1.15.0"
@ -955,6 +1104,50 @@ version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "openssl"
version = "0.10.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12df40a956736488b7b44fe79fe12d4f245bb5b3f5a1f6095e499760015be392"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.16",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.1" version = "0.12.1"
@ -973,7 +1166,7 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"redox_syscall", "redox_syscall 0.2.16",
"smallvec", "smallvec",
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
@ -1065,6 +1258,12 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "quoted_printable"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3866219251662ec3b26fc217e3e05bf9c4f84325234dfb96bf0bf840889e49"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@ -1118,6 +1317,15 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.8.2" version = "1.8.2"
@ -1164,12 +1372,44 @@ version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "schannel"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
dependencies = [
"windows-sys 0.42.0",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.1.0" version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "security-framework"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.17" version = "1.0.17"
@ -1298,6 +1538,19 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "tempfile"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
dependencies = [
"cfg-if",
"fastrand",
"redox_syscall 0.3.5",
"rustix",
"windows-sys 0.45.0",
]
[[package]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.2.0" version = "1.2.0"
@ -1494,6 +1747,21 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.45.0" version = "0.45.0"

View File

@ -18,3 +18,5 @@ serde_json = "1.0.96"
actix-remote-ip = "0.1.0" actix-remote-ip = "0.1.0"
mailchecker = "5.0.9" mailchecker = "5.0.9"
redis = "0.23.0" redis = "0.23.0"
lettre = "0.10.4"
rand = "0.8.5"

View File

@ -55,6 +55,38 @@ pub struct AppConfig {
/// Redis password /// Redis password
#[clap(long, env, default_value = "secretredis")] #[clap(long, env, default_value = "secretredis")]
redis_password: String, redis_password: String,
/// Mail sender
#[clap(long, env, default_value = "geneit@example.com")]
pub mail_sender: String,
/// SMTP relay
#[clap(long, env, default_value = "localhost")]
pub smtp_relay: String,
/// SMTP port
#[clap(long, env, default_value_t = 1025)]
pub smtp_port: u16,
/// SMTP use TLS to connect to relay
#[clap(long, env)]
pub smtp_tls: bool,
/// SMTP username
#[clap(long, env)]
pub smtp_username: Option<String>,
/// SMTP password
#[clap(long, env)]
pub smtp_password: Option<String>,
/// Password reset URL
#[clap(
long,
env,
default_value = "http://localhost:3000/reset_password#TOKEN"
)]
pub reset_password_url: String,
} }
lazy_static::lazy_static! { lazy_static::lazy_static! {
@ -88,4 +120,9 @@ impl AppConfig {
}, },
} }
} }
/// Get password reset URL
pub fn get_password_reset_url(&self, token: &str) -> String {
self.reset_password_url.replace("TOKEN", token)
}
} }

View File

@ -39,9 +39,12 @@ pub async fn create_account(remote_ip: RemoteIP, req: web::Json<CreateAccountBod
} }
// Create the account // Create the account
let user_id = users_service::create_account(&req.name, &req.email).await?; let mut user = users_service::create_account(&req.name, &req.email).await?;
// TODO : trigger reset password (send mail) // Trigger reset password (send mail)
users_service::request_reset_password(&mut user).await?;
// TODO : cleanup in a cron not validated accounts after 24 hours
// Account successfully created // Account successfully created
Ok(HttpResponse::Created().finish()) Ok(HttpResponse::Created().finish())

View File

@ -3,20 +3,20 @@ use diesel::prelude::*;
/// User ID holder /// User ID holder
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct UserID(i32); pub struct UserID(pub i32);
#[derive(Queryable, Debug)] #[derive(Queryable, Debug)]
pub struct User { pub struct User {
id: i32, pub id: i32,
name: String, pub name: String,
email: String, pub email: String,
password: Option<String>, pub password: Option<String>,
reset_password_token: Option<String>, pub reset_password_token: Option<String>,
time_create: i64, pub time_create: i64,
time_gen_reset_token: i64, pub time_gen_reset_token: i64,
time_activate: i64, pub time_activate: i64,
active: bool, pub active: bool,
admin: bool, pub admin: bool,
} }
impl User { impl User {

View File

@ -0,0 +1,33 @@
use crate::app_config::AppConfig;
use lettre::message::header::ContentType;
use lettre::transport::smtp::authentication::Credentials;
use lettre::{Message, SmtpTransport, Transport};
use std::fmt::Display;
pub async fn send_mail<D: Display>(to: &str, subject: &str, body: D) -> anyhow::Result<()> {
let conf = AppConfig::get();
let email = Message::builder()
.from(conf.mail_sender.parse()?)
.to(to.parse()?)
.subject(subject)
.header(ContentType::TEXT_PLAIN)
.body(body.to_string())?;
let mut mailer = match conf.smtp_tls {
true => SmtpTransport::relay(&conf.smtp_relay)?,
false => SmtpTransport::builder_dangerous(&conf.smtp_relay),
}
.port(conf.smtp_port);
if let (Some(username), Some(password)) = (&conf.smtp_username, &conf.smtp_password) {
mailer = mailer.credentials(Credentials::new(username.to_string(), password.to_string()))
}
let mailer = mailer.build();
mailer.send(&email)?;
log::debug!("A mail was sent to {} (subject = {})", to, subject);
Ok(())
}

View File

@ -1,4 +1,5 @@
//! # Backend services //! # Backend services
pub mod mail_service;
pub mod rate_limiter_service; pub mod rate_limiter_service;
pub mod users_service; pub mod users_service;

View File

@ -1,11 +1,19 @@
//! # Users service //! # Users service
use crate::app_config::AppConfig;
use crate::connections::db_connection; use crate::connections::db_connection;
use crate::models::{NewUser, User}; use crate::models::{NewUser, User, UserID};
use crate::schema::users; use crate::schema::users;
use crate::services::mail_service;
use crate::utils::string_utils::rand_str;
use crate::utils::time_utils::time; use crate::utils::time_utils::time;
use diesel::prelude::*; use diesel::prelude::*;
/// Get the information of the user
pub async fn get_by_id(id: UserID) -> anyhow::Result<User> {
db_connection::execute(|conn| Ok(users::table.filter(users::dsl::id.eq(id.0)).first(conn)?))
}
/// Create a new account /// Create a new account
pub async fn create_account(name: &str, email: &str) -> anyhow::Result<User> { pub async fn create_account(name: &str, email: &str) -> anyhow::Result<User> {
db_connection::execute(|conn| { db_connection::execute(|conn| {
@ -32,3 +40,41 @@ pub async fn exists_email(email: &str) -> anyhow::Result<bool> {
Ok(count != 0) Ok(count != 0)
}) })
} }
/// Request password reset
pub async fn request_reset_password(user: &mut User) -> anyhow::Result<()> {
// If required, regenerate reset token
if user.reset_password_token.is_none() || user.time_gen_reset_token as u64 + 3600 * 2 < time() {
user.reset_password_token = Some(rand_str(149));
user.time_gen_reset_token = time() as i64;
db_connection::execute(|conn| {
Ok(
diesel::update(users::dsl::users.filter(users::dsl::id.eq(user.id)))
.set((
users::dsl::time_gen_reset_token.eq(user.time_gen_reset_token),
users::dsl::reset_password_token.eq(user.reset_password_token.clone()),
))
.execute(conn)?,
)
})?;
}
// Send mail
mail_service::send_mail(
&user.email,
"Réinitialisation de votre mot de passe",
format!(
"Bonjour, \n\n\
Vous pouvez réinitialiser le mot de passe de votre compte à l'adresse suivante : {} \n\n\
Ce lien est valide durant 24 heures.\n\n\
Cordialement,\n\n\
L'équipe de GeneIT",
AppConfig::get()
.get_password_reset_url(user.reset_password_token.as_deref().unwrap_or(""))
),
)
.await?;
Ok(())
}

View File

@ -1,3 +1,4 @@
//! # App utilities //! # App utilities
pub mod string_utils;
pub mod time_utils; pub mod time_utils;

View File

@ -0,0 +1,11 @@
use rand::distributions::Alphanumeric;
use rand::Rng;
/// Generate a random string of a given size
pub fn rand_str(len: usize) -> String {
rand::thread_rng()
.sample_iter(&Alphanumeric)
.map(char::from)
.take(len)
.collect()
}