volga 0.9.1

Easy & Fast Web Framework for Rust
Documentation
#![allow(missing_docs)]
#![cfg(all(feature = "test", feature = "tls"))]

use reqwest::{Certificate, Identity, redirect::Policy};
use std::{sync::Once, time::Duration};
use volga::headers::{LOCATION, STRICT_TRANSPORT_SECURITY};
use volga::http::StatusCode;
use volga::test::TestServer;
use volga::tls::TlsConfig;

static INIT: Once = Once::new();

fn init_crypto() {
    INIT.call_once(|| {
        tokio_rustls::rustls::crypto::aws_lc_rs::default_provider()
            .install_default()
            .expect("Failed to install crypto provider");
    });
}

#[tokio::test]
async fn it_works_with_tls_with_no_auth() {
    init_crypto();

    let server = TestServer::builder()
        .with_https()
        .configure(|app| {
            app.set_tls(TlsConfig::from_pem_files(
                "tests/tls/server.pem",
                "tests/tls/server.key",
            ))
        })
        .setup(|app| {
            app.map_get("/tls", || async { "Pass!" });
        })
        .build()
        .await;

    let ca_cert = include_bytes!("tls/ca.pem");
    let ca_certificate = Certificate::from_pem(ca_cert).unwrap();

    let response = server
        .client_builder()
        .add_root_certificate(ca_certificate)
        .build()
        .unwrap()
        .get(server.url("/tls"))
        .send()
        .await
        .unwrap();

    assert_eq!(response.text().await.unwrap(), "Pass!");

    server.shutdown().await;
}

#[tokio::test]
async fn it_works_with_tls_with_required_auth_authenticated() {
    init_crypto();

    let server = TestServer::builder()
        .with_https()
        .configure(|app| {
            app.with_tls(|tls| {
                tls.with_cert_path("tests/tls/server.pem")
                    .with_key_path("tests/tls/server.key")
                    .with_required_client_auth("tests/tls/ca.pem")
            })
        })
        .setup(|app| {
            app.map_get("/tls", || async { "Pass!" });
        })
        .build()
        .await;

    let cert = std::fs::read_to_string("tests/tls/client.pem").unwrap();
    let key = std::fs::read_to_string("tests/tls/client.key").unwrap();
    let combined = format!("{}\n{}", cert, key);

    let identity = Identity::from_pem(combined.as_bytes()).unwrap();

    let ca_cert = include_bytes!("tls/ca.pem");
    let ca_certificate = Certificate::from_pem(ca_cert).unwrap();

    let response = server
        .client_builder()
        .add_root_certificate(ca_certificate)
        .identity(identity)
        .build()
        .unwrap()
        .get(server.url("/tls"))
        .send()
        .await
        .unwrap();

    assert_eq!(response.text().await.unwrap(), "Pass!");

    server.shutdown().await;
}

#[tokio::test]
async fn it_works_with_tls_with_required_auth_unauthenticated() {
    init_crypto();

    let server = TestServer::builder()
        .with_https()
        .configure(|app| {
            app.with_tls(|tls| {
                tls.with_cert_path("tests/tls/server.pem")
                    .with_key_path("tests/tls/server.key")
                    .with_required_client_auth("tests/tls/ca.pem")
            })
        })
        .setup(|app| {
            app.map_get("/tls", || async { "Pass!" });
        })
        .build()
        .await;

    let ca_cert = include_bytes!("tls/ca.pem");
    let ca_certificate = Certificate::from_pem(ca_cert).unwrap();

    let response = server
        .client_builder()
        .add_root_certificate(ca_certificate)
        .build()
        .unwrap()
        .get(server.url("/tls"))
        .send()
        .await;

    assert!(response.is_err());

    server.shutdown().await;
}

#[tokio::test]
async fn it_works_with_tls_with_optional_auth_authenticated() {
    init_crypto();

    let server = TestServer::builder()
        .with_https()
        .configure(|app| {
            app.with_tls(|tls| {
                tls.with_cert_path("tests/tls/server.pem")
                    .with_key_path("tests/tls/server.key")
                    .with_optional_client_auth("tests/tls/ca.pem")
            })
        })
        .setup(|app| {
            app.map_get("/tls", || async { "Pass!" });
        })
        .build()
        .await;

    let cert = std::fs::read_to_string("tests/tls/client.pem").unwrap();
    let key = std::fs::read_to_string("tests/tls/client.key").unwrap();
    let combined = format!("{}\n{}", cert, key);

    let identity = Identity::from_pem(combined.as_bytes()).unwrap();

    let ca_cert = include_bytes!("tls/ca.pem");
    let ca_certificate = Certificate::from_pem(ca_cert).unwrap();

    let response = server
        .client_builder()
        .add_root_certificate(ca_certificate)
        .identity(identity)
        .build()
        .unwrap()
        .get(server.url("/tls"))
        .send()
        .await
        .unwrap();

    assert_eq!(response.text().await.unwrap(), "Pass!");

    server.shutdown().await;
}

#[tokio::test]
async fn it_works_with_tls_with_optional_auth_unauthenticated() {
    init_crypto();

    let server = TestServer::builder()
        .with_https()
        .configure(|app| {
            app.with_tls(|tls| {
                tls.with_cert_path("tests/tls/server.pem")
                    .with_key_path("tests/tls/server.key")
                    .with_optional_client_auth("tests/tls/ca.pem")
            })
        })
        .setup(|app| {
            app.map_get("/tls", || async { "Pass!" });
        })
        .build()
        .await;

    let ca_cert = include_bytes!("tls/ca.pem");
    let ca_certificate = Certificate::from_pem(ca_cert).unwrap();

    let response = server
        .client_builder()
        .add_root_certificate(ca_certificate)
        .build()
        .unwrap()
        .get(server.url("/tls"))
        .send()
        .await
        .unwrap();

    assert_eq!(response.text().await.unwrap(), "Pass!");

    server.shutdown().await;
}

#[tokio::test]
async fn it_works_with_tls_with_required_auth_authenticated_and_https_redirection() {
    init_crypto();

    let http_port = TestServer::get_free_port();
    let server = TestServer::builder()
        .configure(move |app| {
            app.set_tls(TlsConfig::from_pem_files(
                "tests/tls/server.pem",
                "tests/tls/server.key",
            ))
            .with_tls(|tls| {
                tls.with_required_client_auth("tests/tls/ca.pem")
                    .with_https_redirection()
                    .with_http_port(http_port)
            })
            .with_hsts(|hsts| {
                hsts.without_preload()
                    .with_sub_domains()
                    .with_max_age(Duration::from_secs(60))
                    .with_exclude_hosts(["example.com", "example.net"])
            })
        })
        .setup(|app| {
            app.map_get("/tls", || async { "Pass!" });
        })
        .build()
        .await;

    let cert = std::fs::read_to_string("tests/tls/client.pem").unwrap();
    let key = std::fs::read_to_string("tests/tls/client.key").unwrap();
    let combined = format!("{}\n{}", cert, key);

    let identity = Identity::from_pem(combined.as_bytes()).unwrap();

    let ca_cert = include_bytes!("tls/ca.pem");
    let ca_certificate = Certificate::from_pem(ca_cert).unwrap();

    let response = server
        .client_builder()
        .add_root_certificate(ca_certificate)
        .identity(identity)
        .build()
        .unwrap()
        .get(format!("http://localhost:{http_port}/tls"))
        .header("host", "localhost")
        .send()
        .await
        .unwrap();

    assert_eq!(
        response.headers().get(STRICT_TRANSPORT_SECURITY).unwrap(),
        "max-age=60; includeSubDomains"
    );
    assert_eq!(response.text().await.unwrap(), "Pass!");

    server.shutdown().await;
}

#[tokio::test]
async fn it_works_with_tls_with_https_redirection() {
    init_crypto();

    let http_port = TestServer::get_free_port();
    let server = TestServer::builder()
        .configure(move |app| {
            app.set_tls(TlsConfig::from_pem_files(
                "tests/tls/server.pem",
                "tests/tls/server.key",
            ))
            .with_tls(|tls| tls.with_https_redirection().with_http_port(http_port))
            .with_hsts(|hsts| {
                hsts.without_preload()
                    .with_sub_domains()
                    .with_max_age(Duration::from_secs(60))
                    .with_exclude_hosts(["example.com", "example.net"])
            })
        })
        .setup(|app| {
            app.map_get("/tls", || async { "Pass!" });
        })
        .build()
        .await;

    let ca_cert = include_bytes!("tls/ca.pem");
    let ca_certificate = Certificate::from_pem(ca_cert).unwrap();

    let response = server
        .client_builder()
        .add_root_certificate(ca_certificate)
        .redirect(Policy::none())
        .build()
        .unwrap()
        .get(format!("http://localhost:{http_port}/tls"))
        .header("host", "localhost")
        .send()
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::TEMPORARY_REDIRECT);
    assert_eq!(
        response.headers().get(&LOCATION).unwrap(),
        format!("https://localhost:{}/tls", server.port).as_str()
    );

    server.shutdown().await;
}

#[tokio::test]
async fn it_returns_404_if_no_host() {
    init_crypto();

    let http_port = TestServer::get_free_port();
    let server = TestServer::builder()
        .configure(move |app| {
            app.set_tls(TlsConfig::from_pem_files(
                "tests/tls/server.pem",
                "tests/tls/server.key",
            ))
            .with_tls(|tls| tls.with_https_redirection().with_http_port(http_port))
            .with_hsts(|hsts| {
                hsts.without_preload()
                    .with_sub_domains()
                    .with_max_age(Duration::from_secs(60))
                    .with_exclude_hosts(["example.com", "example.net"])
            })
        })
        .setup(|app| {
            app.map_get("/tls", || async { "Pass!" });
        })
        .build()
        .await;

    let ca_cert = include_bytes!("tls/ca.pem");
    let ca_certificate = Certificate::from_pem(ca_cert).unwrap();

    let response = server
        .client_builder()
        .add_root_certificate(ca_certificate)
        .redirect(Policy::none())
        .build()
        .unwrap()
        .get(format!("http://localhost:{http_port}/tls"))
        .send()
        .await
        .unwrap();

    assert_eq!(response.status(), StatusCode::NOT_FOUND);
}