Skip to content

Commit 7622d68

Browse files
committed
display email login_hint when login_with_email_allowed is activated
1 parent 0e2b508 commit 7622d68

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
@@ -28,3 +28,5 @@ ruma-common.workspace = true
2828
mas-iana.workspace = true
2929
mas-jose.workspace = true
3030
oauth2-types.workspace = true
31+
# Emails
32+
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
55
// Please see LICENSE 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
}
@@ -177,6 +178,7 @@ pub(crate) async fn post(
177178
&mut rng,
178179
&templates,
179180
&homeserver,
181+
&site_config,
180182
)
181183
.await;
182184
}
@@ -187,7 +189,7 @@ pub(crate) async fn post(
187189
.unwrap_or(&form.username);
188190

189191
// First, lookup the user
190-
let Some(user) = get_user_by_email_or_by_username(site_config, &mut repo, username).await?
192+
let Some(user) = get_user_by_email_or_by_username(&site_config, &mut repo, username).await?
191193
else {
192194
let form_state = form_state.with_error_on_form(FormError::InvalidCredentials);
193195
PASSWORD_LOGIN_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
@@ -201,6 +203,7 @@ pub(crate) async fn post(
201203
&mut rng,
202204
&templates,
203205
&homeserver,
206+
&site_config,
204207
)
205208
.await;
206209
};
@@ -220,6 +223,7 @@ pub(crate) async fn post(
220223
&mut rng,
221224
&templates,
222225
&homeserver,
226+
&site_config,
223227
)
224228
.await;
225229
}
@@ -240,6 +244,7 @@ pub(crate) async fn post(
240244
&mut rng,
241245
&templates,
242246
&homeserver,
247+
&site_config,
243248
)
244249
.await;
245250
};
@@ -283,6 +288,7 @@ pub(crate) async fn post(
283288
&mut rng,
284289
&templates,
285290
&homeserver,
291+
&site_config,
286292
)
287293
.await;
288294
}
@@ -339,7 +345,7 @@ pub(crate) async fn post(
339345
}
340346

341347
async fn get_user_by_email_or_by_username<R: RepositoryAccess>(
342-
site_config: SiteConfig,
348+
site_config: &SiteConfig,
343349
repo: &mut R,
344350
username_or_email: &str,
345351
) -> Result<Option<mas_data_model::User>, R::Error> {
@@ -364,6 +370,7 @@ fn handle_login_hint(
364370
mut ctx: LoginContext,
365371
next: &PostAuthContext,
366372
homeserver: &dyn HomeserverConnection,
373+
site_config: &SiteConfig,
367374
) -> LoginContext {
368375
let form_state = ctx.form_state_mut();
369376

@@ -373,8 +380,12 @@ fn handle_login_hint(
373380
}
374381

375382
if let PostAuthContextInner::ContinueAuthorizationGrant { ref grant } = next.ctx {
376-
let value = match grant.parse_login_hint(homeserver.homeserver()) {
383+
let value = match grant.parse_login_hint(
384+
homeserver.homeserver(),
385+
site_config.login_with_email_allowed,
386+
) {
377387
LoginHint::MXID(mxid) => Some(mxid.localpart().to_owned()),
388+
LoginHint::EMAIL(email) => Some(email.to_string()),
378389
LoginHint::None => None,
379390
};
380391
form_state.set_value(LoginFormField::Username, value);
@@ -393,6 +404,7 @@ async fn render(
393404
rng: impl Rng,
394405
templates: &Templates,
395406
homeserver: &dyn HomeserverConnection,
407+
site_config: &SiteConfig,
396408
) -> Result<Response, InternalError> {
397409
let (csrf_token, cookie_jar) = cookie_jar.csrf_token(clock, rng);
398410
let providers = repo.upstream_oauth_provider().all_enabled().await?;
@@ -406,7 +418,7 @@ async fn render(
406418
.await
407419
.map_err(InternalError::from_anyhow)?;
408420
let ctx = if let Some(next) = next {
409-
let ctx = handle_login_hint(ctx, &next, homeserver);
421+
let ctx = handle_login_hint(ctx, &next, homeserver, site_config);
410422
ctx.with_post_action(next)
411423
} else {
412424
ctx

0 commit comments

Comments
 (0)