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)))
}