Skip to content

Commit 522c472

Browse files
authored
Merge pull request #3784 from element-hq/quenting/optional-email
2 parents a8e6c68 + 8d50088 commit 522c472

File tree

129 files changed

+5213
-3473
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

129 files changed

+5213
-3473
lines changed

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"frontend/src/gql/**",
1919
"frontend/src/routeTree.gen.ts",
2020
"frontend/.storybook/locales.ts",
21-
"frontend/.storybook/mockServiceWorker.js",
21+
"frontend/.storybook/public/mockServiceWorker.js",
2222
"frontend/locales/*.json",
2323
"**/coverage/**",
2424
"**/dist/**"

crates/axum-utils/src/cookies.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,13 @@ impl CookieJar {
138138
self
139139
}
140140

141+
/// Remove a cookie from the jar
142+
#[must_use]
143+
pub fn remove(mut self, key: &str) -> Self {
144+
self.inner = self.inner.remove(key.to_owned());
145+
self
146+
}
147+
141148
/// Load and deserialize a cookie from the jar
142149
///
143150
/// Returns `None` if the cookie is not present

crates/cli/src/commands/manage.rs

Lines changed: 4 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ enum Subcommand {
6565
/// Add an email address to the specified user
6666
AddEmail { username: String, email: String },
6767

68-
/// Mark email address as verified
68+
/// [DEPRECATED] Mark email address as verified
6969
VerifyEmail { username: String, email: String },
7070

7171
/// Set a user password
@@ -252,15 +252,8 @@ impl Options {
252252
.await?
253253
};
254254

255-
let email = repo.user_email().mark_as_verified(&clock, email).await?;
256-
257-
// If the user has no primary email, set this one as primary.
258-
if user.primary_user_email_id.is_none() {
259-
repo.user_email().set_as_primary(&email).await?;
260-
}
261-
262255
repo.into_inner().commit().await?;
263-
info!(?email, "Email added and marked as verified");
256+
info!(?email, "Email added");
264257

265258
Ok(ExitCode::SUCCESS)
266259
}
@@ -273,31 +266,7 @@ impl Options {
273266
)
274267
.entered();
275268

276-
let database_config = DatabaseConfig::extract_or_default(figment)?;
277-
let mut conn = database_connection_from_config(&database_config).await?;
278-
let txn = conn.begin().await?;
279-
let mut repo = PgRepository::from_conn(txn);
280-
281-
let user = repo
282-
.user()
283-
.find_by_username(&username)
284-
.await?
285-
.context("User not found")?;
286-
287-
let email = repo
288-
.user_email()
289-
.find(&user, &email)
290-
.await?
291-
.context("Email not found")?;
292-
let email = repo.user_email().mark_as_verified(&clock, email).await?;
293-
294-
// If the user has no primary email, set this one as primary.
295-
if user.primary_user_email_id.is_none() {
296-
repo.user_email().set_as_primary(&email).await?;
297-
}
298-
299-
repo.into_inner().commit().await?;
300-
info!(?email, "Email marked as verified");
269+
tracing::warn!("The 'verify-email' command is deprecated and will be removed in a future version. Use 'add-email' instead.");
301270

302271
Ok(ExitCode::SUCCESS)
303272
}
@@ -943,20 +912,9 @@ impl UserCreationRequest<'_> {
943912
}
944913

945914
for email in emails {
946-
let user_email = repo
947-
.user_email()
915+
repo.user_email()
948916
.add(rng, clock, &user, email.to_string())
949917
.await?;
950-
951-
let user_email = repo
952-
.user_email()
953-
.mark_as_verified(clock, user_email)
954-
.await?;
955-
956-
if user.primary_user_email_id.is_none() {
957-
repo.user_email().set_as_primary(&user_email).await?;
958-
user.primary_user_email_id = Some(user_email.id);
959-
}
960918
}
961919

962920
for (provider, subject) in upstream_provider_mappings {

crates/cli/src/sync.rs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,17 +56,6 @@ fn map_claims_imports(
5656
action: map_import_action(config.email.action),
5757
template: config.email.template.clone(),
5858
},
59-
verify_email: match config.email.set_email_verification {
60-
mas_config::UpstreamOAuth2SetEmailVerification::Always => {
61-
mas_data_model::UpsreamOAuthProviderSetEmailVerification::Always
62-
}
63-
mas_config::UpstreamOAuth2SetEmailVerification::Never => {
64-
mas_data_model::UpsreamOAuthProviderSetEmailVerification::Never
65-
}
66-
mas_config::UpstreamOAuth2SetEmailVerification::Import => {
67-
mas_data_model::UpsreamOAuthProviderSetEmailVerification::Import
68-
}
69-
},
7059
account_name: mas_data_model::UpstreamOAuthProviderSubjectPreference {
7160
template: config.account_name.template.clone(),
7261
},

crates/config/src/sections/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ pub use self::{
5252
EmailImportPreference as UpstreamOAuth2EmailImportPreference,
5353
ImportAction as UpstreamOAuth2ImportAction, PkceMethod as UpstreamOAuth2PkceMethod,
5454
ResponseMode as UpstreamOAuth2ResponseMode,
55-
SetEmailVerification as UpstreamOAuth2SetEmailVerification,
5655
TokenAuthMethod as UpstreamOAuth2TokenAuthMethod, UpstreamOAuth2Config,
5756
},
5857
};

crates/config/src/sections/rate_limiting.rs

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 New Vector Ltd.
1+
// Copyright 2024, 2025 New Vector Ltd.
22
// Copyright 2024 The Matrix.org Foundation C.I.C.
33
//
44
// SPDX-License-Identifier: AGPL-3.0-only
@@ -18,13 +18,19 @@ pub struct RateLimitingConfig {
1818
/// Account Recovery-specific rate limits
1919
#[serde(default)]
2020
pub account_recovery: AccountRecoveryRateLimitingConfig,
21+
2122
/// Login-specific rate limits
2223
#[serde(default)]
2324
pub login: LoginRateLimitingConfig,
25+
2426
/// Controls how many registrations attempts are permitted
2527
/// based on source address.
2628
#[serde(default = "default_registration")]
2729
pub registration: RateLimiterConfiguration,
30+
31+
/// Email authentication-specific rate limits
32+
#[serde(default)]
33+
pub email_authentication: EmailauthenticationRateLimitingConfig,
2834
}
2935

3036
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
@@ -37,6 +43,7 @@ pub struct LoginRateLimitingConfig {
3743
/// change their own password.
3844
#[serde(default = "default_login_per_ip")]
3945
pub per_ip: RateLimiterConfiguration,
46+
4047
/// Controls how many login attempts are permitted
4148
/// based on the account that is being attempted to be logged into.
4249
/// This can protect against a distributed brute force attack
@@ -58,6 +65,7 @@ pub struct AccountRecoveryRateLimitingConfig {
5865
/// Note: this limit also applies to re-sends.
5966
#[serde(default = "default_account_recovery_per_ip")]
6067
pub per_ip: RateLimiterConfiguration,
68+
6169
/// Controls how many account recovery attempts are permitted
6270
/// based on the e-mail address entered into the recovery form.
6371
/// This can protect against causing e-mail spam to one target.
@@ -67,6 +75,35 @@ pub struct AccountRecoveryRateLimitingConfig {
6775
pub per_address: RateLimiterConfiguration,
6876
}
6977

78+
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
79+
pub struct EmailauthenticationRateLimitingConfig {
80+
/// Controls how many email authentication attempts are permitted
81+
/// based on the source IP address.
82+
/// This can protect against causing e-mail spam to many targets.
83+
#[serde(default = "default_email_authentication_per_ip")]
84+
pub per_ip: RateLimiterConfiguration,
85+
86+
/// Controls how many email authentication attempts are permitted
87+
/// based on the e-mail address entered into the authentication form.
88+
/// This can protect against causing e-mail spam to one target.
89+
///
90+
/// Note: this limit also applies to re-sends.
91+
#[serde(default = "default_email_authentication_per_address")]
92+
pub per_address: RateLimiterConfiguration,
93+
94+
/// Controls how many authentication emails are permitted to be sent per
95+
/// authentication session. This ensures not too many authentication codes
96+
/// are created for the same authentication session.
97+
#[serde(default = "default_email_authentication_emails_per_session")]
98+
pub emails_per_session: RateLimiterConfiguration,
99+
100+
/// Controls how many code authentication attempts are permitted per
101+
/// authentication session. This can protect against brute-forcing the
102+
/// code.
103+
#[serde(default = "default_email_authentication_attempt_per_session")]
104+
pub attempt_per_session: RateLimiterConfiguration,
105+
}
106+
70107
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
71108
pub struct RateLimiterConfiguration {
72109
/// A one-off burst of actions that the user can perform
@@ -193,12 +230,41 @@ fn default_account_recovery_per_address() -> RateLimiterConfiguration {
193230
}
194231
}
195232

233+
fn default_email_authentication_per_ip() -> RateLimiterConfiguration {
234+
RateLimiterConfiguration {
235+
burst: NonZeroU32::new(5).unwrap(),
236+
per_second: 1.0 / 60.0,
237+
}
238+
}
239+
240+
fn default_email_authentication_per_address() -> RateLimiterConfiguration {
241+
RateLimiterConfiguration {
242+
burst: NonZeroU32::new(3).unwrap(),
243+
per_second: 1.0 / 3600.0,
244+
}
245+
}
246+
247+
fn default_email_authentication_emails_per_session() -> RateLimiterConfiguration {
248+
RateLimiterConfiguration {
249+
burst: NonZeroU32::new(2).unwrap(),
250+
per_second: 1.0 / 300.0,
251+
}
252+
}
253+
254+
fn default_email_authentication_attempt_per_session() -> RateLimiterConfiguration {
255+
RateLimiterConfiguration {
256+
burst: NonZeroU32::new(10).unwrap(),
257+
per_second: 1.0 / 60.0,
258+
}
259+
}
260+
196261
impl Default for RateLimitingConfig {
197262
fn default() -> Self {
198263
RateLimitingConfig {
199264
login: LoginRateLimitingConfig::default(),
200265
registration: default_registration(),
201266
account_recovery: AccountRecoveryRateLimitingConfig::default(),
267+
email_authentication: EmailauthenticationRateLimitingConfig::default(),
202268
}
203269
}
204270
}
@@ -220,3 +286,14 @@ impl Default for AccountRecoveryRateLimitingConfig {
220286
}
221287
}
222288
}
289+
290+
impl Default for EmailauthenticationRateLimitingConfig {
291+
fn default() -> Self {
292+
EmailauthenticationRateLimitingConfig {
293+
per_ip: default_email_authentication_per_ip(),
294+
per_address: default_email_authentication_per_address(),
295+
emails_per_session: default_email_authentication_emails_per_session(),
296+
attempt_per_session: default_email_authentication_attempt_per_session(),
297+
}
298+
}
299+
}

crates/config/src/sections/upstream_oauth2.rs

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -180,29 +180,6 @@ impl ImportAction {
180180
}
181181
}
182182

183-
/// Should the email address be marked as verified
184-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
185-
#[serde(rename_all = "lowercase")]
186-
pub enum SetEmailVerification {
187-
/// Mark the email address as verified
188-
Always,
189-
190-
/// Don't mark the email address as verified
191-
Never,
192-
193-
/// Mark the email address as verified if the upstream provider says it is
194-
/// through the `email_verified` claim
195-
#[default]
196-
Import,
197-
}
198-
199-
impl SetEmailVerification {
200-
#[allow(clippy::trivially_copy_pass_by_ref)]
201-
const fn is_default(&self) -> bool {
202-
matches!(self, SetEmailVerification::Import)
203-
}
204-
}
205-
206183
/// What should be done for the subject attribute
207184
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
208185
pub struct SubjectImportPreference {
@@ -271,17 +248,11 @@ pub struct EmailImportPreference {
271248
/// If not provided, the default template is `{{ user.email }}`
272249
#[serde(default, skip_serializing_if = "Option::is_none")]
273250
pub template: Option<String>,
274-
275-
/// Should the email address be marked as verified
276-
#[serde(default, skip_serializing_if = "SetEmailVerification::is_default")]
277-
pub set_email_verification: SetEmailVerification,
278251
}
279252

280253
impl EmailImportPreference {
281254
const fn is_default(&self) -> bool {
282-
self.action.is_default()
283-
&& self.template.is_none()
284-
&& self.set_email_verification.is_default()
255+
self.action.is_default() && self.template.is_none()
285256
}
286257
}
287258

crates/data-model/src/lib.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,17 @@ pub use self::{
3737
AccessToken, AccessTokenState, RefreshToken, RefreshTokenState, TokenFormatError, TokenType,
3838
},
3939
upstream_oauth2::{
40-
UpsreamOAuthProviderSetEmailVerification, UpstreamOAuthAuthorizationSession,
41-
UpstreamOAuthAuthorizationSessionState, UpstreamOAuthLink, UpstreamOAuthProvider,
42-
UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderDiscoveryMode,
43-
UpstreamOAuthProviderImportAction, UpstreamOAuthProviderImportPreference,
44-
UpstreamOAuthProviderPkceMode, UpstreamOAuthProviderResponseMode,
45-
UpstreamOAuthProviderSubjectPreference, UpstreamOAuthProviderTokenAuthMethod,
40+
UpstreamOAuthAuthorizationSession, UpstreamOAuthAuthorizationSessionState,
41+
UpstreamOAuthLink, UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports,
42+
UpstreamOAuthProviderDiscoveryMode, UpstreamOAuthProviderImportAction,
43+
UpstreamOAuthProviderImportPreference, UpstreamOAuthProviderPkceMode,
44+
UpstreamOAuthProviderResponseMode, UpstreamOAuthProviderSubjectPreference,
45+
UpstreamOAuthProviderTokenAuthMethod,
4646
},
4747
user_agent::{DeviceType, UserAgent},
4848
users::{
4949
Authentication, AuthenticationMethod, BrowserSession, Password, User, UserEmail,
50-
UserEmailVerification, UserEmailVerificationState, UserRecoverySession, UserRecoveryTicket,
50+
UserEmailAuthentication, UserEmailAuthenticationCode, UserRecoverySession,
51+
UserRecoveryTicket, UserRegistration, UserRegistrationPassword,
5152
},
5253
};

crates/data-model/src/upstream_oauth2/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ pub use self::{
1717
ImportPreference as UpstreamOAuthProviderImportPreference,
1818
PkceMode as UpstreamOAuthProviderPkceMode,
1919
ResponseMode as UpstreamOAuthProviderResponseMode,
20-
SetEmailVerification as UpsreamOAuthProviderSetEmailVerification,
2120
SubjectPreference as UpstreamOAuthProviderSubjectPreference,
2221
TokenAuthMethod as UpstreamOAuthProviderTokenAuthMethod, UpstreamOAuthProvider,
2322
},

crates/data-model/src/upstream_oauth2/provider.rs

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -263,32 +263,6 @@ impl UpstreamOAuthProvider {
263263
}
264264
}
265265

266-
/// Whether to set the email as verified when importing it from the upstream
267-
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
268-
#[serde(rename_all = "lowercase")]
269-
pub enum SetEmailVerification {
270-
/// Set the email as verified
271-
Always,
272-
273-
/// Never set the email as verified
274-
Never,
275-
276-
/// Set the email as verified if the upstream provider claims it is verified
277-
#[default]
278-
Import,
279-
}
280-
281-
impl SetEmailVerification {
282-
#[must_use]
283-
pub fn should_mark_as_verified(&self, upstream_verified: bool) -> bool {
284-
match self {
285-
Self::Always => true,
286-
Self::Never => false,
287-
Self::Import => upstream_verified,
288-
}
289-
}
290-
}
291-
292266
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
293267
pub struct ClaimsImports {
294268
#[serde(default)]
@@ -305,9 +279,6 @@ pub struct ClaimsImports {
305279

306280
#[serde(default)]
307281
pub account_name: SubjectPreference,
308-
309-
#[serde(default)]
310-
pub verify_email: SetEmailVerification,
311282
}
312283

313284
// XXX: this should have another name

0 commit comments

Comments
 (0)