1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#![cfg(feature = "web")]

use actix_web::http::StatusCode;
use actix_web::{web, HttpRequest, Responder, Result};
use serde::{Deserialize, Serialize};
use std::str;

use crate::build_puzzle::build_puzzle;
use crate::config::get;
use crate::verify_puzzle_result::verify_puzzle_result;

/// An input to the puzzle builder web service.
#[derive(Deserialize)]
pub struct BuildPuzzleServiceInput {
    sitekey: String,
}

#[derive(Serialize)]
struct BuildPuzzleServiceOutputData {
    puzzle: String,
}

#[derive(Serialize)]
struct BuildPuzzleServiceOutput {
    data: BuildPuzzleServiceOutputData,
}

impl BuildPuzzleServiceOutput {
    fn new(puzzle: String) -> BuildPuzzleServiceOutput {
        BuildPuzzleServiceOutput {
            data: BuildPuzzleServiceOutputData { puzzle },
        }
    }
}

/// An input to the puzzle verification web service.
#[derive(Deserialize, Debug)]
pub struct VerifyPuzzleResultServiceInput {
    solution: String,
    secret: String,
}

#[derive(Serialize)]
struct VerifyPuzzleResultServiceOutput {
    success: bool,
    errors: Option<String>,
}

lazy_static! {
    static ref API_KEY: Vec<u8> = get::<Vec<u8>>("API_KEY");
}

/// A web service that serves puzzles to be solved.
pub async fn build_puzzle_service(
    req: HttpRequest,
    input: web::Query<BuildPuzzleServiceInput>,
) -> Result<impl Responder> {
    let con_info = req.connection_info();
    let remote_address = con_info.realip_remote_addr();
    if (input.sitekey.as_bytes() != *API_KEY) || (remote_address.is_none()) {
        return Ok((
            web::Json(BuildPuzzleServiceOutput::new("".to_string())),
            StatusCode::FORBIDDEN,
        ));
    }

    let puzzle_result = build_puzzle(con_info.realip_remote_addr().unwrap());
    match puzzle_result {
        Ok(puzzle) => Ok((
            web::Json(BuildPuzzleServiceOutput::new(puzzle)),
            StatusCode::OK,
        )),
        Err(_) => Ok((
            // TODO: Propagate error information
            web::Json(BuildPuzzleServiceOutput::new("".to_string())),
            StatusCode::INTERNAL_SERVER_ERROR,
        )),
    }
}

/// A web service that verifies solutions to a puzzle.
pub async fn verify_puzzle_result_service(
    input: web::Json<VerifyPuzzleResultServiceInput>,
) -> Result<impl Responder> {
    if input.secret.as_bytes() != *API_KEY {
        return Ok((
            web::Json(VerifyPuzzleResultServiceOutput {
                success: false,
                errors: Some("secret_invalid".to_string()),
            }),
            StatusCode::FORBIDDEN,
        ));
    }

    info!("Got puzzle result verify request with {:?}", input);
    let puzzle_result = verify_puzzle_result(&input.solution);

    match puzzle_result {
        Ok(_) => Ok((
            web::Json(VerifyPuzzleResultServiceOutput {
                success: true,
                errors: None,
            }),
            StatusCode::OK,
        )),
        Err(_) => Ok((
            web::Json(VerifyPuzzleResultServiceOutput {
                success: false,
                errors: None, // TODO: Add description
            }),
            StatusCode::OK,
        )),
    }
}