this repo has no description
1use crate::auth::validate_bearer_token;
2use crate::state::AppState;
3use axum::{
4 Json,
5 extract::State,
6 http::{HeaderMap, StatusCode},
7 response::{IntoResponse, Response},
8};
9use chrono::Utc;
10use serde::Deserialize;
11use serde_json::json;
12use tracing::{error, info};
13
14#[derive(Deserialize)]
15#[serde(rename_all = "camelCase")]
16pub struct ConfirmChannelVerificationInput {
17 pub channel: String,
18 pub code: String,
19}
20
21pub async fn confirm_channel_verification(
22 State(state): State<AppState>,
23 headers: HeaderMap,
24 Json(input): Json<ConfirmChannelVerificationInput>,
25) -> Response {
26 let token = match crate::auth::extract_bearer_token_from_header(
27 headers.get("Authorization").and_then(|h| h.to_str().ok()),
28 ) {
29 Some(t) => t,
30 None => return (
31 StatusCode::UNAUTHORIZED,
32 Json(json!({"error": "AuthenticationRequired", "message": "Authentication required"})),
33 )
34 .into_response(),
35 };
36 let user = match validate_bearer_token(&state.db, &token).await {
37 Ok(u) => u,
38 Err(_) => {
39 return (
40 StatusCode::UNAUTHORIZED,
41 Json(json!({"error": "AuthenticationFailed", "message": "Invalid token"})),
42 )
43 .into_response();
44 }
45 };
46
47 let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", user.did)
48 .fetch_one(&state.db)
49 .await
50 {
51 Ok(id) => id,
52 Err(_) => return (
53 StatusCode::INTERNAL_SERVER_ERROR,
54 Json(json!({"error": "InternalError", "message": "User not found"})),
55 )
56 .into_response(),
57 };
58
59 let channel_str = input.channel.as_str();
60 if !["email", "discord", "telegram", "signal"].contains(&channel_str) {
61 return (
62 StatusCode::BAD_REQUEST,
63 Json(json!({"error": "InvalidRequest", "message": "Invalid channel"})),
64 )
65 .into_response();
66 }
67
68 let record = match sqlx::query!(
69 r#"
70 SELECT code, pending_identifier, expires_at FROM channel_verifications
71 WHERE user_id = $1 AND channel = $2::notification_channel
72 "#,
73 user_id,
74 channel_str as _
75 )
76 .fetch_optional(&state.db)
77 .await {
78 Ok(Some(r)) => r,
79 Ok(None) => return (
80 StatusCode::BAD_REQUEST,
81 Json(json!({"error": "InvalidRequest", "message": "No pending verification found. Update notification preferences first."})),
82 )
83 .into_response(),
84 Err(e) => return (
85 StatusCode::INTERNAL_SERVER_ERROR,
86 Json(json!({"error": "InternalError", "message": format!("Database error: {}", e)})),
87 )
88 .into_response(),
89 };
90
91 let pending_identifier = match record.pending_identifier {
92 Some(p) => p,
93 None => return (
94 StatusCode::BAD_REQUEST,
95 Json(json!({"error": "InvalidRequest", "message": "No pending identifier found"})),
96 )
97 .into_response(),
98 };
99
100 if record.expires_at < Utc::now() {
101 return (
102 StatusCode::BAD_REQUEST,
103 Json(json!({"error": "ExpiredToken", "message": "Verification code expired"})),
104 )
105 .into_response();
106 }
107
108 if record.code != input.code {
109 return (
110 StatusCode::BAD_REQUEST,
111 Json(json!({"error": "InvalidCode", "message": "Invalid verification code"})),
112 )
113 .into_response();
114 }
115
116 let mut tx = match state.db.begin().await {
117 Ok(tx) => tx,
118 Err(_) => return (
119 StatusCode::INTERNAL_SERVER_ERROR,
120 Json(json!({"error": "InternalError"})),
121 )
122 .into_response(),
123 };
124
125 let update_result = match channel_str {
126 "email" => sqlx::query!(
127 "UPDATE users SET email = $1, updated_at = NOW() WHERE id = $2",
128 pending_identifier,
129 user_id
130 ).execute(&mut *tx).await,
131 "discord" => sqlx::query!(
132 "UPDATE users SET discord_id = $1, discord_verified = TRUE, updated_at = NOW() WHERE id = $2",
133 pending_identifier,
134 user_id
135 ).execute(&mut *tx).await,
136 "telegram" => sqlx::query!(
137 "UPDATE users SET telegram_username = $1, telegram_verified = TRUE, updated_at = NOW() WHERE id = $2",
138 pending_identifier,
139 user_id
140 ).execute(&mut *tx).await,
141 "signal" => sqlx::query!(
142 "UPDATE users SET signal_number = $1, signal_verified = TRUE, updated_at = NOW() WHERE id = $2",
143 pending_identifier,
144 user_id
145 ).execute(&mut *tx).await,
146 _ => unreachable!(),
147 };
148
149 if let Err(e) = update_result {
150 error!("Failed to update user channel: {:?}", e);
151 if channel_str == "email" && e.as_database_error().map(|db| db.is_unique_violation()).unwrap_or(false) {
152 return (
153 StatusCode::BAD_REQUEST,
154 Json(json!({"error": "EmailTaken", "message": "Email already in use"})),
155 )
156 .into_response();
157 }
158 return (
159 StatusCode::INTERNAL_SERVER_ERROR,
160 Json(json!({"error": "InternalError", "message": "Failed to update channel"})),
161 )
162 .into_response();
163 }
164
165 if let Err(e) = sqlx::query!(
166 "DELETE FROM channel_verifications WHERE user_id = $1 AND channel = $2::notification_channel",
167 user_id,
168 channel_str as _
169 )
170 .execute(&mut *tx)
171 .await {
172 error!("Failed to delete verification record: {:?}", e);
173 return (
174 StatusCode::INTERNAL_SERVER_ERROR,
175 Json(json!({"error": "InternalError"})),
176 )
177 .into_response();
178 }
179
180 if let Err(_) = tx.commit().await {
181 return (
182 StatusCode::INTERNAL_SERVER_ERROR,
183 Json(json!({"error": "InternalError"})),
184 )
185 .into_response();
186 }
187
188 info!(did = %user.did, channel = %channel_str, "Channel verified successfully");
189
190 Json(json!({"success": true})).into_response()
191}