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(_) => {
53 return (
54 StatusCode::INTERNAL_SERVER_ERROR,
55 Json(json!({"error": "InternalError", "message": "User not found"})),
56 )
57 .into_response();
58 }
59 };
60
61 let channel_str = input.channel.as_str();
62 if !["email", "discord", "telegram", "signal"].contains(&channel_str) {
63 return (
64 StatusCode::BAD_REQUEST,
65 Json(json!({"error": "InvalidRequest", "message": "Invalid channel"})),
66 )
67 .into_response();
68 }
69
70 let record = match sqlx::query!(
71 r#"
72 SELECT code, pending_identifier, expires_at FROM channel_verifications
73 WHERE user_id = $1 AND channel = $2::comms_channel
74 "#,
75 user_id,
76 channel_str as _
77 )
78 .fetch_optional(&state.db)
79 .await {
80 Ok(Some(r)) => r,
81 Ok(None) => return (
82 StatusCode::BAD_REQUEST,
83 Json(json!({"error": "InvalidRequest", "message": "No pending verification found. Update notification preferences first."})),
84 )
85 .into_response(),
86 Err(e) => return (
87 StatusCode::INTERNAL_SERVER_ERROR,
88 Json(json!({"error": "InternalError", "message": format!("Database error: {}", e)})),
89 )
90 .into_response(),
91 };
92
93 let pending_identifier =
94 match record.pending_identifier {
95 Some(p) => p,
96 None => return (
97 StatusCode::BAD_REQUEST,
98 Json(json!({"error": "InvalidRequest", "message": "No pending identifier found"})),
99 )
100 .into_response(),
101 };
102
103 if record.expires_at < Utc::now() {
104 return (
105 StatusCode::BAD_REQUEST,
106 Json(json!({"error": "ExpiredToken", "message": "Verification code expired"})),
107 )
108 .into_response();
109 }
110
111 if record.code != input.code {
112 return (
113 StatusCode::BAD_REQUEST,
114 Json(json!({"error": "InvalidCode", "message": "Invalid verification code"})),
115 )
116 .into_response();
117 }
118
119 let mut tx = match state.db.begin().await {
120 Ok(tx) => tx,
121 Err(_) => {
122 return (
123 StatusCode::INTERNAL_SERVER_ERROR,
124 Json(json!({"error": "InternalError"})),
125 )
126 .into_response();
127 }
128 };
129
130 let update_result = match channel_str {
131 "email" => sqlx::query!(
132 "UPDATE users SET email = $1, updated_at = NOW() WHERE id = $2",
133 pending_identifier,
134 user_id
135 ).execute(&mut *tx).await,
136 "discord" => sqlx::query!(
137 "UPDATE users SET discord_id = $1, discord_verified = TRUE, updated_at = NOW() WHERE id = $2",
138 pending_identifier,
139 user_id
140 ).execute(&mut *tx).await,
141 "telegram" => sqlx::query!(
142 "UPDATE users SET telegram_username = $1, telegram_verified = TRUE, updated_at = NOW() WHERE id = $2",
143 pending_identifier,
144 user_id
145 ).execute(&mut *tx).await,
146 "signal" => sqlx::query!(
147 "UPDATE users SET signal_number = $1, signal_verified = TRUE, updated_at = NOW() WHERE id = $2",
148 pending_identifier,
149 user_id
150 ).execute(&mut *tx).await,
151 _ => unreachable!(),
152 };
153
154 if let Err(e) = update_result {
155 error!("Failed to update user channel: {:?}", e);
156 if channel_str == "email"
157 && e.as_database_error()
158 .map(|db| db.is_unique_violation())
159 .unwrap_or(false)
160 {
161 return (
162 StatusCode::BAD_REQUEST,
163 Json(json!({"error": "EmailTaken", "message": "Email already in use"})),
164 )
165 .into_response();
166 }
167 return (
168 StatusCode::INTERNAL_SERVER_ERROR,
169 Json(json!({"error": "InternalError", "message": "Failed to update channel"})),
170 )
171 .into_response();
172 }
173
174 if let Err(e) = sqlx::query!(
175 "DELETE FROM channel_verifications WHERE user_id = $1 AND channel = $2::comms_channel",
176 user_id,
177 channel_str as _
178 )
179 .execute(&mut *tx)
180 .await
181 {
182 error!("Failed to delete verification record: {:?}", e);
183 return (
184 StatusCode::INTERNAL_SERVER_ERROR,
185 Json(json!({"error": "InternalError"})),
186 )
187 .into_response();
188 }
189
190 if tx.commit().await.is_err() {
191 return (
192 StatusCode::INTERNAL_SERVER_ERROR,
193 Json(json!({"error": "InternalError"})),
194 )
195 .into_response();
196 }
197
198 info!(did = %user.did, channel = %channel_str, "Channel verified successfully");
199
200 Json(json!({"success": true})).into_response()
201}