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