From 513d5bf4f99e4748e90d67d47c56e2901b740525 Mon Sep 17 00:00:00 2001 From: "pr-automation-bot-public[bot]" Date: Tue, 29 Sep 2026 09:17:17 +0000 Subject: [PATCH] chore: sync II spec to dfinity/internet-identity release-2026-09-25 --- .sources/VERSIONS | 2 +- .sources/internetidentity | 2 +- public/references/internet-identity.did | 194 ++++++++++++++++++++++++ 3 files changed, 196 insertions(+), 2 deletions(-) diff --git a/.sources/VERSIONS b/.sources/VERSIONS index 63b1ca35..39be1de1 100644 --- a/.sources/VERSIONS +++ b/.sources/VERSIONS @@ -39,4 +39,4 @@ # ------------------------------------------------------- motoko v1.16.1 01aee06 -internetidentity release-2026-09-11 3cd91d62 +internetidentity release-2026-09-25 64ba1637 diff --git a/.sources/internetidentity b/.sources/internetidentity index 3cd91d62..64ba1637 160000 --- a/.sources/internetidentity +++ b/.sources/internetidentity @@ -1 +1 @@ -Subproject commit 3cd91d621bb060308d04ff41155f378f1bc857cb +Subproject commit 64ba163788085b0606fcaa0e106d8a15ab73338b diff --git a/public/references/internet-identity.did b/public/references/internet-identity.did index d96d3c0a..5f2f5cde 100644 --- a/public/references/internet-identity.did +++ b/public/references/internet-identity.did @@ -299,6 +299,11 @@ type InternetIdentityInit = record { // `https` for every discovery host. Never enable in production — non-loopback // hosts always require `https` regardless. sso_allow_insecure_discovery : opt bool; + // Deploy flag relaxing the https requirement for sender-list outcalls to + // loopback hosts (localhost / 127.0.0.1) so e2e tests and local development can + // serve the list over plain http. null / opt false (the default) require https + // for every notifying origin, and a non-loopback origin always requires https. + notifications_allow_insecure_sender_list : opt bool; // Configuration for Web Analytics analytics_config : opt opt AnalyticsConfig; // Configuration to show dapps explorer or not @@ -334,6 +339,12 @@ type InternetIdentityInit = record { // set/clear pattern as `dnssec_config`: null keeps the previously stored // value, `opt null` clears it, `opt opt "https://..."` sets it. mcp_official_url : opt opt text; + // Server-side kill switch for the notifications feature. null / `opt false` + // (the default) disables every notification endpoint; `opt true` enables + // them. Omitting it on upgrade keeps the stored value. + // Apps allowed to notify. Omitted on upgrade keeps the stored list, an empty list + // turns notifications off, and entries enable them for those origins only. + notifications_enabled_origins : opt vec text; }; // DNSSEC trust-anchor list. Any feature that needs DNSSEC-verified DNS @@ -540,6 +551,14 @@ type OpenIdDelegationError = variant { JwtVerificationFailed; NoSuchDelegation; JwtExpired; + // The credential is registered on an anchor, but through a different SSO + // discovery domain than the one this login was verified through, so the + // domain-scoped anchor lookup cannot resolve it. `registered_sso_domain` is + // the domain the credential is registered through (`null` for a credential + // stored without a domain stamp). A sign-up with the same credential would be + // rejected with `OpenIdCredentialAlreadyRegistered`, since registration + // uniqueness spans all discovery domains. + SsoDomainMismatch : record { registered_sso_domain : opt text }; }; type OpenIdPrepareDelegationResponse = record { @@ -1732,6 +1751,162 @@ type ListAvailableAttributesError = variant { AuthorizationError : principal; }; +// Why a notification call was refused. +type NotificationGrantConsentError = variant { + Unauthorized : principal; + InternalCanisterError : text; +}; + +type NotificationRevokeConsentError = variant { + Unauthorized : principal; + InternalCanisterError : text; +}; + +type NotificationGrantConsentRequest = record { + anchor_number : UserNumber; + origin : text; +}; + +type NotificationRevokeConsentRequest = record { + anchor_number : UserNumber; + origin : text; +}; + +type NotificationConsentGrantedRequest = record { + anchor_number : UserNumber; + origin : text; +}; + +// What a browser uploads when it registers for Web Push, and how it replaces a pool +// that is running out: the same endpoint with a newer jwt_issued_at_ns. Signed with the +// browser key it signs in with, which is what says the subscription is this browser's. +type SetWebPushSubscriptionRequest = record { + anchor_number : UserNumber; + endpoint : text; // the relay URL the browser was issued + vapid_public_key : blob; // uncompressed SEC1 P-256, echoed to the relay as k= + jwt_signatures : vec blob; // one raw ECDSA signature per validity window + jwt_issued_at_ns : nat64; +}; + +type RemoveWebPushSubscriptionRequest = record { + anchor_number : UserNumber; + browser_id : nat32; +}; + +type SetWebPushSubscriptionError = variant { + // The caller signs with no key this identity is signed in from. + InvalidBrowserKey; + // The pool offered is not newer than the one this endpoint already holds. + StaleJwtPool; + InternalCanisterError : text; +}; + +type RemoveWebPushSubscriptionError = variant { + Unauthorized : principal; + InternalCanisterError : text; +}; + +type GetWebPushSubscriptionStatusRequest = record { + anchor_number : UserNumber; +}; + +// What a browser is registered with, so the frontend can tell a registration that is +// still live from one another identity's re-subscribe left behind, and knows when to +// sign the next pool. +type WebPushSubscriptionStatus = record { + endpoint : text; // compared against the one the browser holds + pool_len : nat32; // windows covered, not a count of unused signatures + issued_at_ns : nat64; // window i expires at issued_at_ns + (i + 1) * window +}; + +// ===== Notifications sent by an app ===== + +// Scoped to (origin, recipient), and shared across the canisters sending for +// one origin. +type NotificationId = nat64; + +type Urgency = variant { VeryLow; Low; Normal; High }; + +type Notification = record { + id : NotificationId; + recipient : principal; + // Null means II's default retention, which is also the ceiling. + expires_at : opt Timestamp; + // Null means Normal. + urgency : opt Urgency; +}; + +// Applies in order: a later entry for a (recipient, id) replaces an earlier +// one, as a re-send replaces a notification that is still pending. +type SendNotificationArg = record { + origin : FrontendHostname; + notifications : vec Notification; +}; + +type NotAcceptedReason = variant { + NoSuchRecipient; + NoChannel; + Deferred : record { retry_after : Timestamp }; +}; + +type NotAccepted = record { + id : NotificationId; + recipient : principal; + reason : NotAcceptedReason; +}; + +// Anything not_accepted does not name was accepted. +type SendNotificationResponse = record { + not_accepted : vec NotAccepted; +}; + +type SendNotificationError = variant { + // The origin lists no such sender: no file, an empty or unusable one, or + // one that does not name the caller. + NoSuchSender; + TooManyNotifications : record { limit : nat32 }; + InternalCanisterError : text; +}; + +// ===== Notification pull delegation ===== + +type PrepareNotificationDelegationRequest = record { + anchor_number : UserNumber; + origin : FrontendHostname; + account_number : opt AccountNumber; // null = the unreserved default account + session_key : SessionKey; +}; + +type PrepareNotificationDelegationResponse = record { + user_key : UserKey; + expiration : Timestamp; + // Goes in the sender_info field of every call made with this delegation; + // tells the app which account it is being called for. + sender_info : blob; +}; + +type GetNotificationDelegationRequest = record { + anchor_number : UserNumber; + origin : FrontendHostname; + account_number : opt AccountNumber; // null = the unreserved default account + session_key : SessionKey; + expiration : Timestamp; +}; + +type GetNotificationDelegationResponse = record { + signed_delegation : SignedDelegation; + // Authenticates sender_info on those calls. + sender_info_signature : blob; +}; + +type NotificationDelegationError = variant { + // The caller is no browser of this identity, that browser is not registered + // for Web Push, or the identity has not allowed this app to notify it. + NoNotificationAccess; + NoSuchDelegation; + InternalCanisterError : text; +}; + service : (opt InternetIdentityInit) -> { // Legacy identity management API // ============================== @@ -2206,4 +2381,23 @@ service : (opt InternetIdentityInit) -> { // Looks up identity number when called with a recovery phrase lookup_caller_identity_by_recovery_phrase : () -> (opt IdentityNumber); + + // ===== Notifications ===== + notification_grant_consent : (NotificationGrantConsentRequest) -> (variant { Ok; Err : NotificationGrantConsentError }); + notification_revoke_consent : (NotificationRevokeConsentRequest) -> (variant { Ok; Err : NotificationRevokeConsentError }); + notification_consent_granted : (NotificationConsentGrantedRequest) -> (bool) query; + + // Authorized by the browser key the caller signs with. + prepare_notification_delegation : (PrepareNotificationDelegationRequest) -> (variant { Ok : PrepareNotificationDelegationResponse; Err : NotificationDelegationError }); + get_notification_delegation : (GetNotificationDelegationRequest) -> (variant { Ok : GetNotificationDelegationResponse; Err : NotificationDelegationError }) query; + + // Called by the browser itself, signed with the browser key it signs in with. + set_webpush_subscription : (SetWebPushSubscriptionRequest) -> (variant { Ok; Err : SetWebPushSubscriptionError }); + // Called by the identity, so one browser can silence another. + remove_webpush_subscription : (RemoveWebPushSubscriptionRequest) -> (variant { Ok; Err : RemoveWebPushSubscriptionError }); + get_webpush_subscription_status : (GetWebPushSubscriptionStatusRequest) -> (opt WebPushSubscriptionStatus) query; + + // Called by an app's backend canister for the origin it names. Not + // implemented yet: every call is refused. + app_send_notification : (SendNotificationArg) -> (variant { Ok : SendNotificationResponse; Err : SendNotificationError }); };