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}