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..ce82117 100644 --- a/docs/references/internet-identity-spec.md +++ b/docs/references/internet-identity-spec.md @@ -717,19 +717,5 @@ 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/docs/references/verifiable-credentials-spec.md b/docs/references/verifiable-credentials-spec.md index 0d7c26a..e6b3503 100644 --- a/docs/references/verifiable-credentials-spec.md +++ b/docs/references/verifiable-credentials-spec.md @@ -406,11 +406,5 @@ Given the interactive nature of the flow, the relying party should not expect to The relying party may also close the II window after some timeout. The user should then be notified by the relying party that the flow failed. - + 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..5daf0d6 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,66 @@ 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. Unknown links are left +// untouched so the validation pass can flag them. +function rewriteDocsLinks(text) { + return text.replace(DOCS_LINK, (url) => PATH_MAP[canonicalize(url)] ?? url); +} + +// Trailing marker appended to each generated spec. Flags the file as synced +// (do not hand-edit — the next sync overwrites it) and records the upstream +// source, per the repo's `` convention. +function generatedMarker(upstream) { + return ( + '\n\n' + + `\n` + ); +} + +// 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,44 +134,10 @@ 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); -} +content = rewriteDocsLinks(content); + +// The source links to the VC spec by its in-repo filename; retarget to ours. +content = content.replaceAll('](vc-spec.md)', '](./verifiable-credentials-spec.md)'); // 4. Convert Mermaid sequenceDiagram blocks to PlantUML function convertMermaidSequence(body) { @@ -124,7 +162,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 +170,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 +181,11 @@ content = `---\n\n` + content; -// 8. Append the link-adaptation log and Upstream comment +// 7. Append the generated-file marker and Upstream comment content = content.trimEnd() + '\n' + - `\n\n` + - `\n`; + generatedMarker('sync from dfinity/internet-identity — docs/ii-spec.mdx, src/internet_identity/internet_identity.did'); writeFileSync(TARGET, content); console.log(`Written: ${TARGET}`); @@ -174,12 +197,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 +224,8 @@ 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 +vcContent = rewriteDocsLinks(vcContent); // 3. Strip leading blank lines, then inject frontmatter vcContent = vcContent.replace(/^\n+/, ''); @@ -232,29 +238,20 @@ vcContent = `---\n\n` + vcContent; -// 4. Append the link-adaptation log and Upstream comment +// 4. Append the generated-file marker and Upstream comment vcContent = vcContent.trimEnd() + '\n' + - `\n\n` + - `\n`; + generatedMarker('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; }