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
use super::error::{err_to_response, ApiErrorKind};
use super::Controller;
use crate::controller::error::IntoApiError;
use crate::database::connect::ConnectError;
use crate::types::{ConnectRequest, ConnectResponse, RevokeRequest};
use axum::{extract::State, response::Response, Json};
use reqwest::StatusCode;

fn connect_error_to_response(connect_error: &ConnectError) -> Response {
    match connect_error {
        ConnectError::FailedToAcquireKey => err_to_response(
            connect_error,
            StatusCode::INTERNAL_SERVER_ERROR,
            "Failed to acquire lock.",
            ApiErrorKind::FailedToAcquireKey,
        ),
        ConnectError::KeyUnheldNoSpawnConfig => err_to_response(
            connect_error,
            StatusCode::CONFLICT,
            "Lock is unheld but no spawn config was provided.",
            ApiErrorKind::KeyUnheldNoSpawnConfig,
        ),
        ConnectError::KeyHeldUnhealthy => err_to_response(
            connect_error,
            StatusCode::INTERNAL_SERVER_ERROR,
            "Lock is held but unhealthy.",
            ApiErrorKind::KeyHeldUnhealthy,
        ),
        ConnectError::KeyHeld { .. } => err_to_response(
            connect_error,
            StatusCode::CONFLICT,
            "Lock is held but tag does not match.",
            ApiErrorKind::KeyHeld,
        ),
        ConnectError::NoDroneAvailable => err_to_response(
            connect_error,
            StatusCode::INTERNAL_SERVER_ERROR,
            "No active drone available.",
            ApiErrorKind::NoDroneAvailable,
        ),
        ConnectError::FailedToRemoveKey => err_to_response(
            connect_error,
            StatusCode::CONFLICT,
            "Failed to remove lock.",
            ApiErrorKind::FailedToRemoveKey,
        ),
        ConnectError::DatabaseError(_) | ConnectError::Serialization(_) => err_to_response(
            connect_error,
            StatusCode::INTERNAL_SERVER_ERROR,
            "Internal error.",
            ApiErrorKind::Other,
        ),
        ConnectError::NoClusterProvided => err_to_response(
            connect_error,
            StatusCode::BAD_REQUEST,
            "No cluster provided, and no default cluster for this controller.",
            ApiErrorKind::NoClusterProvided,
        ),
        ConnectError::Other(_) => err_to_response(
            connect_error,
            StatusCode::INTERNAL_SERVER_ERROR,
            "Internal error.",
            ApiErrorKind::Other,
        ),
    }
}

pub async fn handle_connect(
    State(controller): State<Controller>,
    Json(request): Json<ConnectRequest>,
) -> Result<Json<ConnectResponse>, Response> {
    let response = controller
        .connect(&request)
        .await
        .map_err(|e| connect_error_to_response(&e))?;
    Ok(Json(response))
}

// TODO: Make proxies aware when a token is revoked, because they cache the
// token->backend mapping. This will probably require a larger re-thinking of
// how data is synchronized between the controller and proxies. Eventually we
// could even have it propagate to the proxies all the way so that it even
// interrupts existing connections!
pub async fn handle_revoke(
    State(controller): State<Controller>,
    Json(request): Json<RevokeRequest>,
) -> Result<Json<&'static str>, Response> {
    controller
        .db
        .revoke(&request)
        .await
        .or_internal_error("Failed to revoke token")?;
    Ok(Json("Token revoked successfully"))
}