Something went wrong. Try again.
Our Personal Data Server from scratch!
Something went wrong. Try again.
7.8 kB · 272 lines
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273use crate::common;use axum::{ Json, extract::State, response::{IntoResponse, Response},};use serde::{Deserialize, Serialize};use tracing::{info, warn};use tranquil_pds::api::SuccessResponse;use tranquil_pds::api::error::{ApiError, DbResultExt};use tranquil_pds::comms::comms_repo;use tranquil_pds::types::Did;
use tranquil_db_traits::CommsChannel;use tranquil_pds::auth::verification_token::{ VerificationPurpose, normalize_token_input, verify_token_signature,};use tranquil_pds::state::AppState;
#[derive(Deserialize, Clone)]#[serde(rename_all = "camelCase")]pub struct VerifyTokenInput { pub token: String, pub identifier: String,}
#[derive(Serialize, Clone)]#[serde(rename_all = "camelCase")]pub struct VerifyTokenOutput { pub success: bool, pub did: Did, pub purpose: VerificationPurpose, pub channel: CommsChannel,}
pub async fn verify_token( State(state): State<AppState>, Json(input): Json<VerifyTokenInput>,) -> Result<Json<VerifyTokenOutput>, ApiError> { verify_token_internal(&state, input).await}
pub async fn verify_token_internal( state: &AppState, input: VerifyTokenInput,) -> Result<Json<VerifyTokenOutput>, ApiError> { let normalized_token = normalize_token_input(&input.token); let identifier = input.identifier.trim().to_lowercase();
let token_data = verify_token_signature(&normalized_token).map_err(|e| { warn!(error = ?e, "Token verification failed"); ApiError::from(e) })?;
let expected_hash = tranquil_pds::auth::verification_token::hash_identifier(&identifier); if token_data.identifier_hash != expected_hash { return Err(ApiError::IdentifierMismatch); }
match token_data.purpose { VerificationPurpose::Migration => { handle_migration_verification(state, &token_data.did, token_data.channel, &identifier) .await } VerificationPurpose::ChannelUpdate => { handle_channel_update(state, &token_data.did, token_data.channel, &identifier).await } VerificationPurpose::Signup => { handle_signup_verification(state, &token_data.did, token_data.channel, &identifier) .await } }}
async fn handle_migration_verification( state: &AppState, did: &Did, channel: CommsChannel, identifier: &str,) -> Result<Json<VerifyTokenOutput>, ApiError> { let user = state .repos .user .get_verification_info(did) .await .log_db_err("during migration verification")? .ok_or(ApiError::AccountNotFound)?;
match channel { CommsChannel::Email => { if user.email.as_ref().map(|e| e.to_lowercase()) != Some(identifier.to_string()) { return Err(ApiError::IdentifierMismatch); } if !user.channel_verification.email { state .repos .user .set_email_verified_flag(user.id) .await .log_db_err("updating email_verified status")?; } } _ => common::set_channel_verified_flag(state.repos.user.as_ref(), user.id, channel).await?, };
info!(did = %did, channel = ?channel, "Migration verification completed successfully");
Ok(Json(VerifyTokenOutput { success: true, did: did.clone(), purpose: VerificationPurpose::Migration, channel, }))}
async fn handle_channel_update( state: &AppState, did: &Did, channel: CommsChannel, identifier: &str,) -> Result<Json<VerifyTokenOutput>, ApiError> { let user_id = state .repos .user .get_id_by_did(did) .await .log_db_err("fetching user id")? .ok_or(ApiError::AccountNotFound)?;
match channel { CommsChannel::Email => { let success = state .repos .user .verify_email_channel(user_id, identifier) .await .log_db_err("updating email channel")?; if !success { return Err(ApiError::EmailTaken); } } CommsChannel::Discord => { state .repos .user .verify_discord_channel(user_id, identifier) .await .log_db_err("updating discord channel")?; } CommsChannel::Telegram => { state .repos .user .verify_telegram_channel(user_id, identifier) .await .log_db_err("updating telegram channel")?; } CommsChannel::Signal => { state .repos .user .verify_signal_channel(user_id, identifier) .await .log_db_err("updating signal channel")?; } };
info!(did = %did, channel = ?channel, "Channel verified successfully");
notify_channel_verified(state, user_id, channel, identifier).await;
Ok(Json(VerifyTokenOutput { success: true, did: did.clone(), purpose: VerificationPurpose::ChannelUpdate, channel, }))}
async fn notify_channel_verified( state: &AppState, user_id: uuid::Uuid, channel: CommsChannel, identifier: &str,) { let recipient = match channel { CommsChannel::Telegram => state .repos .user .get_telegram_chat_id(user_id) .await .ok() .flatten() .map(|id| id.to_string()) .unwrap_or_else(|| identifier.to_string()), _ => identifier.to_string(), }; if let Err(e) = comms_repo::enqueue_channel_verified( state.repos.user.as_ref(), state.repos.infra.as_ref(), user_id, channel, &recipient, &tranquil_config::get().server.hostname, ) .await { warn!(error = %e, "Failed to enqueue channel verified notification"); }}
async fn handle_signup_verification( state: &AppState, did: &Did, channel: CommsChannel, identifier: &str,) -> Result<Json<VerifyTokenOutput>, ApiError> { let user = state .repos .user .get_verification_info(did) .await .log_db_err("during signup verification")? .ok_or(ApiError::AccountNotFound)?;
let is_verified = user.channel_verification.has_any_verified(); if is_verified { info!(did = %did, "Account already verified"); return Ok(Json(VerifyTokenOutput { success: true, did: did.clone(), purpose: VerificationPurpose::Signup, channel, })); }
common::set_channel_verified_flag(state.repos.user.as_ref(), user.id, channel).await?;
info!(did = %did, channel = ?channel, "Signup verified successfully");
notify_channel_verified(state, user.id, channel, identifier).await;
Ok(Json(VerifyTokenOutput { success: true, did: did.clone(), purpose: VerificationPurpose::Signup, channel, }))}
#[derive(Deserialize)]#[serde(rename_all = "camelCase")]pub struct ConfirmChannelVerificationInput { pub channel: CommsChannel, pub identifier: String, pub code: String,}
pub async fn confirm_channel_verification( State(state): State<AppState>, Json(input): Json<ConfirmChannelVerificationInput>,) -> Response { let token_input = VerifyTokenInput { token: input.code, identifier: input.identifier, };
match verify_token_internal(&state, token_input).await { Ok(_output) => SuccessResponse::ok().into_response(), Err(e) => e.into_response(), }}