use crate::advent::day::Day; use crate::advent::{AdventChallenge, AdventError, AdventPart, ChallengeCheckResponse}; use crate::lexicons::codes::advent; use crate::lexicons::record::KnownRecord; use crate::{OAuthAgentType, PasswordAgent}; use async_trait::async_trait; use atrium_api::types::Collection; use atrium_api::types::string::Tid; use serde_json::json; use sqlx::PgPool; pub struct DayFive { pub pool: PgPool, pub oauth_client: Option, pub secret_agent: Option, } impl DayFive { /// Creates the challenge record on the secret agent's repo with the user's verification code. /// This is called from the `/day/5/{did}` endpoint. pub async fn create_secret_record(&self, did: &str) -> Result<(), AdventError> { let Some(agent) = &self.secret_agent else { log::warn!("No secret agent configured, skipping record creation for day five"); return Err(AdventError::ShouldNotHappen( "No secret agent configured".to_string(), )); }; // Get the user's challenge to find their verification code let challenge = self.get_days_challenge(did).await?.ok_or_else(|| { AdventError::ShouldNotHappen("Could not find challenge record for day 5".to_string()) })?; let code = challenge.verification_code_one.ok_or_else(|| { AdventError::ShouldNotHappen( "No verification code found for day 5 challenge".to_string(), ) })?; let agent_did = agent .did() .await .ok_or_else(|| AdventError::ShouldNotHappen("Secret agent has no DID".to_string()))?; let record_data = advent::challenge::shhh::RecordData { secret_part_one: code, created_at: atrium_api::types::string::Datetime::now(), subject: did.parse().unwrap(), }; let known_record: KnownRecord = record_data.into(); let record_value: atrium_api::types::Unknown = known_record.into(); let tid = Tid::from_datetime(23.try_into().unwrap(), challenge.time_started); let result = agent .api .com .atproto .repo .put_record( atrium_api::com::atproto::repo::put_record::InputData { collection: advent::challenge::Shhh::NSID.parse().unwrap(), repo: agent_did.as_ref().parse().unwrap(), rkey: tid.as_ref().parse().unwrap(), swap_record: None, record: record_value, swap_commit: None, validate: Some(false), } .into(), ) .await; match result { Ok(_) => { log::info!("Created secret record for day 5 for user: {did}"); Ok(()) } Err(e) => { log::error!("Failed to create secret record for day 5: {e}"); Err(AdventError::ShouldNotHappen(format!( "Failed to create secret record: {e}" ))) } } } } #[async_trait] impl AdventChallenge for DayFive { fn pool(&self) -> &PgPool { &self.pool } fn day(&self) -> Day { Day::Five } fn has_part_two(&self) -> bool { false } fn requires_manual_verification_part_one(&self) -> bool { true } async fn build_additional_context( &self, did: &str, part: &AdventPart, _code: &str, ) -> Result, AdventError> { match part { AdventPart::One => Ok(Some(json!({ "did": did }))), AdventPart::Two => Ok(None), } } async fn check_part_one( &self, did: String, verification_code: Option, ) -> Result { let submitted_code = match verification_code { Some(code) if !code.is_empty() => code, _ => { return Ok(ChallengeCheckResponse::Incorrect( "Please enter a verification code".to_string(), )); } }; let Some(challenge) = self.get_days_challenge(&did).await? else { log::error!("Could not find a challenge record for day: 5 for the user: {did:?}"); return Err(AdventError::ShouldNotHappen( "Could not find challenge record".to_string(), )); }; let expected_code = challenge .verification_code_one .ok_or(AdventError::ShouldNotHappen( "no verification code for day 5 challenge".to_string(), ))?; Ok(if submitted_code == expected_code { ChallengeCheckResponse::Correct } else { ChallengeCheckResponse::Incorrect(format!("The code {} is incorrect", submitted_code)) }) } }