use std::{str::FromStr, time::SystemTime}; use axum::{ debug_handler, extract::{Path, Query, State}, response::{IntoResponse, Response}, Json, }; use http::StatusCode; use pasetors::{claims::ClaimsValidationRules, keys::SymmetricKey, version4::V4}; use tracing::{debug, error}; use uuid::Uuid; use crate::{ db::{insert_new_user_auth_token, verify_user, DbPool, NewUserAuthTokenEntity}, models::{ApiResponse, AppError, Session}, requests::{user::verify::UserVerifyGetResponseTokenAndExpiration, AppState}, services::{ auth_token::{generate_auth_token, generate_session_token, verify_token}, user_session, CachePool, }, }; use super::{UserVerifyGetParams, UserVerifyGetResponse}; #[debug_handler] pub async fn user_verification_get_handler( State(state): State, Path(user_id): Path, Query(query): Query, ) -> Result { let db_pool = state.db_pool(); let cache_pool = state.cache_pool(); let env = state.env(); let UserVerifyGetParams { verification_token } = query; let token_key = env.token_key(); verify_new_user_request(db_pool, cache_pool, user_id, verification_token, token_key).await } async fn verify_new_user_request( db_pool: &DbPool, cache_pool: &CachePool, user_id: i32, verification_token: String, token_key: &SymmetricKey, ) -> Result { debug!(user_id); let validation_rules = { let mut rules = ClaimsValidationRules::new(); rules.validate_audience_with(format!("/user/{user_id}/verify").as_str()); rules }; let verified_token = verify_token( token_key, verification_token.as_str(), Some(validation_rules.clone()), ) .inspect_err(|err| error!(?err))?; let verification_token_id = verified_token .payload_claims() .map(|claims| claims.get_claim("jti")) .flatten() .map(|jti| Uuid::from_str(jti.as_str().unwrap()).unwrap()) .unwrap(); user_session::exists(cache_pool, verification_token_id) .await .and_then(|exists| { if exists { Ok(user_session::get_user_session( cache_pool, verification_token_id, )) } else { Err(AppError::no_session_found()) } })? .await?; // TODO: Add new auth token to database, cache session access token let (session_token, session_token_id, session_token_expiration) = generate_session_token(token_key, user_id); let (auth_token, auth_token_id, auth_token_expiration) = generate_auth_token(token_key, user_id); let session = Session { user_id, created_at: SystemTime::now(), expires_at: session_token_expiration, }; user_session::store_user_session( cache_pool, session_token_id, session, Some( session_token_expiration .duration_since(SystemTime::now()) .unwrap(), ), ) .await?; insert_new_user_auth_token( db_pool, NewUserAuthTokenEntity { user_id, token_hash: blake3::hash(auth_token_id.as_bytes()), expires_at: auth_token_expiration, }, ) .await?; let response = verify_token( token_key, verification_token.as_str(), Some(validation_rules), ) .map(|_| UserVerifyGetResponse { user_id, session: UserVerifyGetResponseTokenAndExpiration { token: session_token, expires_at: session_token_expiration, }, auth: UserVerifyGetResponseTokenAndExpiration { token: auth_token, expires_at: auth_token_expiration, }, })?; verify_user(db_pool, user_id) .await .inspect_err(|err| error!(?err))?; Ok(( StatusCode::OK, Json(ApiResponse::::new(response)), ) .into_response()) }