forked from
oppi.li/at-advent
this repo has no description
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}