zlicenser-server 0.1.2

Server library for the zlicenser hardware-bound software licensing framework.
Documentation
pub mod enroll;
pub mod revoke;
pub mod transfer;
pub mod upgrade;
pub mod webhook;

use std::sync::Arc;

use axum::{
    Json, Router,
    http::StatusCode,
    response::{IntoResponse, Response},
    routing::{delete, post},
};
use serde_json::json;

use crate::issuance::handlers::HandlerContext;
use crate::storage::Storage;

pub fn build_router<S: Storage + Clone + Send + Sync + 'static>(
    ctx: Arc<HandlerContext<S>>,
) -> Router {
    Router::new()
        .route("/enroll/request", post(enroll::request_handler::<S>))
        .route(
            "/enroll/receipt/{session_id}",
            post(enroll::receipt_handler::<S>),
        )
        .route("/webhook/stripe", post(webhook::stripe_handler::<S>))
        .route("/licenses/{id}", delete(revoke::revoke_handler::<S>))
        .route(
            "/licenses/{id}/transfer/request",
            post(transfer::request_handler::<S>),
        )
        .route(
            "/transfers/{id}/approve",
            post(transfer::approve_handler::<S>),
        )
        .route(
            "/transfers/{id}/reject",
            post(transfer::reject_handler::<S>),
        )
        .route(
            "/licenses/{id}/upgrade",
            post(upgrade::upgrade_handler::<S>),
        )
        .with_state(ctx)
}

impl IntoResponse for crate::Error {
    fn into_response(self) -> Response {
        let (status, code) = match &self {
            crate::Error::NotFound
            | crate::Error::SessionNotFound
            | crate::Error::BindingNotFound => (StatusCode::NOT_FOUND, "not_found"),

            crate::Error::OfferExpired => (StatusCode::GONE, "offer_expired"),

            crate::Error::SeatLimitReached
            | crate::Error::Conflict(_)
            | crate::Error::TransferAlreadyPending => (StatusCode::CONFLICT, "conflict"),

            crate::Error::InvalidTransition(_) => (StatusCode::CONFLICT, "invalid_transition"),

            crate::Error::PaymentNotHeld => (StatusCode::PAYMENT_REQUIRED, "payment_not_held"),

            crate::Error::TsaFailed(_) | crate::Error::PaymentCaptureFailed(_) => {
                (StatusCode::BAD_GATEWAY, "upstream_failure")
            }

            crate::Error::ClientVersionRejected { .. }
            | crate::Error::OfferNonceMismatch
            | crate::Error::InvalidReceiptSignature
            | crate::Error::ConsentInvalid(_) => (StatusCode::BAD_REQUEST, "bad_request"),

            crate::Error::ProductInactive
            | crate::Error::RevocationNotSupported
            | crate::Error::ForeignKeyViolation(_) => {
                (StatusCode::UNPROCESSABLE_ENTITY, "rejected")
            }

            _ => (StatusCode::INTERNAL_SERVER_ERROR, "internal_error"),
        };

        let body = Json(json!({ "error": code, "message": self.to_string() }));
        (status, body).into_response()
    }
}