diff --git a/CHANGELOG.md b/CHANGELOG.md index 822b507b..55f8aaf2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Unreleased +Important: A network launcher more recent than v12.0.0-83c3f95e8c4ce28e02493df83df5f84a166451c0 is +required to use internet identity. + * feat: Many more commands support `--json` and `--quiet`. +* feat: When a local network is started internet identity is available at id.ai.localhost * fix: Network would fail to start if a stale descriptor was present # v0.2.1 diff --git a/crates/icp-canister-interfaces/src/internet_identity.rs b/crates/icp-canister-interfaces/src/internet_identity.rs index fdb8b90b..d6208fc9 100644 --- a/crates/icp-canister-interfaces/src/internet_identity.rs +++ b/crates/icp-canister-interfaces/src/internet_identity.rs @@ -1,15 +1,18 @@ use candid::Principal; -pub const INTERNET_IDENTITY_CID: &str = "rdmx6-jaaaa-aaaaa-aaadq-cai"; -pub const INTERNET_IDENTITY_PRINCIPAL: Principal = - Principal::from_slice(&[0, 0, 0, 0, 0, 0, 0, 7, 1, 1]); +pub const INTERNET_IDENTITY_FRONTEND_CID: &str = "uqzsh-gqaaa-aaaaq-qaada-cai"; +pub const INTERNET_IDENTITY_FRONTEND_PRINCIPAL: Principal = + Principal::from_slice(&[0, 0, 0, 0, 2, 16, 0, 6, 1, 1]); #[cfg(test)] mod tests { use super::*; #[test] - fn internet_identity_cid_and_principal_match() { - assert_eq!(INTERNET_IDENTITY_CID, INTERNET_IDENTITY_PRINCIPAL.to_text()); + fn internet_identity_frontend_cid_and_principal_match() { + assert_eq!( + INTERNET_IDENTITY_FRONTEND_CID, + INTERNET_IDENTITY_FRONTEND_PRINCIPAL.to_text() + ); } } diff --git a/crates/icp-cli/tests/network_tests.rs b/crates/icp-cli/tests/network_tests.rs index 8f652f98..320e3157 100644 --- a/crates/icp-cli/tests/network_tests.rs +++ b/crates/icp-cli/tests/network_tests.rs @@ -4,7 +4,7 @@ use candid::Principal; use icp_canister_interfaces::{ cycles_ledger::CYCLES_LEDGER_PRINCIPAL, cycles_minting_canister::CYCLES_MINTING_CANISTER_PRINCIPAL, icp_ledger::ICP_LEDGER_PRINCIPAL, - internet_identity::INTERNET_IDENTITY_PRINCIPAL, registry::REGISTRY_PRINCIPAL, + internet_identity::INTERNET_IDENTITY_FRONTEND_PRINCIPAL, registry::REGISTRY_PRINCIPAL, }; use indoc::{formatdoc, indoc}; use predicates::{ @@ -495,7 +495,7 @@ async fn network_starts_with_canisters_preset() { .unwrap(); // Internet identity agent - .read_state_canister_module_hash(INTERNET_IDENTITY_PRINCIPAL) + .read_state_canister_module_hash(INTERNET_IDENTITY_FRONTEND_PRINCIPAL) .await .unwrap(); } diff --git a/crates/icp/src/context/mod.rs b/crates/icp/src/context/mod.rs index 94be911e..270d08a5 100644 --- a/crates/icp/src/context/mod.rs +++ b/crates/icp/src/context/mod.rs @@ -548,9 +548,15 @@ impl Context { env_mappings.insert(env_name.clone(), mapping); } } - if let Err(e) = - crate::network::custom_domains::write_custom_domains(status_dir, domain, &env_mappings) - { + let extra: Vec<_> = crate::network::custom_domains::ii_custom_domain_entry(desc.ii, domain) + .into_iter() + .collect(); + if let Err(e) = crate::network::custom_domains::write_custom_domains( + status_dir, + domain, + &env_mappings, + &extra, + ) { tracing::warn!("Failed to update custom domains: {e}"); } } diff --git a/crates/icp/src/network/config.rs b/crates/icp/src/network/config.rs index cc663ff4..c66ac4b2 100644 --- a/crates/icp/src/network/config.rs +++ b/crates/icp/src/network/config.rs @@ -75,6 +75,9 @@ pub struct NetworkDescriptorModel { pub candid_ui_canister_id: Option, /// Canister ID of the deployed proxy canister, if any. pub proxy_canister_id: Option, + /// Whether the Internet Identity canister is deployed on this network. + #[serde(default)] + pub ii: bool, /// Path to the status directory shared with the network launcher. /// Used to write `custom-domains.txt` for friendly domain routing. #[serde(default)] diff --git a/crates/icp/src/network/custom_domains.rs b/crates/icp/src/network/custom_domains.rs index ce0f615e..9938a070 100644 --- a/crates/icp/src/network/custom_domains.rs +++ b/crates/icp/src/network/custom_domains.rs @@ -11,13 +11,17 @@ use crate::{prelude::*, store_id::IdMapping}; /// Each line has the format `..:`. /// The file is written fresh each time from the full set of current mappings /// across all environments sharing this network. +/// +/// `extra_entries` are raw `(full_domain, canister_id)` pairs appended after the +/// environment-based entries (e.g. system canisters like Internet Identity). pub fn write_custom_domains( status_dir: &Path, domain: &str, env_mappings: &BTreeMap, + extra_entries: &[(String, String)], ) -> Result<(), WriteCustomDomainsError> { let file_path = status_dir.join("custom-domains.txt"); - let content: String = env_mappings + let mut content: String = env_mappings .iter() .flat_map(|(env_name, mappings)| { mappings @@ -25,10 +29,25 @@ pub fn write_custom_domains( .map(move |(name, principal)| format!("{name}.{env_name}.{domain}:{principal}\n")) }) .collect(); + for (full_domain, canister_id) in extra_entries { + content.push_str(&format!("{full_domain}:{canister_id}\n")); + } crate::fs::write(&file_path, content.as_bytes())?; Ok(()) } +/// Returns the custom domain entry for the II frontend canister, if II is enabled. +pub fn ii_custom_domain_entry(ii: bool, domain: &str) -> Option<(String, String)> { + if ii { + Some(( + format!("id.ai.{domain}"), + icp_canister_interfaces::internet_identity::INTERNET_IDENTITY_FRONTEND_CID.to_string(), + )) + } else { + None + } +} + /// Extracts the domain authority from a gateway URL for use in subdomain-based /// canister routing. /// @@ -110,7 +129,7 @@ mod tests { ); env_mappings.insert("staging".to_string(), staging_mappings); - write_custom_domains(dir.path(), "localhost", &env_mappings).unwrap(); + write_custom_domains(dir.path(), "localhost", &env_mappings, &[]).unwrap(); let content = std::fs::read_to_string(dir.path().join("custom-domains.txt")).unwrap(); // BTreeMap is ordered, so local comes before staging @@ -122,6 +141,38 @@ mod tests { ); } + #[test] + fn write_custom_domains_with_extra_entries() { + let dir = camino_tempfile::Utf8TempDir::new().unwrap(); + let env_mappings = BTreeMap::new(); + let extra = vec![( + "id.ai.localhost".to_string(), + "uqzsh-gqaaa-aaaaq-qaada-cai".to_string(), + )]; + + write_custom_domains(dir.path(), "localhost", &env_mappings, &extra).unwrap(); + + let content = std::fs::read_to_string(dir.path().join("custom-domains.txt")).unwrap(); + assert_eq!(content, "id.ai.localhost:uqzsh-gqaaa-aaaaq-qaada-cai\n"); + } + + #[test] + fn ii_custom_domain_entry_returns_entry_when_enabled() { + let entry = ii_custom_domain_entry(true, "localhost"); + assert_eq!( + entry, + Some(( + "id.ai.localhost".to_string(), + "uqzsh-gqaaa-aaaaq-qaada-cai".to_string() + )) + ); + } + + #[test] + fn ii_custom_domain_entry_returns_none_when_disabled() { + assert_eq!(ii_custom_domain_entry(false, "localhost"), None); + } + #[test] fn canister_gateway_url_with_friendly_domain() { let base: Url = "http://localhost:8000".parse().unwrap(); diff --git a/crates/icp/src/network/managed/run.rs b/crates/icp/src/network/managed/run.rs index 27ad3c25..dcc47eca 100644 --- a/crates/icp/src/network/managed/run.rs +++ b/crates/icp/src/network/managed/run.rs @@ -241,10 +241,12 @@ async fn run_network_launcher( info!("Network started on port {}", instance.gateway_port); } + let gateway_url: Url = format!("http://{}:{}", gateway.host, gateway.port) + .parse() + .unwrap(); + let (candid_ui_canister_id, proxy_canister_id) = initialize_network( - &format!("http://{}:{}", gateway.host, gateway.port) - .parse() - .unwrap(), + &gateway_url, &instance.root_key, all_identities, default_identity, @@ -253,6 +255,8 @@ async fn run_network_launcher( ) .await?; + let ii = matches!(&config.mode, ManagedMode::Launcher(cfg) if cfg.ii); + network_root .with_write(async |root| -> Result<_, RunNetworkLauncherError> { // Acquire locks for all fixed ports @@ -274,6 +278,7 @@ async fn run_network_launcher( pocketic_instance_id: instance.pocketic_instance_id, candid_ui_canister_id, proxy_canister_id, + ii, status_dir: Some(status_dir_path.clone()), use_friendly_domains: instance.use_friendly_domains, }; @@ -284,6 +289,23 @@ async fn run_network_launcher( Ok(()) }) .await??; + + // Write initial custom-domains.txt with system canister entries (e.g. II) + if instance.use_friendly_domains + && let Some(domain) = crate::network::custom_domains::gateway_domain(&gateway_url) + { + let extra: Vec<_> = crate::network::custom_domains::ii_custom_domain_entry(ii, domain) + .into_iter() + .collect(); + if !extra.is_empty() { + let _ = crate::network::custom_domains::write_custom_domains( + &status_dir_path, + domain, + &std::collections::BTreeMap::new(), + &extra, + ); + } + } if background { info!("To stop the network, run `icp network stop`"); guard.defuse();