Give refresh tokens the same invalidation treatment as access tokens

This commit is contained in:
Olivier 'reivilibre' 2024-01-14 19:58:38 +00:00
parent c5eef85e6b
commit 2d23eae641
4 changed files with 50 additions and 8 deletions

View File

@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "\n DELETE FROM application_refresh_tokens WHERE refresh_token_hash = $1\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Bytea"
]
},
"nullable": []
},
"hash": "623516eda004bf65b650978e95c6fbc1f29651cb68c2c44241f2b69a7de31120"
}

View File

@ -85,8 +85,8 @@ impl<'a, 'txn> IdCoopStoreTxn<'a, 'txn> {
pub async fn invalidate_access_token_by_hash( pub async fn invalidate_access_token_by_hash(
&mut self, &mut self,
access_token_hash: &[u8], access_token_hash: &[u8],
refresh_token_hash: &[u8],
) -> eyre::Result<()> { ) -> eyre::Result<()> {
// TODO presumably we also want to do the same for refresh tokens
sqlx::query!( sqlx::query!(
" "
DELETE FROM application_access_tokens WHERE access_token_hash = $1 DELETE FROM application_access_tokens WHERE access_token_hash = $1
@ -96,6 +96,15 @@ impl<'a, 'txn> IdCoopStoreTxn<'a, 'txn> {
.execute(&mut **self.txn) .execute(&mut **self.txn)
.await .await
.context("failed to invalidate access token by hash")?; .context("failed to invalidate access token by hash")?;
sqlx::query!(
"
DELETE FROM application_refresh_tokens WHERE refresh_token_hash = $1
",
refresh_token_hash
)
.execute(&mut **self.txn)
.await
.context("failed to invalidate refresh token by hash")?;
Ok(()) Ok(())
} }

View File

@ -64,6 +64,7 @@ pub struct RedeemedAuthCode {
/// The access token hash of whoever redeemed the auth code. /// The access token hash of whoever redeemed the auth code.
/// Is used to invalidate the access token if an auth code is double-redeemed. /// Is used to invalidate the access token if an auth code is double-redeemed.
access_token_hash: AccessTokenHash, access_token_hash: AccessTokenHash,
refresh_token_hash: RefreshTokenHash,
} }
#[derive(Default)] #[derive(Default)]
@ -84,10 +85,12 @@ impl VolatileCodeStoreInner {
&mut self, &mut self,
auth_code: &AuthCode, auth_code: &AuthCode,
access_token_hash: AccessTokenHash, access_token_hash: AccessTokenHash,
refresh_token_hash: RefreshTokenHash,
) -> CodeRedemption { ) -> CodeRedemption {
if let Some(conflicted) = self.conflictable_codes.get(auth_code) { if let Some(conflicted) = self.conflictable_codes.get(auth_code) {
return CodeRedemption::Conflicted { return CodeRedemption::Conflicted {
access_token_to_invalidate: conflicted.access_token_hash.clone(), access_token_to_invalidate: conflicted.access_token_hash.clone(),
refresh_token_to_invalidate: conflicted.refresh_token_hash.clone(),
}; };
} }
@ -95,8 +98,13 @@ impl VolatileCodeStoreInner {
return CodeRedemption::Invalid; return CodeRedemption::Invalid;
}; };
self.conflictable_codes self.conflictable_codes.insert(
.insert(auth_code, RedeemedAuthCode { access_token_hash }); auth_code,
RedeemedAuthCode {
access_token_hash,
refresh_token_hash,
},
);
CodeRedemption::Valid { binding } CodeRedemption::Valid { binding }
} }
@ -186,9 +194,10 @@ impl VolatileCodeStore {
&self, &self,
auth_code: &AuthCode, auth_code: &AuthCode,
access_token_hash: AccessTokenHash, access_token_hash: AccessTokenHash,
refresh_token_hash: RefreshTokenHash,
) -> CodeRedemption { ) -> CodeRedemption {
let mut inner = self.inner.lock().unwrap(); let mut inner = self.inner.lock().unwrap();
inner.redeem(auth_code, access_token_hash) inner.redeem(auth_code, access_token_hash, refresh_token_hash)
} }
pub fn add_redeemable(&self, auth_code: AuthCode, binding: AuthCodeBinding, expires_at: u64) { pub fn add_redeemable(&self, auth_code: AuthCode, binding: AuthCodeBinding, expires_at: u64) {
@ -204,6 +213,8 @@ pub enum CodeRedemption {
Valid { binding: AuthCodeBinding }, Valid { binding: AuthCodeBinding },
/// That auth code had already been redeemed: please invalidate the given access token and reject this redemption. /// That auth code had already been redeemed: please invalidate the given access token and reject this redemption.
Conflicted { Conflicted {
// TODO what if the token was refreshed since?
access_token_to_invalidate: AccessTokenHash, access_token_to_invalidate: AccessTokenHash,
refresh_token_to_invalidate: RefreshTokenHash,
}, },
} }

View File

@ -176,7 +176,11 @@ pub async fn oidc_token(
let refresh_token_hash: RefreshTokenHash = Blake2s256::digest(&refresh_token).into(); let refresh_token_hash: RefreshTokenHash = Blake2s256::digest(&refresh_token).into();
// Redeem the auth code so we can check it and then maybe issue an access token. // Redeem the auth code so we can check it and then maybe issue an access token.
let binding = match code_store.redeem(&auth_code, access_token_hash.clone()) { let binding = match code_store.redeem(
&auth_code,
access_token_hash.clone(),
refresh_token_hash.clone(),
) {
CodeRedemption::Invalid => { CodeRedemption::Invalid => {
return ( return (
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
@ -190,12 +194,16 @@ pub async fn oidc_token(
CodeRedemption::Valid { binding } => binding, CodeRedemption::Valid { binding } => binding,
CodeRedemption::Conflicted { CodeRedemption::Conflicted {
access_token_to_invalidate, access_token_to_invalidate,
refresh_token_to_invalidate,
} => { } => {
// Invalidate the access token that was issued before // Invalidate the access token that was issued before
if let Err(err) = store if let Err(err) = store
.txn(move |mut txn| { .txn(move |mut txn| {
Box::pin(async move { Box::pin(async move {
txn.invalidate_access_token_by_hash(&access_token_to_invalidate) txn.invalidate_access_token_by_hash(
&access_token_to_invalidate,
&refresh_token_to_invalidate,
)
.await .await
.context("failed to invalidate access token") .context("failed to invalidate access token")
}) })