revoke all refresh tokens and use all authorization codes for user

This commit is contained in:
Philippe Loctaux 2023-03-18 22:03:03 +01:00
parent 5100aa1b4e
commit 009b8664fd
11 changed files with 94 additions and 8 deletions

View file

@ -50,4 +50,11 @@ impl AuthorizationCode {
pub async fn use_code(self, conn: impl SqliteExecutor<'_>) -> Result<Option<()>, Error> { pub async fn use_code(self, conn: impl SqliteExecutor<'_>) -> Result<Option<()>, Error> {
Ok(DatabaseAuthorizationCodes::use_code(conn, &self.code).await?) Ok(DatabaseAuthorizationCodes::use_code(conn, &self.code).await?)
} }
pub async fn use_all_for_user(
&self,
conn: impl SqliteExecutor<'_>,
) -> Result<Option<()>, Error> {
Ok(DatabaseAuthorizationCodes::use_all_for_user(conn, self.user.as_ref()).await?)
}
} }

View file

@ -26,4 +26,7 @@ impl AuthorizationCode {
pub fn has_expired(&self) -> bool { pub fn has_expired(&self) -> bool {
self.expires_at < Utc::now() self.expires_at < Utc::now()
} }
pub fn user(&self) -> &UserID {
&self.user
}
} }

View file

@ -0,0 +1,6 @@
update authorization_codes
set used_at = CURRENT_TIMESTAMP
where user is ?
and used_at is null

View file

@ -0,0 +1,6 @@
update refresh_tokens
set revoked_at = CURRENT_TIMESTAMP
where user is ?
and revoked_at is null

View file

@ -474,6 +474,16 @@
}, },
"query": "select id,\n created_at as \"created_at: DateTime<Utc>\",\n updated_at as \"updated_at: DateTime<Utc>\",\n is_admin as \"is_admin: bool\",\n username,\n name,\n email,\n password,\n password_recover,\n paper_key,\n is_archived as \"is_archived: bool\"\nfrom users\n\nwhere username is (?)\n" "query": "select id,\n created_at as \"created_at: DateTime<Utc>\",\n updated_at as \"updated_at: DateTime<Utc>\",\n is_admin as \"is_admin: bool\",\n username,\n name,\n email,\n password,\n password_recover,\n paper_key,\n is_archived as \"is_archived: bool\"\nfrom users\n\nwhere username is (?)\n"
}, },
"c00e5fce25caebdeeb24db20880e6c2210f583cddb0d478075f78124258712dd": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 1
}
},
"query": "update refresh_tokens\n\nset revoked_at = CURRENT_TIMESTAMP\n\nwhere user is ?\n and revoked_at is null"
},
"c5a57c971d07532ec0cc897b5ac06e0814e506f9c24647d1eaf44174dc0a5954": { "c5a57c971d07532ec0cc897b5ac06e0814e506f9c24647d1eaf44174dc0a5954": {
"describe": { "describe": {
"columns": [ "columns": [
@ -832,6 +842,16 @@
}, },
"query": "select id,\n created_at as \"created_at: DateTime<Utc>\",\n updated_at as \"updated_at: DateTime<Utc>\",\n label,\n redirect_uri,\n secret,\n is_confidential as \"is_confidential: bool\",\n is_archived as \"is_archived: bool\"\nfrom apps\n\nwhere id is (?)\n and redirect_uri is (?)\n and is_archived is 0\n" "query": "select id,\n created_at as \"created_at: DateTime<Utc>\",\n updated_at as \"updated_at: DateTime<Utc>\",\n label,\n redirect_uri,\n secret,\n is_confidential as \"is_confidential: bool\",\n is_archived as \"is_archived: bool\"\nfrom apps\n\nwhere id is (?)\n and redirect_uri is (?)\n and is_archived is 0\n"
}, },
"ebe28f418d28303b2efe1fe192a63538d29d75c57b67d5eac1ac4ceaa1472a5c": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Right": 1
}
},
"query": "update authorization_codes\n\nset used_at = CURRENT_TIMESTAMP\n\nwhere user is ?\n and used_at is null"
},
"ed27954feb3e21b5c519ccd0312526e68fb3d88a1feb28bdafb414e990da55e8": { "ed27954feb3e21b5c519ccd0312526e68fb3d88a1feb28bdafb414e990da55e8": {
"describe": { "describe": {
"columns": [], "columns": [],

View file

@ -54,4 +54,17 @@ impl AuthorizationCodes {
Ok((query.rows_affected() == 1).then_some(())) Ok((query.rows_affected() == 1).then_some(()))
} }
pub async fn use_all_for_user(
conn: impl SqliteExecutor<'_>,
user: &str,
) -> Result<Option<()>, Error> {
let query: SqliteQueryResult =
sqlx::query_file!("queries/authorization_codes/use_all_for_user.sql", user)
.execute(conn)
.await
.map_err(handle_error)?;
Ok((query.rows_affected() >= 1).then_some(()))
}
} }

View file

@ -58,4 +58,17 @@ impl RefreshTokens {
Ok((query.rows_affected() == 1).then_some(())) Ok((query.rows_affected() == 1).then_some(()))
} }
pub async fn revoke_all_for_user(
conn: impl SqliteExecutor<'_>,
user: &str,
) -> Result<Option<()>, Error> {
let query: SqliteQueryResult =
sqlx::query_file!("queries/refresh_tokens/revoke_all_for_user.sql", user)
.execute(conn)
.await
.map_err(handle_error)?;
Ok((query.rows_affected() >= 1).then_some(()))
}
} }

View file

@ -34,7 +34,12 @@ pub async fn redirect_page(
// Make sure code has not been used // Make sure code has not been used
if code.has_been_used() { if code.has_been_used() {
// TODO: revoke all codes and refresh tokens for user // Revoke all codes and refresh tokens for user
code.use_all_for_user(&mut transaction).await?;
RefreshToken::revoke_all_for_user(&mut transaction, code.user()).await?;
transaction.commit().await?;
return Err(Error::bad_request( return Err(Error::bad_request(
"Authorization code has already been used", "Authorization code has already been used",
)); ));

View file

@ -105,11 +105,17 @@ async fn logout(
.await? .await?
.ok_or_else(|| Error::not_found("Unknown refresh token"))?; .ok_or_else(|| Error::not_found("Unknown refresh token"))?;
// Delete cookies
cookie_jar.remove(Cookie::named("access_token"));
cookie_jar.remove(Cookie::named("refresh_token"));
// If refresh token has already been used // If refresh token has already been used
if refresh_token.has_been_used() { if refresh_token.has_been_used() {
// TODO: Revoke all tokens for user // Revoke all refresh tokens for user
// user.revoke_all_refresh_tokens(&mut transaction).await?; refresh_tokens::RefreshToken::revoke_all_for_user(&mut transaction, refresh_token.user())
// transaction.commit().await?; .await?;
transaction.commit().await?;
return Err(Error::forbidden("This refresh token has already been used")); return Err(Error::forbidden("This refresh token has already been used"));
} }
@ -129,9 +135,5 @@ async fn logout(
transaction.commit().await?; transaction.commit().await?;
// Delete cookies
cookie_jar.remove(Cookie::named("access_token"));
cookie_jar.remove(Cookie::named("refresh_token"));
Ok(Redirect::to(uri!(homepage))) Ok(Redirect::to(uri!(homepage)))
} }

View file

@ -54,4 +54,11 @@ impl RefreshToken {
pub async fn revoke(self, conn: impl SqliteExecutor<'_>) -> Result<Option<()>, Error> { pub async fn revoke(self, conn: impl SqliteExecutor<'_>) -> Result<Option<()>, Error> {
Ok(DatabaseRefreshTokens::revoke(conn, &self.token).await?) Ok(DatabaseRefreshTokens::revoke(conn, &self.token).await?)
} }
pub async fn revoke_all_for_user(
conn: impl SqliteExecutor<'_>,
user: &UserID,
) -> Result<Option<()>, Error> {
Ok(DatabaseRefreshTokens::revoke_all_for_user(conn, user.as_ref()).await?)
}
} }

View file

@ -32,4 +32,8 @@ impl RefreshToken {
pub fn is_revoked(&self) -> bool { pub fn is_revoked(&self) -> bool {
self.revoked_at.is_some() self.revoked_at.is_some()
} }
pub fn user(&self) -> &UserID {
&self.user
}
} }