this repo has no description
1use axum::{
2 Json,
3 extract::State,
4 http::{HeaderMap, StatusCode},
5};
6use serde::{Deserialize, Serialize};
7use serde_json::json;
8use tracing::{error, info, warn};
9
10use crate::auth::verification_token::{
11 VerificationPurpose, VerifyError, normalize_token_input, verify_token_signature,
12};
13use crate::state::AppState;
14
15#[derive(Deserialize, Clone)]
16#[serde(rename_all = "camelCase")]
17pub struct VerifyTokenInput {
18 pub token: String,
19 pub identifier: String,
20}
21
22#[derive(Serialize, Clone)]
23#[serde(rename_all = "camelCase")]
24pub struct VerifyTokenOutput {
25 pub success: bool,
26 pub did: String,
27 pub purpose: String,
28 pub channel: String,
29}
30
31pub async fn verify_token(
32 State(state): State<AppState>,
33 headers: HeaderMap,
34 Json(input): Json<VerifyTokenInput>,
35) -> Result<Json<VerifyTokenOutput>, (StatusCode, Json<serde_json::Value>)> {
36 verify_token_internal(&state, Some(&headers), input).await
37}
38
39pub async fn verify_token_internal(
40 state: &AppState,
41 headers: Option<&HeaderMap>,
42 input: VerifyTokenInput,
43) -> Result<Json<VerifyTokenOutput>, (StatusCode, Json<serde_json::Value>)> {
44 let normalized_token = normalize_token_input(&input.token);
45 let identifier = input.identifier.trim().to_lowercase();
46
47 let token_data = match verify_token_signature(&normalized_token) {
48 Ok(data) => data,
49 Err(e) => {
50 let (status, error, message) = match e {
51 VerifyError::InvalidFormat => (
52 StatusCode::BAD_REQUEST,
53 "InvalidToken",
54 "The verification token is invalid or malformed",
55 ),
56 VerifyError::UnsupportedVersion => (
57 StatusCode::BAD_REQUEST,
58 "InvalidToken",
59 "This verification token version is not supported",
60 ),
61 VerifyError::Expired => (
62 StatusCode::BAD_REQUEST,
63 "ExpiredToken",
64 "The verification token has expired. Please request a new one.",
65 ),
66 VerifyError::InvalidSignature => (
67 StatusCode::BAD_REQUEST,
68 "InvalidToken",
69 "The verification token signature is invalid",
70 ),
71 _ => (
72 StatusCode::BAD_REQUEST,
73 "InvalidToken",
74 "The verification token is not valid",
75 ),
76 };
77 warn!(error = ?e, "Token verification failed");
78 return Err((status, Json(json!({ "error": error, "message": message }))));
79 }
80 };
81
82 let expected_hash = crate::auth::verification_token::hash_identifier(&identifier);
83 if token_data.identifier_hash != expected_hash {
84 return Err((
85 StatusCode::BAD_REQUEST,
86 Json(
87 json!({ "error": "IdentifierMismatch", "message": "The identifier does not match the verification token" }),
88 ),
89 ));
90 }
91
92 match token_data.purpose {
93 VerificationPurpose::Migration => {
94 handle_migration_verification(state, &token_data.did, &token_data.channel, &identifier)
95 .await
96 }
97 VerificationPurpose::ChannelUpdate => {
98 let auth_did = extract_and_validate_auth(state, headers).await?;
99 if auth_did != token_data.did {
100 return Err((
101 StatusCode::BAD_REQUEST,
102 Json(
103 json!({ "error": "InvalidToken", "message": "Token does not match authenticated account" }),
104 ),
105 ));
106 }
107 handle_channel_update(state, &token_data.did, &token_data.channel, &identifier).await
108 }
109 VerificationPurpose::Signup => {
110 handle_signup_verification(state, &token_data.did, &token_data.channel, &identifier)
111 .await
112 }
113 }
114}
115
116async fn extract_and_validate_auth(
117 state: &AppState,
118 headers: Option<&HeaderMap>,
119) -> Result<String, (StatusCode, Json<serde_json::Value>)> {
120 let headers = headers.ok_or_else(|| {
121 (
122 StatusCode::UNAUTHORIZED,
123 Json(json!({ "error": "AuthenticationRequired", "message": "Authentication required for this verification" })),
124 )
125 })?;
126
127 let token = crate::auth::extract_bearer_token_from_header(
128 headers.get("Authorization").and_then(|h| h.to_str().ok()),
129 )
130 .ok_or_else(|| {
131 (
132 StatusCode::UNAUTHORIZED,
133 Json(json!({ "error": "AuthenticationRequired", "message": "Authentication required for this verification" })),
134 )
135 })?;
136
137 let user = crate::auth::validate_bearer_token(&state.db, &token)
138 .await
139 .map_err(|_| {
140 (
141 StatusCode::UNAUTHORIZED,
142 Json(json!({ "error": "AuthenticationFailed", "message": "Invalid authentication token" })),
143 )
144 })?;
145
146 Ok(user.did)
147}
148
149async fn handle_migration_verification(
150 state: &AppState,
151 did: &str,
152 channel: &str,
153 identifier: &str,
154) -> Result<Json<VerifyTokenOutput>, (StatusCode, Json<serde_json::Value>)> {
155 if channel != "email" {
156 return Err((
157 StatusCode::BAD_REQUEST,
158 Json(
159 json!({ "error": "InvalidChannel", "message": "Migration verification is only supported for email" }),
160 ),
161 ));
162 }
163
164 let user = sqlx::query!(
165 "SELECT id, email, email_verified FROM users WHERE did = $1",
166 did
167 )
168 .fetch_optional(&state.db)
169 .await
170 .map_err(|e| {
171 warn!(error = %e, "Database error during migration verification");
172 (
173 StatusCode::INTERNAL_SERVER_ERROR,
174 Json(json!({ "error": "InternalError", "message": "Database error" })),
175 )
176 })?;
177
178 let user = user.ok_or_else(|| {
179 (
180 StatusCode::NOT_FOUND,
181 Json(json!({ "error": "AccountNotFound", "message": "No account found for this verification token" })),
182 )
183 })?;
184
185 if user.email.as_ref().map(|e| e.to_lowercase()) != Some(identifier.to_string()) {
186 return Err((
187 StatusCode::BAD_REQUEST,
188 Json(
189 json!({ "error": "IdentifierMismatch", "message": "The email address does not match the account" }),
190 ),
191 ));
192 }
193
194 if !user.email_verified {
195 sqlx::query!(
196 "UPDATE users SET email_verified = true WHERE id = $1",
197 user.id
198 )
199 .execute(&state.db)
200 .await
201 .map_err(|e| {
202 warn!(error = %e, "Failed to update email_verified status");
203 (
204 StatusCode::INTERNAL_SERVER_ERROR,
205 Json(json!({ "error": "InternalError", "message": "Failed to verify email" })),
206 )
207 })?;
208 }
209
210 info!(did = %did, "Migration email verified successfully");
211
212 Ok(Json(VerifyTokenOutput {
213 success: true,
214 did: did.to_string(),
215 purpose: "migration".to_string(),
216 channel: channel.to_string(),
217 }))
218}
219
220async fn handle_channel_update(
221 state: &AppState,
222 did: &str,
223 channel: &str,
224 identifier: &str,
225) -> Result<Json<VerifyTokenOutput>, (StatusCode, Json<serde_json::Value>)> {
226 let user_id = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
227 .fetch_one(&state.db)
228 .await
229 .map_err(|_| {
230 (
231 StatusCode::INTERNAL_SERVER_ERROR,
232 Json(json!({ "error": "InternalError", "message": "User not found" })),
233 )
234 })?;
235
236 let update_result = match channel {
237 "email" => sqlx::query!(
238 "UPDATE users SET email = $1, email_verified = TRUE, updated_at = NOW() WHERE id = $2",
239 identifier,
240 user_id
241 ).execute(&state.db).await,
242 "discord" => sqlx::query!(
243 "UPDATE users SET discord_id = $1, discord_verified = TRUE, updated_at = NOW() WHERE id = $2",
244 identifier,
245 user_id
246 ).execute(&state.db).await,
247 "telegram" => sqlx::query!(
248 "UPDATE users SET telegram_username = $1, telegram_verified = TRUE, updated_at = NOW() WHERE id = $2",
249 identifier,
250 user_id
251 ).execute(&state.db).await,
252 "signal" => sqlx::query!(
253 "UPDATE users SET signal_number = $1, signal_verified = TRUE, updated_at = NOW() WHERE id = $2",
254 identifier,
255 user_id
256 ).execute(&state.db).await,
257 _ => {
258 return Err((
259 StatusCode::BAD_REQUEST,
260 Json(json!({ "error": "InvalidChannel", "message": "Invalid channel" })),
261 ));
262 }
263 };
264
265 if let Err(e) = update_result {
266 error!("Failed to update user channel: {:?}", e);
267 if channel == "email"
268 && e.as_database_error()
269 .map(|db| db.is_unique_violation())
270 .unwrap_or(false)
271 {
272 return Err((
273 StatusCode::BAD_REQUEST,
274 Json(json!({ "error": "EmailTaken", "message": "Email already in use" })),
275 ));
276 }
277 return Err((
278 StatusCode::INTERNAL_SERVER_ERROR,
279 Json(json!({ "error": "InternalError", "message": "Failed to update channel" })),
280 ));
281 }
282
283 info!(did = %did, channel = %channel, "Channel verified successfully");
284
285 Ok(Json(VerifyTokenOutput {
286 success: true,
287 did: did.to_string(),
288 purpose: "channel_update".to_string(),
289 channel: channel.to_string(),
290 }))
291}
292
293async fn handle_signup_verification(
294 state: &AppState,
295 did: &str,
296 channel: &str,
297 _identifier: &str,
298) -> Result<Json<VerifyTokenOutput>, (StatusCode, Json<serde_json::Value>)> {
299 let user = sqlx::query!(
300 "SELECT id, handle, email, email_verified, discord_verified, telegram_verified, signal_verified FROM users WHERE did = $1",
301 did
302 )
303 .fetch_optional(&state.db)
304 .await
305 .map_err(|e| {
306 warn!(error = %e, "Database error during signup verification");
307 (
308 StatusCode::INTERNAL_SERVER_ERROR,
309 Json(json!({ "error": "InternalError", "message": "Database error" })),
310 )
311 })?;
312
313 let user = user.ok_or_else(|| {
314 (
315 StatusCode::NOT_FOUND,
316 Json(json!({ "error": "AccountNotFound", "message": "No account found for this verification token" })),
317 )
318 })?;
319
320 let is_verified = user.email_verified
321 || user.discord_verified
322 || user.telegram_verified
323 || user.signal_verified;
324 if is_verified {
325 info!(did = %did, "Account already verified");
326 return Ok(Json(VerifyTokenOutput {
327 success: true,
328 did: did.to_string(),
329 purpose: "signup".to_string(),
330 channel: channel.to_string(),
331 }));
332 }
333
334 let update_result = match channel {
335 "email" => {
336 sqlx::query!(
337 "UPDATE users SET email_verified = TRUE WHERE id = $1",
338 user.id
339 )
340 .execute(&state.db)
341 .await
342 }
343 "discord" => {
344 sqlx::query!(
345 "UPDATE users SET discord_verified = TRUE WHERE id = $1",
346 user.id
347 )
348 .execute(&state.db)
349 .await
350 }
351 "telegram" => {
352 sqlx::query!(
353 "UPDATE users SET telegram_verified = TRUE WHERE id = $1",
354 user.id
355 )
356 .execute(&state.db)
357 .await
358 }
359 "signal" => {
360 sqlx::query!(
361 "UPDATE users SET signal_verified = TRUE WHERE id = $1",
362 user.id
363 )
364 .execute(&state.db)
365 .await
366 }
367 _ => {
368 return Err((
369 StatusCode::BAD_REQUEST,
370 Json(json!({ "error": "InvalidChannel", "message": "Invalid channel" })),
371 ));
372 }
373 };
374
375 update_result.map_err(|e| {
376 warn!(error = %e, "Failed to update channel verified status");
377 (
378 StatusCode::INTERNAL_SERVER_ERROR,
379 Json(json!({ "error": "InternalError", "message": "Failed to verify channel" })),
380 )
381 })?;
382
383 info!(did = %did, channel = %channel, "Signup verified successfully");
384
385 Ok(Json(VerifyTokenOutput {
386 success: true,
387 did: did.to_string(),
388 purpose: "signup".to_string(),
389 channel: channel.to_string(),
390 }))
391}