myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

commit f2716b52aefe4da80629ae555ab13466d3044286
parent d410cf28ae2c3e098c54505ebe0ed79769367c71
Author: triesap <tyson@radroots.org>
Date:   Sun, 22 Mar 2026 19:09:09 +0000

config: allow localhost http discovery templates

Diffstat:
M.env.example | 26+++++++++++++-------------
Msrc/audit.rs | 2++
Msrc/config.rs | 36+++++++++++++++++++++++++++---------
3 files changed, 42 insertions(+), 22 deletions(-)

diff --git a/.env.example b/.env.example @@ -5,29 +5,29 @@ MYC_SERVICE_INSTANCE_NAME=myc MYC_LOGGING_FILTER=info,myc=info MYC_PATHS_STATE_DIR=var -MYC_PATHS_SIGNER_IDENTITY_PATH=identity.json -MYC_PATHS_USER_IDENTITY_PATH=user-identity.json +MYC_PATHS_SIGNER_IDENTITY_PATH=var/signer-identity.json +MYC_PATHS_USER_IDENTITY_PATH=var/user-identity.json MYC_AUDIT_DEFAULT_READ_LIMIT=200 MYC_AUDIT_MAX_ACTIVE_FILE_BYTES=262144 MYC_AUDIT_MAX_ARCHIVED_FILES=8 MYC_DISCOVERY_ENABLED=true -MYC_DISCOVERY_DOMAIN=signer.example.com +MYC_DISCOVERY_DOMAIN=localhost MYC_DISCOVERY_HANDLER_IDENTIFIER=myc -MYC_DISCOVERY_APP_IDENTITY_PATH=app-identity.json -MYC_DISCOVERY_PUBLIC_RELAYS=wss://relay.example.com -MYC_DISCOVERY_PUBLISH_RELAYS=wss://relay.example.com -MYC_DISCOVERY_NOSTRCONNECT_URL_TEMPLATE=https://signer.example.com/connect?uri=<nostrconnect> -MYC_DISCOVERY_NIP05_OUTPUT_PATH=public/.well-known/nostr.json +MYC_DISCOVERY_APP_IDENTITY_PATH=var/app-identity.json +MYC_DISCOVERY_PUBLIC_RELAYS=ws://localhost:8080 +MYC_DISCOVERY_PUBLISH_RELAYS=ws://localhost:8080 +MYC_DISCOVERY_NOSTRCONNECT_URL_TEMPLATE=http://localhost/connect?uri=<nostrconnect> +MYC_DISCOVERY_NIP05_OUTPUT_PATH=var/public/.well-known/nostr.json MYC_DISCOVERY_METADATA_NAME=myc -MYC_DISCOVERY_METADATA_DISPLAY_NAME=Mycorrhiza -MYC_DISCOVERY_METADATA_ABOUT=NIP-46 signer -MYC_DISCOVERY_METADATA_WEBSITE=https://signer.example.com -MYC_DISCOVERY_METADATA_PICTURE=https://signer.example.com/logo.png +MYC_DISCOVERY_METADATA_DISPLAY_NAME=Radroots Signer +MYC_DISCOVERY_METADATA_ABOUT=Radroots NIP-46 signer +MYC_DISCOVERY_METADATA_WEBSITE=https://radroots.org +MYC_DISCOVERY_METADATA_PICTURE= MYC_POLICY_CONNECTION_APPROVAL=explicit_user MYC_TRANSPORT_ENABLED=true MYC_TRANSPORT_CONNECT_TIMEOUT_SECS=10 -MYC_TRANSPORT_RELAYS=wss://relay.example.com +MYC_TRANSPORT_RELAYS=ws://localhost:8080 diff --git a/src/audit.rs b/src/audit.rs @@ -709,6 +709,8 @@ fn now_unix_secs() -> u64 { #[cfg(test)] mod tests { + use std::fs; + use radroots_nostr_signer::prelude::RadrootsNostrSignerConnectionId; use crate::config::MycAuditConfig; diff --git a/src/config.rs b/src/config.rs @@ -694,18 +694,24 @@ fn validate_nostrconnect_url_template(template: &str) -> Result<(), MycError> { .to_owned(), )); } - if !trimmed.starts_with("https://") { - return Err(MycError::InvalidConfig( - "discovery.nostrconnect_url_template must start with `https://`".to_owned(), - )); - } let candidate = trimmed.replace("<nostrconnect>", "nostrconnect%3A%2F%2Fclient"); - nostr::Url::parse(&candidate).map_err(|source| { + let url = nostr::Url::parse(&candidate).map_err(|source| { MycError::InvalidConfig(format!( "discovery.nostrconnect_url_template is invalid: {source}" )) })?; - Ok(()) + + match url.scheme() { + "https" => Ok(()), + "http" if discovery_host_is_local(url.host_str()) => Ok(()), + _ => Err(MycError::InvalidConfig( + "discovery.nostrconnect_url_template must use `https://`, except loopback hosts may use `http://`".to_owned(), + )), + } +} + +fn discovery_host_is_local(host: Option<&str>) -> bool { + matches!(host, Some("localhost" | "127.0.0.1" | "::1")) } #[cfg(test)] @@ -894,6 +900,18 @@ MYC_UNKNOWN=nope } #[test] + fn discovery_validation_allows_localhost_http_nostrconnect_template() { + let mut config = MycConfig::default(); + config.discovery.enabled = true; + config.discovery.domain = Some("localhost".to_owned()); + config.discovery.public_relays = vec!["ws://localhost:8080".to_owned()]; + config.discovery.nostrconnect_url_template = + Some("http://localhost/connect?uri=<nostrconnect>".to_owned()); + + config.validate().expect("localhost http template"); + } + + #[test] fn discovery_validation_rejects_invalid_nostrconnect_template() { let mut config = MycConfig::default(); config.discovery.enabled = true; @@ -920,12 +938,12 @@ MYC_UNKNOWN=nope assert!(config.discovery.enabled); assert_eq!( config.discovery.domain.as_deref(), - Some("signer.example.com") + Some("localhost") ); assert_eq!(config.discovery.handler_identifier, "myc"); assert_eq!( config.discovery.nip05_output_path, - Some(PathBuf::from("public/.well-known/nostr.json")) + Some(PathBuf::from("var/public/.well-known/nostr.json")) ); } }