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