diff --git a/.sources/VERSIONS b/.sources/VERSIONS index f61c90b..8a0d7f7 100644 --- a/.sources/VERSIONS +++ b/.sources/VERSIONS @@ -62,4 +62,4 @@ motoko-core v2.4.0 cdk-rs ic-cdk v0.20.1 / ic-cdk-timers v1.0.0 / ic-cdk-executor v2.0.0 317f55c candid 2025-12-18 # candid v0.10.20, didc v0.5.4 2e4a2cf response-verification v3.1.0 18c5a37 -internetidentity release-2026-06-01 18130689 +internetidentity release-2026-06-15 1f4104b5 diff --git a/.sources/internetidentity b/.sources/internetidentity index 1813068..1f4104b 160000 --- a/.sources/internetidentity +++ b/.sources/internetidentity @@ -1 +1 @@ -Subproject commit 18130689eda68a3ec51af2aaa7ba13eb32e6568e +Subproject commit 1f4104b53ddaecf745426769e895013168591b17 diff --git a/docs/references/internet-identity-spec.md b/docs/references/internet-identity-spec.md index 5ecd16b..8387b3a 100644 --- a/docs/references/internet-identity-spec.md +++ b/docs/references/internet-identity-spec.md @@ -718,16 +718,15 @@ On the flip side, implementing `canister_inspect_message` adds code, and thus a Therefore, the Internet Identity Canister intentionally does not implement `canister_inspect_message`. diff --git a/public/references/internet-identity.did b/public/references/internet-identity.did index 40c7259..e617bd2 100644 --- a/public/references/internet-identity.did +++ b/public/references/internet-identity.did @@ -246,6 +246,21 @@ type CaptchaConfig = record { }; }; +// One entry of the `sso_credential_migration` backfill. Maps the +// (iss, aud) pair of stored SSO credentials to the discovery domain (and +// optional human-readable name) they were registered through. Field names +// match the `discovered_oidc_configs` query output so the deployer can +// transcribe its result field-for-field. +type SsoCredentialMigrationEntry = record { + discovery_domain : text; + // Matches the stored credential's `iss`. + issuer : text; + // Matches the stored credential's `aud`. + client_id : text; + // Human-readable SSO label; stamped onto the credential's `sso_name`. + name : opt text; +}; + // Init arguments of II which can be supplied on install and upgrade. // // Each field is wrapped is `opt` to indicate whether the field should @@ -286,6 +301,15 @@ type InternetIdentityInit = record { // (production) or `beta.dfinity.org` (everything else), keyed off // `is_production`. sso_discoverable_domains : opt vec text; + // One-shot backfill of the `sso_domain` / `sso_name` fields on stored + // OpenID credentials. When set, a batched timer-driven migration stamps + // every stored credential whose (iss, aud) matches an entry and whose + // `sso_domain` is not set yet. Idempotent — already-stamped credentials + // are skipped, so re-submitting (e.g. with a corrected list) is safe. + // When unset, no backfill runs. The deployer builds the list from the + // running canister's `discovered_oidc_configs` query before + // submitting the upgrade proposal. + sso_credential_migration : opt vec SsoCredentialMigrationEntry; // Configuration for Web Analytics analytics_config : opt opt AnalyticsConfig; // Configuration to show dapps explorer or not @@ -299,6 +323,9 @@ type InternetIdentityInit = record { backend_canister_id : opt principal; // Backend origin, needed to sync configuration with frontend. backend_origin : opt text; + // Deploy flag for the legacy DNSSEC email-recovery path. Defaults to + // off (DoH-only); `opt true` re-enables it. + enable_dnssec_email_recovery : opt bool; // DNSSEC verification configuration. Trust anchors used by any feature // that verifies DNS records against the IANA-rooted DNSSEC chain // (currently the email-recovery DKIM/DMARC flow). See @@ -544,6 +571,13 @@ type EmailRecoverySubmitDkimLeafArg = record { // least one hop required; bounded by `MAX_CNAME_HOPS = 4` at the // canister side. For the Gmail-style direct-TXT case this is a // single-element vec. + // + // When the FE cannot walk a fully-signed DNSSEC resolution for the + // leaf — the DKIM record CNAMEs into an unsigned zone (e.g. + // `selector1._domainkey.outlook.com` is a signed CNAME into the + // unsigned `outbound.protection.outlook.com`) — it must NOT submit + // an empty vec here; it drives `email_recovery_resolve_via_doh` + // instead, which resolves the key over the canister's DoH path. hops : vec SignedRRset; // Delegation chains for signed zones touched by `hops` that // weren't already covered by the skeleton chain anchored at @@ -551,6 +585,14 @@ type EmailRecoverySubmitDkimLeafArg = record { extra_chains : vec DelegationChain; }; +// Argument to email_recovery_resolve_via_doh. Wrapped in a record (like +// EmailRecoverySubmitDkimLeafArg) so the method can grow fields without a +// breaking interface change; nonce is the lookup key and is always +// required. +type EmailRecoveryResolveViaDohArg = record { + nonce : text; +}; + // DNSSEC proof bundle and supporting types — see // `internet_identity_interface::types::dnssec`. type Rrsig = record { @@ -595,15 +637,28 @@ type DnsProofBundle = record { hops : vec SignedRRset; }; +// Why a DoH resolution failed, as a typed discriminant rather than a +// free-form string. The FE reads this directly to segment the +// `doh_reason` analytics property — no string parsing. +type DohFailureReason = variant { + AllProvidersFailed; + QuorumFailed : record { agreeing : nat32; total : nat32 }; + ResponseMalformed : text; +}; + type EmailRecoveryError = variant { Unauthorized : principal; NonceUnknown; NonceExpired; DomainNotAllowlisted : text; - DohFetchFailed : text; + DohFetchFailed : DohFailureReason; DomainNotSupported : text; EmailVerificationFailed : text; DkimLeafMismatch; + // email_recovery_submit_dkim_leaf was called with an empty `hops` + // vector; an FE that can't walk DNSSEC must drive + // email_recovery_resolve_via_doh instead. + EmptyDkimLeafHops; NoDkimLeafExpected; AddressMismatch; SubjectNotSigned; @@ -614,6 +669,7 @@ type EmailRecoveryError = variant { type EmailRecoveryStatus = variant { Pending; + ResolvingDoh; NeedDkimLeaf : record { selector : text }; RegistrationSucceeded; RecoveryReady : record { @@ -625,6 +681,24 @@ type EmailRecoveryStatus = variant { Expired; }; +// Which trust path the canister used (or will use) to verify the +// challenge email. Public — already chosen by the FE and derivable +// from the public deploy config. +type VerificationPath = variant { Doh; Dnssec }; + +// Strictly-public, user-copyable diagnostics for one pending challenge +// (see email_recovery_diagnostics). Intended for a support ticket so a +// case can be lined up across the SMTP gateway logs and the canister's +// production logs via message_id. NO email address, anchor, principal, +// delegation/seed, or inner error string — reason_code is the failing +// variant's name only. +type EmailRecoveryDiagnostics = record { + message_id : opt text; + reason_code : text; + verification_path : VerificationPath; + created_at : Timestamp; +}; + type EmailRecoveryGetDelegationArgs = record { nonce : text; session_key : SessionKey; @@ -671,6 +745,13 @@ type SmtpRequest = record { message : opt SmtpMessage; envelope : opt SmtpEnvelope; gateway_flags : opt vec text; + // Optional gateway-supplied correlation id for one inbound message + // (e.g. the RFC 5322 Message-ID or a gateway-assigned tracking id). + // The canister does not interpret it; it lets a reported case be + // lined up across the SMTP gateway logs and the canister's production + // logs during support investigations. Capped at 256 bytes; oversize + // values are rejected with code 555. + message_id : opt text; }; // Error returned by `smtp_request` / `smtp_request_validate`. @@ -1489,7 +1570,14 @@ service : (opt InternetIdentityInit) -> { email_recovery_credential_prepare_add : (IdentityNumber, EmailRecoveryDnsInput) -> (variant { Ok : EmailRecoveryChallenge; Err : EmailRecoveryError }); email_recovery_prepare_delegation : (EmailRecoveryDnsInput, SessionKey) -> (variant { Ok : EmailRecoveryChallenge; Err : EmailRecoveryError }); email_recovery_status : (text) -> (EmailRecoveryStatus) query; - email_recovery_submit_dkim_leaf : (EmailRecoverySubmitDkimLeafArg) -> (variant { Ok : EmailRecoveryStatus; Err : EmailRecoveryError }); + email_recovery_diagnostics : (text) -> (opt EmailRecoveryDiagnostics) query; + email_recovery_submit_dkim_leaf : (EmailRecoverySubmitDkimLeafArg) -> (variant { Ok; Err : EmailRecoveryError }); + // Resolves the DKIM key over the canister's own allowlist-gated DoH + // path, called with just the nonce. Used for the pure-DoH (Gmail) + // case and as the fallback when the FE can't walk a fully-signed + // DNSSEC resolution (the DKIM record CNAMEs into an unsigned zone). + // Polled: the FE calls it repeatedly while the status is ResolvingDoh. + email_recovery_resolve_via_doh : (EmailRecoveryResolveViaDohArg) -> (variant { Ok; Err : EmailRecoveryError }); email_recovery_get_delegation : (EmailRecoveryGetDelegationArgs) -> (variant { Ok : SignedDelegation; Err : EmailRecoveryError }) query; email_recovery_credential_remove : (IdentityNumber, text) -> (variant { Ok; Err : EmailRecoveryError }); diff --git a/scripts/sync-ii-spec.mjs b/scripts/sync-ii-spec.mjs index 5b91557..ee0ef1f 100755 --- a/scripts/sync-ii-spec.mjs +++ b/scripts/sync-ii-spec.mjs @@ -7,17 +7,29 @@ // Transformations applied to ii-spec: // - Strip MDX import lines // - Remove the H1 heading (Starlight renders the frontmatter title as H1) -// - Rewrite absolute / relative links that point outside this site +// - Rewrite absolute links that point to the public docs site back to internal paths // - Convert Mermaid sequenceDiagram blocks to PlantUML (site uses remarkPlantUML) // - Replace component with a download link to internet-identity.did // - Copy internet_identity.did to public/references/internet-identity.did // // Transformations applied to vc-spec: // - Remove the H1 heading (Starlight renders the frontmatter title as H1) -// - Rewrite absolute links that point to the retired portal +// - Rewrite absolute links that point to the public docs site back to internal paths // -// Validation (exits non-zero on failure): -// - Unhandled absolute internetcomputer.org/docs links (both specs) +// Link rewriting (shared by both specs): +// Upstream links to pages that also live in this repo as absolute URLs. Those +// URLs are unstable — the same destination has appeared as +// `internetcomputer.org/docs/current/...` and as `docs.internetcomputer.org/...`, +// and the two source files are not migrated in lockstep. We therefore strip the +// volatile domain/prefix to a canonical `path#fragment` (canonicalize) and look +// it up in PATH_MAP. The map only encodes the irregular bits that no string +// transform can infer — e.g. upstream's single `ic-interface-spec` page is split +// across several files here, with each anchor in a different file. +// +// Validation (exits non-zero on failure — fail loud, never silent): +// - Any absolute link to the public docs site left unrewritten, in EITHER the +// `internetcomputer.org/docs` (path) or `docs.internetcomputer.org` (subdomain) +// form. A miss means a new/renamed link upstream that needs a PATH_MAP entry. // - Unconverted Mermaid blocks (ii-spec only) // // Usage: node scripts/sync-ii-spec.mjs @@ -33,6 +45,80 @@ const TARGET = 'docs/references/internet-identity-spec.md'; const TARGET_VC = 'docs/references/verifiable-credentials-spec.md'; const TARGET_DID = 'public/references/internet-identity.did'; +// --- Link rewriting --------------------------------------------------------- + +// Strip the volatile domain/prefix from a public-docs URL, leaving a canonical +// `path#fragment` used as the PATH_MAP key. Handles both the legacy +// `internetcomputer.org/docs/current/` prefix and the current +// `docs.internetcomputer.org/` subdomain, plus a stray slash before the fragment +// (`.../ic-interface-spec/#signatures`) or at the end. +function canonicalize(url) { + return url + .replace(/^https?:\/\/(?:docs\.)?internetcomputer\.org\/(?:docs\/current\/)?/, '') + .replace(/\/(#|$)/, '$1'); +} + +// canonical path#fragment → internal relative link (paths are relative to +// docs/references/, where both synced specs live). +const PATH_MAP = { + 'references/ic-interface-spec#id-classes': './ic-interface-spec/index.md#id-classes', + 'references/ic-interface-spec#canister-signatures': './ic-interface-spec/index.md#canister-signatures', + 'references/ic-interface-spec#signatures': './ic-interface-spec/index.md#signatures', + 'references/ic-interface-spec#system-api-inspect-message': './ic-interface-spec/canister-interface.md#system-api-inspect-message', + 'references/ic-interface-spec#authentication': './ic-interface-spec/https-interface.md#authentication', + 'references/http-gateway-protocol-spec': './http-gateway-protocol-spec.md', + 'developer-docs/web-apps/custom-domains/using-custom-domains': '../guides/frontends/custom-domains.md', + // The II spec page has been served under both slugs; map both to be safe. + 'references/ii-spec#alternative-frontend-origins': './internet-identity-spec.md#alternative-frontend-origins', + 'references/internet-identity-spec#alternative-frontend-origins': './internet-identity-spec.md#alternative-frontend-origins', +}; + +// Matches an absolute link to the public docs site in either URL form. Does NOT +// match other internetcomputer.org subdomains (e.g. identity.internetcomputer.org), +// bare internetcomputer.org, or developer.mozilla.org. +const DOCS_LINK = /https?:\/\/(?:docs\.)?internetcomputer\.org(?:\/docs\/current)?\/[^\s\)">]+/g; + +// Rewrite every public-docs link found via PATH_MAP. Records each applied +// rewrite (deduped) in `applied` for the adaptation log. Unknown links are left +// untouched so the validation pass can flag them. +function rewriteDocsLinks(text, applied) { + return text.replace(DOCS_LINK, (url) => { + const replacement = PATH_MAP[canonicalize(url)]; + if (replacement) { + applied.add(`${canonicalize(url)} → ${replacement}`); + return replacement; + } + return url; + }); +} + +// Build the trailing adaptation-log comment from the rewrites actually applied +// plus the spec-specific structural changes. +function adaptationLog(applied, otherChanges, upstream) { + let out = '\n\n'; + out += `\n`; + return out; +} + +// Scan rewritten content for any public-docs link left unhandled, in either the +// path form (internetcomputer.org/docs) or the subdomain form +// (docs.internetcomputer.org). Returns the unique offenders. +function unhandledDocsLinks(text) { + const re = /https?:\/\/(?:internetcomputer\.org\/docs|docs\.internetcomputer\.org)[^\s\)">]*/g; + return [...new Set([...text.matchAll(re)].map(m => m[0]))]; +} + +// ---------------------------------------------------------------------------- + if (!existsSync(SOURCE_MDX)) { console.error( `ERROR: ${SOURCE_MDX} not found.\n` + @@ -62,43 +148,14 @@ content = content.replace(/^import .*\n/gm, ''); content = content.replace(/^# The Internet Identity Specification\n\n/m, ''); // 3. Rewrite links -const linkMap = [ - [ - 'https://internetcomputer.org/docs/current/references/ic-interface-spec#id-classes', - './ic-interface-spec/index.md#id-classes', - ], - [ - 'https://internetcomputer.org/docs/current/references/ic-interface-spec/#canister-signatures', - './ic-interface-spec/index.md#canister-signatures', - ], - [ - 'https://internetcomputer.org/docs/current/references/ic-interface-spec/#signatures', - './ic-interface-spec/index.md#signatures', - ], - [ - 'https://internetcomputer.org/docs/current/references/ic-interface-spec/#system-api-inspect-message', - './ic-interface-spec/canister-interface.md#system-api-inspect-message', - ], - [ - 'https://internetcomputer.org/docs/current/references/ic-interface-spec#authentication', - './ic-interface-spec/https-interface.md#authentication', - ], - [ - 'https://internetcomputer.org/docs/current/references/http-gateway-protocol-spec', - './http-gateway-protocol-spec.md', - ], - [ - 'https://internetcomputer.org/docs/current/developer-docs/web-apps/custom-domains/using-custom-domains', - '../guides/frontends/custom-domains.md', - ], - [ - '](vc-spec.md)', - '](./verifiable-credentials-spec.md)', - ], -]; - -for (const [old, replacement] of linkMap) { - content = content.replaceAll(old, replacement); +const iiApplied = new Set(); +content = rewriteDocsLinks(content, iiApplied); + +// The source links to the VC spec by its in-repo filename; retarget to ours. +const iiOtherChanges = []; +if (content.includes('](vc-spec.md)')) { + content = content.replaceAll('](vc-spec.md)', '](./verifiable-credentials-spec.md)'); + iiOtherChanges.push('`](vc-spec.md)` (relative, same dir in source repo) → `](./verifiable-credentials-spec.md)`'); } // 4. Convert Mermaid sequenceDiagram blocks to PlantUML @@ -124,7 +181,7 @@ content = content.replace(/^```mermaid\n([\s\S]*?)^```/gm, (_, body) => { return '```plantuml\n' + convertMermaidSequence(body) + '\n```'; }); -// 6. Replace the component with a download link to the .did file +// 5. Replace the component with a download link to the .did file content = content.replace( '{IICandidInterface}', 'The complete Candid interface definition is available at [`internet-identity.did`](/references/internet-identity.did).' + @@ -132,7 +189,7 @@ content = content.replace( ' and can be used for binding generation and type checking.' ); -// 7. Strip leading blank lines, then inject frontmatter +// 6. Strip leading blank lines, then inject frontmatter content = content.replace(/^\n+/, ''); content = `---\n` + @@ -143,26 +200,20 @@ content = `---\n\n` + content; -// 8. Append the link-adaptation log and Upstream comment +// 7. Append the link-adaptation log and Upstream comment +iiOtherChanges.push( + '`# The Internet Identity Specification` H1 removed (Starlight renders frontmatter title as H1)', + '`{IICandidInterface}` replaced with download link to /references/internet-identity.did', + 'Mermaid sequenceDiagram blocks converted to PlantUML (site uses remarkPlantUML, not Mermaid)', +); content = content.trimEnd() + '\n' + - `\n\n` + - `\n`; + adaptationLog( + iiApplied, + iiOtherChanges, + 'sync from dfinity/internet-identity — docs/ii-spec.mdx, src/internet_identity/internet_identity.did', + ); writeFileSync(TARGET, content); console.log(`Written: ${TARGET}`); @@ -174,12 +225,10 @@ console.log(`Written: ${TARGET_DID}`); let failed = false; // Warn about any remaining absolute docs links that weren't rewritten. -const remaining = [...content.matchAll(/https?:\/\/internetcomputer\.org\/docs[^\s\)">]*/g)] - .map(m => m[0]); -const unique = [...new Set(remaining)]; -if (unique.length) { - console.warn('\nWARNING: Unhandled absolute links — add them to the linkMap:'); - unique.forEach(l => console.warn(` ${l}`)); +const remaining = unhandledDocsLinks(content); +if (remaining.length) { + console.warn('\nWARNING: Unhandled absolute docs links — add them to PATH_MAP:'); + remaining.forEach(l => console.warn(` ${l} (key: ${canonicalize(l)})`)); failed = true; } @@ -203,23 +252,9 @@ let vcContent = readFileSync(SOURCE_VC, 'utf8'); // 1. Strip the H1 (Starlight renders it from frontmatter) vcContent = vcContent.replace(/^# II Verifiable Credential Spec \(MVP\)\n\n/m, ''); -// 2. Rewrite retired portal links to internal paths -// Note: upstream still uses internetcomputer.org/docs/current/ — tracked in -// https://github.com/dfinity/internet-identity/issues/3889 -const vcLinkMap = [ - [ - 'https://internetcomputer.org/docs/current/references/ii-spec#alternative-frontend-origins', - './internet-identity-spec.md#alternative-frontend-origins', - ], - [ - 'https://internetcomputer.org/docs/current/references/ic-interface-spec#canister-signatures', - './ic-interface-spec/index.md#canister-signatures', - ], -]; - -for (const [old, replacement] of vcLinkMap) { - vcContent = vcContent.replaceAll(old, replacement); -} +// 2. Rewrite public-docs links to internal paths +const vcApplied = new Set(); +vcContent = rewriteDocsLinks(vcContent, vcApplied); // 3. Strip leading blank lines, then inject frontmatter vcContent = vcContent.replace(/^\n+/, ''); @@ -236,25 +271,20 @@ vcContent = vcContent = vcContent.trimEnd() + '\n' + - `\n\n` + - `\n`; + adaptationLog( + vcApplied, + ['`# II Verifiable Credential Spec (MVP)` H1 removed (Starlight renders frontmatter title as H1)'], + 'sync from dfinity/internet-identity — docs/vc-spec.md', + ); writeFileSync(TARGET_VC, vcContent); console.log(`Written: ${TARGET_VC}`); -// Validate vc-spec for unhandled portal links -const vcRemaining = [...vcContent.matchAll(/https?:\/\/internetcomputer\.org\/docs[^\s\)">]*/g)] - .map(m => m[0]); -const vcUnique = [...new Set(vcRemaining)]; -if (vcUnique.length) { - console.warn('\nWARNING: Unhandled absolute links in vc-spec — add them to vcLinkMap:'); - vcUnique.forEach(l => console.warn(` ${l}`)); +// Validate vc-spec for unhandled docs links +const vcRemaining = unhandledDocsLinks(vcContent); +if (vcRemaining.length) { + console.warn('\nWARNING: Unhandled absolute docs links in vc-spec — add them to PATH_MAP:'); + vcRemaining.forEach(l => console.warn(` ${l} (key: ${canonicalize(l)})`)); failed = true; }