diff --git a/.sqlx/query-623516eda004bf65b650978e95c6fbc1f29651cb68c2c44241f2b69a7de31120.json b/.sqlx/query-623516eda004bf65b650978e95c6fbc1f29651cb68c2c44241f2b69a7de31120.json new file mode 100644 index 0000000..7e31a78 --- /dev/null +++ b/.sqlx/query-623516eda004bf65b650978e95c6fbc1f29651cb68c2c44241f2b69a7de31120.json @@ -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" +} diff --git a/src/store.rs b/src/store.rs index c270e07..dacd5a0 100644 --- a/src/store.rs +++ b/src/store.rs @@ -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(()) } diff --git a/src/web/oauth_openid/ext_codes.rs b/src/web/oauth_openid/ext_codes.rs index 9341c3f..212e506 100644 --- a/src/web/oauth_openid/ext_codes.rs +++ b/src/web/oauth_openid/ext_codes.rs @@ -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, }, } diff --git a/src/web/oauth_openid/token.rs b/src/web/oauth_openid/token.rs index 1e9b942..68e69e5 100644 --- a/src/web/oauth_openid/token.rs +++ b/src/web/oauth_openid/token.rs @@ -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