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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
use super::AuthorizationState;
use http_body_util::BodyExt;
use hyper::{body::Incoming, Request, Response, StatusCode};
use reqwest::{Body, Method};
use tokio::net::TcpListener;

/// A utility struct used to manage the local server for browser-based authorization.
#[derive(Clone)]
pub(super) struct BrowserAuthContext {
    pub server_shutdown_tx: tokio::sync::mpsc::Sender<bool>,
    pub token_tx: tokio::sync::mpsc::Sender<AuthorizationState>,
}

/// Payload from the frontend after the user has authenticated.
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub(super) enum TokenStatus {
    /// Signifying that the token is cancelled
    Cancelled,
    /// Signifying that the token is authorized
    Authorized,
}

#[inline]
pub(super) async fn setup_listener() -> Result<(TcpListener, String), anyhow::Error> {
    let listener = TcpListener::bind("127.0.0.1:0").await?;
    let addr = listener.local_addr()?;
    let port = addr.port();

    let server_url = format!("http://localhost:{port}");

    Ok((listener, server_url))
}

/// Payload from the frontend after the user has authenticated.
///
/// This has the token that we need to set in the WASMER_TOML file.
#[derive(Clone, Debug, serde::Deserialize)]
pub(super) struct ValidatedNonceOutput {
    /// Token Received from the frontend
    pub token: Option<String>,
    /// Status of the token , whether it is authorized or cancelled
    pub status: TokenStatus,
}

pub(super) async fn service_router(
    context: BrowserAuthContext,
    req: Request<Incoming>,
) -> Result<Response<Body>, anyhow::Error> {
    match *req.method() {
        Method::OPTIONS => preflight(req).await,
        Method::POST => handle_post_save_token(context, req).await,
        _ => handle_unknown_method(context).await,
    }
}

async fn preflight(_: Request<Incoming>) -> Result<Response<Body>, anyhow::Error> {
    let response = Response::builder()
        .status(http::StatusCode::OK)
        .header("Access-Control-Allow-Origin", "*") // FIXME: this is not secure, Don't allow all origins. @syrusakbary
        .header("Access-Control-Allow-Headers", "Content-Type")
        .header("Access-Control-Allow-Methods", "POST, OPTIONS")
        .body(Body::default())?;
    Ok(response)
}

async fn handle_post_save_token(
    context: BrowserAuthContext,
    req: Request<Incoming>,
) -> Result<Response<Body>, anyhow::Error> {
    let BrowserAuthContext {
        server_shutdown_tx,
        token_tx,
    } = context;
    let (.., body) = req.into_parts();
    let body = body.collect().await?.to_bytes();

    let ValidatedNonceOutput {
        token,
        status: token_status,
    } = serde_json::from_slice::<ValidatedNonceOutput>(&body)?;

    // send the AuthorizationState based on token_status to the main thread and get the response message
    let (response_message, parse_failure) = match token_status {
        TokenStatus::Cancelled => {
            token_tx
                .send(AuthorizationState::Cancelled)
                .await
                .expect("Failed to send token");

            ("Token Cancelled by the user", false)
        }
        TokenStatus::Authorized => {
            if let Some(token) = token {
                token_tx
                    .send(AuthorizationState::TokenSuccess(token.clone()))
                    .await
                    .expect("Failed to send token");
                ("Token Authorized", false)
            } else {
                ("Token not found", true)
            }
        }
    };

    server_shutdown_tx
        .send(true)
        .await
        .expect("Failed to send shutdown signal");

    let status = if parse_failure {
        StatusCode::BAD_REQUEST
    } else {
        StatusCode::OK
    };

    Ok(Response::builder()
        .status(status)
        .header("Access-Control-Allow-Origin", "*") // FIXME: this is not secure, Don't allow all origins. @syrusakbary
        .header("Access-Control-Allow-Headers", "Content-Type")
        .header("Access-Control-Allow-Methods", "POST, OPTIONS")
        .body(Body::from(response_message))?)
}

async fn handle_unknown_method(
    context: BrowserAuthContext,
) -> Result<Response<Body>, anyhow::Error> {
    let BrowserAuthContext {
        server_shutdown_tx,
        token_tx,
    } = context;

    token_tx
        .send(AuthorizationState::UnknownMethod)
        .await
        .expect("Failed to send token");

    server_shutdown_tx
        .send(true)
        .await
        .expect("Failed to send shutdown signal");

    Ok(Response::builder()
        .status(StatusCode::METHOD_NOT_ALLOWED)
        .body(Body::from("Method not allowed"))?)
}