wasmer_cli/commands/auth/login/
auth_server.rs

1use super::AuthorizationState;
2use http_body_util::BodyExt;
3use hyper::{Request, Response, StatusCode, body::Incoming};
4use reqwest::{Body, Method};
5use tokio::net::TcpListener;
6
7/// A utility struct used to manage the local server for browser-based authorization.
8#[derive(Clone)]
9pub(super) struct BrowserAuthContext {
10    pub server_shutdown_tx: tokio::sync::mpsc::Sender<bool>,
11    pub token_tx: tokio::sync::mpsc::Sender<AuthorizationState>,
12}
13
14/// Payload from the frontend after the user has authenticated.
15#[derive(Clone, Debug, serde::Deserialize)]
16#[serde(rename_all = "lowercase")]
17pub(super) enum TokenStatus {
18    /// Signifying that the token is cancelled
19    Cancelled,
20    /// Signifying that the token is authorized
21    Authorized,
22}
23
24#[inline]
25pub(super) async fn setup_listener() -> Result<(TcpListener, String), anyhow::Error> {
26    let listener = TcpListener::bind("127.0.0.1:0").await?;
27    let addr = listener.local_addr()?;
28    let port = addr.port();
29
30    let server_url = format!("http://localhost:{port}");
31
32    Ok((listener, server_url))
33}
34
35/// Payload from the frontend after the user has authenticated.
36///
37/// This has the token that we need to set in the WASMER_TOML file.
38#[derive(Clone, Debug, serde::Deserialize)]
39pub(super) struct ValidatedNonceOutput {
40    /// Token Received from the frontend
41    pub token: Option<String>,
42    /// Status of the token , whether it is authorized or cancelled
43    pub status: TokenStatus,
44}
45
46pub(super) async fn service_router(
47    context: BrowserAuthContext,
48    req: Request<Incoming>,
49) -> Result<Response<Body>, anyhow::Error> {
50    match *req.method() {
51        Method::OPTIONS => preflight(req).await,
52        Method::POST => handle_post_save_token(context, req).await,
53        _ => handle_unknown_method(context).await,
54    }
55}
56
57async fn preflight(_: Request<Incoming>) -> Result<Response<Body>, anyhow::Error> {
58    let response = Response::builder()
59        .status(http::StatusCode::OK)
60        .header("Access-Control-Allow-Origin", "*") // FIXME: this is not secure, Don't allow all origins. @syrusakbary
61        .header("Access-Control-Allow-Headers", "Content-Type")
62        .header("Access-Control-Allow-Methods", "POST, OPTIONS")
63        .body(Body::default())?;
64    Ok(response)
65}
66
67async fn handle_post_save_token(
68    context: BrowserAuthContext,
69    req: Request<Incoming>,
70) -> Result<Response<Body>, anyhow::Error> {
71    let BrowserAuthContext {
72        server_shutdown_tx,
73        token_tx,
74    } = context;
75    let (.., body) = req.into_parts();
76    let body = body.collect().await?.to_bytes();
77
78    let ValidatedNonceOutput {
79        token,
80        status: token_status,
81    } = serde_json::from_slice::<ValidatedNonceOutput>(&body)?;
82
83    // send the AuthorizationState based on token_status to the main thread and get the response message
84    let (response_message, parse_failure) = match token_status {
85        TokenStatus::Cancelled => {
86            token_tx
87                .send(AuthorizationState::Cancelled)
88                .await
89                .expect("Failed to send token");
90
91            ("Token Cancelled by the user", false)
92        }
93        TokenStatus::Authorized => {
94            if let Some(token) = token {
95                token_tx
96                    .send(AuthorizationState::TokenSuccess(token.clone()))
97                    .await
98                    .expect("Failed to send token");
99                ("Token Authorized", false)
100            } else {
101                ("Token not found", true)
102            }
103        }
104    };
105
106    server_shutdown_tx
107        .send(true)
108        .await
109        .expect("Failed to send shutdown signal");
110
111    let status = if parse_failure {
112        StatusCode::BAD_REQUEST
113    } else {
114        StatusCode::OK
115    };
116
117    Ok(Response::builder()
118        .status(status)
119        .header("Access-Control-Allow-Origin", "*") // FIXME: this is not secure, Don't allow all origins. @syrusakbary
120        .header("Access-Control-Allow-Headers", "Content-Type")
121        .header("Access-Control-Allow-Methods", "POST, OPTIONS")
122        .body(Body::from(response_message))?)
123}
124
125async fn handle_unknown_method(
126    context: BrowserAuthContext,
127) -> Result<Response<Body>, anyhow::Error> {
128    let BrowserAuthContext {
129        server_shutdown_tx,
130        token_tx,
131    } = context;
132
133    token_tx
134        .send(AuthorizationState::UnknownMethod)
135        .await
136        .expect("Failed to send token");
137
138    server_shutdown_tx
139        .send(true)
140        .await
141        .expect("Failed to send shutdown signal");
142
143    Ok(Response::builder()
144        .status(StatusCode::METHOD_NOT_ALLOWED)
145        .body(Body::from("Method not allowed"))?)
146}