Skip to content

Commit 29cc9dc

Browse files
committed
feat: [#654] remove config option email_on_signup
1 parent 1c5e862 commit 29cc9dc

File tree

10 files changed

+208
-130
lines changed

10 files changed

+208
-130
lines changed

src/config/mod.rs

+80-6
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
pub mod v2;
33
pub mod validator;
44

5-
use std::env;
5+
use std::str::FromStr;
66
use std::sync::Arc;
7+
use std::{env, fmt};
78

89
use camino::Utf8PathBuf;
910
use derive_more::Display;
@@ -22,8 +23,10 @@ pub type Settings = v2::Settings;
2223

2324
pub type Api = v2::api::Api;
2425

26+
pub type Registration = v2::registration::Registration;
27+
pub type Email = v2::registration::Email;
28+
2529
pub type Auth = v2::auth::Auth;
26-
pub type EmailOnSignup = v2::auth::EmailOnSignup;
2730
pub type SecretKey = v2::auth::SecretKey;
2831
pub type PasswordConstraints = v2::auth::PasswordConstraints;
2932

@@ -301,12 +304,26 @@ impl Configuration {
301304
pub async fn get_public(&self) -> ConfigurationPublic {
302305
let settings_lock = self.settings.read().await;
303306

307+
let email_on_signup = match &settings_lock.registration {
308+
Some(registration) => match &registration.email {
309+
Some(email) => {
310+
if email.required {
311+
EmailOnSignup::Required
312+
} else {
313+
EmailOnSignup::Optional
314+
}
315+
}
316+
None => EmailOnSignup::NotIncluded,
317+
},
318+
None => EmailOnSignup::NotIncluded,
319+
};
320+
304321
ConfigurationPublic {
305322
website_name: settings_lock.website.name.clone(),
306323
tracker_url: settings_lock.tracker.url.clone(),
307324
tracker_listed: settings_lock.tracker.listed,
308325
tracker_private: settings_lock.tracker.private,
309-
email_on_signup: settings_lock.auth.email_on_signup.clone(),
326+
email_on_signup,
310327
}
311328
}
312329

@@ -333,12 +350,56 @@ pub struct ConfigurationPublic {
333350
email_on_signup: EmailOnSignup,
334351
}
335352

353+
/// Whether the email is required on signup or not.
354+
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
355+
#[serde(rename_all = "lowercase")]
356+
pub enum EmailOnSignup {
357+
/// The email is required on signup.
358+
Required,
359+
/// The email is optional on signup.
360+
Optional,
361+
/// The email is not allowed on signup. It will only be ignored if provided.
362+
NotIncluded,
363+
}
364+
365+
impl Default for EmailOnSignup {
366+
fn default() -> Self {
367+
Self::Optional
368+
}
369+
}
370+
371+
impl fmt::Display for EmailOnSignup {
372+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373+
let display_str = match self {
374+
EmailOnSignup::Required => "required",
375+
EmailOnSignup::Optional => "optional",
376+
EmailOnSignup::NotIncluded => "ignored",
377+
};
378+
write!(f, "{display_str}")
379+
}
380+
}
381+
382+
impl FromStr for EmailOnSignup {
383+
type Err = String;
384+
385+
fn from_str(s: &str) -> Result<Self, Self::Err> {
386+
match s.to_lowercase().as_str() {
387+
"required" => Ok(EmailOnSignup::Required),
388+
"optional" => Ok(EmailOnSignup::Optional),
389+
"none" => Ok(EmailOnSignup::NotIncluded),
390+
_ => Err(format!(
391+
"Unknown config 'email_on_signup' option (required, optional, none): {s}"
392+
)),
393+
}
394+
}
395+
}
396+
336397
#[cfg(test)]
337398
mod tests {
338399

339400
use url::Url;
340401

341-
use crate::config::{ApiToken, Configuration, ConfigurationPublic, Info, SecretKey, Settings};
402+
use crate::config::{ApiToken, Configuration, ConfigurationPublic, EmailOnSignup, Info, SecretKey, Settings};
342403

343404
#[cfg(test)]
344405
fn default_config_toml() -> String {
@@ -362,7 +423,6 @@ mod tests {
362423
bind_address = "0.0.0.0:3001"
363424
364425
[auth]
365-
email_on_signup = "optional"
366426
secret_key = "MaxVerstappenWC2021"
367427
368428
[auth.password_constraints]
@@ -430,14 +490,28 @@ mod tests {
430490
let configuration = Configuration::default();
431491
let all_settings = configuration.get_all().await;
432492

493+
let email_on_signup = match &all_settings.registration {
494+
Some(registration) => match &registration.email {
495+
Some(email) => {
496+
if email.required {
497+
EmailOnSignup::Required
498+
} else {
499+
EmailOnSignup::Optional
500+
}
501+
}
502+
None => EmailOnSignup::NotIncluded,
503+
},
504+
None => EmailOnSignup::NotIncluded,
505+
};
506+
433507
assert_eq!(
434508
configuration.get_public().await,
435509
ConfigurationPublic {
436510
website_name: all_settings.website.name,
437511
tracker_url: all_settings.tracker.url,
438512
tracker_listed: all_settings.tracker.listed,
439513
tracker_private: all_settings.tracker.private,
440-
email_on_signup: all_settings.auth.email_on_signup,
514+
email_on_signup,
441515
}
442516
);
443517
}

src/config/v2/auth.rs

-54
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
use std::fmt;
2-
use std::str::FromStr;
32

43
use serde::{Deserialize, Serialize};
54

65
/// Authentication options.
76
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
87
pub struct Auth {
9-
/// Whether or not to require an email on signup.
10-
#[serde(default = "Auth::default_email_on_signup")]
11-
pub email_on_signup: EmailOnSignup,
12-
138
/// The secret key used to sign JWT tokens.
149
#[serde(default = "Auth::default_secret_key")]
1510
pub secret_key: SecretKey,
@@ -22,7 +17,6 @@ pub struct Auth {
2217
impl Default for Auth {
2318
fn default() -> Self {
2419
Self {
25-
email_on_signup: EmailOnSignup::default(),
2620
password_constraints: Self::default_password_constraints(),
2721
secret_key: Self::default_secret_key(),
2822
}
@@ -34,10 +28,6 @@ impl Auth {
3428
self.secret_key = SecretKey::new(secret_key);
3529
}
3630

37-
fn default_email_on_signup() -> EmailOnSignup {
38-
EmailOnSignup::default()
39-
}
40-
4131
fn default_secret_key() -> SecretKey {
4232
SecretKey::new("MaxVerstappenWC2021")
4333
}
@@ -47,50 +37,6 @@ impl Auth {
4737
}
4838
}
4939

50-
/// Whether the email is required on signup or not.
51-
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
52-
#[serde(rename_all = "lowercase")]
53-
pub enum EmailOnSignup {
54-
/// The email is required on signup.
55-
Required,
56-
/// The email is optional on signup.
57-
Optional,
58-
/// The email is not allowed on signup. It will only be ignored if provided.
59-
Ignored,
60-
}
61-
62-
impl Default for EmailOnSignup {
63-
fn default() -> Self {
64-
Self::Optional
65-
}
66-
}
67-
68-
impl fmt::Display for EmailOnSignup {
69-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70-
let display_str = match self {
71-
EmailOnSignup::Required => "required",
72-
EmailOnSignup::Optional => "optional",
73-
EmailOnSignup::Ignored => "ignored",
74-
};
75-
write!(f, "{display_str}")
76-
}
77-
}
78-
79-
impl FromStr for EmailOnSignup {
80-
type Err = String;
81-
82-
fn from_str(s: &str) -> Result<Self, Self::Err> {
83-
match s.to_lowercase().as_str() {
84-
"required" => Ok(EmailOnSignup::Required),
85-
"optional" => Ok(EmailOnSignup::Optional),
86-
"none" => Ok(EmailOnSignup::Ignored),
87-
_ => Err(format!(
88-
"Unknown config 'email_on_signup' option (required, optional, ignored): {s}"
89-
)),
90-
}
91-
}
92-
}
93-
9440
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9541
pub struct SecretKey(String);
9642

src/lib.rs

-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@
181181
//! bind_address = "0.0.0.0:3001"
182182
//!
183183
//! [auth]
184-
//! email_on_signup = "optional"
185184
//! secret_key = "MaxVerstappenWC2021"
186185
//!
187186
//! [auth.password_constraints]

src/services/user.rs

+62-58
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use tracing::{debug, info};
1212

1313
use super::authentication::DbUserAuthenticationRepository;
1414
use super::authorization::{self, ACTION};
15-
use crate::config::{Configuration, EmailOnSignup, PasswordConstraints};
15+
use crate::config::{Configuration, PasswordConstraints};
1616
use crate::databases::database::{Database, Error};
1717
use crate::errors::ServiceError;
1818
use crate::mailer;
@@ -74,71 +74,75 @@ impl RegistrationService {
7474
pub async fn register_user(&self, registration_form: &RegistrationForm, api_base_url: &str) -> Result<UserId, ServiceError> {
7575
info!("registering user: {}", registration_form.username);
7676

77-
let Ok(username) = registration_form.username.parse::<Username>() else {
78-
return Err(ServiceError::UsernameInvalid);
79-
};
80-
8177
let settings = self.configuration.settings.read().await;
8278

83-
let opt_email = match settings.auth.email_on_signup {
84-
EmailOnSignup::Required => {
85-
if registration_form.email.is_none() {
86-
return Err(ServiceError::EmailMissing);
79+
match &settings.registration {
80+
Some(registration) => {
81+
let Ok(username) = registration_form.username.parse::<Username>() else {
82+
return Err(ServiceError::UsernameInvalid);
83+
};
84+
85+
let opt_email = match &registration.email {
86+
Some(email) => {
87+
if email.required && registration_form.email.is_none() {
88+
return Err(ServiceError::EmailMissing);
89+
}
90+
registration_form.email.clone()
91+
}
92+
None => None,
93+
};
94+
95+
if let Some(email) = &registration_form.email {
96+
if !validate_email_address(email) {
97+
return Err(ServiceError::EmailInvalid);
98+
}
8799
}
88-
registration_form.email.clone()
89-
}
90-
EmailOnSignup::Ignored => None,
91-
EmailOnSignup::Optional => registration_form.email.clone(),
92-
};
93-
94-
if let Some(email) = &registration_form.email {
95-
if !validate_email_address(email) {
96-
return Err(ServiceError::EmailInvalid);
97-
}
98-
}
99-
100-
let password_constraints = PasswordConstraints {
101-
min_password_length: settings.auth.password_constraints.min_password_length,
102-
max_password_length: settings.auth.password_constraints.max_password_length,
103-
};
104100

105-
validate_password_constraints(
106-
&registration_form.password,
107-
&registration_form.confirm_password,
108-
&password_constraints,
109-
)?;
110-
111-
let password_hash = hash_password(&registration_form.password)?;
112-
113-
let user_id = self
114-
.user_repository
115-
.add(
116-
&username.to_string(),
117-
&opt_email.clone().unwrap_or(no_email()),
118-
&password_hash,
119-
)
120-
.await?;
121-
122-
// If this is the first created account, give administrator rights
123-
if user_id == 1 {
124-
drop(self.user_repository.grant_admin_role(&user_id).await);
125-
}
126-
127-
if settings.mail.email_verification_enabled {
128-
if let Some(email) = opt_email {
129-
let mail_res = self
130-
.mailer
131-
.send_verification_mail(&email, &registration_form.username, user_id, api_base_url)
132-
.await;
101+
let password_constraints = PasswordConstraints {
102+
min_password_length: settings.auth.password_constraints.min_password_length,
103+
max_password_length: settings.auth.password_constraints.max_password_length,
104+
};
105+
106+
validate_password_constraints(
107+
&registration_form.password,
108+
&registration_form.confirm_password,
109+
&password_constraints,
110+
)?;
111+
112+
let password_hash = hash_password(&registration_form.password)?;
113+
114+
let user_id = self
115+
.user_repository
116+
.add(
117+
&username.to_string(),
118+
&opt_email.clone().unwrap_or(no_email()),
119+
&password_hash,
120+
)
121+
.await?;
122+
123+
// If this is the first created account, give administrator rights
124+
if user_id == 1 {
125+
drop(self.user_repository.grant_admin_role(&user_id).await);
126+
}
133127

134-
if mail_res.is_err() {
135-
drop(self.user_repository.delete(&user_id).await);
136-
return Err(ServiceError::FailedToSendVerificationEmail);
128+
if settings.mail.email_verification_enabled {
129+
if let Some(email) = opt_email {
130+
let mail_res = self
131+
.mailer
132+
.send_verification_mail(&email, &registration_form.username, user_id, api_base_url)
133+
.await;
134+
135+
if mail_res.is_err() {
136+
drop(self.user_repository.delete(&user_id).await);
137+
return Err(ServiceError::FailedToSendVerificationEmail);
138+
}
139+
}
137140
}
141+
142+
Ok(user_id)
138143
}
144+
None => Err(ServiceError::ClosedForRegistration),
139145
}
140-
141-
Ok(user_id)
142146
}
143147

144148
/// It verifies the email address of a user via the token sent to the

src/web/api/client/v1/contexts/settings/mod.rs

-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ pub struct Network {
4949

5050
#[derive(Deserialize, Serialize, PartialEq, Debug, Clone)]
5151
pub struct Auth {
52-
pub email_on_signup: String,
5352
pub secret_key: String,
5453
pub password_constraints: PasswordConstraints,
5554
}
@@ -154,7 +153,6 @@ impl From<DomainNetwork> for Network {
154153
impl From<DomainAuth> for Auth {
155154
fn from(auth: DomainAuth) -> Self {
156155
Self {
157-
email_on_signup: format!("{:?}", auth.email_on_signup),
158156
secret_key: auth.secret_key.to_string(),
159157
password_constraints: auth.password_constraints.into(),
160158
}

0 commit comments

Comments
 (0)