11use crate :: auth:: email:: send_email;
22use crate :: auth:: validate:: get_user_record_from_bearer_token;
33use crate :: auth:: { AuthProvider , AuthenticationError , get_user_from_headers} ;
4+ use crate :: database:: models:: DBUser ;
45use crate :: database:: models:: flow_item:: DBFlow ;
56use crate :: database:: redis:: RedisPool ;
67use crate :: file_hosting:: FileHost ;
@@ -76,7 +77,7 @@ impl TempUser {
7677 redis : & RedisPool ,
7778 ) -> Result < crate :: database:: models:: DBUserId , AuthenticationError > {
7879 if let Some ( email) = & self . email {
79- if crate :: database:: models:: DBUser :: get_email ( email, client)
80+ if crate :: database:: models:: DBUser :: get_by_email ( email, client)
8081 . await ?
8182 . is_some ( )
8283 {
@@ -1385,9 +1386,12 @@ pub async fn create_account_with_password(
13851386 . hash_password ( new_account. password . as_bytes ( ) , & salt) ?
13861387 . to_string ( ) ;
13871388
1388- if crate :: database:: models:: DBUser :: get_email ( & new_account. email , & * * pool)
1389- . await ?
1390- . is_some ( )
1389+ if crate :: database:: models:: DBUser :: get_by_email (
1390+ & new_account. email ,
1391+ & * * pool,
1392+ )
1393+ . await ?
1394+ . is_some ( )
13911395 {
13921396 return Err ( ApiError :: InvalidInput (
13931397 "Email is already registered on Modrinth!" . to_string ( ) ,
@@ -1450,7 +1454,8 @@ pub async fn create_account_with_password(
14501454
14511455#[ derive( Deserialize , Validate ) ]
14521456pub struct Login {
1453- pub username : String ,
1457+ #[ serde( rename = "username" ) ]
1458+ pub username_or_email : String ,
14541459 pub password : String ,
14551460 pub challenge : String ,
14561461}
@@ -1466,14 +1471,17 @@ pub async fn login_password(
14661471 return Err ( ApiError :: Turnstile ) ;
14671472 }
14681473
1469- let user = if let Some ( user) =
1470- crate :: database:: models:: DBUser :: get ( & login. username , & * * pool, & redis)
1471- . await ?
1474+ let user = if let Some ( user) = crate :: database:: models:: DBUser :: get (
1475+ & login. username_or_email ,
1476+ & * * pool,
1477+ & redis,
1478+ )
1479+ . await ?
14721480 {
14731481 user
14741482 } else {
1475- let user = crate :: database:: models:: DBUser :: get_email (
1476- & login. username ,
1483+ let user = crate :: database:: models:: DBUser :: get_by_email (
1484+ & login. username_or_email ,
14771485 & * * pool,
14781486 )
14791487 . await ?
@@ -1903,7 +1911,8 @@ pub async fn remove_2fa(
19031911
19041912#[ derive( Deserialize ) ]
19051913pub struct ResetPassword {
1906- pub username : String ,
1914+ #[ serde( rename = "username" ) ]
1915+ pub username_or_email : String ,
19071916 pub challenge : String ,
19081917}
19091918
@@ -1918,46 +1927,77 @@ pub async fn reset_password_begin(
19181927 return Err ( ApiError :: Turnstile ) ;
19191928 }
19201929
1921- let user = if let Some ( user_id ) =
1922- crate :: database:: models:: DBUser :: get_email (
1923- & reset_password. username ,
1930+ let user =
1931+ match crate :: database:: models:: DBUser :: get_by_case_insensitive_email (
1932+ & reset_password. username_or_email ,
19241933 & * * pool,
19251934 )
1926- . await ?
1927- {
1928- crate :: database:: models:: DBUser :: get_id ( user_id, & * * pool, & redis)
1929- . await ?
1930- } else {
1931- crate :: database:: models:: DBUser :: get (
1932- & reset_password. username ,
1933- & * * pool,
1934- & redis,
1935- )
1936- . await ?
1937- } ;
1935+ . await ?[ ..]
1936+ {
1937+ [ ] => {
1938+ // Try finding by username or ID
1939+ crate :: database:: models:: DBUser :: get (
1940+ & reset_password. username_or_email ,
1941+ & * * pool,
1942+ & redis,
1943+ )
1944+ . await ?
1945+ }
1946+ [ user_id] => {
1947+ // If there is only one user with the given email, ignoring case,
1948+ // we can assume it's the user we want to reset the password for
1949+ crate :: database:: models:: DBUser :: get_id (
1950+ user_id, & * * pool, & redis,
1951+ )
1952+ . await ?
1953+ }
1954+ _ => {
1955+ // When several users use variations of the same email with
1956+ // different cases, we cannot reliably tell which user should
1957+ // receive the password reset email, so fall back to case sensitive
1958+ // search to avoid spamming multiple users
1959+ if let Some ( user_id) =
1960+ crate :: database:: models:: DBUser :: get_by_email (
1961+ & reset_password. username_or_email ,
1962+ & * * pool,
1963+ )
1964+ . await ?
1965+ {
1966+ crate :: database:: models:: DBUser :: get_id (
1967+ user_id, & * * pool, & redis,
1968+ )
1969+ . await ?
1970+ } else {
1971+ None
1972+ }
1973+ }
1974+ } ;
19381975
1939- if let Some ( user) = user {
1940- let flow = DBFlow :: ForgotPassword { user_id : user. id }
1976+ if let Some ( DBUser {
1977+ id : user_id,
1978+ email : Some ( email) ,
1979+ ..
1980+ } ) = user
1981+ {
1982+ let flow = DBFlow :: ForgotPassword { user_id }
19411983 . insert ( Duration :: hours ( 24 ) , & redis)
19421984 . await ?;
19431985
1944- if let Some ( email) = user. email {
1945- send_email (
1946- email,
1947- "Reset your password" ,
1948- "Please visit the following link below to reset your password. If the button does not work, you can copy the link and paste it into your browser." ,
1949- "If you did not request for your password to be reset, you can safely ignore this email." ,
1950- Some ( (
1951- "Reset password" ,
1952- & format ! (
1953- "{}/{}?flow={}" ,
1954- dotenvy:: var( "SITE_URL" ) ?,
1955- dotenvy:: var( "SITE_RESET_PASSWORD_PATH" ) ?,
1956- flow
1957- ) ,
1958- ) ) ,
1959- ) ?;
1960- }
1986+ send_email (
1987+ email,
1988+ "Reset your password" ,
1989+ "Please visit the following link below to reset your password. If the button does not work, you can copy the link and paste it into your browser." ,
1990+ "If you did not request for your password to be reset, you can safely ignore this email." ,
1991+ Some ( (
1992+ "Reset password" ,
1993+ & format ! (
1994+ "{}/{}?flow={}" ,
1995+ dotenvy:: var( "SITE_URL" ) ?,
1996+ dotenvy:: var( "SITE_RESET_PASSWORD_PATH" ) ?,
1997+ flow
1998+ ) ,
1999+ ) ) ,
2000+ ) ?;
19612001 }
19622002
19632003 Ok ( HttpResponse :: Ok ( ) . finish ( ) )
0 commit comments