this repo has no description
1use chrono::{DateTime, Duration, Utc}; 2use rand::Rng; 3use sqlx::PgPool; 4use uuid::Uuid; 5use super::super::OAuthError; 6pub struct TwoFactorChallenge { 7 pub id: Uuid, 8 pub did: String, 9 pub request_uri: String, 10 pub code: String, 11 pub attempts: i32, 12 pub created_at: DateTime<Utc>, 13 pub expires_at: DateTime<Utc>, 14} 15pub fn generate_2fa_code() -> String { 16 let mut rng = rand::thread_rng(); 17 let code: u32 = rng.gen_range(0..1_000_000); 18 format!("{:06}", code) 19} 20pub async fn create_2fa_challenge( 21 pool: &PgPool, 22 did: &str, 23 request_uri: &str, 24) -> Result<TwoFactorChallenge, OAuthError> { 25 let code = generate_2fa_code(); 26 let expires_at = Utc::now() + Duration::minutes(10); 27 let row = sqlx::query!( 28 r#" 29 INSERT INTO oauth_2fa_challenge (did, request_uri, code, expires_at) 30 VALUES ($1, $2, $3, $4) 31 RETURNING id, did, request_uri, code, attempts, created_at, expires_at 32 "#, 33 did, 34 request_uri, 35 code, 36 expires_at, 37 ) 38 .fetch_one(pool) 39 .await?; 40 Ok(TwoFactorChallenge { 41 id: row.id, 42 did: row.did, 43 request_uri: row.request_uri, 44 code: row.code, 45 attempts: row.attempts, 46 created_at: row.created_at, 47 expires_at: row.expires_at, 48 }) 49} 50pub async fn get_2fa_challenge( 51 pool: &PgPool, 52 request_uri: &str, 53) -> Result<Option<TwoFactorChallenge>, OAuthError> { 54 let row = sqlx::query!( 55 r#" 56 SELECT id, did, request_uri, code, attempts, created_at, expires_at 57 FROM oauth_2fa_challenge 58 WHERE request_uri = $1 59 "#, 60 request_uri 61 ) 62 .fetch_optional(pool) 63 .await?; 64 Ok(row.map(|r| TwoFactorChallenge { 65 id: r.id, 66 did: r.did, 67 request_uri: r.request_uri, 68 code: r.code, 69 attempts: r.attempts, 70 created_at: r.created_at, 71 expires_at: r.expires_at, 72 })) 73} 74pub async fn increment_2fa_attempts(pool: &PgPool, id: Uuid) -> Result<i32, OAuthError> { 75 let row = sqlx::query!( 76 r#" 77 UPDATE oauth_2fa_challenge 78 SET attempts = attempts + 1 79 WHERE id = $1 80 RETURNING attempts 81 "#, 82 id 83 ) 84 .fetch_one(pool) 85 .await?; 86 Ok(row.attempts) 87} 88pub async fn delete_2fa_challenge(pool: &PgPool, id: Uuid) -> Result<(), OAuthError> { 89 sqlx::query!( 90 r#" 91 DELETE FROM oauth_2fa_challenge WHERE id = $1 92 "#, 93 id 94 ) 95 .execute(pool) 96 .await?; 97 Ok(()) 98} 99pub async fn delete_2fa_challenge_by_request_uri( 100 pool: &PgPool, 101 request_uri: &str, 102) -> Result<(), OAuthError> { 103 sqlx::query!( 104 r#" 105 DELETE FROM oauth_2fa_challenge WHERE request_uri = $1 106 "#, 107 request_uri 108 ) 109 .execute(pool) 110 .await?; 111 Ok(()) 112} 113pub async fn cleanup_expired_2fa_challenges(pool: &PgPool) -> Result<u64, OAuthError> { 114 let result = sqlx::query!( 115 r#" 116 DELETE FROM oauth_2fa_challenge WHERE expires_at < NOW() 117 "# 118 ) 119 .execute(pool) 120 .await?; 121 Ok(result.rows_affected()) 122} 123pub async fn check_user_2fa_enabled(pool: &PgPool, did: &str) -> Result<bool, OAuthError> { 124 let row = sqlx::query!( 125 r#" 126 SELECT two_factor_enabled 127 FROM users 128 WHERE did = $1 129 "#, 130 did 131 ) 132 .fetch_optional(pool) 133 .await?; 134 Ok(row.map(|r| r.two_factor_enabled).unwrap_or(false)) 135}