diff --git a/Cargo.lock b/Cargo.lock index a7ed645..1d02378 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + [[package]] name = "ahash" version = "0.8.11" @@ -71,6 +81,12 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "async-stream" version = "0.3.6" @@ -155,6 +171,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals 0.3.0", + "bitcoin_hashes 0.14.0", +] + [[package]] name = "base64" version = "0.21.7" @@ -173,12 +199,99 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + [[package]] name = "binascii" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" +[[package]] +name = "bip39" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" +dependencies = [ + "bitcoin_hashes 0.13.0", + "serde", + "unicode-normalization", +] + +[[package]] +name = "bitcoin" +version = "0.32.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "788902099d47c8682efe6a7afb01c8d58b9794ba66c06affd81c3d6b560743eb" +dependencies = [ + "base58ck", + "bech32", + "bitcoin-internals 0.3.0", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes 0.14.0", + "hex-conservative 0.2.1", + "hex_lit", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + +[[package]] +name = "bitcoin-units" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" +dependencies = [ + "bitcoin-internals 0.3.0", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals 0.2.0", + "hex-conservative 0.1.2", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.1", + "serde", +] + [[package]] name = "bitflags" version = "2.6.0" @@ -197,6 +310,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -221,6 +343,15 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.1.31" @@ -236,6 +367,30 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.38" @@ -246,10 +401,22 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.52.6", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -392,6 +559,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] @@ -722,8 +890,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -834,6 +1004,27 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" + +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + [[package]] name = "hkdf" version = "0.12.4" @@ -1081,6 +1272,28 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "ipnet" version = "2.10.1" @@ -1167,9 +1380,11 @@ name = "lnvps" version = "0.1.0" dependencies = [ "anyhow", + "base64 0.22.1", "chrono", "config", "log", + "nostr", "pretty_env_logger", "reqwest", "rocket", @@ -1304,6 +1519,18 @@ dependencies = [ "tempfile", ] +[[package]] +name = "negentropy" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e664971378a3987224f7a0e10059782035e89899ae403718ee07de85bec42afe" + +[[package]] +name = "negentropy" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a88da9dd148bbcdce323dd6ac47d369b4769d4a3b78c6c52389b9269f77932" + [[package]] name = "nom" version = "7.1.3" @@ -1314,6 +1541,31 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nostr" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56db234b2e07901e372f34e9463f91590579cd8e6dbd34ed2ccc7e461e4ba639" +dependencies = [ + "base64 0.22.1", + "bech32", + "bip39", + "bitcoin", + "cbc", + "chacha20", + "chacha20poly1305", + "getrandom", + "instant", + "negentropy 0.3.1", + "negentropy 0.4.3", + "once_cell", + "scrypt", + "serde", + "serde_json", + "unicode-normalization", + "url", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1402,6 +1654,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.68" @@ -1491,6 +1749,17 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -1503,6 +1772,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "pear" version = "0.2.9" @@ -1625,6 +1904,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1868,6 +2158,7 @@ dependencies = [ "rocket_codegen", "rocket_http", "serde", + "serde_json", "state", "tempfile", "time", @@ -2035,6 +2326,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + [[package]] name = "schannel" version = "0.1.26" @@ -2056,6 +2356,39 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes 0.14.0", + "rand", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -2105,6 +2438,7 @@ version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ + "indexmap", "itoa", "memchr", "ryu", @@ -2852,9 +3186,9 @@ checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] @@ -2883,6 +3217,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -2898,6 +3242,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 048c492..de3a27e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,5 +15,7 @@ serde = { version = "1.0.213", features = ["derive"] } reqwest = { version = "0.12.8", features = ["json"] } serde_json = "1.0.132" sqlx = { version = "0.8.2", features = ["mysql", "chrono", "migrate", "runtime-tokio"] } -rocket = "0.5.1" -chrono = "0.4.38" +rocket = { version = "0.5.1", features = ["json"] } +chrono = { version = "0.4.38", features = ["serde"] } +nostr = { version = "0.35.0", default-features = false, features = ["std"] } +base64 = "0.22.1" diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 0000000..1b5c67b --- /dev/null +++ b/src/api.rs @@ -0,0 +1,46 @@ +use crate::db; +use crate::nip98::Nip98Auth; +use crate::provisioner::Provisioner; +use anyhow::Error; +use rocket::serde::json::Json; +use rocket::{get, routes, Responder, Route, State}; +use serde::{Deserialize, Serialize}; + +pub fn routes() -> Vec { + routes![v1_list_vms] +} + +type ApiResult = Result>, ApiError>; + +#[derive(Serialize)] +struct ApiData { + pub data: T, +} + +impl ApiData { + pub fn ok(data: T) -> ApiResult { + Ok(Json::from(ApiData { data })) + } +} + +#[derive(Responder)] +#[response(status = 500)] +struct ApiError { + pub error: String, +} + +impl From for ApiError { + fn from(value: Error) -> Self { + Self { + error: value.to_string(), + } + } +} + +#[get("/api/v1/vms")] +async fn v1_list_vms(auth: Nip98Auth, provisioner: &State) -> ApiResult> { + let pubkey = auth.event.pubkey.to_bytes(); + let uid = provisioner.upsert_user(&pubkey).await?; + let vms = provisioner.list_vms(uid).await?; + ApiData::ok(vms) +} diff --git a/src/cors.rs b/src/cors.rs new file mode 100644 index 0000000..3f8db01 --- /dev/null +++ b/src/cors.rs @@ -0,0 +1,32 @@ +use rocket::fairing::{Fairing, Info, Kind}; +use rocket::http::{Header, Method, Status}; +use rocket::{Request, Response}; +use std::io::Cursor; + +pub struct CORS; + +#[rocket::async_trait] +impl Fairing for CORS { + fn info(&self) -> Info { + Info { + name: "CORS headers", + kind: Kind::Response, + } + } + + async fn on_response<'r>(&self, req: &'r Request<'_>, response: &mut Response<'r>) { + response.set_header(Header::new("Access-Control-Allow-Origin", "*")); + response.set_header(Header::new( + "Access-Control-Allow-Methods", + "PUT, GET, HEAD, DELETE, OPTIONS, POST", + )); + response.set_header(Header::new("Access-Control-Allow-Headers", "*")); + response.set_header(Header::new("Access-Control-Allow-Credentials", "true")); + + // force status 200 for options requests + if req.method() == Method::Options { + response.set_status(Status::Ok); + response.set_sized_body(None, Cursor::new("")) + } + } +} diff --git a/src/db.rs b/src/db.rs index af2bdf6..eea875b 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,5 +1,8 @@ use chrono::{DateTime, Utc}; +use rocket::serde::Serialize; +use sqlx::FromRow; +#[derive(Serialize, FromRow)] /// Users who buy VM's pub struct User { /// Unique ID of this user (database generated) @@ -10,11 +13,13 @@ pub struct User { pub created: DateTime, } +#[derive(Serialize)] /// The type of VM host pub enum VmHostKind { Proxmox, } +#[derive(Serialize, FromRow)] /// A VM host pub struct VmHost { pub id: u64, @@ -30,6 +35,7 @@ pub struct VmHost { pub api_token: String, } +#[derive(Serialize, FromRow)] pub struct VmHostDisk { pub id: u64, pub host_id: u64, @@ -40,29 +46,34 @@ pub struct VmHostDisk { pub enabled: bool, } +#[derive(Serialize)] pub enum DiskType { HDD, SSD, } +#[derive(Serialize)] pub enum DiskInterface { SATA, SCSI, PCIe, } +#[derive(Serialize, FromRow)] pub struct VmOsImage { pub id: u64, pub name: String, pub enabled: bool, } +#[derive(Serialize, FromRow)] pub struct IpRange { pub id: u64, pub cidr: String, pub enabled: bool, } +#[derive(Serialize, FromRow)] pub struct Vm { /// Unique VM ID (Same in proxmox) pub id: u64, diff --git a/src/main.rs b/src/main.rs index b37955c..0132619 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,25 @@ +extern crate core; + +use crate::cors::CORS; use crate::provisioner::Provisioner; use crate::settings::Settings; +use anyhow::Error; use config::{Config, File}; +use log::error; +use rocket::routes; use sqlx::MySqlPool; +mod api; +mod cors; mod db; +mod nip98; mod provisioner; mod proxmox; mod settings; mod vm; #[rocket::main] -async fn main() -> Result<(), anyhow::Error> { +async fn main() -> Result<(), Error> { pretty_env_logger::init(); let config: Settings = Config::builder() @@ -20,7 +29,17 @@ async fn main() -> Result<(), anyhow::Error> { let db = MySqlPool::connect(&config.db).await?; sqlx::migrate!("./migrations").run(&db).await?; - let provisioner = Provisioner::new(db.clone()); + let provisioner = Provisioner::new(db); + + if let Err(e) = rocket::build() + .attach(CORS) + .manage(provisioner) + .mount("/", api::routes()) + .launch() + .await + { + error!("{}", e); + } Ok(()) } diff --git a/src/nip98.rs b/src/nip98.rs new file mode 100644 index 0000000..3f30fe9 --- /dev/null +++ b/src/nip98.rs @@ -0,0 +1,92 @@ +use base64::prelude::BASE64_STANDARD; +use base64::Engine; +use log::info; +use nostr::{Event, JsonUtil, Kind, Timestamp}; +use rocket::http::uri::{Absolute, Uri}; +use rocket::http::Status; +use rocket::request::{FromRequest, Outcome}; +use rocket::{async_trait, Request}; + +pub struct Nip98Auth { + pub event: Event, +} + +#[async_trait] +impl<'r> FromRequest<'r> for Nip98Auth { + type Error = &'static str; + + async fn from_request(request: &'r Request<'_>) -> Outcome { + if let Some(auth) = request.headers().get_one("authorization") { + if auth.starts_with("Nostr ") { + let event = if let Ok(j) = BASE64_STANDARD.decode(&auth[6..]) { + if let Ok(ev) = Event::from_json(j) { + ev + } else { + return Outcome::Error((Status::new(403), "Invalid nostr event")); + } + } else { + return Outcome::Error((Status::new(403), "Invalid auth string")); + }; + + if event.kind != Kind::HttpAuth { + return Outcome::Error((Status::new(401), "Wrong event kind")); + } + if event + .created_at + .as_u64() + .abs_diff(Timestamp::now().as_u64()) + > 600 + { + return Outcome::Error((Status::new(401), "Created timestamp is out of range")); + } + + // check url tag + if let Some(url) = event.tags.iter().find_map(|t| { + let vec = t.as_slice(); + if vec[0] == "u" { + Some(vec[1].clone()) + } else { + None + } + }) { + if let Ok(u_req) = Uri::parse::(&url) { + if request.uri().path() != u_req.absolute().unwrap().path() { + return Outcome::Error((Status::new(401), "U tag does not match")); + } + } else { + return Outcome::Error((Status::new(401), "Invalid U tag")); + } + } else { + return Outcome::Error((Status::new(401), "Missing url tag")); + } + + // check method tag + if let Some(method) = event.tags.iter().find_map(|t| { + let vec = t.as_slice(); + if vec[0] == "method" { + Some(vec[1].clone()) + } else { + None + } + }) { + if request.method().to_string() != *method { + return Outcome::Error((Status::new(401), "Method tag incorrect")); + } + } else { + return Outcome::Error((Status::new(401), "Missing method tag")); + } + + if let Err(_err) = event.verify() { + return Outcome::Error((Status::new(401), "Event signature invalid")); + } + + info!("{}", event.as_json()); + Outcome::Success(Nip98Auth { event }) + } else { + Outcome::Error((Status::new(403), "Auth scheme must be Nostr")) + } + } else { + Outcome::Error((Status::new(403), "Auth header not found")) + } + } +} diff --git a/src/provisioner.rs b/src/provisioner.rs index ac936de..956182f 100644 --- a/src/provisioner.rs +++ b/src/provisioner.rs @@ -1,8 +1,9 @@ use crate::db; use crate::vm::VMSpec; use anyhow::Error; -use sqlx::MySqlPool; +use sqlx::{MySqlPool, Row}; +#[derive(Debug, Clone)] pub struct Provisioner { db: MySqlPool, } @@ -16,4 +17,30 @@ impl Provisioner { pub async fn provision(&self, spec: VMSpec) -> Result { todo!() } + + /// Insert/Fetch user id + pub async fn upsert_user(&self, pubkey: &[u8; 32]) -> Result { + let res = sqlx::query("insert ignore into users(pubkey) values(?) returning id") + .bind(pubkey.as_slice()) + .fetch_optional(&self.db) + .await?; + match res { + None => sqlx::query("select id from users where pubkey = ?") + .bind(pubkey.as_slice()) + .fetch_one(&self.db) + .await? + .try_get(0) + .map_err(Error::new), + Some(res) => res.try_get(0).map_err(Error::new), + } + } + + /// List VM's owned by a specific user + pub async fn list_vms(&self, id: u64) -> Result, Error> { + sqlx::query_as("select * from vm where user_id = ?") + .bind(&id) + .fetch_all(&self.db) + .await + .map_err(Error::new) + } }