ironshield_api/handler/
request.rs1use axum::extract::Json;
4use base64::{
5 Engine,
6 engine::general_purpose::STANDARD
7};
8use ed25519_dalek::{
9 SigningKey,
10 VerifyingKey,
11};
12use serde_json::{
13 json,
14 Value
15};
16
17use ironshield_types::{
18 load_private_key_from_env,
19 load_public_key_from_env,
20 IronShieldChallenge,
21 IronShieldRequest
22};
23use crate::constant;
24use crate::handler::{
25 error::{
26 ErrorHandler,
27 CLOCK_SKEW,
28 INVALID_ENDPOINT,
29 MAX_TIME_DIFF_MS,
30 SIG_KEY_FAIL,
31 PUB_KEY_FAIL
32 },
33 result::ResultHandler
34};
35
36use std::string::ToString;
37use std::env;
38
39pub async fn handle_challenge_request(
40 Json(payload): Json<IronShieldRequest>,
41) -> ResultHandler<Json<Value>> {
42 validate_challenge_request(&payload)?;
44
45 let challenge: IronShieldChallenge = generate_challenge_for_request(payload).await?;
47
48 Ok(Json(json!({
50 "status": constant::STATUS_OK,
51 "message": constant::STATUS_OK_MSG,
52 "challenge": challenge
53 })))
54}
55
56fn validate_challenge_request(
57 request: &IronShieldRequest
58) -> ResultHandler<()> {
59 let time_diff = (chrono::Utc::now().timestamp_millis() - request.timestamp).abs();
60
61 if !request.endpoint.starts_with("https://") {
63 return Err(ErrorHandler::InvalidRequest(
64 INVALID_ENDPOINT.to_string(),
65 ));
66 }
67
68 if time_diff > MAX_TIME_DIFF_MS {
70 return Err(ErrorHandler::InvalidRequest(
71 CLOCK_SKEW.to_string(),
72 ))
73 }
74
75 Ok(())
76}
77
78async fn generate_challenge_for_request(
79 request: IronShieldRequest
80) -> ResultHandler<IronShieldChallenge> {
81 let signing_key = load_private_key_from_env()
83 .map_err(|e| ErrorHandler::ProcessingError(format!("Failed to load signing key: {}", e)))?;
84
85 let public_key = load_public_key_from_env()
87 .map_err(|e| ErrorHandler::ProcessingError(format!("Failed to load public key: {}", e)))?;
88
89 let challenge_param = IronShieldChallenge::difficulty_to_challenge_param(ironshield_types::CHALLENGE_DIFFICULTY);
90
91 let mut challenge = IronShieldChallenge::new(
98 request.endpoint.clone(),
99 challenge_param,
100 signing_key,
101 public_key.to_bytes(),
102 );
103
104 challenge.set_recommended_attempts(ironshield_types::CHALLENGE_DIFFICULTY);
106
107 Ok(challenge)
110}
111
112#[cfg(test)]
113mod test {
114
115}