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