Onboarding wizard and MA signup/enroll site
This commit is contained in:
@@ -38,6 +38,7 @@ fn save_registry(dir: &Path, ma: &Keypair, members: Vec<RegistryMember>) -> Path
|
||||
version: 1,
|
||||
issued_at: now_ts(),
|
||||
ma_key: String::new(),
|
||||
zone: "frx.invalid".to_string(),
|
||||
members,
|
||||
relays: Vec::new(),
|
||||
},
|
||||
|
||||
+2
-2
@@ -1,11 +1,10 @@
|
||||
mod common;
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
use common::{
|
||||
client, collection, config_for, messages_of_type, poll, poll_messages, publish, query_envelope,
|
||||
client, collection, config_for, messages_of_type, poll_messages, publish, query_envelope,
|
||||
register, test_envelope,
|
||||
};
|
||||
use frxd::crypto::{Keypair, now_ts};
|
||||
@@ -99,6 +98,7 @@ fn registry_doc(
|
||||
version,
|
||||
issued_at: now_ts(),
|
||||
ma_key: String::new(),
|
||||
zone: "frx.invalid".to_string(),
|
||||
members,
|
||||
relays,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
use frxd::commands;
|
||||
use frxd::config::Config;
|
||||
use frxd::crypto::{Keypair, now_ts};
|
||||
use frxd::onboard;
|
||||
use frxd::registry::{self};
|
||||
use serde_json::Value;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
async fn spawn_registry_server(dir: &Path, signup_code: Option<&str>) -> String {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
let base = format!("http://{addr}");
|
||||
let router = commands::registry_router(
|
||||
dir,
|
||||
signup_code.map(str::to_string),
|
||||
Some(format!("{base}/registry.json")),
|
||||
);
|
||||
tokio::spawn(async move {
|
||||
let _ = axum::serve(listener, router).await;
|
||||
});
|
||||
base
|
||||
}
|
||||
|
||||
fn setup_ma(root: &Path) -> std::path::PathBuf {
|
||||
let dir = root.join("ma");
|
||||
commands::registry_init(&dir, "frx.invalid").unwrap();
|
||||
dir
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn signup_issues_invite_and_enroll_binds_key() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let dir = setup_ma(root.path());
|
||||
let base = spawn_registry_server(&dir, Some("sesame")).await;
|
||||
let http = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(5))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let rejected = http
|
||||
.post(format!("{base}/v1/signup"))
|
||||
.json(&serde_json::json!({"label": "alice", "code": "wrong"}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(rejected.status(), reqwest::StatusCode::FORBIDDEN);
|
||||
|
||||
let response = http
|
||||
.post(format!("{base}/v1/signup"))
|
||||
.json(&serde_json::json!({"label": "Alice Dev", "code": "sesame"}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
let body: Value = response.json().await.unwrap();
|
||||
let id = body.get("id").and_then(Value::as_str).unwrap().to_string();
|
||||
assert_eq!(id, "alice-dev.frx.invalid");
|
||||
let token = body
|
||||
.get("token")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
|
||||
let signed = registry::load_registry(&dir.join("registry.json")).unwrap();
|
||||
assert!(
|
||||
registry::authorized_keys(&signed, now_ts()).is_empty(),
|
||||
"signup must not authorize a key before enrollment"
|
||||
);
|
||||
|
||||
let key = Keypair::generate();
|
||||
let response = http
|
||||
.post(format!("{base}/v1/enroll"))
|
||||
.json(&serde_json::json!({
|
||||
"id": id,
|
||||
"token": token,
|
||||
"pubkey": key.public_hex(),
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
let signed = registry::load_registry(&dir.join("registry.json")).unwrap();
|
||||
assert!(registry::authorized_keys(&signed, now_ts()).contains_key(&key.public_hex()));
|
||||
|
||||
let replayed = http
|
||||
.post(format!("{base}/v1/enroll"))
|
||||
.json(&serde_json::json!({
|
||||
"id": id,
|
||||
"token": token,
|
||||
"pubkey": Keypair::generate().public_hex(),
|
||||
}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(replayed.status(), reqwest::StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn wizard_enrolls_and_writes_config() {
|
||||
let root = tempfile::tempdir().unwrap();
|
||||
let dir = setup_ma(root.path());
|
||||
let base = spawn_registry_server(&dir, Some("sesame")).await;
|
||||
let http = reqwest::Client::new();
|
||||
let body: Value = http
|
||||
.post(format!("{base}/v1/signup"))
|
||||
.json(&serde_json::json!({"label": "Wizard Test", "code": "sesame"}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.json()
|
||||
.await
|
||||
.unwrap();
|
||||
let credentials = body.get("credentials").and_then(Value::as_str).unwrap();
|
||||
|
||||
let config_path = root.path().join("wizard.toml");
|
||||
let input = format!("{}\n{credentials}\n\n\n\nn\n", config_path.display());
|
||||
let mut reader = input.as_bytes();
|
||||
let mut output = Vec::new();
|
||||
onboard::run(&mut reader, &mut output, &config_path)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let text = String::from_utf8(output).unwrap();
|
||||
assert!(text.contains("enrolled"), "{text}");
|
||||
|
||||
let config = Config::load(&config_path).unwrap();
|
||||
assert_eq!(config.node.id.as_deref(), Some("wizard-test.frx.invalid"));
|
||||
assert_eq!(
|
||||
config.node.registry.as_deref(),
|
||||
Some(format!("{base}/registry.json").as_str())
|
||||
);
|
||||
let key = config.load_key().unwrap();
|
||||
let signed = registry::load_registry(&dir.join("registry.json")).unwrap();
|
||||
assert!(
|
||||
registry::authorized_keys(&signed, now_ts()).contains_key(&key.public_hex()),
|
||||
"wizard did not bind the key"
|
||||
);
|
||||
}
|
||||
@@ -41,6 +41,7 @@ fn ma_registry(ma: &Keypair, members: Vec<RegistryMember>, version: u64) -> Sign
|
||||
version,
|
||||
issued_at: now_ts(),
|
||||
ma_key: String::new(),
|
||||
zone: "frx.invalid".to_string(),
|
||||
members,
|
||||
relays: Vec::new(),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user