diff --git a/.sources/VERSIONS b/.sources/VERSIONS index 39be1de1..8c1bd1c0 100644 --- a/.sources/VERSIONS +++ b/.sources/VERSIONS @@ -39,4 +39,4 @@ # ------------------------------------------------------- motoko v1.16.1 01aee06 -internetidentity release-2026-09-25 64ba1637 +internetidentity release-2026-10-16 8d3e876b diff --git a/.sources/internetidentity b/.sources/internetidentity index 64ba1637..8d3e876b 160000 --- a/.sources/internetidentity +++ b/.sources/internetidentity @@ -1 +1 @@ -Subproject commit 64ba163788085b0606fcaa0e106d8a15ab73338b +Subproject commit 8d3e876b074a5a93e167cb09a550a3c20f4c3ed8 diff --git a/docs/references/internet-identity-spec.md b/docs/references/internet-identity-spec.md index fb0664b1..936c6d43 100644 --- a/docs/references/internet-identity-spec.md +++ b/docs/references/internet-identity-spec.md @@ -457,7 +457,7 @@ The complete Candid interface definition is available at [`internet-identity.did **Authorization**: This `register` request must be sent to the canister with `caller` that is the self-authenticating id derived from the given `DeviceKey`. -The `register` method is used to create a new user. The Internet Identity Service backend creates a _fresh_ Identity Anchor, creates the Identity record, and adds the given device as the first device. +The `register` method is used to create a new user. The Internet Identity Service backend creates a _fresh_ Identity Anchor, creates the Identity record, and adds the given device as the first device. If the public key or the credential id of the given device is already used by an identity, the call returns `device_already_used` and no Identity Anchor is created. In order to protect the Internet Computer from too many "free" update calls, and to protect the Internet Identity Service from too many user registrations, this call is protected using a CAPTCHA challenge. The `register` call can only succeed if the `ChallengeResult` contains a `key` for a challenge that was created with `create_challenge` (see below) in the last 5 minutes _and_ if the `chars` match the characters that the Internet Identity Service has stored internally for that `key`. diff --git a/public/references/internet-identity.did b/public/references/internet-identity.did index 5f2f5cde..dfe966a5 100644 --- a/public/references/internet-identity.did +++ b/public/references/internet-identity.did @@ -123,6 +123,8 @@ type RegisterResponse = variant { canister_full; // The challenge was not successful. bad_challenge; + // The device (its public key or credential id) is already used by an identity. + device_already_used; }; type AddTentativeDeviceResponse = variant { @@ -304,6 +306,11 @@ type InternetIdentityInit = record { // 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; + // Deploy flag relaxing the https requirement for the Web Push endpoint a + // browser registers, for a deployment whose relay is a local server rather + // than a push service. As above, only loopback hosts (localhost / 127.0.0.1) + // may then be plain http; null / opt false (the default) require https. + notifications_allow_insecure_endpoint : opt bool; // Configuration for Web Analytics analytics_config : opt opt AnalyticsConfig; // Configuration to show dapps explorer or not @@ -339,12 +346,11 @@ 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; + // Server-side switch for the notifications feature. `opt true` enables every + // notification endpoint, for any app; `opt false` disables them. Omitting it on + // upgrade keeps the stored value, and a canister that never stored one has them + // disabled. + notifications_enabled : opt bool; }; // DNSSEC trust-anchor list. Any feature that needs DNSSEC-verified DNS @@ -451,6 +457,10 @@ type OpenIdEmailVerification = variant { Unknown; Google; Microsoft; + // Trusts email_verified like Google. Apple's private relay addresses, + // which only senders registered with II's Apple developer team can deliver + // mail to, are not kept on the credential at all. + Apple; }; type OpenIdConfig = record { @@ -491,12 +501,24 @@ type SsoDiscovery = record { session_max_age_ns : nat64; }; -// Status of a domain's SSO discovery, read by `get_sso_discovery_status`. A -// failed fetch isn't a distinct status — it reads as `Pending` and the frontend -// times out — so the statuses are: resolved, or in flight. +// Status of a domain's SSO discovery, read by `get_sso_discovery_status`. +// `Failed.retry_after` is when II fetches the domain again (a retry before then +// fails the same way), and is absent when no retry can help, such as a value +// that is not a domain. `reason` explains the failure, to show as is, when II +// has one. type SsoDiscoveryStatus = variant { Resolved : SsoDiscovery; Pending; + Failed : record { retry_after : opt Timestamp; reason : opt text }; +}; + +// Whether an organization domain can be used for SSO sign-in, read by +// `app_sso_domain_status`. `name` is the organization's display name; +// `retry_after` and `reason` are as for `SsoDiscoveryStatus.Failed`. +type AppSsoDomainStatus = variant { + Available : record { name : opt text }; + Pending; + Unavailable : record { retry_after : opt Timestamp; reason : opt text }; }; // Request for `get_sso_discovery_status`. @@ -1509,6 +1531,43 @@ type AccountUpdate = record { name : opt text; }; +// An app the identity decided a permission for, allowed or denied, for the user's own +// list of their apps. +type ApplicationInfo = record { + // The origin the app's identity is derived for: what its accounts and its + // notification consent are stored under. + origin : FrontendHostname; + // The latest sign-in to any of the identity's accounts at the app, or null where + // none was used. + last_used : opt Timestamp; + // Whether the app may notify the identity: it holds consent, and this deployment + // notifies for it, as notification_consent_granted answers. + notifications_allowed : bool; + // When the app last had a notification queued for one of the identity's browsers, + // or null where it never has. + last_notified : opt Timestamp; +}; + +type ListApplicationsRequest = record { + anchor_number : UserNumber; +}; + +type ListApplicationsError = variant { + Unauthorized : principal; + InternalCanisterError : text; +}; + +type ForgetApplicationsRequest = record { + anchor_number : UserNumber; + // As list_applications spells them. + origins : vec FrontendHostname; +}; + +type ForgetApplicationsError = variant { + Unauthorized : principal; + InternalCanisterError : text; +}; + type DummyAuthConfig = record { // Prompts user for a index value (0 - 255) when set to true, // this is used in e2e to have multiple dummy auth identities. @@ -1907,6 +1966,44 @@ type NotificationDelegationError = variant { InternalCanisterError : text; }; +// ===== Notifications a browser has yet to show ===== + +type GetNextNotificationRequest = record { + anchor_number : UserNumber; +}; + +// What a service worker shows for one wake-up: the app and account it is for, the +// canister holding its content, and which notification. +type NotificationToShow = record { + origin : FrontendHostname; + account_number : opt AccountNumber; // null = the unreserved default account + canister_id : principal; + id : NotificationId; +}; + +// Null once nothing is left to show. +type GetNextNotificationResponse = record { + notification : opt NotificationToShow; +}; + +type GetNextNotificationError = variant { + // Also a caller that is no browser of the identity. + InternalCanisterError : text; +}; + +type RemoveNotificationRequest = record { + anchor_number : UserNumber; + // As browser_get_next_notification returned it. + notification : NotificationToShow; +}; + +type RemoveNotificationResponse = record {}; + +type RemoveNotificationError = variant { + // Also a caller that is no browser of the identity. + InternalCanisterError : text; +}; + service : (opt InternetIdentityInit) -> { // Legacy identity management API // ============================== @@ -2049,16 +2146,24 @@ service : (opt InternetIdentityInit) -> { openid_prepare_delegation : (JWT, Salt, SessionKey, opt text) -> (variant { Ok : OpenIdPrepareDelegationResponse; Pending; Err : OpenIdDelegationError }); openid_get_delegation : (JWT, Salt, SessionKey, Timestamp, opt text) -> (variant { Ok : SignedDelegation; Pending; Err : OpenIdDelegationError }) query; - // SSO discovery for the sign-in initiation flow. The frontend polls - // `get_sso_discovery_status` (query) and, while it reads `Pending`, drives the - // on-demand two-hop discovery fetch with `discover_sso` (update); once the - // fetch completes the query returns `Resolved` with the config. + // SSO discovery for the sign-in initiation flow. The frontend calls + // `discover_sso` (update) once, which starts the two-hop discovery fetch and + // the domain's key fetch, or refreshes a stale result, then polls + // `get_sso_discovery_status` (query) until it reads `Resolved` or `Failed`. // // The optional second argument is the target dapp origin; when supplied, // `resolved_client_id` reports the client that origin must use (`null` = denied). discover_sso : (text) -> (); get_sso_discovery_status : (GetSsoDiscoveryStatusRequest) -> (SsoDiscoveryStatus) query; + // Whether an organization domain can be used for SSO sign-in, for apps that + // check a domain before sending the user into the flow. Call + // `app_sso_domain_check` (update) once to start the fetch, then poll + // `app_sso_domain_status` (query) until it reads `Available` or + // `Unavailable`. + app_sso_domain_check : (text) -> (); + app_sso_domain_status : (text) -> (AppSsoDomainStatus) query; + // SSO sign-in path. Mints a delegation only if the JWT's `aud` matches the // client the target origin resolves to. `Pending` means the discovery/JWKS // cache is cold — re-call to drive the fetch. @@ -2185,6 +2290,15 @@ service : (opt InternetIdentityInit) -> { origin : FrontendHostname, ) -> (variant { Ok : vec AccountInfo; Err: GetAccountsError }) query; + // Every app the identity decided a permission for, so the user can change or forget + // what they decided. Authorized by the identity's own access methods. + list_applications : (ListApplicationsRequest) -> (variant { Ok : vec ApplicationInfo; Err : ListApplicationsError }) query; + // Undoes every permission decision for the apps, drops what they queued for the + // identity's browsers and signs out every session at them, as one write. Accounts and + // the default account stay. An origin the identity holds nothing at is already + // forgotten. + forget_applications : (ForgetApplicationsRequest) -> (variant { Ok; Err : ForgetApplicationsError }); + create_account : ( anchor_number : UserNumber, origin : FrontendHostname, @@ -2391,13 +2505,18 @@ service : (opt InternetIdentityInit) -> { prepare_notification_delegation : (PrepareNotificationDelegationRequest) -> (variant { Ok : PrepareNotificationDelegationResponse; Err : NotificationDelegationError }); get_notification_delegation : (GetNotificationDelegationRequest) -> (variant { Ok : GetNotificationDelegationResponse; Err : NotificationDelegationError }) query; + // Called by the service worker on a wake-up, signed with the browser key: the oldest + // notification its browser has yet to show, passing over the ones it skips. + browser_get_next_notification : (GetNextNotificationRequest) -> (variant { Ok : GetNextNotificationResponse; Err : GetNextNotificationError }) query; + // Called by the service worker for the one it shows, while it fetches the content. + browser_remove_notification : (RemoveNotificationRequest) -> (variant { Ok : RemoveNotificationResponse; Err : RemoveNotificationError }); + // 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. + // Called by an app's backend canister for the origin it names. app_send_notification : (SendNotificationArg) -> (variant { Ok : SendNotificationResponse; Err : SendNotificationError }); };