@@ -246,6 +246,21 @@ type CaptchaConfig = record {
246246 };
247247};
248248
249+ // One entry of the `sso_credential_migration` backfill. Maps the
250+ // (iss, aud) pair of stored SSO credentials to the discovery domain (and
251+ // optional human-readable name) they were registered through. Field names
252+ // match the `discovered_oidc_configs` query output so the deployer can
253+ // transcribe its result field-for-field.
254+ type SsoCredentialMigrationEntry = record {
255+ discovery_domain : text;
256+ // Matches the stored credential's `iss`.
257+ issuer : text;
258+ // Matches the stored credential's `aud`.
259+ client_id : text;
260+ // Human-readable SSO label; stamped onto the credential's `sso_name`.
261+ name : opt text;
262+ };
263+
249264// Init arguments of II which can be supplied on install and upgrade.
250265//
251266// Each field is wrapped is `opt` to indicate whether the field should
@@ -286,6 +301,15 @@ type InternetIdentityInit = record {
286301 // (production) or `beta.dfinity.org` (everything else), keyed off
287302 // `is_production`.
288303 sso_discoverable_domains : opt vec text;
304+ // One-shot backfill of the `sso_domain` / `sso_name` fields on stored
305+ // OpenID credentials. When set, a batched timer-driven migration stamps
306+ // every stored credential whose (iss, aud) matches an entry and whose
307+ // `sso_domain` is not set yet. Idempotent — already-stamped credentials
308+ // are skipped, so re-submitting (e.g. with a corrected list) is safe.
309+ // When unset, no backfill runs. The deployer builds the list from the
310+ // running canister's `discovered_oidc_configs` query before
311+ // submitting the upgrade proposal.
312+ sso_credential_migration : opt vec SsoCredentialMigrationEntry;
289313 // Configuration for Web Analytics
290314 analytics_config : opt opt AnalyticsConfig;
291315 // Configuration to show dapps explorer or not
@@ -299,6 +323,9 @@ type InternetIdentityInit = record {
299323 backend_canister_id : opt principal;
300324 // Backend origin, needed to sync configuration with frontend.
301325 backend_origin : opt text;
326+ // Deploy flag for the legacy DNSSEC email-recovery path. Defaults to
327+ // off (DoH-only); `opt true` re-enables it.
328+ enable_dnssec_email_recovery : opt bool;
302329 // DNSSEC verification configuration. Trust anchors used by any feature
303330 // that verifies DNS records against the IANA-rooted DNSSEC chain
304331 // (currently the email-recovery DKIM/DMARC flow). See
@@ -544,13 +571,28 @@ type EmailRecoverySubmitDkimLeafArg = record {
544571 // least one hop required; bounded by `MAX_CNAME_HOPS = 4` at the
545572 // canister side. For the Gmail-style direct-TXT case this is a
546573 // single-element vec.
574+ //
575+ // When the FE cannot walk a fully-signed DNSSEC resolution for the
576+ // leaf — the DKIM record CNAMEs into an unsigned zone (e.g.
577+ // `selector1._domainkey.outlook.com` is a signed CNAME into the
578+ // unsigned `outbound.protection.outlook.com`) — it must NOT submit
579+ // an empty vec here; it drives `email_recovery_resolve_via_doh`
580+ // instead, which resolves the key over the canister's DoH path.
547581 hops : vec SignedRRset;
548582 // Delegation chains for signed zones touched by `hops` that
549583 // weren't already covered by the skeleton chain anchored at
550584 // prepare time. Empty for same-zone resolution.
551585 extra_chains : vec DelegationChain;
552586};
553587
588+ // Argument to email_recovery_resolve_via_doh. Wrapped in a record (like
589+ // EmailRecoverySubmitDkimLeafArg) so the method can grow fields without a
590+ // breaking interface change; nonce is the lookup key and is always
591+ // required.
592+ type EmailRecoveryResolveViaDohArg = record {
593+ nonce : text;
594+ };
595+
554596// DNSSEC proof bundle and supporting types — see
555597// `internet_identity_interface::types::dnssec`.
556598type Rrsig = record {
@@ -595,15 +637,28 @@ type DnsProofBundle = record {
595637 hops : vec SignedRRset;
596638};
597639
640+ // Why a DoH resolution failed, as a typed discriminant rather than a
641+ // free-form string. The FE reads this directly to segment the
642+ // `doh_reason` analytics property — no string parsing.
643+ type DohFailureReason = variant {
644+ AllProvidersFailed;
645+ QuorumFailed : record { agreeing : nat32; total : nat32 };
646+ ResponseMalformed : text;
647+ };
648+
598649type EmailRecoveryError = variant {
599650 Unauthorized : principal;
600651 NonceUnknown;
601652 NonceExpired;
602653 DomainNotAllowlisted : text;
603- DohFetchFailed : text ;
654+ DohFetchFailed : DohFailureReason ;
604655 DomainNotSupported : text;
605656 EmailVerificationFailed : text;
606657 DkimLeafMismatch;
658+ // email_recovery_submit_dkim_leaf was called with an empty `hops`
659+ // vector; an FE that can't walk DNSSEC must drive
660+ // email_recovery_resolve_via_doh instead.
661+ EmptyDkimLeafHops;
607662 NoDkimLeafExpected;
608663 AddressMismatch;
609664 SubjectNotSigned;
@@ -614,6 +669,7 @@ type EmailRecoveryError = variant {
614669
615670type EmailRecoveryStatus = variant {
616671 Pending;
672+ ResolvingDoh;
617673 NeedDkimLeaf : record { selector : text };
618674 RegistrationSucceeded;
619675 RecoveryReady : record {
@@ -625,6 +681,24 @@ type EmailRecoveryStatus = variant {
625681 Expired;
626682};
627683
684+ // Which trust path the canister used (or will use) to verify the
685+ // challenge email. Public — already chosen by the FE and derivable
686+ // from the public deploy config.
687+ type VerificationPath = variant { Doh; Dnssec };
688+
689+ // Strictly-public, user-copyable diagnostics for one pending challenge
690+ // (see email_recovery_diagnostics). Intended for a support ticket so a
691+ // case can be lined up across the SMTP gateway logs and the canister's
692+ // production logs via message_id. NO email address, anchor, principal,
693+ // delegation/seed, or inner error string — reason_code is the failing
694+ // variant's name only.
695+ type EmailRecoveryDiagnostics = record {
696+ message_id : opt text;
697+ reason_code : text;
698+ verification_path : VerificationPath;
699+ created_at : Timestamp;
700+ };
701+
628702type EmailRecoveryGetDelegationArgs = record {
629703 nonce : text;
630704 session_key : SessionKey;
@@ -671,6 +745,13 @@ type SmtpRequest = record {
671745 message : opt SmtpMessage;
672746 envelope : opt SmtpEnvelope;
673747 gateway_flags : opt vec text;
748+ // Optional gateway-supplied correlation id for one inbound message
749+ // (e.g. the RFC 5322 Message-ID or a gateway-assigned tracking id).
750+ // The canister does not interpret it; it lets a reported case be
751+ // lined up across the SMTP gateway logs and the canister's production
752+ // logs during support investigations. Capped at 256 bytes; oversize
753+ // values are rejected with code 555.
754+ message_id : opt text;
674755};
675756
676757// Error returned by `smtp_request` / `smtp_request_validate`.
@@ -1489,7 +1570,14 @@ service : (opt InternetIdentityInit) -> {
14891570 email_recovery_credential_prepare_add : (IdentityNumber, EmailRecoveryDnsInput) -> (variant { Ok : EmailRecoveryChallenge; Err : EmailRecoveryError });
14901571 email_recovery_prepare_delegation : (EmailRecoveryDnsInput, SessionKey) -> (variant { Ok : EmailRecoveryChallenge; Err : EmailRecoveryError });
14911572 email_recovery_status : (text) -> (EmailRecoveryStatus) query;
1492- email_recovery_submit_dkim_leaf : (EmailRecoverySubmitDkimLeafArg) -> (variant { Ok : EmailRecoveryStatus; Err : EmailRecoveryError });
1573+ email_recovery_diagnostics : (text) -> (opt EmailRecoveryDiagnostics) query;
1574+ email_recovery_submit_dkim_leaf : (EmailRecoverySubmitDkimLeafArg) -> (variant { Ok; Err : EmailRecoveryError });
1575+ // Resolves the DKIM key over the canister's own allowlist-gated DoH
1576+ // path, called with just the nonce. Used for the pure-DoH (Gmail)
1577+ // case and as the fallback when the FE can't walk a fully-signed
1578+ // DNSSEC resolution (the DKIM record CNAMEs into an unsigned zone).
1579+ // Polled: the FE calls it repeatedly while the status is ResolvingDoh.
1580+ email_recovery_resolve_via_doh : (EmailRecoveryResolveViaDohArg) -> (variant { Ok; Err : EmailRecoveryError });
14931581 email_recovery_get_delegation : (EmailRecoveryGetDelegationArgs) -> (variant { Ok : SignedDelegation; Err : EmailRecoveryError }) query;
14941582 email_recovery_credential_remove : (IdentityNumber, text) -> (variant { Ok; Err : EmailRecoveryError });
14951583
0 commit comments