diff --git a/backend/src/auth.rs b/backend/src/auth.rs index bc9e229..2b453e1 100644 --- a/backend/src/auth.rs +++ b/backend/src/auth.rs @@ -1,10 +1,8 @@ use crate::auth_persistence::AuthStorage; -use crate::csrf::{CsrfState, set_csrf_cookie}; use crate::items; use crate::proto_utils::Proto; use rocket::State; use rocket::futures::lock::Mutex; -use rocket::http::CookieJar; use std::collections::{HashMap, HashSet}; use uuid::Uuid; @@ -130,9 +128,7 @@ impl<'r> rocket::request::FromRequest<'r> for AdminToken { #[post("/login", data = "")] pub async fn login( state: &State, - csrf_state: &State, user_list: &State>>, - jar: &CookieJar<'_>, request: Proto, ) -> items::LoginResponse { let req = request.into_inner(); @@ -148,9 +144,6 @@ pub async fn login( tokens.insert(token.clone(), req.username.clone()); state.storage.save_tokens(&tokens); - let csrf_token = csrf_state.generate_token(); - set_csrf_cookie(jar, &csrf_token); - return items::LoginResponse { token, success: true, @@ -191,8 +184,6 @@ pub async fn logout( pub async fn get_auth_status( state: &State, admin_state: &State, - csrf_state: &State, - jar: &CookieJar<'_>, request: Proto, ) -> items::AuthStatusResponse { let req = request.into_inner(); @@ -202,9 +193,6 @@ pub async fn get_auth_status( let admins = admin_state.admins.lock().await; let is_admin = crate::store::is_admin(username, &admins); - let csrf_token = csrf_state.generate_token(); - set_csrf_cookie(jar, &csrf_token); - items::AuthStatusResponse { authenticated: true, username: username.clone(), diff --git a/backend/src/csrf.rs b/backend/src/csrf.rs deleted file mode 100644 index d516b93..0000000 --- a/backend/src/csrf.rs +++ /dev/null @@ -1,95 +0,0 @@ -use rocket::Build; -use rocket::Request; -use rocket::Rocket; -use rocket::fairing::{Fairing, Info, Kind}; -use rocket::http::{Cookie, CookieJar, SameSite, Status}; -use rocket::request::{FromRequest, Outcome}; -use std::collections::HashSet; -use std::sync::Arc; -use uuid::Uuid; - -const CSRF_COOKIE_NAME: &str = "csrf_token"; -const CSRF_HEADER_NAME: &str = "X-CSRF-Token"; -const CSRF_EXPIRATION_HOURS: u64 = 24; - -#[derive(Clone)] -pub struct CsrfToken(pub String); - -#[derive(Clone)] -pub struct CsrfState { - tokens: Arc>>, -} - -impl CsrfState { - pub fn new() -> Self { - Self { - tokens: Arc::new(std::sync::Mutex::new(HashSet::new())), - } - } - - pub fn generate_token(&self) -> String { - let token = Uuid::new_v4().to_string(); - self.tokens.lock().unwrap().insert(token.clone()); - token - } - - pub fn validate_token(&self, token: &str) -> bool { - self.tokens.lock().unwrap().remove(token) - } -} - -impl Default for CsrfState { - fn default() -> Self { - Self::new() - } -} - -#[rocket::async_trait] -impl<'r> FromRequest<'r> for CsrfToken { - type Error = (); - - async fn from_request(request: &'r Request<'_>) -> Outcome { - let header_token = request.headers().get_one(CSRF_HEADER_NAME); - let cookie_jar = request.guard::<&CookieJar<'_>>().await; - - let cookie_token = match cookie_jar { - Outcome::Success(jar) => jar - .get(CSRF_COOKIE_NAME) - .map(|c: &Cookie<'_>| c.value().to_string()), - _ => None, - }; - - match (header_token, cookie_token) { - (Some(header), Some(cookie)) if header == cookie => { - Outcome::Success(CsrfToken(header.to_string())) - } - _ => Outcome::Error((Status::Forbidden, ())), - } - } -} - -pub struct CsrfFairing; - -#[rocket::async_trait] -impl Fairing for CsrfFairing { - fn info(&self) -> Info { - Info { - name: "CSRF Protection", - kind: Kind::Ignite, - } - } - - async fn on_ignite(&self, rocket: Rocket) -> Result, Rocket> { - Ok(rocket.manage(CsrfState::new())) - } -} - -pub fn set_csrf_cookie(jar: &CookieJar<'_>, token: &str) { - jar.add( - Cookie::build((CSRF_COOKIE_NAME, token.to_owned())) - .http_only(true) - .same_site(SameSite::Strict) - .max_age(rocket::time::Duration::hours(CSRF_EXPIRATION_HOURS as i64)) - .path("/"), - ); -} diff --git a/backend/src/lib.rs b/backend/src/lib.rs index bb3d80a..075f018 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -7,13 +7,11 @@ pub mod items { pub mod auth; pub mod auth_persistence; -pub mod csrf; pub mod proto_utils; pub mod security_headers; pub mod store; pub mod validation; pub use auth::AdminState; -pub use csrf::{CsrfFairing, CsrfState, CsrfToken, set_csrf_cookie}; pub use security_headers::SecurityHeaders; pub use store::User; diff --git a/backend/src/main.rs b/backend/src/main.rs index 6314fb9..a24ef37 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -4,7 +4,6 @@ use rocket::futures::lock::Mutex; use backend::auth; use backend::auth::AdminState; -use backend::csrf::{CsrfFairing, CsrfToken}; use backend::items::{self, Game}; use backend::proto_utils; use backend::security_headers::SecurityHeaders; @@ -90,7 +89,6 @@ async fn get_games( #[post("/game", data = "", rank = 1)] async fn add_game( _token: auth::Token, - _csrf: CsrfToken, game_list: &rocket::State>>, user_list: &rocket::State>>, game: proto_utils::Proto, @@ -133,7 +131,6 @@ async fn add_game( #[patch("/game", data = "", rank = 1)] async fn update_game( _token: auth::AdminToken, - _csrf: CsrfToken, game_list: &rocket::State>>, user_list: &rocket::State>>, game: proto_utils::Proto, @@ -196,7 +193,6 @@ async fn update_game( #[delete("/game/", rank = 1)] async fn delete_game( _token: auth::AdminToken, - _csrf: CsrfToken, game_list: &rocket::State<Mutex<Vec<Game>>>, user_list: &rocket::State<Mutex<Vec<User>>>, title: &str, @@ -227,7 +223,6 @@ async fn delete_game( #[post("/refresh", rank = 1)] async fn refresh_state( _token: auth::AdminToken, - _csrf: CsrfToken, game_list: &rocket::State<Mutex<Vec<Game>>>, user_list: &rocket::State<Mutex<Vec<User>>>, admin_state: &rocket::State<AdminState>, @@ -256,7 +251,6 @@ async fn refresh_state( #[post("/opinion", data = "<req>", rank = 1)] async fn add_opinion( token: auth::Token, - _csrf: CsrfToken, game_list: &rocket::State<Mutex<Vec<Game>>>, user_list: &rocket::State<Mutex<Vec<User>>>, req: proto_utils::Proto<items::AddOpinionRequest>, @@ -309,7 +303,6 @@ async fn add_opinion( #[patch("/opinion", data = "<req>", rank = 1)] async fn remove_opinion( token: auth::Token, - _csrf: CsrfToken, game_list: &rocket::State<Mutex<Vec<Game>>>, user_list: &rocket::State<Mutex<Vec<User>>>, req: proto_utils::Proto<items::RemoveOpinionRequest>, @@ -495,7 +488,6 @@ async fn main() -> Result<(), std::io::Error> { rocket::build() .attach(SecurityHeaders) - .attach(CsrfFairing) .manage(Mutex::new(user_list)) .manage(auth::AuthState::new()) .manage(auth::AdminState::new()) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 9926158..668e121 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -1,26 +1,16 @@ import { AuthStatusRequest, AuthStatusResponse } from "../items"; -export const getCsrfToken = (): string | null => { - const match = document.cookie.match(/csrf_token=([^;]+)/); - return match ? decodeURIComponent(match[1]) : null; -}; - export const apiFetch = async ( url: string, options: RequestInit = {} ): Promise<Response> => { const token = localStorage.getItem("token"); - const csrfToken = getCsrfToken(); const headers = new Headers(options.headers); if (token) { headers.set("Authorization", `Bearer ${token}`); } - if (csrfToken) { - headers.set("X-CSRF-Token", csrfToken); - } - const config = { ...options, headers,