use axum::{
extract::{Extension, Path},
http::StatusCode,
response::{IntoResponse, Json},
routing::{get, post},
Router,
};
use guarantee::{state, attest, Encrypted, crypto::Encryptable};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Encrypted, Serialize, Deserialize, Clone, Debug)]
struct UserRecord {
user_id: String,
#[encrypt]
ssn: String,
email: String,
}
#[derive(Serialize, Deserialize, Default, Clone, Debug)]
struct DbConfig {
connection_url: String,
}
state! {
#[mrenclave]
DbConfig,
#[mrsigner]
DbConfig,
#[external]
UserRecord,
}
struct MockDb {
records: tokio::sync::RwLock<std::collections::HashMap<String, EncryptedUserRecord>>,
}
impl MockDb {
fn new() -> Self {
Self {
records: tokio::sync::RwLock::new(std::collections::HashMap::new()),
}
}
}
#[attest]
async fn create_user(
Extension(state): Extension<Arc<TeeState>>,
Extension(db): Extension<Arc<MockDb>>,
Json(user): Json<UserRecord>,
) -> impl IntoResponse {
let encrypted = match state.encrypt_user_record(&user) {
Ok(e) => e,
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response()
}
};
println!("Storing in DB:");
println!(" user_id: {}", encrypted.user_id);
println!(" ssn: {} (encrypted)", &encrypted.ssn[..30]);
println!(" email: {}", encrypted.email);
db.records
.write()
.await
.insert(encrypted.user_id.clone(), encrypted);
(
StatusCode::CREATED,
Json(serde_json::json!({"status": "created", "user_id": user.user_id})),
)
.into_response()
}
#[attest]
async fn get_user(
Extension(state): Extension<Arc<TeeState>>,
Extension(db): Extension<Arc<MockDb>>,
Path(user_id): Path<String>,
) -> impl IntoResponse {
let records = db.records.read().await;
let encrypted = match records.get(&user_id) {
Some(r) => r,
None => {
return (
StatusCode::NOT_FOUND,
Json(serde_json::json!({"error": "User not found"})),
)
.into_response()
}
};
match state.decrypt_user_record(encrypted) {
Ok(user) => Json(serde_json::json!({
"user_id": user.user_id,
"ssn": user.ssn,
"email": user.email,
}))
.into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"error": format!("Decryption failed: {e}")})),
)
.into_response(),
}
}
async fn get_user_raw(
Extension(db): Extension<Arc<MockDb>>,
Path(user_id): Path<String>,
) -> impl IntoResponse {
let records = db.records.read().await;
match records.get(&user_id) {
Some(encrypted) => Json(serde_json::json!({
"user_id": encrypted.user_id,
"ssn": encrypted.ssn,
"email": encrypted.email,
"_note": "This is what the database stores. The ssn field is AES-256-GCM encrypted."
}))
.into_response(),
None => (
StatusCode::NOT_FOUND,
Json(serde_json::json!({"error": "User not found"})),
)
.into_response(),
}
}
async fn attestation_info(
Extension(state): Extension<Arc<TeeState>>,
) -> Json<serde_json::Value> {
Json(state.attestation_json())
}
#[tokio::main]
async fn main() {
let port = std::env::var("PORT").unwrap_or_else(|_| "8080".to_string());
let state = TeeState::initialize(std::path::Path::new("./sealed"))
.expect("Failed to initialize TeeState");
let db = Arc::new(MockDb::new());
println!("Encrypted DB example on port {port}");
println!(" POST /users — create user (ssn encrypted at rest)");
println!(" GET /users/:id — get user (decrypted in enclave)");
println!(" GET /users/:id/raw — see what the DB stores (ciphertext)");
let app = Router::new()
.route("/users", post(create_user))
.route("/users/:id", get(get_user))
.route("/users/:id/raw", get(get_user_raw))
.route("/.well-known/tee-attestation", get(attestation_info))
.layer(Extension(Arc::new(state)))
.layer(Extension(db));
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}"))
.await
.expect("Failed to bind");
axum::serve(listener, app).await.expect("Server failed");
}