Skip to content

Commit 8af50a1

Browse files
committed
display email login_hint when login_with_email_allowed is activated
1 parent 94722e9 commit 8af50a1

File tree

4 files changed

+74
-10
lines changed

4 files changed

+74
-10
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/data-model/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ ruma-common.workspace = true
3333
mas-iana.workspace = true
3434
mas-jose.workspace = true
3535
oauth2-types.workspace = true
36+
# Emails
37+
lettre.workspace = true

crates/data-model/src/oauth2/authorization_grant.rs

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
55
// Please see LICENSE files in the repository root for full details.
66

7+
use std::str::FromStr as _;
8+
79
use chrono::{DateTime, Utc};
810
use mas_iana::oauth::PkceCodeChallengeMethod;
911
use oauth2_types::{
@@ -142,6 +144,7 @@ impl AuthorizationGrantStage {
142144

143145
pub enum LoginHint<'a> {
144146
MXID(&'a UserId),
147+
EMAIL(lettre::Address),
145148
None,
146149
}
147150

@@ -173,7 +176,7 @@ impl std::ops::Deref for AuthorizationGrant {
173176

174177
impl AuthorizationGrant {
175178
#[must_use]
176-
pub fn parse_login_hint(&self, homeserver: &str) -> LoginHint {
179+
pub fn parse_login_hint(&self, homeserver: &str, login_with_email_allowed: bool) -> LoginHint {
177180
let Some(login_hint) = &self.login_hint else {
178181
return LoginHint::None;
179182
};
@@ -197,6 +200,16 @@ impl AuthorizationGrant {
197200

198201
LoginHint::MXID(mxid)
199202
}
203+
"email" => {
204+
if !login_with_email_allowed {
205+
return LoginHint::None;
206+
}
207+
// Validate the email
208+
let Ok(address) = lettre::Address::from_str(value) else {
209+
return LoginHint::None;
210+
};
211+
LoginHint::EMAIL(address)
212+
}
200213
// Unknown hint type, treat as none
201214
_ => LoginHint::None,
202215
}
@@ -288,7 +301,7 @@ mod tests {
288301
..AuthorizationGrant::sample(now, &mut rng)
289302
};
290303

291-
let hint = grant.parse_login_hint("example.com");
304+
let hint = grant.parse_login_hint("example.com", false);
292305

293306
assert!(matches!(hint, LoginHint::None));
294307
}
@@ -306,11 +319,47 @@ mod tests {
306319
..AuthorizationGrant::sample(now, &mut rng)
307320
};
308321

309-
let hint = grant.parse_login_hint("example.com");
322+
let hint = grant.parse_login_hint("example.com", false);
310323

311324
assert!(matches!(hint, LoginHint::MXID(mxid) if mxid.localpart() == "example-user"));
312325
}
313326

327+
#[test]
328+
fn valid_login_hint_with_email() {
329+
#[allow(clippy::disallowed_methods)]
330+
let mut rng = thread_rng();
331+
332+
#[allow(clippy::disallowed_methods)]
333+
let now = Utc::now();
334+
335+
let grant = AuthorizationGrant {
336+
login_hint: Some(String::from("email:example@user")),
337+
..AuthorizationGrant::sample(now, &mut rng)
338+
};
339+
340+
let hint = grant.parse_login_hint("example.com", true);
341+
342+
assert!(matches!(hint, LoginHint::EMAIL(email) if email.to_string() == "example@user"));
343+
}
344+
345+
#[test]
346+
fn valid_login_hint_with_email_when_login_with_email_not_allowed() {
347+
#[allow(clippy::disallowed_methods)]
348+
let mut rng = thread_rng();
349+
350+
#[allow(clippy::disallowed_methods)]
351+
let now = Utc::now();
352+
353+
let grant = AuthorizationGrant {
354+
login_hint: Some(String::from("email:example@user")),
355+
..AuthorizationGrant::sample(now, &mut rng)
356+
};
357+
358+
let hint = grant.parse_login_hint("example.com", false);
359+
360+
assert!(matches!(hint, LoginHint::None));
361+
}
362+
314363
#[test]
315364
fn invalid_login_hint() {
316365
#[allow(clippy::disallowed_methods)]
@@ -324,7 +373,7 @@ mod tests {
324373
..AuthorizationGrant::sample(now, &mut rng)
325374
};
326375

327-
let hint = grant.parse_login_hint("example.com");
376+
let hint = grant.parse_login_hint("example.com", false);
328377

329378
assert!(matches!(hint, LoginHint::None));
330379
}
@@ -342,7 +391,7 @@ mod tests {
342391
..AuthorizationGrant::sample(now, &mut rng)
343392
};
344393

345-
let hint = grant.parse_login_hint("example.com");
394+
let hint = grant.parse_login_hint("example.com", false);
346395

347396
assert!(matches!(hint, LoginHint::None));
348397
}
@@ -360,7 +409,7 @@ mod tests {
360409
..AuthorizationGrant::sample(now, &mut rng)
361410
};
362411

363-
let hint = grant.parse_login_hint("example.com");
412+
let hint = grant.parse_login_hint("example.com", false);
364413

365414
assert!(matches!(hint, LoginHint::None));
366415
}

crates/handlers/src/views/login.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ pub(crate) async fn get(
123123
&mut rng,
124124
&templates,
125125
&homeserver,
126+
&site_config,
126127
)
127128
.await
128129
}
@@ -178,6 +179,7 @@ pub(crate) async fn post(
178179
&mut rng,
179180
&templates,
180181
&homeserver,
182+
&site_config,
181183
)
182184
.await;
183185
}
@@ -188,7 +190,7 @@ pub(crate) async fn post(
188190
.unwrap_or(&form.username);
189191

190192
// First, lookup the user
191-
let Some(user) = get_user_by_email_or_by_username(site_config, &mut repo, username).await?
193+
let Some(user) = get_user_by_email_or_by_username(&site_config, &mut repo, username).await?
192194
else {
193195
tracing::warn!(username, "User not found");
194196
let form_state = form_state.with_error_on_form(FormError::InvalidCredentials);
@@ -203,6 +205,7 @@ pub(crate) async fn post(
203205
&mut rng,
204206
&templates,
205207
&homeserver,
208+
&site_config,
206209
)
207210
.await;
208211
};
@@ -222,6 +225,7 @@ pub(crate) async fn post(
222225
&mut rng,
223226
&templates,
224227
&homeserver,
228+
&site_config,
225229
)
226230
.await;
227231
}
@@ -243,6 +247,7 @@ pub(crate) async fn post(
243247
&mut rng,
244248
&templates,
245249
&homeserver,
250+
&site_config,
246251
)
247252
.await;
248253
};
@@ -287,6 +292,7 @@ pub(crate) async fn post(
287292
&mut rng,
288293
&templates,
289294
&homeserver,
295+
&site_config,
290296
)
291297
.await;
292298
}
@@ -346,7 +352,7 @@ pub(crate) async fn post(
346352
}
347353

348354
async fn get_user_by_email_or_by_username<R: RepositoryAccess>(
349-
site_config: SiteConfig,
355+
site_config: &SiteConfig,
350356
repo: &mut R,
351357
username_or_email: &str,
352358
) -> Result<Option<mas_data_model::User>, R::Error> {
@@ -371,6 +377,7 @@ fn handle_login_hint(
371377
mut ctx: LoginContext,
372378
next: &PostAuthContext,
373379
homeserver: &dyn HomeserverConnection,
380+
site_config: &SiteConfig,
374381
) -> LoginContext {
375382
let form_state = ctx.form_state_mut();
376383

@@ -380,8 +387,12 @@ fn handle_login_hint(
380387
}
381388

382389
if let PostAuthContextInner::ContinueAuthorizationGrant { ref grant } = next.ctx {
383-
let value = match grant.parse_login_hint(homeserver.homeserver()) {
390+
let value = match grant.parse_login_hint(
391+
homeserver.homeserver(),
392+
site_config.login_with_email_allowed,
393+
) {
384394
LoginHint::MXID(mxid) => Some(mxid.localpart().to_owned()),
395+
LoginHint::EMAIL(email) => Some(email.to_string()),
385396
LoginHint::None => None,
386397
};
387398
form_state.set_value(LoginFormField::Username, value);
@@ -400,6 +411,7 @@ async fn render(
400411
rng: impl Rng,
401412
templates: &Templates,
402413
homeserver: &dyn HomeserverConnection,
414+
site_config: &SiteConfig,
403415
) -> Result<Response, InternalError> {
404416
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock, rng);
405417
let providers = repo.upstream_oauth_provider().all_enabled().await?;
@@ -413,7 +425,7 @@ async fn render(
413425
.await
414426
.map_err(InternalError::from_anyhow)?;
415427
let ctx = if let Some(next) = next {
416-
let ctx = handle_login_hint(ctx, &next, homeserver);
428+
let ctx = handle_login_hint(ctx, &next, homeserver, site_config);
417429
ctx.with_post_action(next)
418430
} else {
419431
ctx

0 commit comments

Comments
 (0)