Skip to content

Commit a6c1466

Browse files
committed
Reset password
1 parent dd0a888 commit a6c1466

File tree

5 files changed

+94
-42
lines changed

5 files changed

+94
-42
lines changed

apps/labrinth/migrations/20250902133943_notification-extension.sql

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,54 @@ CREATE TABLE notifications_templates (
5555
);
5656

5757
-- Add existing notification types
58+
INSERT INTO notifications_types (name, delivery_priority, expose_in_user_preferences, expose_in_site_notifications) VALUES ('reset_password', 3, FALSE, FALSE);
5859
INSERT INTO notifications_types (name, delivery_priority, expose_in_user_preferences, expose_in_site_notifications) VALUES ('project_update', 1, TRUE, TRUE);
5960
INSERT INTO notifications_types (name, delivery_priority, expose_in_user_preferences, expose_in_site_notifications) VALUES ('team_invite', 1, TRUE, TRUE);
6061
INSERT INTO notifications_types (name, delivery_priority, expose_in_user_preferences, expose_in_site_notifications) VALUES ('organization_invite', 1, TRUE, TRUE);
6162
INSERT INTO notifications_types (name, delivery_priority, expose_in_user_preferences, expose_in_site_notifications) VALUES ('status_change', 1, TRUE, TRUE);
6263
INSERT INTO notifications_types (name, delivery_priority, expose_in_user_preferences, expose_in_site_notifications) VALUES ('moderator_message', 1, TRUE, TRUE);
6364
INSERT INTO notifications_types (name, delivery_priority, expose_in_user_preferences, expose_in_site_notifications) VALUES ('legacy_markdown', 1, TRUE, TRUE);
6465
INSERT INTO notifications_types (name, delivery_priority, expose_in_user_preferences, expose_in_site_notifications) VALUES ('unknown', 1, TRUE, TRUE);
65-
INSERT INTO notifications_types (name, delivery_priority, expose_in_user_preferences, expose_in_site_notifications) VALUES ('reset_password', 3, FALSE, FALSE);
66+
67+
INSERT INTO users_notifications_preferences (user_id, channel, notification_type, enabled)
68+
VALUES (NULL, 'email', 'reset_password', TRUE);
69+
70+
INSERT INTO users_notifications_preferences (user_id, channel, notification_type, enabled)
71+
VALUES (NULL, 'email', 'project_update', FALSE);
72+
73+
INSERT INTO users_notifications_preferences (user_id, channel, notification_type, enabled)
74+
VALUES (NULL, 'email', 'team_invite', FALSE);
75+
76+
INSERT INTO users_notifications_preferences (user_id, channel, notification_type, enabled)
77+
VALUES (NULL, 'email', 'organization_invite', FALSE);
78+
79+
INSERT INTO users_notifications_preferences (user_id, channel, notification_type, enabled)
80+
VALUES (NULL, 'email', 'status_change', FALSE);
81+
82+
INSERT INTO users_notifications_preferences (user_id, channel, notification_type, enabled)
83+
VALUES (NULL, 'email', 'moderator_message', FALSE);
84+
85+
INSERT INTO users_notifications_preferences (user_id, channel, notification_type, enabled)
86+
VALUES (NULL, 'email', 'legacy_markdown', FALSE);
87+
88+
INSERT INTO users_notifications_preferences (user_id, channel, notification_type, enabled)
89+
VALUES (NULL, 'email', 'unknown', FALSE);
90+
91+
-- Pre-insert any templates
92+
INSERT INTO notifications_templates (channel, notification_type, subject_line, body_fetch_url, plaintext_fallback)
93+
VALUES (
94+
'email', 'reset_password', 'Reset your Modrinth password', 'https://modrinth.com/mail/resetpassword.html',
95+
CONCAT(
96+
'Hi {user.name},',
97+
CHR(10),
98+
CHR(10),
99+
'Please visit the below link below to reset your password. If you did not request for your password to be reset, you can safely ignore this email.',
100+
CHR(10),
101+
'Reset your password: {resetpassword.url}',
102+
CHR(10),
103+
CHR(10),
104+
'- The Modrinth Team',
105+
CHR(10),
106+
'modrinth.com'
107+
)
108+
);

apps/labrinth/src/database/models/user_item.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ impl DBUser {
233233
exec: E,
234234
) -> Result<Option<DBUserId>, sqlx::Error>
235235
where
236-
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
236+
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
237237
{
238238
let user = sqlx::query!(
239239
"
@@ -254,7 +254,7 @@ impl DBUser {
254254
exec: E,
255255
) -> Result<Vec<DBUserId>, sqlx::Error>
256256
where
257-
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
257+
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
258258
{
259259
let users = sqlx::query!(
260260
"
@@ -276,7 +276,7 @@ impl DBUser {
276276
redis: &RedisPool,
277277
) -> Result<Vec<DBProjectId>, DatabaseError>
278278
where
279-
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
279+
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
280280
{
281281
use futures::stream::TryStreamExt;
282282

@@ -324,7 +324,7 @@ impl DBUser {
324324
exec: E,
325325
) -> Result<Vec<DBOrganizationId>, sqlx::Error>
326326
where
327-
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
327+
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
328328
{
329329
use futures::stream::TryStreamExt;
330330

@@ -349,7 +349,7 @@ impl DBUser {
349349
exec: E,
350350
) -> Result<Vec<DBCollectionId>, sqlx::Error>
351351
where
352-
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
352+
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
353353
{
354354
use futures::stream::TryStreamExt;
355355

@@ -373,7 +373,7 @@ impl DBUser {
373373
exec: E,
374374
) -> Result<Vec<DBProjectId>, sqlx::Error>
375375
where
376-
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
376+
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
377377
{
378378
use futures::stream::TryStreamExt;
379379

@@ -397,7 +397,7 @@ impl DBUser {
397397
exec: E,
398398
) -> Result<Vec<DBReportId>, sqlx::Error>
399399
where
400-
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
400+
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
401401
{
402402
use futures::stream::TryStreamExt;
403403

@@ -421,7 +421,7 @@ impl DBUser {
421421
exec: E,
422422
) -> Result<Vec<String>, sqlx::Error>
423423
where
424-
E: sqlx::Executor<'a, Database = sqlx::Postgres> + Copy,
424+
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
425425
{
426426
use futures::stream::TryStreamExt;
427427

apps/labrinth/src/models/v3/notifications.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ impl From<DBNotification> for Notification {
247247
},
248248
vec![],
249249
),
250+
// Don't expose the `flow` field
250251
NotificationBody::ResetPassword { .. } => (
251252
"Password reset requested".to_string(),
252253
"You've requested to reset your password. Please check your email for a reset link.".to_string(),

apps/labrinth/src/queue/email/templates.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ use tracing::{error, warn};
1818
const USER_NAME: &str = "user.name";
1919
const USER_EMAIL: &str = "user.email";
2020

21+
const RESETPASSWORD_URL: &str = "resetpassword.url";
22+
2123
const TEAMINVITE_INVITER_NAME: &str = "teaminvite.inviter.name";
2224
const TEAMINVITE_PROJECT_NAME: &str = "teaminvite.project.name";
2325
const TEAMINVITE_ROLE_NAME: &str = "teaminvite.role.name";
@@ -170,12 +172,12 @@ async fn collect_template_variables(
170172
exec: impl sqlx::PgExecutor<'_>,
171173
redis: &RedisPool,
172174
n: &DBNotification,
173-
) -> Result<TemplateVariables, DatabaseError> {
175+
) -> Result<TemplateVariables, ApiError> {
174176
async fn only_select_default_variables(
175177
exec: impl sqlx::PgExecutor<'_>,
176178
redis: &RedisPool,
177179
user_id: DBUserId,
178-
) -> Result<TemplateVariables, DatabaseError> {
180+
) -> Result<TemplateVariables, ApiError> {
179181
let mut map = HashMap::new();
180182

181183
let user = DBUser::get_id(user_id, exec, redis)
@@ -187,10 +189,6 @@ async fn collect_template_variables(
187189
}
188190

189191
match &n.body {
190-
NotificationBody::ProjectUpdate { .. } => {
191-
only_select_default_variables(exec, redis, n.user_id).await
192-
}
193-
194192
NotificationBody::TeamInvite {
195193
team_id: _,
196194
project_id,
@@ -289,6 +287,25 @@ async fn collect_template_variables(
289287
Ok(TemplateVariables::with_email(map, result.user_email))
290288
}
291289

292-
_ => only_select_default_variables(exec, redis, n.user_id).await,
290+
NotificationBody::ResetPassword { flow } => {
291+
let url = format!(
292+
"{}/{}?flow={}",
293+
dotenvy::var("SITE_URL")?,
294+
dotenvy::var("SITE_RESET_PASSWORD_PATH")?,
295+
flow
296+
);
297+
298+
let mut map = HashMap::new();
299+
map.insert(RESETPASSWORD_URL, url);
300+
301+
Ok(TemplateVariables::with_email(map, None))
302+
}
303+
304+
NotificationBody::ProjectUpdate { .. }
305+
| NotificationBody::LegacyMarkdown { .. }
306+
| NotificationBody::ModeratorMessage { .. }
307+
| NotificationBody::Unknown => {
308+
only_select_default_variables(exec, redis, n.user_id).await
309+
}
293310
}
294311
}

apps/labrinth/src/routes/internal/flows.rs

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ use crate::auth::validate::{
55
use crate::auth::{AuthProvider, AuthenticationError, get_user_from_headers};
66
use crate::database::models::DBUser;
77
use crate::database::models::flow_item::DBFlow;
8+
use crate::database::models::notification_item::NotificationBuilder;
89
use crate::database::redis::RedisPool;
910
use crate::file_hosting::{FileHost, FileHostPublicity};
11+
use crate::models::notifications::NotificationBody;
1012
use crate::models::pats::Scopes;
1113
use crate::models::users::{Badges, Role};
1214
use crate::queue::session::AuthQueue;
@@ -1930,18 +1932,20 @@ pub async fn reset_password_begin(
19301932
return Err(ApiError::Turnstile);
19311933
}
19321934

1935+
let mut txn = pool.begin().await?;
1936+
19331937
let user =
19341938
match crate::database::models::DBUser::get_by_case_insensitive_email(
19351939
&reset_password.username_or_email,
1936-
&**pool,
1940+
&mut *txn,
19371941
)
19381942
.await?[..]
19391943
{
19401944
[] => {
19411945
// Try finding by username or ID
19421946
crate::database::models::DBUser::get(
19431947
&reset_password.username_or_email,
1944-
&**pool,
1948+
&mut *txn,
19451949
&redis,
19461950
)
19471951
.await?
@@ -1950,7 +1954,7 @@ pub async fn reset_password_begin(
19501954
// If there is only one user with the given email, ignoring case,
19511955
// we can assume it's the user we want to reset the password for
19521956
crate::database::models::DBUser::get_id(
1953-
user_id, &**pool, &redis,
1957+
user_id, &mut *txn, &redis,
19541958
)
19551959
.await?
19561960
}
@@ -1962,12 +1966,12 @@ pub async fn reset_password_begin(
19621966
if let Some(user_id) =
19631967
crate::database::models::DBUser::get_by_email(
19641968
&reset_password.username_or_email,
1965-
&**pool,
1969+
&mut *txn,
19661970
)
19671971
.await?
19681972
{
19691973
crate::database::models::DBUser::get_id(
1970-
user_id, &**pool, &redis,
1974+
user_id, &mut *txn, &redis,
19711975
)
19721976
.await?
19731977
} else {
@@ -1976,33 +1980,20 @@ pub async fn reset_password_begin(
19761980
}
19771981
};
19781982

1979-
if let Some(DBUser {
1980-
id: user_id,
1981-
email: Some(email),
1982-
..
1983-
}) = user
1984-
{
1983+
if let Some(DBUser { id: user_id, .. }) = user {
19851984
let flow = DBFlow::ForgotPassword { user_id }
19861985
.insert(Duration::hours(24), &redis)
19871986
.await?;
19881987

1989-
send_email(
1990-
email,
1991-
"Reset your password",
1992-
"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.",
1993-
"If you did not request for your password to be reset, you can safely ignore this email.",
1994-
Some((
1995-
"Reset password",
1996-
&format!(
1997-
"{}/{}?flow={}",
1998-
dotenvy::var("SITE_URL")?,
1999-
dotenvy::var("SITE_RESET_PASSWORD_PATH")?,
2000-
flow
2001-
),
2002-
)),
2003-
)?;
1988+
NotificationBuilder {
1989+
body: NotificationBody::ResetPassword { flow },
1990+
}
1991+
.insert(user_id, &mut txn, &redis)
1992+
.await?;
20041993
}
20051994

1995+
txn.commit().await?;
1996+
20061997
Ok(HttpResponse::Ok().finish())
20071998
}
20081999

0 commit comments

Comments
 (0)