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}