Skip to content

Commit

Permalink
More cookies more stateless
Browse files Browse the repository at this point in the history
  • Loading branch information
CharlesTaylor7 committed May 19, 2024
1 parent 70ae570 commit aa73f18
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 103 deletions.
2 changes: 1 addition & 1 deletion src/server/middleware.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
62 changes: 35 additions & 27 deletions src/server/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,35 +115,38 @@ async fn get_oauth_signin(
async fn get_oauth_callback(
app: State<AppState>,
cookies: Cookies,
session_id: SessionId,
body: Query<OAuthCallbackCode>,
) -> Result<Response, AnyhowError> {
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<AppState>, 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<AppState>) -> impl IntoResponse {
Expand Down Expand Up @@ -311,22 +314,24 @@ async fn get_game(_app: State<AppState>, _cookies: Cookies) -> impl IntoResponse
*/
}

async fn get_game_actions(
app: State<AppState>,
cookies: Cookies,
) -> Result<Html<String>, ErrorResponse> {
async fn get_game_actions(app: State<AppState>, 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<AppState>,
cookies: Cookies,
path: Path<UserName>,
) -> Result<Html<String>, ErrorResponse> {
) -> AppResponse {
Ok(().into_response())
/*
let session = app.session(&cookies).await;
let game = app.game.lock().unwrap();
if let Some(game) = game.as_ref() {
Expand All @@ -339,13 +344,16 @@ async fn get_game_city(
} else {
Err(ErrorResponse::from(Redirect::to("/lobby")))
}
*/
}

async fn get_ws(
state: State<AppState>,
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)
Expand All @@ -354,6 +362,7 @@ async fn get_ws(
} else {
StatusCode::BAD_REQUEST.into_response()
}
*/
}

fn form_feedback(err: Cow<'static, str>) -> ErrorResponse {
Expand All @@ -370,12 +379,11 @@ async fn submit_game_action(
cookies: Cookies,
action: axum::Json<ActionSubmission>,
) -> Result<Response, ErrorResponse> {
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);
Expand Down Expand Up @@ -525,13 +533,13 @@ async fn get_game_menu(
cookies: Cookies,
path: Path<String>,
) -> Result<Response> {
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());
}

Expand Down
67 changes: 12 additions & 55 deletions src/server/state.rs
Original file line number Diff line number Diff line change
@@ -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<T>(item: T) -> Arc<std::sync::Mutex<T>> {
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<std::sync::Mutex<Lobby>>,
pub game: Arc<std::sync::Mutex<Option<Game>>>,
pub supabase: SupabaseAnonClient,
pub logged_in: Arc<RwLock<HashMap<SessionId, UserSession>>>,
pub ws_connections: Arc<Mutex<WebSockets>>,
}

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<UserSession> {
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<UserId> {
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)]
Expand Down
39 changes: 19 additions & 20 deletions src/server/supabase.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand All @@ -25,7 +30,7 @@ impl SupabaseAnonClient {
pub async fn exchange_code_for_session(
&self,
body: ExchangeOAuthCode<'_>,
) -> anyhow::Result<DiscordSigninResponse> {
) -> anyhow::Result<OAuthSigninResponse> {
let response: Response = self
.client
.post(&format!("{}/auth/v1/token?grant_type=pkce", self.url))
Expand All @@ -36,13 +41,13 @@ impl SupabaseAnonClient {
.await?;

let body = response.bytes().await?;
let json = serde_json::from_slice::<SupabaseResponse<DiscordSigninResponse>>(&body)?;
let json: Result<DiscordSigninResponse, _> = json.into();
let json = serde_json::from_slice::<SupabaseResponse<OAuthSigninResponse>>(&body)?;
let json: Result<OAuthSigninResponse, _> = json.into();
let json = json?;
Ok(json)
}

pub async fn refresh(&self, refresh_token: RefreshToken) -> anyhow::Result<Signin> {
pub async fn refresh(&self, refresh_token: &str) -> anyhow::Result<Signin> {
let data = self
.client
.post(&format!(
Expand All @@ -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())
Expand All @@ -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)]
Expand Down

0 comments on commit aa73f18

Please sign in to comment.