auth0-integration 0.6.1

Auth0 client library for M2M token retrieval and JWT validation (RS256)
Documentation
use anyhow::Result;
use auth0_integration::{
    Auth0Config,
    models::{AccessToken, UpdateUserRequest},
    services::{Auth0Client, Auth0ClientToken, validate_token::TokenValidator},
};
use axum::{
    Json, Router,
    extract::{Path, State},
    http::HeaderMap,
    routing::{get, patch, post},
};
use serde::Deserialize;
use std::sync::Arc;
use tower_http::trace::TraceLayer;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

struct AppState {
    config: Auth0Config,
    client: Auth0ClientToken,
    validator: TokenValidator,
}

fn extract_bearer(headers: &HeaderMap) -> Result<String, String> {
    headers
        .get("authorization")
        .and_then(|v| v.to_str().ok())
        .and_then(|v| v.strip_prefix("Bearer "))
        .map(|t| t.to_string())
        .ok_or_else(|| "Missing or invalid Authorization header".to_string())
}

#[tokio::main]
async fn main() -> Result<()> {
    dotenvy::dotenv().ok();

    tracing_subscriber::registry()
        .with(tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
            "debug_server=debug,auth0_integration=debug,tower_http=debug".into()
        }))
        .with(tracing_subscriber::fmt::layer())
        .init();

    let config = Auth0Config::from_env()?;

    let state = Arc::new(AppState {
        client: Auth0ClientToken::new(&config),
        validator: TokenValidator::new(),
        config,
    });

    let app = Router::new()
        .route("/token", get(get_token))
        .route("/user", get(get_user))
        .route("/user/get-or-create", post(get_or_create_user))
        .route("/user/:user_id", patch(update_user))
        .route("/token/decode", post(decode_token))
        .route("/token/validate", post(validate_token))
        .with_state(state)
        .layer(TraceLayer::new_for_http());

    let addr = "0.0.0.0:8081";
    tracing::info!("Debug server listening on {addr}");
    let listener = tokio::net::TcpListener::bind(addr).await?;
    axum::serve(listener, app).await?;

    Ok(())
}

async fn get_token(State(state): State<Arc<AppState>>) -> Result<Json<serde_json::Value>, String> {
    let token = state
        .client
        .get_access_token()
        .await
        .map_err(|e| e.to_string())?;

    Ok(Json(token.to_json()))
}

async fn get_user(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
) -> Result<Json<serde_json::Value>, String> {
    let token = extract_bearer(&headers)?;
    let client = Auth0Client::new(&state.config, token);
    let users = client
        .get_user_by_email("martin.teppa@wyeworks.com")
        .await
        .map_err(|e| e.to_string())?;

    Ok(Json(serde_json::json!(users)))
}

#[derive(Deserialize)]
struct GetOrCreateUserPayload {
    name: String,
    email: String,
}

async fn get_or_create_user(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
    Json(payload): Json<GetOrCreateUserPayload>,
) -> Result<Json<serde_json::Value>, String> {
    let token = extract_bearer(&headers)?;
    let client = Auth0Client::new(&state.config, token);
    let user = client
        .get_or_create_user(&payload.name, &payload.email)
        .await
        .map_err(|e| e.to_string())?;

    Ok(Json(serde_json::json!(user)))
}

async fn decode_token(
    headers: HeaderMap,
) -> Result<Json<serde_json::Value>, String> {
    let token = extract_bearer(&headers)?;
    let permissions = AccessToken::new(token).decoded().map_err(|e| e.to_string())?.permissions;
    Ok(Json(serde_json::json!({ "permissions": permissions })))
}

async fn validate_token(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
) -> Result<Json<serde_json::Value>, Json<serde_json::Value>> {
    let token = extract_bearer(&headers).map_err(|e| Json(serde_json::json!({ "error": e })))?;
    state.validator.validate(&token, &state.config)
        .await
        .map(|token_data| Json(serde_json::json!({ "claims": token_data.claims })))
        .map_err(|e| Json(serde_json::json!({
            "error": e.to_string(),
            "detail": format!("{e:?}"),
        })))
}

async fn update_user(
    State(state): State<Arc<AppState>>,
    headers: HeaderMap,
    Path(user_id): Path<String>,
    Json(payload): Json<UpdateUserRequest>,
) -> Result<Json<serde_json::Value>, String> {
    let token = extract_bearer(&headers)?;
    let client = Auth0Client::new(&state.config, token);
    let user = client
        .update_user(&user_id, payload)
        .await
        .map_err(|e| e.to_string())?;

    Ok(Json(serde_json::json!(user)))
}