// Copyright (c) 2022-2023 Niko Bonnieure, Par le Peuple, NextGraph.org developers // All rights reserved. // Licensed under the Apache License, Version 2.0 // // or the MIT license , // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. #[macro_use] extern crate anyhow; mod types; use duration_str::parse; use p2p_client_ws::remote_ws::ConnectionWebSocket; use p2p_net::actors::add_invitation::*; use p2p_net::broker::BROKER; use p2p_repo::store::StorageError; use serde::{Deserialize, Serialize}; use warp::http::header::{HeaderMap, HeaderValue}; use warp::reply::Response; use warp::{Filter, Reply}; use rust_embed::RustEmbed; use serde_bare::{from_slice, to_vec}; use serde_json::json; use std::convert::Infallible; use std::net::IpAddr; use std::str::FromStr; use std::sync::Arc; use std::{env, fs}; use crate::types::*; use ng_wallet::types::*; use p2p_net::types::{ AdminResponseContentV0, BindAddress, CreateAccountBSP, Invitation, InvitationCode, InvitationV0, APP_ACCOUNT_REGISTERED_SUFFIX, APP_NG_ONE_URL, NG_ONE_URL, }; use p2p_repo::log::*; use p2p_repo::types::*; use p2p_repo::utils::{generate_keypair, sign, timestamp_after, verify}; #[derive(RustEmbed)] #[folder = "web/dist"] struct Static; struct Server { admin_key: PrivKey, local_peer_key: PrivKey, ip: IpAddr, port: u16, peer_id: PubKey, domain: String, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] struct RegisterResponse { url: Option, invite: Option, error: Option, } impl Server { async fn register_(&self, ca: String) -> RegisterResponse { log_debug!("registering {}", ca); let cabsp = TryInto::::try_into(ca); if cabsp.is_err() { return RegisterResponse { error: Some("invalid request".into()), invite: None, url: None, }; } let mut cabsp = cabsp.unwrap(); log_debug!("{:?}", cabsp); // if needed, proceed with payment and verify it here. once validated, add the user let duration = parse("1d").unwrap(); let expiry = timestamp_after(duration); let symkey = SymKey::random(); let invite_code = InvitationCode::Unique(symkey.clone()); let local_peer_pubk = self.local_peer_key.to_pub(); let res = BROKER .write() .await .admin( Box::new(ConnectionWebSocket {}), self.local_peer_key.clone(), local_peer_pubk, self.peer_id, self.admin_key.to_pub(), self.admin_key.clone(), BindAddress { port: self.port, ip: (&self.ip).into(), }, AddInvitation::V0(AddInvitationV0 { invite_code, expiry, memo: None, tos_url: false, }), ) .await; let redirect_url = cabsp.redirect_url().clone().unwrap_or(format!( "https://{}{}", self.domain, APP_ACCOUNT_REGISTERED_SUFFIX )); match res { Err(e) => { log_err!("error while registering: {e} {:?}", cabsp); RegisterResponse { url: Some(format!("{}?re={}", redirect_url, e)), invite: None, error: Some(e.to_string()), } } Ok(AdminResponseContentV0::Invitation(Invitation::V0(mut invitation))) => { log_info!("invitation created successfully {:?}", invitation); invitation.name = Some(self.domain.clone()); let inv = Invitation::V0(invitation); RegisterResponse { url: Some(format!("{}?i={}&rs={}", redirect_url, inv, self.domain)), invite: Some(format!("{inv}")), error: None, } } _ => { log_err!( "error while registering: invalid response from add_invitation {:?}", cabsp ); let e = "add_invitation_failed"; RegisterResponse { url: Some(format!("{}?re={}", redirect_url, e)), invite: None, error: Some(e.to_string()), } } } } pub async fn register(self: Arc, ca: String) -> Result { let res = self.register_(ca).await; match &res { RegisterResponse { error: None, .. } => { let response = warp::reply::json(&res).into_response(); //Response::new(redirect_url.into()); let (mut parts, body) = response.into_parts(); parts.status = warp::http::StatusCode::OK; let response = Response::from_parts(parts, body); Ok(response) } RegisterResponse { error: Some(e), url: redirect_url, .. } => { if redirect_url.is_some() { let response = warp::reply::json(&res).into_response(); let (mut parts, body) = response.into_parts(); parts.status = warp::http::StatusCode::BAD_REQUEST; let response = Response::from_parts(parts, body); Ok(response) } else { Ok(warp::http::StatusCode::NOT_ACCEPTABLE.into_response()) } } } } } fn with_server( server: Arc, ) -> impl Filter,), Error = std::convert::Infallible> + Clone { warp::any().map(move || Arc::clone(&server)) } #[tokio::main] async fn main() -> anyhow::Result<()> { if std::env::var("RUST_LOG").is_err() { std::env::set_var("RUST_LOG", "info"); //trace } env_logger::init(); let domain = env::var("NG_ACCOUNT_DOMAIN").map_err(|_| anyhow!("NG_ACCOUNT_DOMAIN must be set"))?; let admin_user = env::var("NG_ACCOUNT_ADMIN") .map_err(|_| anyhow!("NG_ACCOUNT_ADMIN must be set (with private key)"))?; let admin_key: PrivKey = admin_user.as_str().try_into().map_err(|_| { anyhow!( "NG_ACCOUNT_ADMIN is invalid. It should be a base64-url encoded serde serialization of a [u8; 32] of the private key for an admin user. cannot start" ) })?; let local_peer_privkey = env::var("NG_ACCOUNT_LOCAL_PEER_KEY") .map_err(|_| anyhow!("NG_ACCOUNT_LOCAL_PEER_KEY must be set"))?; let local_peer_key: PrivKey = local_peer_privkey.as_str().try_into().map_err(|_| { anyhow!( "NG_ACCOUNT_LOCAL_PEER_KEY is invalid. It should be a base64-url encoded serde serialization of a [u8; 32] of the private key for the peerId. cannot start" ) })?; // format is IP,PORT,PEERID let server_address = env::var("NG_ACCOUNT_SERVER").map_err(|_| anyhow!("NG_ACCOUNT_SERVER must be set"))?; let addr: Vec<&str> = server_address.split(',').collect(); if addr.len() != 3 { return Err(anyhow!( "NG_ACCOUNT_SERVER is invalid. format is IP,PORT,PEER_ID" )); } let ip = IpAddr::from_str(addr[0]).map_err(|_| { anyhow!("NG_ACCOUNT_SERVER is invalid. format is IP,PORT,PEER_ID. The first part is not an IP address. cannot start") })?; let port = match addr[1].parse::() { Err(_) => { return Err(anyhow!("NG_ACCOUNT_SERVER is invalid. format is IP,PORT,PEER_ID. The port is invalid. It should be a number. cannot start")); } Ok(val) => val, }; let peer_id: PubKey = addr[2].try_into().map_err(|_| { anyhow!( "NG_ACCOUNT_SERVER is invalid. format is IP,PORT,PEER_ID. The PEER_ID is invalid. It should be a base64-url encoded serde serialization of a [u8; 32]. cannot start" ) })?; log::info!("domain {}", domain); let server = Arc::new(Server { admin_key, local_peer_key, ip, port, peer_id, domain: domain.clone(), }); // GET /api/v1/register/ca with the same ?ca= query param => 201 CREATED let register_api = warp::get() .and(with_server(server)) .and(warp::path!("register" / String)) .and_then(Server::register); let api_v1 = warp::path!("api" / "v1" / ..).and(register_api); let mut headers = HeaderMap::new(); headers.insert( "Content-Security-Policy", HeaderValue::from_static( #[cfg(debug_assertions)] "default-src 'self' data:; connect-src ipc: https://ipc.localhost 'self' http://192.168.192.2:3031", #[cfg(not(debug_assertions))] "default-src 'self' data:; connect-src ipc: https://ipc.localhost 'self'", ), ); let static_files = warp::get() .and(warp_embed::embed(&Static)) //.with(warp::reply::with::headers(headers)) .boxed(); let mut cors = warp::cors() .allow_methods(vec!["GET"]) .allow_headers(vec!["Content-Type"]); let incoming_log = warp::log::custom(|info| { if info.remote_addr().is_some() { log_info!( "{:?} {} {}", info.request_headers() .get("X-Forwarded-For") .map(|x| x.to_str().unwrap()) .unwrap_or(info.remote_addr().unwrap().to_string().as_str()), //info.remote_addr().unwrap(), info.method(), info.path() ); } }); #[cfg(not(debug_assertions))] { let origin = format!("https://{}", domain); let origin2 = format!("https://account.{}", domain); cors = cors.allow_origin(origin.as_str()); cors = cors.allow_origin(origin2.as_str()); log::info!("Starting server on http://localhost:3031"); warp::serve(api_v1.or(static_files).with(cors).with(incoming_log)) .run(([127, 0, 0, 1], 3031)) .await; } #[cfg(debug_assertions)] { log_debug!("CORS: any origin"); cors = cors.allow_any_origin(); log::info!("Starting server on http://192.168.192.2:3031"); warp::serve(api_v1.or(static_files).with(cors).with(incoming_log)) .run(([192, 168, 192, 2], 3031)) .await; } Ok(()) }