Skip to content

Commit ac9cf37

Browse files
committed
Job to clean up old challenges
1 parent fb5c2ac commit ac9cf37

File tree

6 files changed

+96
-2
lines changed

6 files changed

+96
-2
lines changed

crates/storage-pg/.sqlx/query-8977b2a7dfb1de2cdb917be1a07b3f660e90d610aa74813ac0fdd2a3439a2e99.json

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/storage-pg/src/user/passkey.rs

+26-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// Please see LICENSE in the repository root for full details.
55

66
use async_trait::async_trait;
7-
use chrono::{DateTime, Utc};
7+
use chrono::{DateTime, Duration, Utc};
88
use mas_data_model::{BrowserSession, User, UserPasskey, UserPasskeyChallenge};
99
use mas_storage::{
1010
Clock, Page, Pagination,
@@ -600,4 +600,29 @@ impl UserPasskeyRepository for PgUserPasskeyRepository<'_> {
600600
user_passkey_challenge.completed_at = Some(completed_at);
601601
Ok(user_passkey_challenge)
602602
}
603+
604+
#[tracing::instrument(
605+
name = "db.user_passkey.cleanup_challenges",
606+
skip_all,
607+
fields(
608+
db.query.text,
609+
),
610+
err,
611+
)]
612+
async fn cleanup_challenges(&mut self, clock: &dyn Clock) -> Result<usize, Self::Error> {
613+
// Cleanup challenges that were created more than an hour ago
614+
let threshold = clock.now() - Duration::microseconds(60 * 60 * 1000 * 1000);
615+
let res = sqlx::query!(
616+
r#"
617+
DELETE FROM user_passkey_challenges
618+
WHERE created_at < $1
619+
"#,
620+
threshold,
621+
)
622+
.traced()
623+
.execute(&mut *self.conn)
624+
.await?;
625+
626+
Ok(res.rows_affected().try_into().unwrap_or(usize::MAX))
627+
}
603628
}

crates/storage/src/queue/tasks.rs

+8
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,14 @@ impl InsertableJob for CleanupExpiredTokensJob {
325325
const QUEUE_NAME: &'static str = "cleanup-expired-tokens";
326326
}
327327

328+
/// Cleanup old passkey challenges
329+
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
330+
pub struct CleanupOldPasskeyChallenges;
331+
332+
impl InsertableJob for CleanupOldPasskeyChallenges {
333+
const QUEUE_NAME: &'static str = "cleanup-old-passkey-challenges";
334+
}
335+
328336
/// Scheduled job to expire inactive sessions
329337
///
330338
/// This job will trigger jobs to expire inactive compat, oauth and user

crates/storage/src/user/passkey.rs

+15
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,19 @@ pub trait UserPasskeyRepository: Send + Sync {
254254
clock: &dyn Clock,
255255
user_passkey_challenge: UserPasskeyChallenge,
256256
) -> Result<UserPasskeyChallenge, Self::Error>;
257+
258+
/// Cleanup old challenges
259+
///
260+
/// Returns the number of challenges that were cleaned up
261+
///
262+
/// # Parameters
263+
///
264+
/// * `clock`: The clock used to get the current time
265+
///
266+
/// # Errors
267+
///
268+
/// Returns [`Self::Error`] if the underlying repository fails
269+
async fn cleanup_challenges(&mut self, clock: &dyn Clock) -> Result<usize, Self::Error>;
257270
}
258271

259272
repository_impl!(UserPasskeyRepository:
@@ -314,4 +327,6 @@ repository_impl!(UserPasskeyRepository:
314327
clock: &dyn Clock,
315328
user_passkey_challenge: UserPasskeyChallenge,
316329
) -> Result<UserPasskeyChallenge, Self::Error>;
330+
331+
async fn cleanup_challenges(&mut self, clock: &dyn Clock) -> Result<usize, Self::Error>;
317332
);

crates/tasks/src/database.rs

+27-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
//! Database-related tasks
88
99
use async_trait::async_trait;
10-
use mas_storage::queue::{CleanupExpiredTokensJob, PruneStalePolicyDataJob};
10+
use mas_storage::queue::{
11+
CleanupExpiredTokensJob, CleanupOldPasskeyChallenges, PruneStalePolicyDataJob,
12+
};
1113
use tracing::{debug, info};
1214

1315
use crate::{
@@ -63,3 +65,27 @@ impl RunnableJob for PruneStalePolicyDataJob {
6365
Ok(())
6466
}
6567
}
68+
69+
#[async_trait]
70+
impl RunnableJob for CleanupOldPasskeyChallenges {
71+
#[tracing::instrument(name = "job.cleanup_old_passkey_challenges", skip_all, err)]
72+
async fn run(&self, state: &State, _context: JobContext) -> Result<(), JobError> {
73+
let clock = state.clock();
74+
let mut repo = state.repository().await.map_err(JobError::retry)?;
75+
76+
let count = repo
77+
.user_passkey()
78+
.cleanup_challenges(&clock)
79+
.await
80+
.map_err(JobError::retry)?;
81+
repo.save().await.map_err(JobError::retry)?;
82+
83+
if count == 0 {
84+
debug!("no challenges to clean up");
85+
} else {
86+
info!(count, "cleaned up old challenges");
87+
}
88+
89+
Ok(())
90+
}
91+
}

crates/tasks/src/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ pub async fn init(
144144
.register_handler::<mas_storage::queue::ExpireInactiveOAuthSessionsJob>()
145145
.register_handler::<mas_storage::queue::ExpireInactiveUserSessionsJob>()
146146
.register_handler::<mas_storage::queue::PruneStalePolicyDataJob>()
147+
.register_handler::<mas_storage::queue::CleanupOldPasskeyChallenges>()
147148
.add_schedule(
148149
"cleanup-expired-tokens",
149150
"0 0 * * * *".parse()?,
@@ -160,6 +161,11 @@ pub async fn init(
160161
// Run once a day
161162
"0 0 2 * * *".parse()?,
162163
mas_storage::queue::PruneStalePolicyDataJob,
164+
)
165+
.add_schedule(
166+
"cleanup-old-passkey-challenges",
167+
"0 0 * * * *".parse()?,
168+
mas_storage::queue::CleanupOldPasskeyChallenges,
163169
);
164170

165171
task_tracker.spawn(worker.run());

0 commit comments

Comments
 (0)