vyuh 0.2.2

Vyuh web framework for Axum and SQLx with handler-first APIs
Documentation
use schemars::JsonSchema;
use serde::Serialize;
use vyuh::{
    SiteConf,
    auth::{AuthUser, BitRole, permit},
    bundles,
    routes::{Json, StatusCode},
    testing::TestClient,
};

fn test_conf() -> SiteConf {
    SiteConf {
        secret_key: "auth-test-secret-minimum-32-chars".to_string(),
        log_init: false,
        logging: vyuh::logging::LoggingConf {
            env_prefix: None,
            rules: vec![],
        },
        ..SiteConf::default()
    }
}

#[derive(BitRole)]
enum TestRole {
    Manager,
    Viewer,
}

#[derive(Debug, Serialize, JsonSchema)]
struct WhoAmI {
    key: String,
    roles: u64,
}

#[bundles::route(path = "/me")]
async fn me(user: AuthUser) -> Json<WhoAmI> {
    Json(WhoAmI {
        key: user.key.to_string(),
        roles: user.roles,
    })
}

#[bundles::route(path = "/secure")]
async fn secure(_permit: permit!(TestRole, Manager)) -> Json<WhoAmI> {
    Json(WhoAmI {
        key: "manager".to_string(),
        roles: TestRole::Manager.to_role_type(),
    })
}

#[tokio::test]
async fn auth_accepts_bearer_authorization_header() {
    let site = vyuh::build_site(
        test_conf(),
        bundles::bundle! {
            me,
        },
    )
    .await
    .unwrap();
    let token = site
        .authenticator()
        .create_token_pair(
            AuthUser::new("user-1", TestRole::Viewer.to_role_type()),
            &[],
        )
        .unwrap()
        .access_token;
    let client = TestClient::new(site.clone());

    client
        .get("/me")
        .header("authorization", &format!("Bearer {token}"))
        .send()
        .await
        .assert_status(StatusCode::OK);

    site.shutdown_and_wait().await;
}

#[tokio::test]
async fn auth_accepts_legacy_jwt_authorization_header() {
    let site = vyuh::build_site(
        test_conf(),
        bundles::bundle! {
            me,
        },
    )
    .await
    .unwrap();
    let token = site
        .authenticator()
        .create_token_pair(
            AuthUser::new("user-1", TestRole::Viewer.to_role_type()),
            &[],
        )
        .unwrap()
        .access_token;
    let client = TestClient::new(site.clone());

    client
        .get("/me")
        .header("authorization", &format!("JWT {token}"))
        .send()
        .await
        .assert_status(StatusCode::OK);

    site.shutdown_and_wait().await;
}

#[tokio::test]
async fn auth_missing_token_returns_unauthorized() {
    let site = vyuh::build_site(
        test_conf(),
        bundles::bundle! {
            me,
        },
    )
    .await
    .unwrap();
    let client = TestClient::new(site.clone());

    client
        .get("/me")
        .send()
        .await
        .assert_status(StatusCode::UNAUTHORIZED);

    site.shutdown_and_wait().await;
}

#[tokio::test]
async fn auth_permit_rejects_missing_role() {
    let site = vyuh::build_site(
        test_conf(),
        bundles::bundle! {
            secure,
        },
    )
    .await
    .unwrap();
    let token = site
        .authenticator()
        .create_token_pair(
            AuthUser::new("user-1", TestRole::Viewer.to_role_type()),
            &[],
        )
        .unwrap()
        .access_token;
    let client = TestClient::new(site.clone());

    client
        .get("/secure")
        .header("authorization", &format!("Bearer {token}"))
        .send()
        .await
        .assert_status(StatusCode::FORBIDDEN);

    site.shutdown_and_wait().await;
}