this repo has no description
at main 215 lines 8.9 kB view raw
1use crate::advent::day::Day; 2use crate::advent::{AdventChallenge, AdventError, AdventPart, ChallengeCheckResponse}; 3use crate::atrium::safe_check_unknown_record_parse; 4use crate::lexicons::codes::advent; 5use crate::lexicons::record::KnownRecord; 6use crate::{OAuthAgentType, PasswordAgent}; 7use async_trait::async_trait; 8use atrium_api::types::Collection; 9use serde_json::json; 10use sqlx::PgPool; 11 12pub struct DayTwo { 13 pub pool: PgPool, 14 pub oauth_client: Option<OAuthAgentType>, 15 pub challenge_agent: Option<PasswordAgent>, 16} 17 18#[async_trait] 19impl AdventChallenge for DayTwo { 20 fn pool(&self) -> &PgPool { 21 &self.pool 22 } 23 24 fn day(&self) -> Day { 25 Day::Two 26 } 27 28 fn has_part_two(&self) -> bool { 29 true 30 } 31 32 fn requires_manual_verification_part_one(&self) -> bool { 33 true 34 } 35 36 /// Create a record via the challenge agent and return the at_uri as additional context 37 async fn build_additional_context( 38 &self, 39 _did: &str, 40 part: &AdventPart, 41 code: &str, 42 ) -> Result<Option<serde_json::Value>, AdventError> { 43 match part { 44 AdventPart::One => { 45 let Some(agent) = &self.challenge_agent else { 46 log::warn!( 47 "No challenge agent configured, skipping record creation for day two" 48 ); 49 return Ok(None); 50 }; 51 52 let agent_did = agent.did().await.ok_or_else(|| { 53 AdventError::ShouldNotHappen("Challenge agent has no DID".to_string()) 54 })?; 55 56 let record_data = advent::challenge::day::RecordData { 57 part_one: Some(code.to_string()), 58 part_two: None, 59 created_at: None, 60 }; 61 let known_record: KnownRecord = record_data.into(); 62 let record_value: atrium_api::types::Unknown = known_record.into(); 63 64 let create_record_result = agent 65 .api 66 .com 67 .atproto 68 .repo 69 .create_record( 70 atrium_api::com::atproto::repo::create_record::InputData { 71 collection: advent::challenge::Day::NSID.parse().unwrap(), 72 repo: agent_did.as_ref().parse().unwrap(), 73 rkey: None, 74 record: record_value, 75 swap_commit: None, 76 validate: Some(false), 77 } 78 .into(), 79 ) 80 .await; 81 82 match create_record_result { 83 Ok(output) => Ok(Some(json!({"at_uri": output.uri}))), 84 Err(e) => { 85 log::error!("Failed to create challenge record via agent: {e}"); 86 Ok(None) 87 } 88 } 89 } 90 AdventPart::Two => Ok(None), 91 } 92 } 93 94 async fn check_part_one( 95 &self, 96 did: String, 97 verification_code: Option<String>, 98 ) -> Result<ChallengeCheckResponse, AdventError> { 99 let submitted_code = match verification_code { 100 Some(code) if !code.is_empty() => code, 101 _ => { 102 return Ok(ChallengeCheckResponse::Incorrect( 103 "Please enter a verification code".to_string(), 104 )); 105 } 106 }; 107 108 let Some(challenge) = self.get_days_challenge(&did).await? else { 109 log::error!("Could not find a challenge record for day: 2 for the user: {did:?}"); 110 return Err(AdventError::ShouldNotHappen( 111 "Could not find challenge record".to_string(), 112 )); 113 }; 114 115 let expected_code = challenge 116 .verification_code_one 117 .ok_or(AdventError::ShouldNotHappen( 118 "no verification code for day 2 challenge".to_string(), 119 ))?; 120 121 Ok(if submitted_code == expected_code { 122 ChallengeCheckResponse::Correct 123 } else { 124 ChallengeCheckResponse::Incorrect(format!("The code {} is incorrect", submitted_code)) 125 }) 126 } 127 128 ///TODO this is just a straight copy and paste of part one since it's a proof of concept needs to share code better between the two 129 async fn check_part_two( 130 &self, 131 did: String, 132 _verification_code: Option<String>, 133 ) -> Result<ChallengeCheckResponse, AdventError> { 134 match &self.oauth_client { 135 None => Err(AdventError::ShouldNotHappen( 136 "No oauth client. This should not happen".to_string(), 137 )), 138 Some(client) => { 139 match client 140 .api 141 .com 142 .atproto 143 .repo 144 .get_record( 145 atrium_api::com::atproto::repo::get_record::ParametersData { 146 cid: None, 147 collection: advent::challenge::Day::NSID.parse().unwrap(), 148 repo: did.parse().unwrap(), 149 rkey: "2".parse().unwrap(), 150 } 151 .into(), 152 ) 153 .await 154 { 155 Ok(record) => { 156 //TODO trouble, and make it double 157 let challenge = self.get_days_challenge(&did).await?; 158 159 match challenge { 160 None => { 161 log::error!( 162 "Could not find a challenge record for day: {} for the user: {}", 163 self.day(), 164 did.clone() 165 ); 166 Err(AdventError::ShouldNotHappen( 167 "Could not find a challenge record".to_string(), 168 )) 169 } 170 Some(challenge) => { 171 let parse_record_result = 172 safe_check_unknown_record_parse::< 173 advent::challenge::day::RecordData, 174 >(record.value.clone()); 175 176 match parse_record_result { 177 Ok(record_data) => match record_data.part_two { 178 None => { 179 Ok(ChallengeCheckResponse::Incorrect("The record is there, it's the right kind. But aren't you forgetting something?".to_string())) 180 } 181 Some(part_two_code) => { 182 match part_two_code 183 == challenge 184 .verification_code_two 185 .unwrap_or("".to_string()) 186 { 187 true => Ok(ChallengeCheckResponse::Correct), 188 false => { 189 Ok(ChallengeCheckResponse::Incorrect(format!( 190 "The code {} is incorrect", 191 record_data.part_one.unwrap_or("".to_string()) 192 ))) 193 } 194 } 195 } 196 }, 197 Err(err) => { 198 log::error!("Error parsing record: {}", err); 199 Ok(ChallengeCheckResponse::Incorrect(format!( 200 "There is a record at the correct location, but it does not seem like it is correct. Try again:\n{err}" 201 ))) 202 } 203 } 204 } 205 } 206 } 207 Err(err) => { 208 log::error!("Error getting record: {}", err); 209 Ok(ChallengeCheckResponse::Incorrect("Does not appear to be a record in your repo in the collection codes.advent.challenge.day with the record key of 1".to_string())) 210 } 211 } 212 } 213 } 214 } 215}