bsv_wallet_cli/server/
mod.rs1pub mod handlers;
2pub mod types;
3
4use anyhow::Result;
5use axum::extract::{DefaultBodyLimit, Request};
6use axum::http::{HeaderMap, StatusCode};
7use axum::middleware::{self, Next};
8use axum::response::{IntoResponse, Response};
9use axum::routing::{get, post};
10use axum::{Json, Router};
11use bsv_wallet_toolbox::{Chain, Services, StorageSqlx, Wallet};
12use serde_json::json;
13use std::net::SocketAddr;
14use std::sync::Arc;
15use tower_http::cors::CorsLayer;
16use tower_http::trace::TraceLayer;
17
18use handlers::WalletState;
19
20pub type SpendingLock = Arc<tokio::sync::Mutex<()>>;
27
28#[derive(Clone)]
30#[allow(dead_code)]
31pub struct TlsConfig {
32 pub cert_path: String,
33 pub key_path: String,
34}
35
36#[derive(Clone, Default)]
38pub struct ServerConfig {
39 pub auth_token: Option<String>,
42 pub tls: Option<TlsConfig>,
44 pub chain: Chain,
47}
48
49async fn auth_middleware(headers: HeaderMap, request: Request, next: Next) -> Response {
51 let token = request
53 .extensions()
54 .get::<ServerConfig>()
55 .and_then(|c| c.auth_token.clone());
56
57 if let Some(expected) = token {
58 let provided = headers
59 .get("authorization")
60 .and_then(|v| v.to_str().ok())
61 .and_then(|v| v.strip_prefix("Bearer "));
62
63 match provided {
64 Some(t) if t == expected => {}
65 _ => {
66 return (
67 StatusCode::UNAUTHORIZED,
68 Json(json!({"code": "UNAUTHORIZED", "message": "Invalid or missing bearer token"})),
69 )
70 .into_response();
71 }
72 }
73 }
74
75 next.run(request).await
76}
77
78pub fn make_router(wallet: WalletState, config: ServerConfig) -> Router {
80 let cors = CorsLayer::very_permissive();
81 let cfg = config.clone();
82 let spending_lock: SpendingLock = Arc::new(tokio::sync::Mutex::new(()));
83 let verifier = crate::broadcast_verify::BroadcastVerifier::from_env(config.chain);
87
88 Router::new()
89 .route(
94 "/isAuthenticated",
95 get(handlers::is_authenticated).post(handlers::is_authenticated),
96 )
97 .route("/getPublicKey", post(handlers::get_public_key))
98 .route("/createSignature", post(handlers::create_signature))
99 .route("/createAction", post(handlers::create_action))
100 .route("/internalizeAction", post(handlers::internalize_action))
101 .route(
103 "/getHeight",
104 get(handlers::get_height).post(handlers::get_height),
105 )
106 .route(
107 "/getNetwork",
108 get(handlers::get_network).post(handlers::get_network),
109 )
110 .route(
111 "/getVersion",
112 get(handlers::get_version).post(handlers::get_version),
113 )
114 .route(
115 "/waitForAuthentication",
116 get(handlers::wait_for_authentication).post(handlers::wait_for_authentication),
117 )
118 .route("/getHeaderForHeight", post(handlers::get_header_for_height))
120 .route("/verifySignature", post(handlers::verify_signature))
122 .route("/encrypt", post(handlers::encrypt))
123 .route("/decrypt", post(handlers::decrypt))
124 .route("/createHmac", post(handlers::create_hmac))
125 .route("/verifyHmac", post(handlers::verify_hmac))
126 .route("/signAction", post(handlers::sign_action))
128 .route("/abortAction", post(handlers::abort_action))
129 .route("/listActions", post(handlers::list_actions))
130 .route("/listOutputs", post(handlers::list_outputs))
131 .route("/relinquishOutput", post(handlers::relinquish_output))
132 .route("/acquireCertificate", post(handlers::acquire_certificate))
134 .route("/listCertificates", post(handlers::list_certificates))
135 .route("/proveCertificate", post(handlers::prove_certificate))
136 .route(
137 "/relinquishCertificate",
138 post(handlers::relinquish_certificate),
139 )
140 .route(
142 "/discoverByIdentityKey",
143 post(handlers::discover_by_identity_key),
144 )
145 .route(
146 "/discoverByAttributes",
147 post(handlers::discover_by_attributes),
148 )
149 .route(
150 "/revealCounterpartyKeyLinkage",
151 post(handlers::reveal_counterparty_key_linkage),
152 )
153 .route(
154 "/revealSpecificKeyLinkage",
155 post(handlers::reveal_specific_key_linkage),
156 )
157 .layer(cors)
159 .layer(middleware::from_fn(auth_middleware))
160 .layer(axum::Extension(cfg))
161 .layer(axum::Extension(spending_lock))
162 .layer(axum::Extension(verifier))
163 .layer(TraceLayer::new_for_http())
164 .layer(DefaultBodyLimit::max(50 * 1024 * 1024))
165 .with_state(wallet)
166}
167
168pub async fn run(wallet: WalletState, port: u16, config: ServerConfig) -> Result<()> {
169 let tls = config.tls.clone();
170 let app = make_router(wallet, config);
171 let addr = SocketAddr::from(([127, 0, 0, 1], port));
172
173 #[cfg(feature = "tls")]
174 if let Some(tls_cfg) = tls {
175 use axum_server::tls_rustls::RustlsConfig;
176 let rustls = RustlsConfig::from_pem_file(&tls_cfg.cert_path, &tls_cfg.key_path).await?;
177 tracing::info!("HTTPS server listening on {addr}");
178 eprintln!("HTTPS server listening on {addr}");
179 axum_server::bind_rustls(addr, rustls)
180 .serve(app.into_make_service())
181 .await?;
182 return Ok(());
183 }
184
185 #[cfg(not(feature = "tls"))]
186 if tls.is_some() {
187 anyhow::bail!("TLS requested but binary was built without `--features tls`");
188 }
189
190 tracing::info!("HTTP server listening on {addr}");
191 eprintln!("HTTP server listening on {addr}");
192
193 let listener = tokio::net::TcpListener::bind(addr).await?;
194 axum::serve(listener, app)
195 .with_graceful_shutdown(shutdown_signal())
196 .await?;
197
198 Ok(())
199}
200
201pub fn make_wallet_state(wallet: Wallet<StorageSqlx, Services>) -> WalletState {
202 Arc::new(wallet)
203}
204
205#[allow(dead_code)] pub fn make_wallet_state_from_arc(wallet: Arc<Wallet<StorageSqlx, Services>>) -> WalletState {
214 wallet
215}
216
217async fn shutdown_signal() {
218 tokio::signal::ctrl_c()
219 .await
220 .expect("failed to listen for ctrl-c");
221 eprintln!("\nShutting down...");
222}