Give refresh tokens the same invalidation treatment as access tokens
This commit is contained in:
parent
c5eef85e6b
commit
2d23eae641
14
.sqlx/query-623516eda004bf65b650978e95c6fbc1f29651cb68c2c44241f2b69a7de31120.json
generated
Normal file
14
.sqlx/query-623516eda004bf65b650978e95c6fbc1f29651cb68c2c44241f2b69a7de31120.json
generated
Normal 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"
|
||||||
|
}
|
11
src/store.rs
11
src/store.rs
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user