ironshield_api/handler/
request.rs

1//! # Request handler and functions.
2
3use 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 the request.
43    validate_challenge_request(&payload)?;
44    
45    // Process the request and generate a challenge.
46    let challenge: IronShieldChallenge = generate_challenge_for_request(payload).await?;
47
48    // Return the challenge response.
49    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    // Validate that request url comes from Hypertext Transfer Protocol Secure.
62    if !request.endpoint.starts_with("https://") {
63        return Err(ErrorHandler::InvalidRequest(
64            INVALID_ENDPOINT.to_string(),
65        ));
66    }
67    
68    // Validate the request is not in the future or in the past.
69    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    // Load the signing key from the env var.
82    let signing_key = load_private_key_from_env()
83        .map_err(|e| ErrorHandler::ProcessingError(format!("Failed to load signing key: {}", e)))?;
84    
85    // Load the public key from the env var.
86    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    // Create the challenge using the construction from ironshield-types.
92    // This constructor automatically:
93    // - Generates a random nonce using IronShieldChallenge::generate_random_nonce().
94    // - Sets created_time using IronShieldChallenge::generate_created_time().
95    // - Sets expiration_time to created_time + 30 seconds.
96    // - Signs the challenge with the provided signing key.
97    let mut challenge = IronShieldChallenge::new(
98        request.endpoint.clone(),
99        challenge_param,
100        signing_key,
101        public_key.to_bytes(),
102    );
103    
104    // Set the challenge properties based on the difficulty.
105    challenge.set_recommended_attempts(ironshield_types::CHALLENGE_DIFFICULTY);
106    
107    // TODO: Store challenge in a cache for later verification.
108    
109    Ok(challenge)
110}
111
112#[cfg(test)]
113mod test {
114
115}