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(
&mut self,
access_token_hash: &[u8],
refresh_token_hash: &[u8],
) -> eyre::Result<()> {
// TODO presumably we also want to do the same for refresh tokens
sqlx::query!(
"
DELETE FROM application_access_tokens WHERE access_token_hash = $1
@ -96,6 +96,15 @@ impl<'a, 'txn> IdCoopStoreTxn<'a, 'txn> {
.execute(&mut **self.txn)
.await
.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(())
}

View File

@ -64,6 +64,7 @@ pub struct RedeemedAuthCode {
/// The access token hash of whoever redeemed the auth code.
/// Is used to invalidate the access token if an auth code is double-redeemed.
access_token_hash: AccessTokenHash,
refresh_token_hash: RefreshTokenHash,
}
#[derive(Default)]
@ -84,10 +85,12 @@ impl VolatileCodeStoreInner {
&mut self,
auth_code: &AuthCode,
access_token_hash: AccessTokenHash,
refresh_token_hash: RefreshTokenHash,
) -> CodeRedemption {
if let Some(conflicted) = self.conflictable_codes.get(auth_code) {
return CodeRedemption::Conflicted {
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;
};
self.conflictable_codes
.insert(auth_code, RedeemedAuthCode { access_token_hash });
self.conflictable_codes.insert(
auth_code,
RedeemedAuthCode {
access_token_hash,
refresh_token_hash,
},
);
CodeRedemption::Valid { binding }
}
@ -186,9 +194,10 @@ impl VolatileCodeStore {
&self,
auth_code: &AuthCode,
access_token_hash: AccessTokenHash,
refresh_token_hash: RefreshTokenHash,
) -> CodeRedemption {
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) {
@ -204,6 +213,8 @@ pub enum CodeRedemption {
Valid { binding: AuthCodeBinding },
/// That auth code had already been redeemed: please invalidate the given access token and reject this redemption.
Conflicted {
// TODO what if the token was refreshed since?
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();
// 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 => {
return (
StatusCode::BAD_REQUEST,
@ -190,14 +194,18 @@ pub async fn oidc_token(
CodeRedemption::Valid { binding } => binding,
CodeRedemption::Conflicted {
access_token_to_invalidate,
refresh_token_to_invalidate,
} => {
// Invalidate the access token that was issued before
if let Err(err) = store
.txn(move |mut txn| {
Box::pin(async move {
txn.invalidate_access_token_by_hash(&access_token_to_invalidate)
.await
.context("failed to invalidate access token")
txn.invalidate_access_token_by_hash(
&access_token_to_invalidate,
&refresh_token_to_invalidate,
)
.await
.context("failed to invalidate access token")
})
})
.await