diff --git a/crates/authorization_codes/src/database.rs b/crates/authorization_codes/src/database.rs index 0a86cd2..b13ef06 100644 --- a/crates/authorization_codes/src/database.rs +++ b/crates/authorization_codes/src/database.rs @@ -50,4 +50,11 @@ impl AuthorizationCode { pub async fn use_code(self, conn: impl SqliteExecutor<'_>) -> Result, Error> { Ok(DatabaseAuthorizationCodes::use_code(conn, &self.code).await?) } + + pub async fn use_all_for_user( + &self, + conn: impl SqliteExecutor<'_>, + ) -> Result, Error> { + Ok(DatabaseAuthorizationCodes::use_all_for_user(conn, self.user.as_ref()).await?) + } } diff --git a/crates/authorization_codes/src/lib.rs b/crates/authorization_codes/src/lib.rs index 361c545..1735a6b 100644 --- a/crates/authorization_codes/src/lib.rs +++ b/crates/authorization_codes/src/lib.rs @@ -26,4 +26,7 @@ impl AuthorizationCode { pub fn has_expired(&self) -> bool { self.expires_at < Utc::now() } + pub fn user(&self) -> &UserID { + &self.user + } } diff --git a/crates/database/queries/authorization_codes/use_all_for_user.sql b/crates/database/queries/authorization_codes/use_all_for_user.sql new file mode 100644 index 0000000..1227793 --- /dev/null +++ b/crates/database/queries/authorization_codes/use_all_for_user.sql @@ -0,0 +1,6 @@ +update authorization_codes + +set used_at = CURRENT_TIMESTAMP + +where user is ? + and used_at is null \ No newline at end of file diff --git a/crates/database/queries/refresh_tokens/revoke_all_for_user.sql b/crates/database/queries/refresh_tokens/revoke_all_for_user.sql new file mode 100644 index 0000000..990154a --- /dev/null +++ b/crates/database/queries/refresh_tokens/revoke_all_for_user.sql @@ -0,0 +1,6 @@ +update refresh_tokens + +set revoked_at = CURRENT_TIMESTAMP + +where user is ? + and revoked_at is null \ No newline at end of file diff --git a/crates/database/sqlx-data.json b/crates/database/sqlx-data.json index 32d452e..1155a09 100644 --- a/crates/database/sqlx-data.json +++ b/crates/database/sqlx-data.json @@ -474,6 +474,16 @@ }, "query": "select id,\n created_at as \"created_at: DateTime\",\n updated_at as \"updated_at: DateTime\",\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": { "describe": { "columns": [ @@ -832,6 +842,16 @@ }, "query": "select id,\n created_at as \"created_at: DateTime\",\n updated_at as \"updated_at: DateTime\",\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": { "describe": { "columns": [], diff --git a/crates/database/src/tables/authorization_codes.rs b/crates/database/src/tables/authorization_codes.rs index 3e69a73..fcf5817 100644 --- a/crates/database/src/tables/authorization_codes.rs +++ b/crates/database/src/tables/authorization_codes.rs @@ -54,4 +54,17 @@ impl AuthorizationCodes { Ok((query.rows_affected() == 1).then_some(())) } + + pub async fn use_all_for_user( + conn: impl SqliteExecutor<'_>, + user: &str, + ) -> Result, 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(())) + } } diff --git a/crates/database/src/tables/refresh_tokens.rs b/crates/database/src/tables/refresh_tokens.rs index 2cc2d4c..8b76101 100644 --- a/crates/database/src/tables/refresh_tokens.rs +++ b/crates/database/src/tables/refresh_tokens.rs @@ -58,4 +58,17 @@ impl RefreshTokens { Ok((query.rows_affected() == 1).then_some(())) } + + pub async fn revoke_all_for_user( + conn: impl SqliteExecutor<'_>, + user: &str, + ) -> Result, 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(())) + } } diff --git a/crates/ezidam/src/routes/oauth/redirect.rs b/crates/ezidam/src/routes/oauth/redirect.rs index e69d857..11cf68a 100644 --- a/crates/ezidam/src/routes/oauth/redirect.rs +++ b/crates/ezidam/src/routes/oauth/redirect.rs @@ -34,7 +34,12 @@ pub async fn redirect_page( // Make sure code has not 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( "Authorization code has already been used", )); diff --git a/crates/ezidam/src/routes/root.rs b/crates/ezidam/src/routes/root.rs index 428609f..95ceb6b 100644 --- a/crates/ezidam/src/routes/root.rs +++ b/crates/ezidam/src/routes/root.rs @@ -105,11 +105,17 @@ async fn logout( .await? .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_been_used() { - // TODO: Revoke all tokens for user - // user.revoke_all_refresh_tokens(&mut transaction).await?; - // transaction.commit().await?; + // Revoke all refresh tokens for user + refresh_tokens::RefreshToken::revoke_all_for_user(&mut transaction, refresh_token.user()) + .await?; + + transaction.commit().await?; return Err(Error::forbidden("This refresh token has already been used")); } @@ -129,9 +135,5 @@ async fn logout( transaction.commit().await?; - // Delete cookies - cookie_jar.remove(Cookie::named("access_token")); - cookie_jar.remove(Cookie::named("refresh_token")); - Ok(Redirect::to(uri!(homepage))) } diff --git a/crates/refresh_tokens/src/database.rs b/crates/refresh_tokens/src/database.rs index 6f0cc45..38767bb 100644 --- a/crates/refresh_tokens/src/database.rs +++ b/crates/refresh_tokens/src/database.rs @@ -54,4 +54,11 @@ impl RefreshToken { pub async fn revoke(self, conn: impl SqliteExecutor<'_>) -> Result, Error> { Ok(DatabaseRefreshTokens::revoke(conn, &self.token).await?) } + + pub async fn revoke_all_for_user( + conn: impl SqliteExecutor<'_>, + user: &UserID, + ) -> Result, Error> { + Ok(DatabaseRefreshTokens::revoke_all_for_user(conn, user.as_ref()).await?) + } } diff --git a/crates/refresh_tokens/src/lib.rs b/crates/refresh_tokens/src/lib.rs index 992aa12..2f71dc0 100644 --- a/crates/refresh_tokens/src/lib.rs +++ b/crates/refresh_tokens/src/lib.rs @@ -32,4 +32,8 @@ impl RefreshToken { pub fn is_revoked(&self) -> bool { self.revoked_at.is_some() } + + pub fn user(&self) -> &UserID { + &self.user + } }