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