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::lexicons::codes::advent;
4use crate::lexicons::record::KnownRecord;
5use crate::{OAuthAgentType, PasswordAgent};
6use async_trait::async_trait;
7use atrium_api::types::Collection;
8use atrium_api::types::string::Tid;
9use serde_json::json;
10use sqlx::PgPool;
11
12pub struct DayFive {
13 pub pool: PgPool,
14 pub oauth_client: Option<OAuthAgentType>,
15 pub secret_agent: Option<PasswordAgent>,
16}
17
18impl DayFive {
19 /// Creates the challenge record on the secret agent's repo with the user's verification code.
20 /// This is called from the `/day/5/{did}` endpoint.
21 pub async fn create_secret_record(&self, did: &str) -> Result<(), AdventError> {
22 let Some(agent) = &self.secret_agent else {
23 log::warn!("No secret agent configured, skipping record creation for day five");
24 return Err(AdventError::ShouldNotHappen(
25 "No secret agent configured".to_string(),
26 ));
27 };
28
29 // Get the user's challenge to find their verification code
30 let challenge = self.get_days_challenge(did).await?.ok_or_else(|| {
31 AdventError::ShouldNotHappen("Could not find challenge record for day 5".to_string())
32 })?;
33
34 let code = challenge.verification_code_one.ok_or_else(|| {
35 AdventError::ShouldNotHappen(
36 "No verification code found for day 5 challenge".to_string(),
37 )
38 })?;
39
40 let agent_did = agent
41 .did()
42 .await
43 .ok_or_else(|| AdventError::ShouldNotHappen("Secret agent has no DID".to_string()))?;
44
45 let record_data = advent::challenge::shhh::RecordData {
46 secret_part_one: code,
47 created_at: atrium_api::types::string::Datetime::now(),
48 subject: did.parse().unwrap(),
49 };
50 let known_record: KnownRecord = record_data.into();
51 let record_value: atrium_api::types::Unknown = known_record.into();
52
53 let tid = Tid::from_datetime(23.try_into().unwrap(), challenge.time_started);
54 let result = agent
55 .api
56 .com
57 .atproto
58 .repo
59 .put_record(
60 atrium_api::com::atproto::repo::put_record::InputData {
61 collection: advent::challenge::Shhh::NSID.parse().unwrap(),
62 repo: agent_did.as_ref().parse().unwrap(),
63 rkey: tid.as_ref().parse().unwrap(),
64 swap_record: None,
65 record: record_value,
66 swap_commit: None,
67 validate: Some(false),
68 }
69 .into(),
70 )
71 .await;
72
73 match result {
74 Ok(_) => {
75 log::info!("Created secret record for day 5 for user: {did}");
76 Ok(())
77 }
78 Err(e) => {
79 log::error!("Failed to create secret record for day 5: {e}");
80 Err(AdventError::ShouldNotHappen(format!(
81 "Failed to create secret record: {e}"
82 )))
83 }
84 }
85 }
86}
87
88#[async_trait]
89impl AdventChallenge for DayFive {
90 fn pool(&self) -> &PgPool {
91 &self.pool
92 }
93
94 fn day(&self) -> Day {
95 Day::Five
96 }
97
98 fn has_part_two(&self) -> bool {
99 false
100 }
101
102 fn requires_manual_verification_part_one(&self) -> bool {
103 true
104 }
105
106 async fn build_additional_context(
107 &self,
108 did: &str,
109 part: &AdventPart,
110 _code: &str,
111 ) -> Result<Option<serde_json::Value>, AdventError> {
112 match part {
113 AdventPart::One => Ok(Some(json!({ "did": did }))),
114 AdventPart::Two => Ok(None),
115 }
116 }
117
118 async fn check_part_one(
119 &self,
120 did: String,
121 verification_code: Option<String>,
122 ) -> Result<ChallengeCheckResponse, AdventError> {
123 let submitted_code = match verification_code {
124 Some(code) if !code.is_empty() => code,
125 _ => {
126 return Ok(ChallengeCheckResponse::Incorrect(
127 "Please enter a verification code".to_string(),
128 ));
129 }
130 };
131
132 let Some(challenge) = self.get_days_challenge(&did).await? else {
133 log::error!("Could not find a challenge record for day: 5 for the user: {did:?}");
134 return Err(AdventError::ShouldNotHappen(
135 "Could not find challenge record".to_string(),
136 ));
137 };
138
139 let expected_code = challenge
140 .verification_code_one
141 .ok_or(AdventError::ShouldNotHappen(
142 "no verification code for day 5 challenge".to_string(),
143 ))?;
144
145 Ok(if submitted_code == expected_code {
146 ChallengeCheckResponse::Correct
147 } else {
148 ChallengeCheckResponse::Incorrect(format!("The code {} is incorrect", submitted_code))
149 })
150 }
151}