Skip to content

Commit be40e1b

Browse files
committed
Add revoke_bulk for personal sessions storage
1 parent eeba7e1 commit be40e1b

File tree

3 files changed

+186
-0
lines changed

3 files changed

+186
-0
lines changed

crates/storage-pg/src/personal/mod.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,104 @@ mod tests {
181181
assert!(session_lookup.is_revoked());
182182
}
183183

184+
#[sqlx::test(migrator = "crate::MIGRATOR")]
185+
async fn test_session_revoke_bulk(pool: PgPool) {
186+
let mut rng = ChaChaRng::seed_from_u64(42);
187+
let clock = MockClock::default();
188+
let mut repo = PgRepository::from_pool(&pool).await.unwrap();
189+
190+
let alice_user = repo
191+
.user()
192+
.add(&mut rng, &clock, "alice".to_owned())
193+
.await
194+
.unwrap();
195+
let bob_user = repo
196+
.user()
197+
.add(&mut rng, &clock, "bob".to_owned())
198+
.await
199+
.unwrap();
200+
201+
let session1 = repo
202+
.personal_session()
203+
.add(
204+
&mut rng,
205+
&clock,
206+
(&alice_user).into(),
207+
&bob_user,
208+
"Test Personal Session".to_owned(),
209+
"openid".parse().unwrap(),
210+
)
211+
.await
212+
.unwrap();
213+
repo.personal_access_token()
214+
.add(
215+
&mut rng,
216+
&clock,
217+
&session1,
218+
"mpt_hiss",
219+
Some(Duration::days(42)),
220+
)
221+
.await
222+
.unwrap();
223+
224+
let session2 = repo
225+
.personal_session()
226+
.add(
227+
&mut rng,
228+
&clock,
229+
(&bob_user).into(),
230+
&bob_user,
231+
"Test Personal Session".to_owned(),
232+
"openid".parse().unwrap(),
233+
)
234+
.await
235+
.unwrap();
236+
repo.personal_access_token()
237+
.add(
238+
&mut rng, &clock, &session2, "mpt_meow", // No expiry
239+
None,
240+
)
241+
.await
242+
.unwrap();
243+
244+
// Just one session without a token expiry time
245+
assert_eq!(
246+
repo.personal_session()
247+
.revoke_bulk(
248+
&clock,
249+
PersonalSessionFilter::new()
250+
.active_only()
251+
.with_expires(false)
252+
)
253+
.await
254+
.unwrap(),
255+
1
256+
);
257+
258+
// Just one session with a token expiry time
259+
assert_eq!(
260+
repo.personal_session()
261+
.revoke_bulk(
262+
&clock,
263+
PersonalSessionFilter::new()
264+
.active_only()
265+
.with_expires(true)
266+
)
267+
.await
268+
.unwrap(),
269+
1
270+
);
271+
272+
// No active sessions left
273+
assert_eq!(
274+
repo.personal_session()
275+
.revoke_bulk(&clock, PersonalSessionFilter::new().active_only())
276+
.await
277+
.unwrap(),
278+
0
279+
);
280+
}
281+
184282
#[sqlx::test(migrator = "crate::MIGRATOR")]
185283
async fn test_access_token_repository(pool: PgPool) {
186284
const FIRST_TOKEN: &str = "first_access_token";

crates/storage-pg/src/personal/session.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,68 @@ impl PersonalSessionRepository for PgPersonalSessionRepository<'_> {
357357
.map_err(DatabaseError::to_invalid_operation)
358358
}
359359

360+
#[tracing::instrument(
361+
name = "db.personal_session.revoke_bulk",
362+
skip_all,
363+
fields(
364+
db.query.text,
365+
),
366+
err,
367+
)]
368+
async fn revoke_bulk(
369+
&mut self,
370+
clock: &dyn Clock,
371+
filter: PersonalSessionFilter<'_>,
372+
) -> Result<usize, Self::Error> {
373+
let revoked_at = clock.now();
374+
375+
let (sql, arguments) = Query::update()
376+
.table(PersonalSessions::Table)
377+
.value(PersonalSessions::RevokedAt, revoked_at)
378+
.and_where(
379+
Expr::col((PersonalSessions::Table, PersonalSessions::PersonalSessionId))
380+
.in_subquery(
381+
Query::select()
382+
.expr(Expr::col((
383+
PersonalSessions::Table,
384+
PersonalSessions::PersonalSessionId,
385+
)))
386+
.from(PersonalSessions::Table)
387+
.left_join(
388+
PersonalAccessTokens::Table,
389+
Cond::all()
390+
.add(
391+
Expr::col((
392+
PersonalSessions::Table,
393+
PersonalSessions::PersonalSessionId,
394+
))
395+
.eq(Expr::col((
396+
PersonalAccessTokens::Table,
397+
PersonalAccessTokens::PersonalSessionId,
398+
))),
399+
)
400+
.add(
401+
Expr::col((
402+
PersonalAccessTokens::Table,
403+
PersonalAccessTokens::RevokedAt,
404+
))
405+
.is_null(),
406+
),
407+
)
408+
.apply_filter(filter)
409+
.take(),
410+
),
411+
)
412+
.build_sqlx(PostgresQueryBuilder);
413+
414+
let res = sqlx::query_with(&sql, arguments)
415+
.traced()
416+
.execute(&mut *self.conn)
417+
.await?;
418+
419+
Ok(res.rows_affected().try_into().unwrap_or(usize::MAX))
420+
}
421+
360422
#[tracing::instrument(
361423
name = "db.personal_session.list",
362424
skip_all,

crates/storage/src/personal/session.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,26 @@ pub trait PersonalSessionRepository: Send + Sync {
8787
personal_session: PersonalSession,
8888
) -> Result<PersonalSession, Self::Error>;
8989

90+
/// Revoke all the [`PersonalSession`]s matching the given filter.
91+
///
92+
/// This will also revoke the relevant personal access tokens.
93+
///
94+
/// Returns the number of sessions affected
95+
///
96+
/// # Parameters
97+
///
98+
/// * `clock`: The clock used to generate timestamps
99+
/// * `filter`: The filter to apply
100+
///
101+
/// # Errors
102+
///
103+
/// Returns [`Self::Error`] if the underlying repository fails
104+
async fn revoke_bulk(
105+
&mut self,
106+
clock: &dyn Clock,
107+
filter: PersonalSessionFilter<'_>,
108+
) -> Result<usize, Self::Error>;
109+
90110
/// List [`PersonalSession`]s matching the given filter and pagination
91111
/// parameters
92112
///
@@ -150,6 +170,12 @@ repository_impl!(PersonalSessionRepository:
150170
personal_session: PersonalSession,
151171
) -> Result<PersonalSession, Self::Error>;
152172

173+
async fn revoke_bulk(
174+
&mut self,
175+
clock: &dyn Clock,
176+
filter: PersonalSessionFilter<'_>,
177+
) -> Result<usize, Self::Error>;
178+
153179
async fn list(
154180
&mut self,
155181
filter: PersonalSessionFilter<'_>,

0 commit comments

Comments
 (0)