this repo has no description
at main 7.8 kB view raw
1use crate::api::error::ApiError; 2use crate::types::Did; 3use axum::{Json, extract::State}; 4use serde::{Deserialize, Serialize}; 5use tracing::{error, info, warn}; 6 7use crate::auth::verification_token::{ 8 VerificationPurpose, normalize_token_input, verify_token_signature, 9}; 10use crate::state::AppState; 11 12#[derive(Deserialize, Clone)] 13#[serde(rename_all = "camelCase")] 14pub struct VerifyTokenInput { 15 pub token: String, 16 pub identifier: String, 17} 18 19#[derive(Serialize, Clone)] 20#[serde(rename_all = "camelCase")] 21pub struct VerifyTokenOutput { 22 pub success: bool, 23 pub did: Did, 24 pub purpose: String, 25 pub channel: String, 26} 27 28pub async fn verify_token( 29 State(state): State<AppState>, 30 Json(input): Json<VerifyTokenInput>, 31) -> Result<Json<VerifyTokenOutput>, ApiError> { 32 verify_token_internal(&state, input).await 33} 34 35pub async fn verify_token_internal( 36 state: &AppState, 37 input: VerifyTokenInput, 38) -> Result<Json<VerifyTokenOutput>, ApiError> { 39 let normalized_token = normalize_token_input(&input.token); 40 let identifier = input.identifier.trim().to_lowercase(); 41 42 let token_data = verify_token_signature(&normalized_token).map_err(|e| { 43 warn!(error = ?e, "Token verification failed"); 44 ApiError::from(e) 45 })?; 46 47 let expected_hash = crate::auth::verification_token::hash_identifier(&identifier); 48 if token_data.identifier_hash != expected_hash { 49 return Err(ApiError::IdentifierMismatch); 50 } 51 52 match token_data.purpose { 53 VerificationPurpose::Migration => { 54 handle_migration_verification(state, &token_data.did, &token_data.channel, &identifier) 55 .await 56 } 57 VerificationPurpose::ChannelUpdate => { 58 handle_channel_update(state, &token_data.did, &token_data.channel, &identifier).await 59 } 60 VerificationPurpose::Signup => { 61 handle_signup_verification(state, &token_data.did, &token_data.channel, &identifier) 62 .await 63 } 64 } 65} 66 67async fn handle_migration_verification( 68 state: &AppState, 69 did: &str, 70 channel: &str, 71 identifier: &str, 72) -> Result<Json<VerifyTokenOutput>, ApiError> { 73 if channel != "email" { 74 return Err(ApiError::InvalidChannel); 75 } 76 77 let user = sqlx::query!( 78 "SELECT id, email, email_verified FROM users WHERE did = $1", 79 did 80 ) 81 .fetch_optional(&state.db) 82 .await 83 .map_err(|e| { 84 warn!(error = %e, "Database error during migration verification"); 85 ApiError::InternalError(None) 86 })?; 87 88 let user = user.ok_or(ApiError::AccountNotFound)?; 89 90 if user.email.as_ref().map(|e| e.to_lowercase()) != Some(identifier.to_string()) { 91 return Err(ApiError::IdentifierMismatch); 92 } 93 94 if !user.email_verified { 95 sqlx::query!( 96 "UPDATE users SET email_verified = true WHERE id = $1", 97 user.id 98 ) 99 .execute(&state.db) 100 .await 101 .map_err(|e| { 102 warn!(error = %e, "Failed to update email_verified status"); 103 ApiError::InternalError(None) 104 })?; 105 } 106 107 info!(did = %did, "Migration email verified successfully"); 108 109 Ok(Json(VerifyTokenOutput { 110 success: true, 111 did: did.to_string().into(), 112 purpose: "migration".to_string(), 113 channel: channel.to_string(), 114 })) 115} 116 117async fn handle_channel_update( 118 state: &AppState, 119 did: &str, 120 channel: &str, 121 identifier: &str, 122) -> Result<Json<VerifyTokenOutput>, ApiError> { 123 let user_id = sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did) 124 .fetch_one(&state.db) 125 .await 126 .map_err(|_| ApiError::InternalError(None))?; 127 128 let update_result = match channel { 129 "email" => sqlx::query!( 130 "UPDATE users SET email = $1, email_verified = TRUE, updated_at = NOW() WHERE id = $2", 131 identifier, 132 user_id 133 ).execute(&state.db).await, 134 "discord" => sqlx::query!( 135 "UPDATE users SET discord_id = $1, discord_verified = TRUE, updated_at = NOW() WHERE id = $2", 136 identifier, 137 user_id 138 ).execute(&state.db).await, 139 "telegram" => sqlx::query!( 140 "UPDATE users SET telegram_username = $1, telegram_verified = TRUE, updated_at = NOW() WHERE id = $2", 141 identifier, 142 user_id 143 ).execute(&state.db).await, 144 "signal" => sqlx::query!( 145 "UPDATE users SET signal_number = $1, signal_verified = TRUE, updated_at = NOW() WHERE id = $2", 146 identifier, 147 user_id 148 ).execute(&state.db).await, 149 _ => { 150 return Err(ApiError::InvalidChannel); 151 } 152 }; 153 154 if let Err(e) = update_result { 155 error!("Failed to update user channel: {:?}", e); 156 if channel == "email" 157 && e.as_database_error() 158 .map(|db| db.is_unique_violation()) 159 .unwrap_or(false) 160 { 161 return Err(ApiError::EmailTaken); 162 } 163 return Err(ApiError::InternalError(None)); 164 } 165 166 info!(did = %did, channel = %channel, "Channel verified successfully"); 167 168 Ok(Json(VerifyTokenOutput { 169 success: true, 170 did: did.to_string().into(), 171 purpose: "channel_update".to_string(), 172 channel: channel.to_string(), 173 })) 174} 175 176async fn handle_signup_verification( 177 state: &AppState, 178 did: &str, 179 channel: &str, 180 _identifier: &str, 181) -> Result<Json<VerifyTokenOutput>, ApiError> { 182 let user = sqlx::query!( 183 "SELECT id, handle, email, email_verified, discord_verified, telegram_verified, signal_verified FROM users WHERE did = $1", 184 did 185 ) 186 .fetch_optional(&state.db) 187 .await 188 .map_err(|e| { 189 warn!(error = %e, "Database error during signup verification"); 190 ApiError::InternalError(None) 191 })?; 192 193 let user = user.ok_or(ApiError::AccountNotFound)?; 194 195 let is_verified = user.email_verified 196 || user.discord_verified 197 || user.telegram_verified 198 || user.signal_verified; 199 if is_verified { 200 info!(did = %did, "Account already verified"); 201 return Ok(Json(VerifyTokenOutput { 202 success: true, 203 did: did.to_string().into(), 204 purpose: "signup".to_string(), 205 channel: channel.to_string(), 206 })); 207 } 208 209 let update_result = match channel { 210 "email" => { 211 sqlx::query!( 212 "UPDATE users SET email_verified = TRUE WHERE id = $1", 213 user.id 214 ) 215 .execute(&state.db) 216 .await 217 } 218 "discord" => { 219 sqlx::query!( 220 "UPDATE users SET discord_verified = TRUE WHERE id = $1", 221 user.id 222 ) 223 .execute(&state.db) 224 .await 225 } 226 "telegram" => { 227 sqlx::query!( 228 "UPDATE users SET telegram_verified = TRUE WHERE id = $1", 229 user.id 230 ) 231 .execute(&state.db) 232 .await 233 } 234 "signal" => { 235 sqlx::query!( 236 "UPDATE users SET signal_verified = TRUE WHERE id = $1", 237 user.id 238 ) 239 .execute(&state.db) 240 .await 241 } 242 _ => { 243 return Err(ApiError::InvalidChannel); 244 } 245 }; 246 247 update_result.map_err(|e| { 248 warn!(error = %e, "Failed to update channel verified status"); 249 ApiError::InternalError(None) 250 })?; 251 252 info!(did = %did, channel = %channel, "Signup verified successfully"); 253 254 Ok(Json(VerifyTokenOutput { 255 success: true, 256 did: did.to_string().into(), 257 purpose: "signup".to_string(), 258 channel: channel.to_string(), 259 })) 260}