Skip to content

Commit

Permalink
Implement Bridge Spec (v1)
Browse files Browse the repository at this point in the history
  • Loading branch information
m1guelpf committed Aug 16, 2023
1 parent 9e96fcb commit 64a114a
Show file tree
Hide file tree
Showing 11 changed files with 858 additions and 83 deletions.
620 changes: 602 additions & 18 deletions Cargo.lock

Large diffs are not rendered by default.

25 changes: 19 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
[package]
name = "world-id-bridge"
version = "0.1.0"
license = "MIT"
edition = "2021"
version = "0.1.0"
name = "world-id-bridge"
authors = ["Miguel Piedrafita <[email protected]>"]
repository = "https://github.com/worldcoin/wallet-bridge"
description = "A bridge between the World ID SDK and the World App"

[dependencies]
axum = "0.6.20"
tower = "0.4.13"
serde = "1.0.183"
dotenvy = "0.15.7"
tokio = { version = "1", features = ["full"] }
lambda_http = "0.8.1"
axum-aws-lambda = "0.5.0"
tracing = { version = "0.1", features = ["log"] }
tower = { version = "0.4.13", features = ["make"] }
hyper = { version = "0.14.26", features = ["http1", "server", "tcp"] }
tokio = { version = "1.31.0", features = ["full"] }
tower-http = { version = "0.4.3", features = ["cors"] }
redis = { version = "0.23.0", features = ["tokio-comp", "connection-manager"] }
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] }
tracing-subscriber = { version = "0.3", default-features = false, features = [
"fmt",
] }

[build-dependencies]
chrono = "0.4.26"
9 changes: 4 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ FROM rust:latest AS builder

RUN update-ca-certificates

WORKDIR /world-id-bridge
WORKDIR /app

COPY ./Cargo.toml .
COPY ./Cargo.lock .
Expand All @@ -25,9 +25,8 @@ RUN cargo build --release
####################################################################################################
FROM gcr.io/distroless/cc

WORKDIR /world-id-bridge
WORKDIR /app

# Copy our build
COPY --from=builder /world-id-bridge/target/release/world-id-bridge /world-id-bridge/world-id-bridge
COPY --from=builder /app/target/release/world-id-bridge /app/world-id-bridge

CMD ["/world-id-bridge/world-id-bridge"]
CMD ["/app/world-id-bridge"]
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
# wallet-bridge
Establishes a very simple zero-knowledge bridge to pass World ID ZKPs from wallets to verifying apps.
# A bridge between the World ID SDK and the World App
29 changes: 29 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use chrono::prelude::{DateTime, Utc};
use std::process::Command;

fn get_git_rev() -> Option<String> {
let output = Command::new("git")
.args(["rev-parse", "--short", "HEAD"])
.output()
.ok()?;

if output.status.success() {
String::from_utf8(output.stdout).ok()
} else {
None
}
}

fn get_compile_date() -> String {
let system_time = std::time::SystemTime::now();
let date_time: DateTime<Utc> = system_time.into();
format!("{}", date_time.format("%+"))
}

fn main() {
println!("cargo:rustc-env=STATIC_BUILD_DATE={}", get_compile_date());

if let Some(rev) = get_git_rev() {
println!("cargo:rustc-env=GIT_REV={}", rev);
}
}
60 changes: 8 additions & 52 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,48 +1,13 @@
use dotenvy::dotenv;
use hyper::Method;
use hyper::{service::service_fn, Body, Request, Response, Server};
use redis::{aio::ConnectionManager, AsyncCommands, Client};
use std::convert::Infallible;
use std::{env, net::SocketAddr};
use tower::make::Shared;

async fn handle_request(
mut conn: ConnectionManager,
req: Request<Body>,
) -> Result<Response<Body>, Infallible> {
let path = req.uri().path();
let id = &path[1..].to_string();

if id.is_empty() {
return Ok(Response::builder().status(404).body(Body::empty()).unwrap());
}
#![warn(clippy::all, clippy::pedantic, clippy::nursery)]

match *req.method() {
Method::GET => {
let Ok(value) = conn.get::<_, String>(id).await else {
return Ok(Response::builder().status(404).body(Body::empty()).unwrap());
};
use dotenvy::dotenv;
use redis::{aio::ConnectionManager, Client};
use std::env;

Ok(Response::builder().status(200).body(value.into()).unwrap())
}
Method::PUT => {
let Ok(value) = hyper::body::to_bytes(&mut req.into_body()).await else {
return Ok(Response::builder().status(400).body(Body::empty()).unwrap());
};
mod routes;
mod server;

if conn
.set_ex::<_, _, ()>(id, value.to_vec(), 600)
.await
.is_err()
{
return Ok(Response::builder().status(500).body(Body::empty()).unwrap());
}

Ok(Response::builder().status(201).body(Body::empty()).unwrap())
}
_ => Ok(Response::builder().status(404).body(Body::empty()).unwrap()),
}
}
const EXPIRE_AFTER_SECONDS: usize = 60;

#[tokio::main]
async fn main() {
Expand All @@ -60,14 +25,5 @@ async fn main() {
.await
.expect("Failed to create redis connection manager");

let make_service = Shared::new(service_fn(move |req| handle_request(redis.clone(), req)));

let addr: SocketAddr = SocketAddr::from(([0, 0, 0, 0], 3000));

let server = Server::bind(&addr).serve(make_service);
println!("Listening on http://{}", addr);

if let Err(e) = server.await {
eprintln!("server error: {}", e);
}
server::start(redis).await;
}
12 changes: 12 additions & 0 deletions src/routes/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use axum::Router;

mod request;
mod response;
mod system;

pub fn handler() -> Router {
Router::new()
.merge(system::handler())
.merge(request::handler())
.merge(response::handler())
}
78 changes: 78 additions & 0 deletions src/routes/request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use axum::{
body::Bytes,
extract::Path,
http::{Method, StatusCode},
routing::head,
Extension, Router,
};
use redis::{aio::ConnectionManager, AsyncCommands};
use tower_http::cors::{Any, CorsLayer};

use crate::EXPIRE_AFTER_SECONDS;

const REQ_PREFIX: &str = "req:";

pub fn handler() -> Router {
let cors = CorsLayer::new()
.allow_methods([Method::PUT, Method::HEAD])
.allow_origin(Any);

Router::new().route(
"/request/:request_id",
head(has_request)
.get(get_request)
.put(insert_request)
.route_layer(cors),
)
}

async fn has_request(
Path(request_id): Path<String>,
Extension(mut redis): Extension<ConnectionManager>,
) -> StatusCode {
let Ok(exists) = redis
.exists::<_, bool>(format!("{REQ_PREFIX}{request_id}"))
.await
else {
return StatusCode::INTERNAL_SERVER_ERROR;
};

if exists {
StatusCode::OK
} else {
StatusCode::NOT_FOUND
}
}

async fn get_request(
Path(request_id): Path<String>,
Extension(mut redis): Extension<ConnectionManager>,
) -> Result<Vec<u8>, StatusCode> {
let value = redis
.get_del::<_, Option<Vec<u8>>>(format!("{REQ_PREFIX}{request_id}"))
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

value.ok_or(StatusCode::NOT_FOUND)
}

async fn insert_request(
Path(request_id): Path<String>,
Extension(mut redis): Extension<ConnectionManager>,
body: Bytes,
) -> Result<StatusCode, StatusCode> {
if !redis
.set_nx::<_, _, bool>(format!("{REQ_PREFIX}{request_id}"), body.to_vec())
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
{
return Ok(StatusCode::CONFLICT);
}

redis
.expire::<_, ()>(format!("{REQ_PREFIX}{request_id}"), EXPIRE_AFTER_SECONDS)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

Ok(StatusCode::CREATED)
}
57 changes: 57 additions & 0 deletions src/routes/response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use axum::{
body::Bytes,
extract::Path,
http::{Method, StatusCode},
routing::get,
Extension, Router,
};
use redis::{aio::ConnectionManager, AsyncCommands};
use tower_http::cors::{Any, CorsLayer};

use crate::EXPIRE_AFTER_SECONDS;

const RES_PREFIX: &str = "res:";

pub fn handler() -> Router {
let cors = CorsLayer::new()
.allow_methods([Method::GET])
.allow_origin(Any);

Router::new().route(
"/response/:request_id",
get(get_response).put(insert_response).route_layer(cors),
)
}

async fn get_response(
Path(request_id): Path<String>,
Extension(mut redis): Extension<ConnectionManager>,
) -> Result<Vec<u8>, StatusCode> {
let value = redis
.get_del::<_, Option<Vec<u8>>>(format!("{RES_PREFIX}{request_id}"))
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

value.ok_or(StatusCode::NOT_FOUND)
}

async fn insert_response(
Path(request_id): Path<String>,
Extension(mut redis): Extension<ConnectionManager>,
body: Bytes,
) -> Result<StatusCode, StatusCode> {
if !redis
.set_nx::<_, _, bool>(format!("{RES_PREFIX}{request_id}"), body.to_vec())
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
{
return Ok(StatusCode::CONFLICT);
}

redis
.expire::<_, ()>(&request_id, EXPIRE_AFTER_SECONDS)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

Ok(StatusCode::CREATED)
}
32 changes: 32 additions & 0 deletions src/routes/system.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use axum::{routing::get, Json, Router};

pub fn handler() -> Router {
Router::new().route("/", get(get_info))
}

#[derive(Debug, serde::Serialize)]
pub struct AppVersion {
semver: String,
rev: Option<String>,
compile_time: String,
}

#[derive(Debug, serde::Serialize)]
pub struct RootResponse {
/// Repository URL
pub repo_url: String,
/// Application version
pub version: AppVersion,
}

#[allow(clippy::unused_async)]
async fn get_info() -> Json<RootResponse> {
Json(RootResponse {
repo_url: "https://github.com/worldcoin/wallet-bridge".to_string(),
version: AppVersion {
semver: env!("CARGO_PKG_VERSION").to_string(),
compile_time: env!("STATIC_BUILD_DATE").to_string(),
rev: option_env!("GIT_REV").map(ToString::to_string),
},
})
}
16 changes: 16 additions & 0 deletions src/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use axum::Extension;
use axum_aws_lambda::LambdaLayer;
use redis::aio::ConnectionManager;
use tower::ServiceBuilder;

use crate::routes;

pub async fn start(redis: ConnectionManager) {
let router = routes::handler().layer(Extension(redis));

let app = ServiceBuilder::new()
.layer(LambdaLayer::default())
.service(router);

lambda_http::run(app).await.unwrap();
}

0 comments on commit 64a114a

Please sign in to comment.