diff --git a/src/server/middleware.rs b/src/server/middleware.rs index 401f669..41c08a4 100644 --- a/src/server/middleware.rs +++ b/src/server/middleware.rs @@ -1,6 +1,6 @@ use http::{Request, Response}; use std::task::{Context, Poll}; -use tower_cookies::{Cookies}; +use tower_cookies::Cookies; use tower_layer::Layer; use tower_service::Service; diff --git a/src/server/routes.rs b/src/server/routes.rs index 6ce72e2..15c1240 100644 --- a/src/server/routes.rs +++ b/src/server/routes.rs @@ -115,35 +115,38 @@ async fn get_oauth_signin( async fn get_oauth_callback( app: State, cookies: Cookies, - session_id: SessionId, body: Query, ) -> Result { - if let Some(mut cookie) = cookies.get("code_verifier") { + if let Some(mut verifier_cookie) = cookies.get("code_verifier") { let response = app .supabase .exchange_code_for_session(ExchangeOAuthCode { auth_code: &body.code, - code_verifier: cookie.value(), + code_verifier: verifier_cookie.value(), }) .await?; - app.add_session(session_id, response).await; - cookie.make_removal(); + verifier_cookie.make_removal(); + cookies.add(auth::cookie( + "access_token", + response.access_token, + time::Duration::seconds(response.expires_in.into()), + )); + cookies.add(auth::cookie( + "refresh_token", + response.refresh_token, + time::Duration::WEEK, + )); + Ok((Redirect::to("/lobby")).into_response()) } else { log::error!("no code verifier"); - return Ok((Redirect::to("/login")).into_response()); - }; - - Ok((Redirect::to("/lobby")).into_response()) + Ok((Redirect::to("/login")).into_response()) + } } async fn post_logout(app: State, cookies: Cookies) -> AppResponse { - let session = app.session(&cookies).await; - if let Some(session) = session { - app.supabase.logout(&session.access_token).await?; - Ok(().into_response()) - } else { - Ok((StatusCode::BAD_REQUEST, "not logged in").into_response()) - } + app.logout(cookies).await?; + + Ok(().into_response()) } async fn get_lobby(app: State) -> impl IntoResponse { @@ -311,22 +314,24 @@ async fn get_game(_app: State, _cookies: Cookies) -> impl IntoResponse */ } -async fn get_game_actions( - app: State, - cookies: Cookies, -) -> Result, ErrorResponse> { +async fn get_game_actions(app: State, cookies: Cookies) -> AppResponse { + Ok(().into_response()) + /* let user_id = app.session(&cookies).await.map(|s| s.user_id); let mut game = app.game.lock().unwrap(); let game = game.as_mut().ok_or("game hasn't started")?; MenuTemplate::from(game, user_id).to_html() + */ } async fn get_game_city( app: State, cookies: Cookies, path: Path, -) -> Result, ErrorResponse> { +) -> AppResponse { + Ok(().into_response()) + /* let session = app.session(&cookies).await; let game = app.game.lock().unwrap(); if let Some(game) = game.as_ref() { @@ -339,6 +344,7 @@ async fn get_game_city( } else { Err(ErrorResponse::from(Redirect::to("/lobby"))) } + */ } async fn get_ws( @@ -346,6 +352,8 @@ async fn get_ws( cookies: Cookies, ws: WebSocketUpgrade, ) -> impl IntoResponse { + "TODO" + /* if let Some(session) = state.session(&cookies).await { ws.on_upgrade(move |socket| { crate::server::ws::handle_socket(state, session.user_id, socket) @@ -354,6 +362,7 @@ async fn get_ws( } else { StatusCode::BAD_REQUEST.into_response() } + */ } fn form_feedback(err: Cow<'static, str>) -> ErrorResponse { @@ -370,12 +379,11 @@ async fn submit_game_action( cookies: Cookies, action: axum::Json, ) -> Result { - let session = if let Some(session) = app.session(&cookies).await { - session + let user_id = if let Ok(user_id) = app.user_id(cookies).await { + user_id } else { Err(AnyhowError(anyhow::anyhow!("not logged").into()).into_response())? }; - let user_id = session.user_id; let mut game = app.game.lock().unwrap(); let game = game.as_mut().ok_or("game hasn't started")?; log::info!("{:#?}", action.0); @@ -525,13 +533,13 @@ async fn get_game_menu( cookies: Cookies, path: Path, ) -> Result { - let session = app.session(&cookies).await.ok_or("missing cookie")?; + let user_id = app.user_id(cookies).await.map_err(AnyhowError)?; let mut game = app.game.lock().unwrap(); - let game = game.as_mut().ok_or("game hasn't started")?; + let game = game.as_mut().ok_or("game hasn't started".into_response())?; let active_player = game.active_player()?; - if session.user_id != active_player.id { + if user_id != active_player.id { return Err((StatusCode::BAD_REQUEST, "not your turn!").into()); } diff --git a/src/server/state.rs b/src/server/state.rs index 62ff412..1eee921 100644 --- a/src/server/state.rs +++ b/src/server/state.rs @@ -1,90 +1,47 @@ -use super::supabase::DiscordSigninResponse; use super::ws::WebSockets; use crate::server::supabase::SupabaseAnonClient; -use crate::server::ws; use crate::strings::UserName; use crate::strings::{AccessToken, RefreshToken, SessionId, UserId}; use crate::{game::Game, lobby::Lobby}; use serde::Deserialize; -use std::collections::HashMap; use std::sync::{Arc, Mutex}; -use tokio::sync::RwLock; use tower_cookies::Cookies; fn new_arc_mutex(item: T) -> Arc> { Arc::new(std::sync::Mutex::new(item)) } -pub struct SessionInfo { +struct SessionInfo { pub user_id: UserId, pub access_token: AccessToken, pub refresh_token: RefreshToken, pub expires_in: u64, } -#[derive(Clone)] +#[derive(Default, Clone)] pub struct AppState { pub lobby: Arc>, pub game: Arc>>, pub supabase: SupabaseAnonClient, - pub logged_in: Arc>>, pub ws_connections: Arc>, } -impl Default for AppState { - fn default() -> Self { - Self { - lobby: new_arc_mutex(Lobby::default()), - game: new_arc_mutex(None), - supabase: SupabaseAnonClient::new(), - logged_in: Arc::new(RwLock::new(HashMap::default())), - ws_connections: Arc::new(Mutex::new(ws::WebSockets::default())), - } - } -} - impl AppState { - pub async fn session(&self, cookies: &Cookies) -> Option { - let session_id = cookies.get("session_id")?; - self.logged_in - .read() - .await - .get(&SessionId::new(session_id.value())) - .cloned() + pub async fn user_id(&self, cookies: Cookies) -> anyhow::Result { + anyhow::bail!("TODO: app.user_id()") } + pub async fn logout(&self, cookies: Cookies) -> anyhow::Result<()> { + if let Some(mut access_token) = cookies.get("access_token") { + self.supabase.logout(access_token.value()).await?; + access_token.make_removal(); + } - pub async fn logout(&self, cookies: &Cookies) -> anyhow::Result<()> { - let session_id = cookies - .get("session_id") - .ok_or(anyhow::anyhow!("not actually logged in"))?; - let session_id = SessionId::new(session_id.value()); - let mut lock = self.logged_in.write().await; - let session = lock - .remove(&session_id) - .ok_or(anyhow::anyhow!("lost track of session"))?; - drop(lock); + if let Some(mut refresh_token) = cookies.get("refresh_token") { + refresh_token.make_removal(); + } - self.supabase.logout(&session.access_token).await?; - self.ws_connections - .lock() - .unwrap() - .0 - .remove(&session.user_id); Ok(()) } - - pub async fn add_session(&self, session_id: SessionId, signin: DiscordSigninResponse) { - let session = UserSession { - access_token: signin.access_token, - refresh_token: signin.refresh_token, - username: None, - user_id: signin.user.id, - }; - self.logged_in - .write() - .await - .insert(session_id.clone(), session.clone()); - } } #[derive(Clone)] diff --git a/src/server/supabase.rs b/src/server/supabase.rs index 4119611..b92db2f 100644 --- a/src/server/supabase.rs +++ b/src/server/supabase.rs @@ -1,8 +1,8 @@ -use crate::strings::{AccessToken, RefreshToken, UserId}; +use crate::strings::{RefreshToken, UserId}; use arcstr::ArcStr; use reqwest::Response; use serde::{Deserialize, Serialize}; -use std::{borrow::Cow, env}; +use std::env; use thiserror::Error; use super::state::Signin; @@ -13,6 +13,11 @@ pub struct SupabaseAnonClient { pub url: ArcStr, pub api_key: ArcStr, } +impl Default for SupabaseAnonClient { + fn default() -> Self { + Self::new() + } +} impl SupabaseAnonClient { pub fn new() -> Self { @@ -25,7 +30,7 @@ impl SupabaseAnonClient { pub async fn exchange_code_for_session( &self, body: ExchangeOAuthCode<'_>, - ) -> anyhow::Result { + ) -> anyhow::Result { let response: Response = self .client .post(&format!("{}/auth/v1/token?grant_type=pkce", self.url)) @@ -36,13 +41,13 @@ impl SupabaseAnonClient { .await?; let body = response.bytes().await?; - let json = serde_json::from_slice::>(&body)?; - let json: Result = json.into(); + let json = serde_json::from_slice::>(&body)?; + let json: Result = json.into(); let json = json?; Ok(json) } - pub async fn refresh(&self, refresh_token: RefreshToken) -> anyhow::Result { + pub async fn refresh(&self, refresh_token: &str) -> anyhow::Result { let data = self .client .post(&format!( @@ -59,7 +64,7 @@ impl SupabaseAnonClient { Ok(data) } - pub async fn logout(&self, access_token: &AccessToken) -> anyhow::Result<()> { + pub async fn logout(&self, access_token: &str) -> anyhow::Result<()> { self.client .post(&format!("{}/auth/v1/logout", self.url)) .header("apikey", self.api_key.as_str()) @@ -70,31 +75,25 @@ impl SupabaseAnonClient { Ok(()) } } -/* DTOS */ +/* DTOS */ #[derive(Debug, Serialize)] pub struct ExchangeOAuthCode<'a> { pub auth_code: &'a str, pub code_verifier: &'a str, } -#[derive(Serialize, Deserialize)] -pub struct EmailCreds<'a> { - pub email: Cow<'a, str>, - pub password: Cow<'a, str>, -} - #[derive(Serialize)] -pub struct RefreshTokenBody { - refresh_token: RefreshToken, +pub struct RefreshTokenBody<'a> { + refresh_token: &'a str, } #[derive(Deserialize, Debug)] -pub struct DiscordSigninResponse { - pub access_token: AccessToken, - pub refresh_token: RefreshToken, - pub expires_in: u64, +pub struct OAuthSigninResponse { + pub access_token: String, + pub refresh_token: String, pub user: SupabaseUser, + pub expires_in: u32, } #[derive(Deserialize, Debug)]