Remove the signup code; applications always queue for MA review, invite per node

This commit is contained in:
George Coles
2026-09-15 12:13:50 -04:00
parent 3de20ed89d
commit 9aa0117af4
5 changed files with 185 additions and 262 deletions
+31 -113
View File
@@ -474,16 +474,29 @@ pub fn registry_approve(dir: &Path, id: &str, registry_url: Option<&str>) -> Res
})?;
let invite = registry::create_invite(dir, id, 24 * 3600)?;
let (_, signed) = open_registry(dir)?;
let registry_url = registry_url.unwrap_or("<registry-url>");
println!("approved {id} ({class})");
println!("hand this credential block to the member:");
println!(
"id={id} token={} registry={registry_url} ma_key={}",
invite.token, signed.doc.ma_key
);
print_credential_block(id, &invite.token, registry_url, &signed.doc.ma_key);
Ok(())
}
/// Issues a fresh invite for an existing member — one single-use token per node
/// the member runs (each node binds its own key at enrollment).
pub fn registry_invite(dir: &Path, id: &str, registry_url: Option<&str>) -> Result<()> {
let (_, signed) = open_registry(dir)?;
if !signed.doc.members.iter().any(|member| member.id == id) {
return Err(anyhow!("no member named {id}"));
}
let invite = registry::create_invite(dir, id, 24 * 3600)?;
print_credential_block(id, &invite.token, registry_url, &signed.doc.ma_key);
Ok(())
}
fn print_credential_block(id: &str, token: &str, registry_url: Option<&str>, ma_key: &str) {
let registry_url = registry_url.unwrap_or("<registry-url>");
println!("hand this credential block to the member (single use, valid 24h):");
println!("id={id} token={token} registry={registry_url} ma_key={ma_key}");
}
pub fn registry_set_relays(dir: &Path, relays: &[String]) -> Result<()> {
mutate_registry(dir, |doc| {
doc.relays = relays.to_vec();
@@ -506,19 +519,11 @@ pub fn registry_show(dir: &Path) -> Result<()> {
struct RegistryServer {
dir: PathBuf,
signup_code: Option<String>,
registry_url: Option<String>,
}
pub fn registry_router(
dir: &Path,
signup_code: Option<String>,
registry_url: Option<String>,
) -> Router {
pub fn registry_router(dir: &Path) -> Router {
let state = std::sync::Arc::new(RegistryServer {
dir: dir.to_path_buf(),
signup_code,
registry_url,
});
Router::new()
.route("/health", get(registry_health))
@@ -529,13 +534,8 @@ pub fn registry_router(
.with_state(state)
}
pub async fn registry_serve(
dir: &Path,
listen: &str,
signup_code: Option<String>,
registry_url: Option<String>,
) -> Result<()> {
let app = registry_router(dir, signup_code, registry_url);
pub async fn registry_serve(dir: &Path, listen: &str) -> Result<()> {
let app = registry_router(dir);
let listener = TcpListener::bind(listen).await?;
println!("registry serving on http://{}", listener.local_addr()?);
axum::serve(listener, app).await?;
@@ -578,7 +578,6 @@ fn sanitize_label(input: &str) -> String {
#[derive(Deserialize)]
struct SignupRequest {
label: String,
code: Option<String>,
#[serde(default)]
org: String,
#[serde(default)]
@@ -613,28 +612,6 @@ async fn registry_signup(
)
.into_response();
}
// A pre-approval code, if supplied, must be valid; an empty code queues for review.
let code = request
.code
.as_deref()
.map(str::trim)
.filter(|code| !code.is_empty());
if let Some(code) = code {
let Some(expected) = &server.signup_code else {
return (
StatusCode::FORBIDDEN,
Json(serde_json::json!({ "error": "no pre-approval code is configured; submit without one to queue for review" })),
)
.into_response();
};
if code != expected.as_str() {
return (
StatusCode::FORBIDDEN,
Json(serde_json::json!({ "error": "wrong signup code" })),
)
.into_response();
}
}
if !request.attestation {
return (
StatusCode::BAD_REQUEST,
@@ -711,31 +688,9 @@ async fn registry_signup(
class: class.to_string(),
payment: request.payment.clone(),
privacy_link: request.privacy_link.clone(),
status: if code.is_some() {
"approved".to_string()
} else {
"pending".to_string()
},
status: "pending".to_string(),
submitted_at: now_ts(),
};
if code.is_none() {
if let Err(error) = registry::record_application(&server.dir, application) {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({ "error": error.to_string() })),
)
.into_response();
}
return (
StatusCode::ACCEPTED,
Json(serde_json::json!({
"status": "pending",
"id": id,
"message": "application received — the membership authority reviews it and issues your credential block"
})),
)
.into_response();
}
if let Err(error) = registry::record_application(&server.dir, application) {
return (
StatusCode::INTERNAL_SERVER_ERROR,
@@ -743,44 +698,12 @@ async fn registry_signup(
)
.into_response();
}
let invite = match registry::create_invite(&server.dir, &id, 24 * 3600) {
Ok(invite) => invite,
Err(error) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({ "error": error.to_string() })),
)
.into_response();
}
};
if let Err(error) = mutate_registry(&server.dir, |doc| {
doc.members.push(RegistryMember {
id: id.clone(),
class: class.to_string(),
keys: Vec::new(),
enc_key: None,
});
Ok(())
}) {
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({ "error": error.to_string() })),
)
.into_response();
}
let registry_url = server.registry_url.clone().unwrap_or_default();
let ma_key = signed.doc.ma_key.clone();
(
StatusCode::OK,
StatusCode::ACCEPTED,
Json(serde_json::json!({
"status": "pending",
"id": id,
"token": invite.token,
"registry": registry_url,
"ma_key": ma_key,
"credentials": format!(
"id={id} token={} registry={registry_url} ma_key={ma_key}",
invite.token
),
"message": "application received — the membership authority reviews it and issues your credential block"
})),
)
.into_response()
@@ -920,11 +843,6 @@ the MA for the membership contract — never published, never on the wire.</p>
<label>Short name — this becomes your identifier<br>
<input name="label" id="label" required pattern="[A-Za-z0-9 -]+" placeholder="keswick-research"></label>
<p class="muted">identifier: <code id="preview">(type a short name)</code> — no domain or DNS of your own is needed.</p>
<label>Signup code (optional)<br>
<input name="code" type="password" placeholder="pre-approval code"></label>
<p class="muted">Leave empty to queue your application for MA review — the MA will contact you
with your credential block. A pre-approval code, issued by the MA out of band, approves you
immediately and returns the block here.</p>
<label>Legal organization name<br>
<input name="org" required placeholder="Keswick Research LLC"></label>
<label>Representative (authorized contact person)<br>
@@ -954,10 +872,10 @@ immediately and returns the block here.</p>
<h2>2. Download</h2>
<div class="card">
<p>Static Linux x86_64 binaries (musl — no runtime dependencies):</p>
<pre class="cmd">curl -LO https://git.federatedsearch.org/frx/frxd/releases/download/v0.1.2/frxd-linux-amd64
curl -LO https://git.federatedsearch.org/frx/frxd/releases/download/v0.1.2/frxd-linux-amd64.sha256
curl -LO https://git.federatedsearch.org/frx/frxd/releases/download/v0.1.2/frx-linux-amd64
curl -LO https://git.federatedsearch.org/frx/frxd/releases/download/v0.1.2/frx-linux-amd64.sha256</pre>
<pre class="cmd">curl -LO https://git.federatedsearch.org/frx/frxd/releases/download/v0.1.3/frxd-linux-amd64
curl -LO https://git.federatedsearch.org/frx/frxd/releases/download/v0.1.3/frxd-linux-amd64.sha256
curl -LO https://git.federatedsearch.org/frx/frxd/releases/download/v0.1.3/frx-linux-amd64
curl -LO https://git.federatedsearch.org/frx/frxd/releases/download/v0.1.3/frx-linux-amd64.sha256</pre>
<p class="muted">All releases: <a href="https://git.federatedsearch.org/frx/frxd/releases">git.federatedsearch.org/frx/frxd/releases</a>.
Source and spec (<code>rfc.txt</code>): <a href="https://git.federatedsearch.org/frx/frxd">git.federatedsearch.org/frx/frxd</a>.</p>
</div>
@@ -1017,7 +935,7 @@ f.onsubmit = async (e) => {
method: "POST",
headers: {"content-type": "application/json"},
body: JSON.stringify({
label: f.label.value, code: f.code.value,
label: f.label.value,
org: f.org.value, representative: f.representative.value, email: f.email.value,
address: f.address.value, domain: f.domain.value, class: f.member_class.value,
payment: f.payment.value, privacy_link: f.privacy_link.value,