use anyhow::{anyhow, Result}; use chrono::{DateTime, Utc}; use sqlx::SqlitePool; pub struct OAuthStateStore { pool: SqlitePool, } #[derive(Debug, sqlx::FromRow)] pub struct OAuthStateRow { #[allow(dead_code)] pub state: String, pub expires_at: DateTime, } impl OAuthStateStore { pub fn new(pool: SqlitePool) -> Self { Self { pool } } pub async fn create(&self, state: &str, expires_at: DateTime) -> Result<()> { sqlx::query( r#" INSERT INTO oauth_state (state, expires_at) VALUES (?, ?) "#, ) .bind(state) .bind(expires_at) .execute(&self.pool) .await?; Ok(()) } pub async fn validate_and_consume(&self, state: &str) -> Result<()> { let row: Option = sqlx::query_as( r#" SELECT state, expires_at FROM oauth_state WHERE state = ? "#, ) .bind(state) .fetch_optional(&self.pool) .await?; let row = row.ok_or_else(|| anyhow!("Invalid or expired state"))?; if row.expires_at < Utc::now() { sqlx::query("DELETE FROM oauth_state WHERE state = ?") .bind(state) .execute(&self.pool) .await?; return Err(anyhow!("State has expired")); } sqlx::query("DELETE FROM oauth_state WHERE state = ?") .bind(state) .execute(&self.pool) .await?; Ok(()) } }