use std::time::Duration; use anyhow::Context; use axum::{ extract::State, http::HeaderMap, response::{IntoResponse, Redirect}, }; use oauth2::{CsrfToken, Scope}; use reqwest::header::SET_COOKIE; use sqlx::types::uuid; use tower_sessions::{ SessionStore, session::{Id, Record}, }; use crate::{ error::AppError, server::{CSRF_TOKEN, OAUTH_CSRF_COOKIE}, state::AppHandle, }; pub async fn discord_auth(State(state): State) -> Result { let (auth_url, csrf_token) = state .discord_client .authorize_url(CsrfToken::new_random) .add_scope(Scope::new("identify".to_string())) .url(); // Store the token in the session and retrieve the session cookie. let session_id = Id(i128::from_le_bytes(uuid::Uuid::new_v4().to_bytes_le())); let store = state.session_store.clone(); store .create(&mut Record { id: session_id, data: [( CSRF_TOKEN.to_string(), serde_json::to_value(csrf_token).unwrap(), )] .into(), expiry_date: time::OffsetDateTime::now_utc() + Duration::from_secs(state.local_config.oauth.session_lifespan), }) .await .context("failed in inserting CSRF token into session")?; // Attach the session cookie to the response header let cookie = format!("{OAUTH_CSRF_COOKIE}={session_id}; SameSite=Lax; HttpOnly; Secure; Path=/"); let mut headers = HeaderMap::new(); headers.insert( SET_COOKIE, cookie.parse().context("failed to parse cookie")?, ); Ok((headers, Redirect::to(auth_url.as_ref()))) }