Skip to content

Commit e308882

Browse files
reivilibresandhose
authored andcommitted
Split access tokens between refreshable and unrefreshable ones
1 parent ca32c5e commit e308882

6 files changed

+118
-105
lines changed

crates/syn2mas/src/migration.rs

Lines changed: 72 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ use uuid::Uuid;
2929

3030
use crate::{
3131
mas_writer::{
32-
self, MasNewCompatAccessToken, MasNewCompatSession, MasNewEmailThreepid,
33-
MasNewUnsupportedThreepid, MasNewUpstreamOauthLink, MasNewUser, MasNewUserPassword,
34-
MasWriteBuffer, MasWriter,
32+
self, MasNewCompatAccessToken, MasNewCompatRefreshToken, MasNewCompatSession,
33+
MasNewEmailThreepid, MasNewUnsupportedThreepid, MasNewUpstreamOauthLink, MasNewUser,
34+
MasNewUserPassword, MasWriteBuffer, MasWriter,
3535
},
3636
synapse_reader::{
3737
self, ExtractLocalpartError, FullUserId, SynapseAccessToken, SynapseDevice,
38-
SynapseExternalId, SynapseRefreshToken, SynapseThreepid, SynapseUser,
38+
SynapseExternalId, SynapseRefreshableTokenPair, SynapseThreepid, SynapseUser,
3939
},
4040
SynapseReader,
4141
};
@@ -140,7 +140,7 @@ pub async fn migrate(
140140
.expect("More than usize::MAX devices — unable to handle this many!"),
141141
);
142142

143-
migrate_access_tokens(
143+
migrate_unrefreshable_access_tokens(
144144
synapse,
145145
mas,
146146
server_name,
@@ -151,10 +151,11 @@ pub async fn migrate(
151151
)
152152
.await?;
153153

154-
migrate_refresh_tokens(
154+
migrate_refreshable_token_pairs(
155155
synapse,
156156
mas,
157157
server_name,
158+
clock,
158159
rng,
159160
&migrated_users.user_localparts_to_uuid,
160161
&mut devices_to_compat_sessions,
@@ -468,8 +469,10 @@ async fn migrate_devices(
468469
Ok(())
469470
}
470471

472+
/// Migrates unrefreshable access tokens (those without an associated refresh
473+
/// token). Some of these may be deviceless.
471474
#[tracing::instrument(skip_all, level = Level::INFO)]
472-
async fn migrate_access_tokens(
475+
async fn migrate_unrefreshable_access_tokens(
473476
synapse: &mut SynapseReader<'_>,
474477
mas: &mut MasWriter<'_>,
475478
server_name: &str,
@@ -478,7 +481,7 @@ async fn migrate_access_tokens(
478481
user_localparts_to_uuid: &HashMap<CompactString, Uuid>,
479482
devices: &mut HashMap<(Uuid, CompactString), Uuid>,
480483
) -> Result<(), Error> {
481-
let mut token_stream = pin!(synapse.read_access_tokens());
484+
let mut token_stream = pin!(synapse.read_unrefreshable_access_tokens());
482485
let mut write_buffer = MasWriteBuffer::new(MasWriter::write_compat_access_tokens);
483486
let mut deviceless_session_write_buffer = MasWriteBuffer::new(MasWriter::write_compat_sessions);
484487

@@ -497,7 +500,7 @@ async fn migrate_access_tokens(
497500
.to_owned();
498501
let Some(user_id) = user_localparts_to_uuid.get(username.as_str()).copied() else {
499502
return Err(Error::MissingUserFromDependentTable {
500-
table: "devices".to_owned(),
503+
table: "access_tokens".to_owned(),
501504
user: synapse_user_id,
502505
});
503506
};
@@ -571,24 +574,31 @@ async fn migrate_access_tokens(
571574
Ok(())
572575
}
573576

577+
/// Migrates (access token, refresh token) pairs.
578+
/// Does not migrate non-refreshable access tokens.
574579
#[tracing::instrument(skip_all, level = Level::INFO)]
575-
async fn migrate_refresh_tokens(
580+
async fn migrate_refreshable_token_pairs(
576581
synapse: &mut SynapseReader<'_>,
577582
mas: &mut MasWriter<'_>,
578583
server_name: &str,
584+
clock: &dyn Clock,
579585
rng: &mut impl RngCore,
580586
user_localparts_to_uuid: &HashMap<CompactString, Uuid>,
581587
devices: &mut HashMap<(Uuid, CompactString), Uuid>,
582588
) -> Result<(), Error> {
583-
let mut token_stream = pin!(synapse.read_refresh_tokens());
584-
let mut write_buffer = MasWriteBuffer::new(MasWriter::write_compat_refresh_tokens);
589+
let mut token_stream = pin!(synapse.read_refreshable_token_pairs());
590+
let mut access_token_write_buffer = MasWriteBuffer::new(MasWriter::write_compat_access_tokens);
591+
let mut refresh_token_write_buffer =
592+
MasWriteBuffer::new(MasWriter::write_compat_refresh_tokens);
585593

586594
while let Some(token_res) = token_stream.next().await {
587-
let SynapseRefreshToken {
595+
let SynapseRefreshableTokenPair {
588596
user_id: synapse_user_id,
589597
device_id,
590-
token,
591-
id,
598+
access_token,
599+
refresh_token,
600+
valid_until_ms,
601+
last_validated,
592602
} = token_res.into_synapse("reading Synapse refresh token")?;
593603

594604
let username = synapse_user_id
@@ -597,15 +607,59 @@ async fn migrate_refresh_tokens(
597607
.to_owned();
598608
let Some(user_id) = user_localparts_to_uuid.get(username.as_str()).copied() else {
599609
return Err(Error::MissingUserFromDependentTable {
600-
table: "devices".to_owned(),
610+
table: "refresh_tokens".to_owned(),
601611
user: synapse_user_id,
602612
});
603613
};
604614

605-
todo!()
615+
// It's not always accurate, but last_validated is *often* the creation time of
616+
// the device If we don't have one, then use the current time as a
617+
// fallback.
618+
let created_at = last_validated.map_or_else(|| clock.now(), DateTime::from);
619+
620+
// Use the existing device_id if this is the second token for a device
621+
let session_id = *devices
622+
.entry((user_id, CompactString::new(&device_id)))
623+
.or_insert_with(|| Uuid::from(Ulid::from_datetime_with_source(created_at.into(), rng)));
624+
625+
let access_token_id = Uuid::from(Ulid::from_datetime_with_source(created_at.into(), rng));
626+
let refresh_token_id = Uuid::from(Ulid::from_datetime_with_source(created_at.into(), rng));
627+
628+
// TODO skip access tokens for deactivated users
629+
access_token_write_buffer
630+
.write(
631+
mas,
632+
MasNewCompatAccessToken {
633+
token_id: access_token_id,
634+
session_id,
635+
access_token,
636+
created_at,
637+
expires_at: valid_until_ms.map(DateTime::from),
638+
},
639+
)
640+
.await
641+
.into_mas("writing compat access tokens")?;
642+
refresh_token_write_buffer
643+
.write(
644+
mas,
645+
MasNewCompatRefreshToken {
646+
refresh_token_id,
647+
session_id,
648+
access_token_id,
649+
refresh_token,
650+
created_at,
651+
},
652+
)
653+
.await
654+
.into_mas("writing compat refresh tokens")?;
606655
}
607656

608-
write_buffer
657+
access_token_write_buffer
658+
.finish(mas)
659+
.await
660+
.into_mas("writing compat access tokens")?;
661+
662+
refresh_token_write_buffer
609663
.finish(mas)
610664
.await
611665
.into_mas("writing compat refresh tokens")?;

crates/syn2mas/src/synapse_reader/mod.rs

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,13 @@ pub struct SynapseAccessToken {
232232

233233
/// Row of the `refresh_tokens` table in Synapse.
234234
#[derive(Clone, Debug, FromRow, PartialEq, Eq, PartialOrd, Ord)]
235-
pub struct SynapseRefreshToken {
236-
pub id: i64,
235+
pub struct SynapseRefreshableTokenPair {
237236
pub user_id: FullUserId,
238237
pub device_id: String,
239-
pub token: String,
238+
pub access_token: String,
239+
pub refresh_token: String,
240+
pub valid_until_ms: Option<MillisecondsTimestamp>,
241+
pub last_validated: Option<MillisecondsTimestamp>,
240242
}
241243

242244
/// List of Synapse tables that we should acquire an `EXCLUSIVE` lock on.
@@ -413,46 +415,42 @@ impl<'conn> SynapseReader<'conn> {
413415
.map_err(|err| err.into_database("reading Synapse devices"))
414416
}
415417

416-
/// Reads access tokens from the Synapse database.
418+
/// Reads unrefreshable access tokens from the Synapse database.
417419
/// This does not include access tokens used for puppetting users, as those
418-
/// are not supported by MAS. This also does not include access tokens
419-
/// which have been made obsolete by using the associated refresh token
420-
/// and then acknowledging the successor access token by using it to
421-
/// authenticate a request.
422-
pub fn read_access_tokens(
420+
/// are not supported by MAS.
421+
pub fn read_unrefreshable_access_tokens(
423422
&mut self,
424423
) -> impl Stream<Item = Result<SynapseAccessToken, Error>> + '_ {
425424
sqlx::query_as(
426425
"
427426
SELECT
428427
at0.user_id, at0.device_id, at0.token, at0.valid_until_ms, at0.last_validated
429428
FROM access_tokens at0
430-
LEFT JOIN refresh_tokens rt0 ON at0.refresh_token_id = rt0.id
431-
LEFT JOIN access_tokens at1 ON rt0.next_token_id = at1.refresh_token_id
432-
WHERE at0.puppets_user_id IS NULL AND (NOT at1.used OR at1.used IS NULL)
429+
WHERE at0.puppets_user_id IS NULL AND at0.refresh_token_id IS NULL
433430
",
434431
)
435432
.fetch(&mut *self.txn)
436433
.map_err(|err| err.into_database("reading Synapse access tokens"))
437434
}
438435

439-
/// Reads refresh tokens from the Synapse database.
440-
/// This also does not include refresh tokens which have been made obsolete
436+
/// Reads (access token, refresh token) pairs from the Synapse database.
437+
/// This does not include token pairs which have been made obsolete
441438
/// by using the refresh token and then acknowledging the
442439
/// successor access token by using it to authenticate a request.
443440
///
444441
/// The `expiry_ts` and `ultimate_session_expiry_ts` columns are ignored as
445442
/// they are not implemented in MAS.
446443
/// Further, they are unused by any real-world deployment to the best of
447444
/// our knowledge.
448-
pub fn read_refresh_tokens(
445+
pub fn read_refreshable_token_pairs(
449446
&mut self,
450-
) -> impl Stream<Item = Result<SynapseRefreshToken, Error>> + '_ {
447+
) -> impl Stream<Item = Result<SynapseRefreshableTokenPair, Error>> + '_ {
451448
sqlx::query_as(
452449
"
453450
SELECT
454-
rt0.id, rt0.user_id, rt0.device_id, rt0.token, rt0.next_token_id
451+
rt0.user_id, rt0.device_id, at0.token AS access_token, rt0.token AS refresh_token, at0.valid_until_ms, at0.last_validated
455452
FROM refresh_tokens rt0
453+
LEFT JOIN access_tokens at0 ON at0.refresh_token_id = rt0.id AND at0.user_id = rt0.user_id AND at0.device_id = rt0.device_id
456454
LEFT JOIN access_tokens at1 ON at1.refresh_token_id = rt0.next_token_id
457455
WHERE NOT at1.used OR at1.used IS NULL
458456
",
@@ -472,7 +470,7 @@ mod test {
472470

473471
use crate::{
474472
synapse_reader::{
475-
SynapseAccessToken, SynapseDevice, SynapseExternalId, SynapseRefreshToken,
473+
SynapseAccessToken, SynapseDevice, SynapseExternalId, SynapseRefreshableTokenPair,
476474
SynapseThreepid, SynapseUser,
477475
},
478476
SynapseReader,
@@ -553,7 +551,7 @@ mod test {
553551
.expect("failed to make SynapseReader");
554552

555553
let access_tokens: BTreeSet<SynapseAccessToken> = reader
556-
.read_access_tokens()
554+
.read_unrefreshable_access_tokens()
557555
.try_collect()
558556
.await
559557
.expect("failed to read Synapse access tokens");
@@ -573,7 +571,7 @@ mod test {
573571
.expect("failed to make SynapseReader");
574572

575573
let access_tokens: BTreeSet<SynapseAccessToken> = reader
576-
.read_access_tokens()
574+
.read_unrefreshable_access_tokens()
577575
.try_collect()
578576
.await
579577
.expect("failed to read Synapse access tokens");
@@ -592,18 +590,21 @@ mod test {
592590
.expect("failed to make SynapseReader");
593591

594592
let access_tokens: BTreeSet<SynapseAccessToken> = reader
595-
.read_access_tokens()
593+
.read_unrefreshable_access_tokens()
596594
.try_collect()
597595
.await
598596
.expect("failed to read Synapse access tokens");
599597

600-
let refresh_tokens: BTreeSet<SynapseRefreshToken> = reader
601-
.read_refresh_tokens()
598+
let refresh_tokens: BTreeSet<SynapseRefreshableTokenPair> = reader
599+
.read_refreshable_token_pairs()
602600
.try_collect()
603601
.await
604602
.expect("failed to read Synapse refresh tokens");
605603

606-
assert_debug_snapshot!(access_tokens);
604+
assert!(
605+
access_tokens.is_empty(),
606+
"there are no unrefreshable access tokens"
607+
);
607608
assert_debug_snapshot!(refresh_tokens);
608609
}
609610

@@ -618,18 +619,21 @@ mod test {
618619
.expect("failed to make SynapseReader");
619620

620621
let access_tokens: BTreeSet<SynapseAccessToken> = reader
621-
.read_access_tokens()
622+
.read_unrefreshable_access_tokens()
622623
.try_collect()
623624
.await
624625
.expect("failed to read Synapse access tokens");
625626

626-
let refresh_tokens: BTreeSet<SynapseRefreshToken> = reader
627-
.read_refresh_tokens()
627+
let refresh_tokens: BTreeSet<SynapseRefreshableTokenPair> = reader
628+
.read_refreshable_token_pairs()
628629
.try_collect()
629630
.await
630631
.expect("failed to read Synapse refresh tokens");
631632

632-
assert_debug_snapshot!(access_tokens);
633+
assert!(
634+
access_tokens.is_empty(),
635+
"there are no unrefreshable access tokens"
636+
);
633637
assert_debug_snapshot!(refresh_tokens);
634638
}
635639
}

crates/syn2mas/src/synapse_reader/snapshots/syn2mas__synapse_reader__test__read_access_and_refresh_tokens-2.snap

Lines changed: 0 additions & 14 deletions
This file was deleted.

crates/syn2mas/src/synapse_reader/snapshots/syn2mas__synapse_reader__test__read_access_and_refresh_tokens.snap

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
---
22
source: crates/syn2mas/src/synapse_reader/mod.rs
3-
expression: access_tokens
3+
expression: refresh_tokens
44
---
55
{
6-
SynapseAccessToken {
6+
SynapseRefreshableTokenPair {
77
user_id: FullUserId(
88
"@alice:example.com",
99
),
10-
device_id: Some(
11-
"ADEVICE",
12-
),
13-
token: "syt_AAAAAAAAAAAAAA_AAAA",
10+
device_id: "ADEVICE",
11+
access_token: "syt_AAAAAAAAAAAAAA_AAAA",
12+
refresh_token: "syr_cccccccccccc_cccc",
1413
valid_until_ms: None,
1514
last_validated: None,
1615
},

crates/syn2mas/src/synapse_reader/snapshots/syn2mas__synapse_reader__test__read_access_and_unused_refresh_tokens-2.snap

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)