From 249a509bf4c6362dabd3668aa26bdd91d1eb3543 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 29 May 2026 12:25:30 +0200 Subject: [PATCH 01/31] chore(vetkeys): migrate password_manager_with_metadata to icp-cli and @icp-sdk/vetkeys - Replace dfx.json with icp.yaml (Rust and Motoko backends) - Use @icp-sdk/vetkeys@0.5.0-beta.0 instead of @dfinity/vetkeys - Use @icp-sdk/auth@7.1.0 and @icp-sdk/core@5.4.0 - Update Motoko ic-vetkeys to 0.5.0, Rust ic-vetkeys to 0.7.0 - Apply KeyManager API changes for ic-vetkeys 0.7.0 in Rust backend - Add moc 1.5.1 toolchain to mops.toml - Rename tailwind.config.cjs to tailwind.config.mjs - Add CI workflow for both Rust and Motoko backends - Drop icp.ninja support Co-Authored-By: Claude Sonnet 4.6 --- ...password-manager-with-metadata-example.yml | 80 +++++++++++++++++++ .../password_manager_with_metadata/README.md | 28 ++++--- .../frontend/package.json | 14 ++-- .../frontend/scripts/gen_bindings.sh | 24 +++--- .../src/components/EditPassword.svelte | 4 +- .../frontend/src/components/EditVault.svelte | 19 ++++- .../src/components/NewPassword.svelte | 14 +++- .../frontend/src/components/Password.svelte | 2 +- .../src/components/SharingEditor.svelte | 4 +- .../src/components/SidebarLayout.svelte | 2 +- .../frontend/src/components/Vault.svelte | 78 +++++++++--------- .../frontend/src/lib/encrypted_maps.ts | 37 ++++----- .../frontend/src/lib/init.ts | 1 - .../frontend/src/lib/password.ts | 4 +- .../frontend/src/lib/password_manager.ts | 63 ++++++++------- .../frontend/src/lib/vault.ts | 4 +- .../frontend/src/store/auth.ts | 53 +++++------- .../frontend/src/store/vaults.ts | 4 +- ...ailwind.config.cjs => tailwind.config.mjs} | 0 .../frontend/vite.config.js | 75 ++++++++++------- .../motoko/dfx.json | 50 ------------ .../motoko/icp.yaml | 22 +++++ .../motoko/mops.toml | 5 +- .../rust/backend/Cargo.toml | 2 +- .../rust/backend/Makefile | 2 +- .../rust/backend/backend.did | 13 ++- .../rust/backend/src/lib.rs | 11 ++- .../rust/dfx.json | 43 ---------- .../rust/icp.yaml | 23 ++++++ 29 files changed, 380 insertions(+), 301 deletions(-) create mode 100644 .github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml rename rust/vetkeys/password_manager_with_metadata/frontend/{tailwind.config.cjs => tailwind.config.mjs} (100%) delete mode 100644 rust/vetkeys/password_manager_with_metadata/motoko/dfx.json create mode 100644 rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml delete mode 100644 rust/vetkeys/password_manager_with_metadata/rust/dfx.json create mode 100644 rust/vetkeys/password_manager_with_metadata/rust/icp.yaml diff --git a/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml b/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml new file mode 100644 index 000000000..fb813c4ad --- /dev/null +++ b/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml @@ -0,0 +1,80 @@ +name: rust-vetkeys-password-manager-with-metadata +on: + push: + branches: + - master + pull_request: + paths: + - rust/vetkeys/password_manager_with_metadata/** + - .github/workflows/provision-darwin.sh + - .github/workflows/provision-linux.sh + - .github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml + - .ic-commit +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + rust-vetkeys-password-manager-with-metadata-rust-darwin: + runs-on: macos-15 + steps: + - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 + - name: Provision Darwin + run: bash .github/workflows/provision-darwin.sh + - name: Pre-download network launcher + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: bash .github/workflows/pre-download-launcher.sh + - name: Rust vetkeys password-manager-with-metadata Rust Darwin + run: | + pushd rust/vetkeys/password_manager_with_metadata/rust + icp network start -d + icp deploy + popd + rust-vetkeys-password-manager-with-metadata-rust-linux: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 + - name: Provision Linux + run: bash .github/workflows/provision-linux.sh + - name: Pre-download network launcher + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: bash .github/workflows/pre-download-launcher.sh + - name: Rust vetkeys password-manager-with-metadata Rust Linux + run: | + pushd rust/vetkeys/password_manager_with_metadata/rust + icp network start -d + icp deploy + popd + rust-vetkeys-password-manager-with-metadata-motoko-darwin: + runs-on: macos-15 + steps: + - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 + - name: Provision Darwin + run: bash .github/workflows/provision-darwin.sh + - name: Pre-download network launcher + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: bash .github/workflows/pre-download-launcher.sh + - name: Rust vetkeys password-manager-with-metadata Motoko Darwin + run: | + pushd rust/vetkeys/password_manager_with_metadata/motoko + icp network start -d + icp deploy + popd + rust-vetkeys-password-manager-with-metadata-motoko-linux: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 + - name: Provision Linux + run: bash .github/workflows/provision-linux.sh + - name: Pre-download network launcher + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: bash .github/workflows/pre-download-launcher.sh + - name: Rust vetkeys password-manager-with-metadata Motoko Linux + run: | + pushd rust/vetkeys/password_manager_with_metadata/motoko + icp network start -d + icp deploy + popd diff --git a/rust/vetkeys/password_manager_with_metadata/README.md b/rust/vetkeys/password_manager_with_metadata/README.md index 0f8d2e80f..4809ff5b8 100644 --- a/rust/vetkeys/password_manager_with_metadata/README.md +++ b/rust/vetkeys/password_manager_with_metadata/README.md @@ -1,8 +1,10 @@ # VetKey Password Manager with Metadata + The **VetKey Password Manager** is an example application demonstrating how to use **VetKeys** and **Encrypted Maps** to build a secure, decentralized password manager on the **Internet Computer (IC)**. This application allows users to create password vaults, store encrypted passwords, and share vaults with other users via their **Internet Identity Principal**. @@ -19,39 +21,43 @@ This version of the application extends the basic password manager by supporting ### Prerequisites -- [Local Internet Computer dev environment](https://internetcomputer.org/docs/building-apps/getting-started/install) +- [ICP CLI](https://cli.internetcomputer.org) - [npm](https://www.npmjs.com/package/npm) ### (Optionally) Choose a Different Master Key -This example uses `test_key_1` by default. To use a different [available master key](https://internetcomputer.org/docs/building-apps/network-features/vetkeys/api#available-master-keys), change the `"init_arg": "(\"test_key_1\")"` line in `dfx.json` to the desired key before running `dfx deploy` in the next step. +This example uses `test_key_1` by default. To use a different [available master key](https://docs.internetcomputer.org/building-apps/network-features/vetkeys/api#available-master-keys), change the `init_args` value in `icp.yaml` to the desired key before running `icp deploy` in the next step. ### Deploy the Canisters Locally If you want to deploy this project locally with a Motoko backend, then run: ```bash -dfx start --background && dfx deploy +icp network start -d && icp deploy ``` from the `motoko` folder. To use the Rust backend instead of Motoko, run the same command in the `rust` folder. -## Running the Project +To run the frontend in development mode with hot reloading (after running `icp deploy`): +```bash +npm run dev +``` + +When you are done testing, stop the local network to free up resources and unblock the default port for other projects: +```bash +icp network stop +``` + +## Example Components ### Backend -The backend consists of an **Encrypted Maps**-enabled canister that securely stores passwords. It is automatically deployed with `dfx deploy`. +The backend consists of an **Encrypted Maps**-enabled canister that securely stores passwords. ### Frontend The frontend is a **Svelte** application providing a user-friendly interface for managing vaults and passwords. -To run the frontend in development mode with hot reloading: - -```bash -npm run dev -``` - ## Limitations This example dapp does not implement key rotation, which is strongly recommended in a production environment. diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/package.json b/rust/vetkeys/password_manager_with_metadata/frontend/package.json index 49dd1e0e1..a50afea07 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/package.json +++ b/rust/vetkeys/password_manager_with_metadata/frontend/package.json @@ -5,7 +5,9 @@ "type": "module", "scripts": { "build": "npm run build:bindings && vite build", - "dev": "npm run build:bindings && vite", + "dev": "printf '\\nNo backend specified. Use one of:\\n\\n npm run dev:motoko\\n npm run dev:rust\\n\\n' && exit 1", + "dev:motoko": "npm run build:bindings && BACKEND=motoko vite", + "dev:rust": "npm run build:bindings && BACKEND=rust vite", "build:bindings": "cd scripts && ./gen_bindings.sh", "lint": "eslint", "prettier": "prettier --write .", @@ -32,16 +34,12 @@ "typescript-eslint": "^8.26.1", "vite": "^5.4.21", "vite-plugin-compression": "^0.5.1", - "vite-plugin-environment": "^1.1.3", "vite-plugin-eslint": "^1.8.1" }, "dependencies": { - "@dfinity/agent": "^2.3.0", - "@dfinity/auth-client": "^2.3.0", - "@dfinity/candid": "^2.3.0", - "@dfinity/identity": "^2.3.0", - "@dfinity/principal": "^2.3.0", - "@dfinity/vetkeys": "^0.3.0", + "@icp-sdk/auth": "^7.1.0", + "@icp-sdk/core": "^5.4.0", + "@icp-sdk/vetkeys": "^0.5.0-beta.0", "@sveltejs/vite-plugin-svelte": "^3.0.2", "daisyui": "^4.12.23", "save": "^2.9.0", diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/scripts/gen_bindings.sh b/rust/vetkeys/password_manager_with_metadata/frontend/scripts/gen_bindings.sh index e8f13011b..4c66807c8 100755 --- a/rust/vetkeys/password_manager_with_metadata/frontend/scripts/gen_bindings.sh +++ b/rust/vetkeys/password_manager_with_metadata/frontend/scripts/gen_bindings.sh @@ -1,15 +1,17 @@ #!/bin/bash +# Bindings are always generated from the Rust backend since both backends +# expose the same Candid interface. -cd ../../backend && make extract-candid - -cd .. && dfx generate password_manager_with_metadata || exit 1 - -rm -r frontend/src/declarations/password_manager_with_metadata > /dev/null 2>&1 || true +# Resolve the physical path of this script so that navigating up works +# correctly even when frontend/ is reached via a symlink (e.g. motoko/frontend). +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd -P) +if command -v candid-extractor >/dev/null 2>&1; then + cd "$SCRIPT_DIR/../../rust/backend" && make extract-candid +fi +cd "$SCRIPT_DIR/../.." +rm -rf frontend/src/declarations/password_manager_with_metadata mkdir -p frontend/src/declarations/password_manager_with_metadata -mv src/declarations/password_manager_with_metadata frontend/src/declarations -rmdir -p src/declarations > /dev/null 2>&1 || true - -# dfx 0.31+ generates @icp-sdk/core imports; rewrite to @dfinity/* to match deps -find frontend/src/declarations -type f \( -name '*.ts' -o -name '*.js' \) -exec \ - perl -i -pe 's|\@icp-sdk/core/agent|\@dfinity/agent|g; s|\@icp-sdk/core/principal|\@dfinity/principal|g; s|\@icp-sdk/core/candid|\@dfinity/candid|g' {} + \ No newline at end of file +npx @icp-sdk/bindgen --did-file rust/backend/backend.did \ + --out-dir frontend/src/declarations/password_manager_with_metadata \ + --declarations-flat --force diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte index b99631a3d..60b39d8e1 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte @@ -11,8 +11,8 @@ import { auth } from "../store/auth"; import Spinner from "./Spinner.svelte"; import { onDestroy } from "svelte"; - import { Principal } from "@dfinity/principal"; - import type { AccessRights } from "@dfinity/vetkeys/encrypted_maps"; + import { Principal } from "@icp-sdk/core/principal"; + import type { AccessRights } from "@icp-sdk/vetkeys/encrypted_maps"; export let currentRoute = ""; const unsubscribe = location.subscribe((value) => { diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte index 7a3695666..53d164f76 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte @@ -6,8 +6,15 @@ import Trash from "svelte-icons/fa/FaTrash.svelte"; import { auth } from "../store/auth"; import Spinner from "./Spinner.svelte"; + import { location } from "svelte-spa-router"; + import { Principal } from "@icp-sdk/core/principal"; + import { onDestroy } from "svelte"; - export let currentRoute = ""; + let currentRoute = ""; + const unsubscribeCurrentRoute = location.subscribe((value) => { + currentRoute = decodeURI(value); + }); + onDestroy(unsubscribeCurrentRoute); let editedVault: VaultModel; let updating = false; @@ -20,10 +27,16 @@ if ( $auth.state === "initialized" && $vaultsStore.state === "loaded" && - !editedVault + !editedVault && + currentRoute.split("/").length > 2 ) { + const split = currentRoute.split("/"); + const vaultOwner = Principal.fromText(split[split.length - 2]); + const vaultName = split[split.length - 1]; const vault = $vaultsStore.list.find( - (vault) => vault.name === currentRoute, + (v) => + v.owner.compareTo(vaultOwner) === "eq" && + v.name === vaultName, ); if (vault) { diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte index 998df9cf7..d288e2a8f 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte @@ -7,7 +7,7 @@ import { addNotification, showError } from "../store/notifications"; import Header from "./Header.svelte"; import PasswordEditor from "./PasswordEditor.svelte"; - import { Principal } from "@dfinity/principal"; + import { Principal } from "@icp-sdk/core/principal"; let creating = false; let vaultOwner = @@ -45,6 +45,14 @@ return; } + if (vaultName.trim() === "" || passwordName.trim() === "") { + addNotification({ + type: "error", + message: "Vault name and password name must not be empty.", + }); + return; + } + creating = true; await setPassword( @@ -131,7 +139,9 @@ diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/Password.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/Password.svelte index 1631811d5..0bfe57331 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/Password.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/Password.svelte @@ -2,7 +2,7 @@ import { type PasswordModel, summarize } from "../lib/password"; import { link, location } from "svelte-spa-router"; import { vaultsStore } from "../store/vaults"; - import { Principal } from "@dfinity/principal"; + import { Principal } from "@icp-sdk/core/principal"; import { onDestroy } from "svelte"; import Spinner from "./Spinner.svelte"; import Header from "./Header.svelte"; diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte index 6e7f0b2a8..bb4837bbf 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte @@ -8,8 +8,8 @@ vaultsStore, } from "../store/vaults"; import { addNotification, showError } from "../store/notifications"; - import { Principal } from "@dfinity/principal"; - import type { AccessRights } from "@dfinity/vetkeys/encrypted_maps"; + import { Principal } from "@icp-sdk/core/principal"; + import type { AccessRights } from "@icp-sdk/vetkeys/encrypted_maps"; export let editedVault: VaultModel; export let canManage = false; diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte index 439db77a7..4b6df0710 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte @@ -7,7 +7,7 @@ // @ts-expect-error: svelte-icons have some problems with ts declarations import FaDoorOpen from "svelte-icons/fa/FaDoorOpen.svelte"; import Disclaimer from "./Disclaimer.svelte"; - import { Principal } from "@dfinity/principal"; + import { Principal } from "@icp-sdk/core/principal"; import { link } from "svelte-spa-router"; diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/Vault.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/Vault.svelte index 4ce52a0d4..598b765a1 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/Vault.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/Vault.svelte @@ -3,14 +3,14 @@ import { link, location } from "svelte-spa-router"; import { onDestroy } from "svelte"; import { vaultsStore } from "../store/vaults"; - import { Principal } from "@dfinity/principal"; + import { Principal } from "@icp-sdk/core/principal"; import Header from "./Header.svelte"; import Spinner from "./Spinner.svelte"; // @ts-expect-error: svelte-icons have some problems with ts declarations import GiOpenTreasureChest from "svelte-icons/gi/GiOpenTreasureChest.svelte"; import { auth } from "../store/auth"; import SharingEditor from "./SharingEditor.svelte"; - import type { AccessRights } from "@dfinity/vetkeys/encrypted_maps"; + import type { AccessRights } from "@icp-sdk/vetkeys/encrypted_maps"; export let vault: VaultModel = { name: "", @@ -27,42 +27,46 @@ }); onDestroy(unsubscribeCurrentRoute); - $: { - if ( - $vaultsStore.state === "loaded" && - $auth.state === "initialized" && - vault.name.length === 0 && - currentRoute.split("/").length > 2 - ) { - const split = currentRoute.split("/"); - const vaultName = split[split.length - 1]; - const vaultOwner = Principal.fromText(split[split.length - 2]); - const searchedForVault = $vaultsStore.list.find( - (v) => - v.owner.compareTo(vaultOwner) === "eq" && - v.name === vaultName, - ); - if (!searchedForVault) { - vaultSummary = - "could not find vault " + - vaultName + - " owned by " + - vaultOwner.toText(); - } else { - vault = searchedForVault; - vaultSummary += summarize(vault); - const me = $auth.client.getIdentity().getPrincipal(); + // Parse owner and vault name from the URL once; stored separately so the + // vault lookup below stays reactive to store updates (e.g. from the poller). + let parsedRoute: { owner: Principal; vaultName: string } | null = null; + $: if (currentRoute.split("/").length > 2 && parsedRoute === null) { + const split = currentRoute.split("/"); + parsedRoute = { + owner: Principal.fromText(split[split.length - 2]), + vaultName: split[split.length - 1], // already decoded via decodeURI on subscribe + }; + } - if (vault.owner.compareTo(me) === "eq") { - accessRights = { ReadWriteManage: null }; - } else { - const foundRights = vault.users.find( - (user) => user[0].compareTo(me) === "eq", - ); - accessRights = foundRights - ? foundRights[1] - : { Read: null }; - } + // Re-runs whenever the store updates so new passwords from the poller appear. + $: if ( + $vaultsStore.state === "loaded" && + $auth.state === "initialized" && + parsedRoute !== null + ) { + const { owner: targetOwner, vaultName: targetVaultName } = parsedRoute; + const searchedForVault = $vaultsStore.list.find( + (v) => + v.owner.compareTo(targetOwner) === "eq" && + v.name === targetVaultName, + ); + if (!searchedForVault) { + vaultSummary = + "could not find vault " + + targetVaultName + + " owned by " + + targetOwner.toText(); + } else { + vault = searchedForVault; + vaultSummary = summarize(vault); + const me = $auth.client.getIdentity().getPrincipal(); + if (vault.owner.compareTo(me) === "eq") { + accessRights = { ReadWriteManage: null }; + } else { + const foundRights = vault.users.find( + (user) => user[0].compareTo(me) === "eq", + ); + accessRights = foundRights ? foundRights[1] : { Read: null }; } } } diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/encrypted_maps.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/encrypted_maps.ts index 543f05f10..e464c683c 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/encrypted_maps.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/encrypted_maps.ts @@ -1,31 +1,22 @@ -import "./init.ts"; -import { HttpAgent, type HttpAgentOptions } from "@dfinity/agent"; +import { HttpAgent } from "@icp-sdk/core/agent"; import { DefaultEncryptedMapsClient, EncryptedMaps, -} from "@dfinity/vetkeys/encrypted_maps"; +} from "@icp-sdk/vetkeys/encrypted_maps"; +import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"; -export async function createEncryptedMaps( - agentOptions: HttpAgentOptions, -): Promise { - const agent = await HttpAgent.create({ ...agentOptions }); - // Fetch root key for certificate validation during development - if (process.env.NODE_ENV !== "production") { - console.log(`Dev environment - fetching root key...`); +const canisterEnv = safeGetCanisterEnv<{ + "PUBLIC_CANISTER_ID:password_manager_with_metadata": string; +}>(); - agent.fetchRootKey().catch((err) => { - console.warn( - "Unable to fetch root key. Check to ensure that your local replica is running", - ); - console.error(err); - }); +export function createEncryptedMaps(agent: HttpAgent): EncryptedMaps { + const canisterId = + canisterEnv?.["PUBLIC_CANISTER_ID:password_manager_with_metadata"]; + if (!canisterId) { + throw new Error( + "Canister ID for password_manager_with_metadata is not set", + ); } - // Creates an actor with using the candid interface and the HttpAgent - return new EncryptedMaps( - new DefaultEncryptedMapsClient( - agent, - process.env.CANISTER_ID_PASSWORD_MANAGER_WITH_METADATA as string, - ), - ); + return new EncryptedMaps(new DefaultEncryptedMapsClient(agent, canisterId)); } diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/init.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/init.ts index 062c8af94..e69de29bb 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/init.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/init.ts @@ -1 +0,0 @@ -window.global ||= window; diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/password.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/password.ts index 88620bd13..ff7d78287 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/password.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/password.ts @@ -1,5 +1,5 @@ -import type { Principal } from "@dfinity/principal"; -import type { PasswordMetadata } from "../declarations/password_manager_with_metadata/password_manager_with_metadata.did"; +import type { Principal } from "@icp-sdk/core/principal"; +import type { PasswordMetadata } from "../declarations/password_manager_with_metadata/backend.did"; export interface PasswordModel { owner: Principal; diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/password_manager.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/password_manager.ts index 232f1873a..64f58bc4b 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/password_manager.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/password_manager.ts @@ -1,13 +1,19 @@ -import "./init.ts"; -import { type ActorSubclass, type HttpAgentOptions } from "@dfinity/agent"; -import { EncryptedMaps } from "@dfinity/vetkeys/encrypted_maps"; +import { Actor, HttpAgent, type ActorSubclass } from "@icp-sdk/core/agent"; +import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"; +import type { Principal } from "@icp-sdk/core/principal"; +import { EncryptedMaps } from "@icp-sdk/vetkeys/encrypted_maps"; +import { + idlFactory, + type _SERVICE, +} from "../declarations/password_manager_with_metadata/backend.did"; import { createEncryptedMaps } from "./encrypted_maps"; -import type { Principal } from "@dfinity/principal"; -import { createActor } from "../declarations/password_manager_with_metadata"; -import type { _SERVICE } from "../declarations/password_manager_with_metadata/password_manager_with_metadata.did"; import { passwordFromContent, type PasswordModel } from "../lib/password"; import { vaultFromContent, type VaultModel } from "../lib/vault"; +const canisterEnv = safeGetCanisterEnv<{ + "PUBLIC_CANISTER_ID:password_manager_with_metadata": string; +}>(); + export class PasswordManager { /// The actor class representing the full interface of the canister. private readonly canisterClient: ActorSubclass<_SERVICE>; @@ -145,35 +151,30 @@ export class PasswordManager { } } -export async function createPasswordManager( - agentOptions?: HttpAgentOptions, -): Promise { - if (!process.env.CANISTER_ID_PASSWORD_MANAGER_WITH_METADATA) { - console.error( - "CANISTER_ID_PASSWORD_MANAGER_WITH_METADATA is not defined", - ); +export async function createPasswordManager(agentOptions?: { + identity?: HttpAgent["config"]["identity"]; +}): Promise { + const canisterId = + canisterEnv?.["PUBLIC_CANISTER_ID:password_manager_with_metadata"]; + if (!canisterId) { throw new Error( - "CANISTER_ID_PASSWORD_MANAGER_WITH_METADATA is not defined", + "Canister ID for password_manager_with_metadata is not defined", ); } - const host = - process.env.DFX_NETWORK === "ic" - ? `https://${process.env.CANISTER_ID_PASSWORD_MANAGER_WITH_METADATA}.ic0.app` - : "http://localhost:8000"; - const hostOptions = { host }; - - if (!agentOptions) { - agentOptions = hostOptions; - } else { - agentOptions.host = hostOptions.host; - } - - const encryptedMaps = await createEncryptedMaps({ ...agentOptions }); - const canisterClient = createActor( - process.env.CANISTER_ID_PASSWORD_MANAGER_WITH_METADATA, - { agentOptions }, - ); + const agent = await HttpAgent.create({ + ...agentOptions, + host: window.location.origin, + ...(canisterEnv?.IC_ROOT_KEY + ? { rootKey: canisterEnv.IC_ROOT_KEY } + : {}), + }); + + const encryptedMaps = createEncryptedMaps(agent); + const canisterClient = Actor.createActor<_SERVICE>(idlFactory, { + agent, + canisterId, + }); return new PasswordManager(canisterClient, encryptedMaps); } diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/vault.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/vault.ts index 717fc4767..49d302a8d 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/vault.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/vault.ts @@ -1,6 +1,6 @@ -import type { Principal } from "@dfinity/principal"; +import type { Principal } from "@icp-sdk/core/principal"; import type { PasswordModel } from "./password"; -import type { AccessRights } from "@dfinity/vetkeys/encrypted_maps"; +import type { AccessRights } from "@icp-sdk/vetkeys/encrypted_maps"; export interface VaultModel { owner: Principal; diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts index aa05921c4..7c223cf3c 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts @@ -1,7 +1,6 @@ -import "../lib/init.ts"; import { get, writable } from "svelte/store"; -import { AuthClient } from "@dfinity/auth-client"; -import type { JsonnableDelegationChain } from "@dfinity/identity/lib/cjs/identity/delegation"; +import { AuthClient } from "@icp-sdk/auth/client"; +import { DelegationIdentity } from "@icp-sdk/core/identity"; import { replace } from "svelte-spa-router"; import { PasswordManager, @@ -51,9 +50,10 @@ export async function login() { await currentAuth.client.login({ maxTimeToLive: BigInt(1800) * BigInt(1_000_000_000), identityProvider: - process.env.DFX_NETWORK === "ic" - ? "https://identity.ic0.app/#authorize" - : `http://rdmx6-jaaaa-aaaaa-aaadq-cai.localhost:8000/#authorize`, + window.location.hostname === "localhost" || + window.location.hostname.endsWith(".localhost") + ? `http://id.ai.localhost:8000/#authorize` + : "https://identity.ic0.app/#authorize", onSuccess: async () => { await authenticate(currentAuth.client); }, @@ -79,7 +79,7 @@ export async function logout() { } export async function authenticate(client: AuthClient) { - handleSessionTimeout(); + handleSessionTimeout(client); try { const passwordManager = await createPasswordManager({ @@ -100,31 +100,20 @@ export async function authenticate(client: AuthClient) { } // set a timer when the II session will expire and log the user out -function handleSessionTimeout() { - // upon login the localstorage items may not be set, wait for next tick - setTimeout(() => { - try { - const rawDelegation = window.localStorage.getItem("ic-delegation"); - if (!rawDelegation) { - throw new Error("No delegation found"); - } - const delegation = JSON.parse( - rawDelegation, - ) as JsonnableDelegationChain; +function handleSessionTimeout(client: AuthClient) { + try { + const identity = client.getIdentity(); + if (!(identity instanceof DelegationIdentity)) return; - const expirationTimeMs = - Number.parseInt( - delegation.delegations[0].delegation.expiration, - 16, - ) / 1000000; + const chain = identity.getDelegation(); + // expiration is a BigInt of nanoseconds since epoch + const expirationMs = + Number(chain.delegations[0].delegation.expiration) / 1_000_000; - setTimeout(() => { - void logout(); - }, expirationTimeMs - Date.now()); - } catch (e) { - console.error( - "Could not handle delegation expiry: " + (e as Error).message, - ); - } - }); + setTimeout(() => { + void logout(); + }, expirationMs - Date.now()); + } catch { + console.error("Could not handle delegation expiry."); + } } diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts index a236a9559..3d9d94b0f 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts @@ -3,8 +3,8 @@ import { type PasswordModel } from "../lib/password"; import { type VaultModel } from "../lib/vault"; import { auth } from "./auth"; import { showError } from "./notifications"; -import { type AccessRights } from "@dfinity/vetkeys/encrypted_maps"; -import type { Principal } from "@dfinity/principal"; +import { type AccessRights } from "@icp-sdk/vetkeys/encrypted_maps"; +import type { Principal } from "@icp-sdk/core/principal"; import type { PasswordManager } from "../lib/password_manager"; export const vaultsStore = writable< diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/tailwind.config.cjs b/rust/vetkeys/password_manager_with_metadata/frontend/tailwind.config.mjs similarity index 100% rename from rust/vetkeys/password_manager_with_metadata/frontend/tailwind.config.cjs rename to rust/vetkeys/password_manager_with_metadata/frontend/tailwind.config.mjs diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/vite.config.js b/rust/vetkeys/password_manager_with_metadata/frontend/vite.config.js index d8ee7a41a..ca53dc054 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/vite.config.js +++ b/rust/vetkeys/password_manager_with_metadata/frontend/vite.config.js @@ -4,23 +4,53 @@ import eslint from "vite-plugin-eslint"; import tailwindcss from "tailwindcss"; import autoprefixer from "autoprefixer"; import css from "rollup-plugin-css-only"; -import typescript from "@rollup/plugin-typescript"; import viteCompression from "vite-plugin-compression"; -import environment from "vite-plugin-environment"; -import path from "path"; +import { execSync } from "child_process"; + +const environment = process.env.ICP_ENVIRONMENT || "local"; +const CANISTER_NAMES = ["password_manager_with_metadata"]; + +function getDevServerConfig() { + const backend = process.env.BACKEND; + if (!backend) { + throw new Error( + "BACKEND env var is required. Use `npm run dev:motoko` or `npm run dev:rust`.", + ); + } + + const networkStatus = JSON.parse( + execSync( + `icp network status -e ${environment} --json --project-root-override ../${backend}`, + { encoding: "utf-8" }, + ), + ); + const canisterParams = CANISTER_NAMES.map((name) => { + const id = execSync( + `icp canister status ${name} -e ${environment} --id-only --project-root-override ../${backend}`, + { encoding: "utf-8", stdio: "pipe" }, + ).trim(); + return `PUBLIC_CANISTER_ID:${name}=${id}`; + }).join("&"); + return { + headers: { + "Set-Cookie": `ic_env=${encodeURIComponent( + `${canisterParams}&ic_root_key=${networkStatus.root_key}`, + )}; SameSite=Lax;`, + }, + proxy: { + "/api": { target: networkStatus.api_url, changeOrigin: true }, + }, + hmr: false, + }; +} // https://vite.dev/config/ -export default defineConfig({ +export default defineConfig(({ command }) => ({ plugins: [ svelte(), css({ output: "bundle.css" }), eslint(), - typescript({ - inlineSources: true, - }), viteCompression(), - environment("all", { prefix: "CANISTER_" }), - environment("all", { prefix: "DFX_" }), ], css: { postcss: { @@ -32,31 +62,14 @@ export default defineConfig({ output: { inlineDynamicImports: true, }, - sourcemap: true, - }, - build: { - rollupOptions: { - output: { - inlineDynamicImports: true, - }, - sourcemap: true, - }, - }, - root: "./", - server: { - hmr: false, }, + sourcemap: true, }, + root: "./", resolve: { alias: { - ic_vetkeys: path.resolve( - __dirname, - "../../../frontend/ic_vetkeys/src", - ), - "ic_vetkeys/encrypted_maps": path.resolve( - __dirname, - "../../../frontend/ic_vetkeys/src/encrypted_maps", - ), + "@dfinity/vetkeys": "@icp-sdk/vetkeys", }, }, -}); + ...(command === "serve" ? { server: getDevServerConfig() } : {}), +})); diff --git a/rust/vetkeys/password_manager_with_metadata/motoko/dfx.json b/rust/vetkeys/password_manager_with_metadata/motoko/dfx.json deleted file mode 100644 index 1cf398f72..000000000 --- a/rust/vetkeys/password_manager_with_metadata/motoko/dfx.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "canisters": { - "password_manager_with_metadata": { - "main": "backend/src/Main.mo", - "args": "--enhanced-orthogonal-persistence", - "type": "motoko", - "init_arg": "(\"test_key_1\")", - "metadata": [ - { - "name": "candid:service", - "visibility": "public" - } - ] - }, - "internet-identity": { - "candid": "https://github.com/dfinity/internet-identity/releases/download/release-2026-03-16/internet_identity.did", - "type": "custom", - "specified_id": "rdmx6-jaaaa-aaaaa-aaadq-cai", - "remote": { - "id": { - "ic": "rdmx6-jaaaa-aaaaa-aaadq-cai" - } - }, - "wasm": "https://github.com/dfinity/internet-identity/releases/download/release-2026-03-16/internet_identity_dev.wasm.gz" - }, - "www": { - "dependencies": ["password_manager_with_metadata", "internet-identity"], - "build": ["cd frontend && npm i --include=dev && npm run build && cd - && rm -r dist > /dev/null 2>&1; mv frontend/dist ./"], - "frontend": { - "entrypoint": "dist/index.html" - }, - "source": ["dist/"], - "type": "assets", - "output_env_file": "frontend/.env" - } - }, - "defaults": { - "build": { - "packtool": "npx ic-mops sources", - "args": "" - } - }, - "networks": { - "local": { - "bind": "127.0.0.1:8000", - "type": "ephemeral" - } - } - } - \ No newline at end of file diff --git a/rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml b/rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml new file mode 100644 index 000000000..c19cfbed6 --- /dev/null +++ b/rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml @@ -0,0 +1,22 @@ +canisters: + - name: password_manager_with_metadata + recipe: + type: "@dfinity/motoko@v4.1.0" + configuration: + main: backend/src/Main.mo + init_args: + type: text + value: "(\"test_key_1\")" + + - name: www + recipe: + type: "@dfinity/asset-canister@v2.1.0" + configuration: + dir: dist + build: + - cd frontend && npm i --include=dev && npm run build && cd - && rm -rf dist; mv frontend/dist ./ + +networks: + - name: local + mode: managed + ii: true diff --git a/rust/vetkeys/password_manager_with_metadata/motoko/mops.toml b/rust/vetkeys/password_manager_with_metadata/motoko/mops.toml index f8b191454..e3638d183 100644 --- a/rust/vetkeys/password_manager_with_metadata/motoko/mops.toml +++ b/rust/vetkeys/password_manager_with_metadata/motoko/mops.toml @@ -1,3 +1,6 @@ +[toolchain] +moc = "1.5.1" + [dependencies] base = "0.14.9" -ic-vetkeys = "0.4.0" \ No newline at end of file +ic-vetkeys = "0.5.0" \ No newline at end of file diff --git a/rust/vetkeys/password_manager_with_metadata/rust/backend/Cargo.toml b/rust/vetkeys/password_manager_with_metadata/rust/backend/Cargo.toml index 097e3b72d..3a8b79aac 100644 --- a/rust/vetkeys/password_manager_with_metadata/rust/backend/Cargo.toml +++ b/rust/vetkeys/password_manager_with_metadata/rust/backend/Cargo.toml @@ -17,6 +17,6 @@ candid = "0.10.2" ic-cdk = "0.18.3" ic-dummy-getrandom-for-wasm = "0.1.0" ic-stable-structures = "0.6.8" -ic-vetkeys = "0.2.0" +ic-vetkeys = "0.7.0" serde = "1.0.217" serde_cbor = "0.11.2" diff --git a/rust/vetkeys/password_manager_with_metadata/rust/backend/Makefile b/rust/vetkeys/password_manager_with_metadata/rust/backend/Makefile index c6860e235..fadd94355 100644 --- a/rust/vetkeys/password_manager_with_metadata/rust/backend/Makefile +++ b/rust/vetkeys/password_manager_with_metadata/rust/backend/Makefile @@ -12,4 +12,4 @@ extract-candid: compile-wasm .SILENT: clean clean: cargo clean - rm -rf ../.dfx \ No newline at end of file + rm -rf ../.icp \ No newline at end of file diff --git a/rust/vetkeys/password_manager_with_metadata/rust/backend/backend.did b/rust/vetkeys/password_manager_with_metadata/rust/backend/backend.did index 459b6c585..1b4e5f994 100644 --- a/rust/vetkeys/password_manager_with_metadata/rust/backend/backend.did +++ b/rust/vetkeys/password_manager_with_metadata/rust/backend/backend.did @@ -1,4 +1,15 @@ -type AccessRights = variant { Read; ReadWrite; ReadWriteManage }; +// Access rights of a user to a vetKey in [`crate::key_manager::KeyManager`] and/or an encrypted map in [`crate::encrypted_maps::EncryptedMaps`]. +type AccessRights = variant { + // User can retrieve the vetKey or encrypted map. + Read; + // User can update values in the encrypted map. + ReadWrite; + // User can view/share/revoke access to the vetKey or encrypted map. + ReadWriteManage; +}; +// Efficiently serializable and deserializable byte vector that is `Storable` with `ic_stable_structures`. +// See, e.g., [https://mmapped.blog/posts/01-effective-rust-canisters#serde-bytes](https://mmapped.blog/posts/01-effective-rust-canisters#serde-bytes) for more details regarding why `Vec` does not work out of the box. +// Also, we cannot use `serde_bytes::ByteBuf` directly because it is not `Storable`. type ByteBuf = record { inner : blob }; type PasswordMetadata = record { url : text; diff --git a/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs b/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs index eec198ea0..ac9f4218d 100644 --- a/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs +++ b/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs @@ -48,6 +48,10 @@ impl PasswordMetadata { } impl Storable for PasswordMetadata { + fn into_bytes(self) -> Vec { + self.to_bytes().into_owned() + } + fn to_bytes(&self) -> Cow<[u8]> { Cow::Owned(serde_cbor::to_vec(self).expect("failed to serialize")) } @@ -143,8 +147,11 @@ fn get_encrypted_values_for_map_with_metadata( METADATA.with_borrow(|metadata| { let iter_metadata = metadata .range((map_owner, map_name, Blob::default())..) - .take_while(|((owner, name, _), _)| owner == &map_owner && name == &map_name) - .map(|((_, _, key), metadata)| (key, metadata)); + .take_while(|entry| { + let (owner, name, _) = entry.key(); + owner == &map_owner && name == &map_name + }) + .map(|entry| (entry.key().2, entry.value())); iter_metadata .zip(map_values) diff --git a/rust/vetkeys/password_manager_with_metadata/rust/dfx.json b/rust/vetkeys/password_manager_with_metadata/rust/dfx.json deleted file mode 100644 index b361f8c27..000000000 --- a/rust/vetkeys/password_manager_with_metadata/rust/dfx.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "canisters": { - "password_manager_with_metadata": { - "candid": "backend/backend.did", - "package": "ic-vetkd-example-password-manager-with-metadata-backend", - "type": "rust", - "init_arg": "(\"test_key_1\")", - "metadata": [ - { - "name": "candid:service", - "visibility": "public" - } - ] - }, - "internet-identity": { - "candid": "https://github.com/dfinity/internet-identity/releases/download/release-2026-03-16/internet_identity.did", - "type": "custom", - "specified_id": "rdmx6-jaaaa-aaaaa-aaadq-cai", - "remote": { - "id": { - "ic": "rdmx6-jaaaa-aaaaa-aaadq-cai" - } - }, - "wasm": "https://github.com/dfinity/internet-identity/releases/download/release-2026-03-16/internet_identity_dev.wasm.gz" - }, - "www": { - "dependencies": ["password_manager_with_metadata", "internet-identity"], - "build": ["cd frontend && npm i --include=dev && npm run build && cd - && rm -r dist > /dev/null 2>&1; mv frontend/dist ./"], - "frontend": { - "entrypoint": "dist/index.html" - }, - "source": ["dist/"], - "type": "assets", - "output_env_file": "frontend/.env" - } - }, - "networks": { - "local": { - "bind": "127.0.0.1:8000", - "type": "ephemeral" - } - } -} diff --git a/rust/vetkeys/password_manager_with_metadata/rust/icp.yaml b/rust/vetkeys/password_manager_with_metadata/rust/icp.yaml new file mode 100644 index 000000000..9d753e1a4 --- /dev/null +++ b/rust/vetkeys/password_manager_with_metadata/rust/icp.yaml @@ -0,0 +1,23 @@ +canisters: + - name: password_manager_with_metadata + recipe: + type: "@dfinity/rust@v3.2.0" + configuration: + package: ic-vetkd-example-password-manager-with-metadata-backend + candid: backend/backend.did + init_args: + type: text + value: "(\"test_key_1\")" + + - name: www + recipe: + type: "@dfinity/asset-canister@v2.1.0" + configuration: + dir: dist + build: + - cd frontend && npm i --include=dev && npm run build && cd - && rm -rf dist; mv frontend/dist ./ + +networks: + - name: local + mode: managed + ii: true From 48ef4efe9324afeac1414baec0b4f235e9e03cd6 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Sat, 30 May 2026 04:13:26 +0200 Subject: [PATCH 02/31] fix(ci): update password_manager_with_metadata to ic-cdk 0.20.1 and @icp-sdk/auth@7.1.0 - Remove rust-toolchain channel pin and profile - Bump ic-cdk to 0.20.1 and add ic-cdk-management-canister 0.1.1 - Update ic_cdk::management_canister imports to ic_cdk_management_canister - Update AuthClient: constructor, signIn/signOut, async getIdentity Co-Authored-By: Claude Sonnet 4.6 --- .../frontend/src/store/auth.ts | 42 +++++++++---------- .../rust/backend/Cargo.toml | 3 +- .../rust/backend/src/lib.rs | 2 +- .../rust/rust-toolchain.toml | 4 +- 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts index 7c223cf3c..212474cc1 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts @@ -30,8 +30,11 @@ export const auth = writable({ }); async function initAuth() { - const client = await AuthClient.create(); - if (await client.isAuthenticated()) { + const isLocalEnv = window.location.hostname === "localhost" || window.location.hostname.endsWith(".localhost"); + const client = new AuthClient({ + identityProvider: isLocalEnv ? "http://id.ai.localhost:8000/#authorize" : undefined, + }); + if (client.isAuthenticated()) { await authenticate(client); } else { auth.update(() => ({ @@ -47,21 +50,16 @@ export async function login() { const currentAuth = get(auth); if (currentAuth.state === "anonymous") { - await currentAuth.client.login({ - maxTimeToLive: BigInt(1800) * BigInt(1_000_000_000), - identityProvider: - window.location.hostname === "localhost" || - window.location.hostname.endsWith(".localhost") - ? `http://id.ai.localhost:8000/#authorize` - : "https://identity.ic0.app/#authorize", - onSuccess: async () => { - await authenticate(currentAuth.client); - }, - onError: (e) => - console.error( - "Failed to authenticate with internet identity: " + e, - ), - }); + void (async () => { + try { + await currentAuth.client.signIn({ + maxTimeToLive: BigInt(1800) * BigInt(1_000_000_000), + }); + void authenticate(currentAuth.client); + } catch (error: unknown) { + console.error("Login failed:", error); + } + })(); } } @@ -69,7 +67,7 @@ export async function logout() { const currentAuth = get(auth); if (currentAuth.state === "initialized") { - await currentAuth.client.logout(); + await currentAuth.client.signOut(); auth.update(() => ({ state: "anonymous", client: currentAuth.client, @@ -79,11 +77,11 @@ export async function logout() { } export async function authenticate(client: AuthClient) { - handleSessionTimeout(client); + void handleSessionTimeout(client); try { const passwordManager = await createPasswordManager({ - identity: client.getIdentity(), + identity: await client.getIdentity(), }); auth.update(() => ({ @@ -100,9 +98,9 @@ export async function authenticate(client: AuthClient) { } // set a timer when the II session will expire and log the user out -function handleSessionTimeout(client: AuthClient) { +async function handleSessionTimeout(client: AuthClient) { try { - const identity = client.getIdentity(); + const identity = await client.getIdentity(); if (!(identity instanceof DelegationIdentity)) return; const chain = identity.getDelegation(); diff --git a/rust/vetkeys/password_manager_with_metadata/rust/backend/Cargo.toml b/rust/vetkeys/password_manager_with_metadata/rust/backend/Cargo.toml index 3a8b79aac..5be9307ad 100644 --- a/rust/vetkeys/password_manager_with_metadata/rust/backend/Cargo.toml +++ b/rust/vetkeys/password_manager_with_metadata/rust/backend/Cargo.toml @@ -14,7 +14,8 @@ crate-type = ["cdylib"] [dependencies] candid = "0.10.2" -ic-cdk = "0.18.3" +ic-cdk = "0.20.1" +ic-cdk-management-canister = "0.1.1" ic-dummy-getrandom-for-wasm = "0.1.0" ic-stable-structures = "0.6.8" ic-vetkeys = "0.7.0" diff --git a/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs b/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs index ac9f4218d..a846367d9 100644 --- a/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs +++ b/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs @@ -1,5 +1,5 @@ use candid::{CandidType, Principal}; -use ic_cdk::management_canister::{VetKDCurve, VetKDKeyId}; +use ic_cdk_management_canister::{VetKDCurve, VetKDKeyId}; use ic_cdk::{init, query, update}; use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory}; use ic_stable_structures::storable::Blob; diff --git a/rust/vetkeys/password_manager_with_metadata/rust/rust-toolchain.toml b/rust/vetkeys/password_manager_with_metadata/rust/rust-toolchain.toml index 2a2058b04..6e5beca4f 100644 --- a/rust/vetkeys/password_manager_with_metadata/rust/rust-toolchain.toml +++ b/rust/vetkeys/password_manager_with_metadata/rust/rust-toolchain.toml @@ -1,4 +1,2 @@ [toolchain] -channel = "1.88.0" -targets = ["wasm32-unknown-unknown"] -profile = "default" \ No newline at end of file +targets = ["wasm32-unknown-unknown"] \ No newline at end of file From caf7272d51249870b34e2dea95af34c041259f65 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 1 Jun 2026 10:39:39 +0200 Subject: [PATCH 03/31] fix(password_manager_with_metadata): eslint/prettier and ic-stable-structures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Format isLocalEnv assignment and identityProvider ternary to fit printWidth 80 (required by vite-plugin-eslint + prettier-recommended) - Remove async from login() — no direct await in outer function - Bump ic-stable-structures to 0.7.2 to match ic-vetkeys 0.7.0 dep Co-Authored-By: Claude Sonnet 4.6 --- .../frontend/src/store/auth.ts | 10 +++++++--- .../rust/backend/Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts index 212474cc1..f169873f0 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts @@ -30,9 +30,13 @@ export const auth = writable({ }); async function initAuth() { - const isLocalEnv = window.location.hostname === "localhost" || window.location.hostname.endsWith(".localhost"); + const isLocalEnv = + window.location.hostname === "localhost" || + window.location.hostname.endsWith(".localhost"); const client = new AuthClient({ - identityProvider: isLocalEnv ? "http://id.ai.localhost:8000/#authorize" : undefined, + identityProvider: isLocalEnv + ? "http://id.ai.localhost:8000/#authorize" + : undefined, }); if (client.isAuthenticated()) { await authenticate(client); @@ -46,7 +50,7 @@ async function initAuth() { void initAuth(); -export async function login() { +export function login() { const currentAuth = get(auth); if (currentAuth.state === "anonymous") { diff --git a/rust/vetkeys/password_manager_with_metadata/rust/backend/Cargo.toml b/rust/vetkeys/password_manager_with_metadata/rust/backend/Cargo.toml index 5be9307ad..964961938 100644 --- a/rust/vetkeys/password_manager_with_metadata/rust/backend/Cargo.toml +++ b/rust/vetkeys/password_manager_with_metadata/rust/backend/Cargo.toml @@ -17,7 +17,7 @@ candid = "0.10.2" ic-cdk = "0.20.1" ic-cdk-management-canister = "0.1.1" ic-dummy-getrandom-for-wasm = "0.1.0" -ic-stable-structures = "0.6.8" +ic-stable-structures = "0.7.2" ic-vetkeys = "0.7.0" serde = "1.0.217" serde_cbor = "0.11.2" From b71a5ce50be81ff26dec9037b657d6e21574a14f Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 1 Jun 2026 10:51:36 +0200 Subject: [PATCH 04/31] fix(password_manager_with_metadata): await getIdentity() in vaults.ts getIdentity() is now async and returns Promise. Calling .getPrincipal() directly on the Promise triggers no-unsafe-call. Add await for the inline call and use .then() for the setInterval callback where async/await isn't directly usable. Co-Authored-By: Claude Sonnet 4.6 --- .../frontend/src/store/vaults.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts index 3d9d94b0f..ee81f6e7f 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts @@ -120,17 +120,22 @@ auth.subscribe((auth) => { void (async () => { try { await refreshVaults( - auth.client.getIdentity().getPrincipal(), + (await auth.client.getIdentity()).getPrincipal(), auth.passwordManager, ).catch((e) => showError(e as Error, "Could not poll vaults.")); vaultPollerHandle = setInterval(() => { - void refreshVaults( - auth.client.getIdentity().getPrincipal(), - auth.passwordManager, - ).catch((e) => - showError(e as Error, "Could not poll vaults."), - ); + void auth.client + .getIdentity() + .then((identity) => + refreshVaults( + identity.getPrincipal(), + auth.passwordManager, + ), + ) + .catch((e) => + showError(e as Error, "Could not poll vaults."), + ); }, 3000); } catch { vaultsStore.set({ From 37ac31888b984ac0edb9653fe4e4c35fbdf30f8e Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 1 Jun 2026 11:08:07 +0200 Subject: [PATCH 05/31] fix(password_manager_with_metadata): await getIdentity in Svelte components Reactive statements ($:) are synchronous so cannot use await directly. Use void .then() to resolve the Identity before calling getPrincipal(). Co-Authored-By: Claude Sonnet 4.6 --- .../frontend/src/components/EditVault.svelte | 14 +++++++----- .../frontend/src/components/Vault.svelte | 22 +++++++++++-------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte index 53d164f76..cd8928058 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte @@ -41,11 +41,15 @@ if (vault) { editedVault = { ...vault }; - const me = $auth.client.getIdentity().getPrincipal(); - canManage = - vault.owner.compareTo(me) === "eq" || - "ReadWriteManage" in - vault.users.find((u) => u[0].compareTo(me) === "eq"); + void $auth.client.getIdentity().then((identity) => { + const me = identity.getPrincipal(); + canManage = + vault.owner.compareTo(me) === "eq" || + "ReadWriteManage" in + vault.users.find( + (u) => u[0].compareTo(me) === "eq", + ); + }); } } } diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/Vault.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/Vault.svelte index 598b765a1..24451dfac 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/Vault.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/Vault.svelte @@ -59,15 +59,19 @@ } else { vault = searchedForVault; vaultSummary = summarize(vault); - const me = $auth.client.getIdentity().getPrincipal(); - if (vault.owner.compareTo(me) === "eq") { - accessRights = { ReadWriteManage: null }; - } else { - const foundRights = vault.users.find( - (user) => user[0].compareTo(me) === "eq", - ); - accessRights = foundRights ? foundRights[1] : { Read: null }; - } + void $auth.client.getIdentity().then((identity) => { + const me = identity.getPrincipal(); + if (vault.owner.compareTo(me) === "eq") { + accessRights = { ReadWriteManage: null }; + } else { + const foundRights = vault.users.find( + (user) => user[0].compareTo(me) === "eq", + ); + accessRights = foundRights + ? foundRights[1] + : { Read: null }; + } + }); } } From da5c19428e23adbfc119ca9ae19af03621f22c10 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 1 Jun 2026 11:35:22 +0200 Subject: [PATCH 06/31] fix(password_manager_with_metadata): await getIdentity in all components getIdentity() is async. Fixed all remaining call sites: - EditPassword.svelte: async functions use await; reactive block uses .then() - SharingEditor.svelte: async functions use await (replace_all) - NewPassword.svelte: reactive initializer + async function use await - SidebarLayout.svelte: template uses principalText reactive variable Co-Authored-By: Claude Sonnet 4.6 --- .../src/components/EditPassword.svelte | 32 +++++++++++-------- .../src/components/NewPassword.svelte | 14 +++++--- .../src/components/SharingEditor.svelte | 4 +-- .../src/components/SidebarLayout.svelte | 13 ++++++-- 4 files changed, 39 insertions(+), 24 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte index 60b39d8e1..81520766d 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte @@ -71,7 +71,7 @@ v.owner.compareTo(parentVaultOwnerPrincipal) === "eq" && v.name === parentVaultName, ); - const me = $auth.client.getIdentity().getPrincipal(); + const me = (await $auth.client.getIdentity()).getPrincipal(); if ( parentVaultOwnerPrincipal.compareTo(me) !== "eq" && (!vault || @@ -152,7 +152,7 @@ }); await refreshVaults( - $auth.client.getIdentity().getPrincipal(), + (await $auth.client.getIdentity()).getPrincipal(), $auth.passwordManager, ).catch((e) => showError(e as Error, "Could not refresh passwords.")); @@ -185,7 +185,7 @@ }); await refreshVaults( - $auth.client.getIdentity().getPrincipal(), + (await $auth.client.getIdentity()).getPrincipal(), $auth.passwordManager, ) .catch((e) => showError(e as Error, "Could not refresh passwords.")) @@ -228,18 +228,22 @@ tagsInput = tags.join(", "); } - const myPrincipal = $auth.client.getIdentity().getPrincipal(); - - if (parentVaultOwnerPrincipal.compareTo(myPrincipal) === "eq") { - accessRights = { ReadWriteManage: null }; - } else { - let foundAccessRights = targetVault.users.find( - (u) => u[0].compareTo(myPrincipal) === "eq", - ); - if (foundAccessRights) { - accessRights = foundAccessRights[1]; + void $auth.client.getIdentity().then((identity) => { + const myPrincipal = identity.getPrincipal(); + if ( + parentVaultOwnerPrincipal.compareTo(myPrincipal) === + "eq" + ) { + accessRights = { ReadWriteManage: null }; + } else { + const foundAccessRights = targetVault.users.find( + (u) => u[0].compareTo(myPrincipal) === "eq", + ); + if (foundAccessRights) { + accessRights = foundAccessRights[1]; + } } - } + }); editor = new Editor({ modules: { placeholder: placeholder("Start typing..."), diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte index d288e2a8f..acaad7751 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte @@ -10,10 +10,14 @@ import { Principal } from "@icp-sdk/core/principal"; let creating = false; - let vaultOwner = - $auth.state === "initialized" - ? $auth.client.getIdentity().getPrincipal().toText() - : Principal.anonymous().toText(); + let vaultOwner = Principal.anonymous().toText(); + $: if ($auth.state === "initialized") { + void $auth.client + .getIdentity() + .then((identity) => { + vaultOwner = identity.getPrincipal().toText(); + }); + } let vaultName = ""; let passwordName = ""; let url: string = ""; @@ -81,7 +85,7 @@ // refresh passwords in the background refreshVaults( - $auth.client.getIdentity().getPrincipal(), + (await $auth.client.getIdentity()).getPrincipal(), $auth.passwordManager, ).catch((e: Error) => showError(e, "Could not refresh passwords.")); } diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte index bb4837bbf..25af700f3 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte @@ -63,7 +63,7 @@ adding = false; } await refreshVaults( - $auth.client.getIdentity().getPrincipal(), + (await $auth.client.getIdentity()).getPrincipal(), $auth.passwordManager, ).catch((e: Error) => showError(e, "Could not refresh vaults.")); } @@ -93,7 +93,7 @@ removing = false; } await refreshVaults( - $auth.client.getIdentity().getPrincipal(), + (await $auth.client.getIdentity()).getPrincipal(), $auth.passwordManager, ).catch((e: Error) => showError(e, "Could not refresh vaults.")); } diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte index 4b6df0710..ae585203f 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte @@ -9,6 +9,15 @@ import Disclaimer from "./Disclaimer.svelte"; import { Principal } from "@icp-sdk/core/principal"; import { link } from "svelte-spa-router"; + + let principalText = Principal.anonymous().toText(); + $: if ($auth.state === "initialized") { + void $auth.client + .getIdentity() + .then((identity) => { + principalText = identity.getPrincipal().toText(); + }); + }
@@ -33,9 +42,7 @@
My Principal:
- {$auth.state === "initialized" - ? $auth.client.getIdentity().getPrincipal().toText() - : Principal.anonymous().toText()} + {principalText}
    Date: Mon, 1 Jun 2026 11:51:20 +0200 Subject: [PATCH 07/31] fix(password_manager_with_metadata): add principal to auth state; rename CI jobs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add principal: Principal to AuthState "initialized" so all components can access it synchronously — eliminating async getIdentity().then() patterns inside Svelte reactive blocks that triggered infinite-reactive-loop. Also rename CI job keys: remove rust-vetkeys- prefix so Motoko jobs no longer misleadingly start with "rust". Co-Authored-By: Claude Sonnet 4.6 --- ...password-manager-with-metadata-example.yml | 10 +++--- .../src/components/EditPassword.svelte | 33 +++++++++---------- .../frontend/src/components/EditVault.svelte | 14 +++----- .../src/components/NewPassword.svelte | 14 +++----- .../src/components/SharingEditor.svelte | 4 +-- .../src/components/SidebarLayout.svelte | 13 ++------ .../frontend/src/components/Vault.svelte | 22 +++++-------- .../frontend/src/store/auth.ts | 8 +++-- .../frontend/src/store/vaults.ts | 19 ++++------- 9 files changed, 56 insertions(+), 81 deletions(-) diff --git a/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml b/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml index fb813c4ad..524f0f67a 100644 --- a/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml +++ b/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml @@ -8,13 +8,13 @@ on: - rust/vetkeys/password_manager_with_metadata/** - .github/workflows/provision-darwin.sh - .github/workflows/provision-linux.sh - - .github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml + - .github/workflows/password-manager-with-metadata-example.yml - .ic-commit concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: - rust-vetkeys-password-manager-with-metadata-rust-darwin: + password-manager-with-metadata-rust-darwin: runs-on: macos-15 steps: - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 @@ -30,7 +30,7 @@ jobs: icp network start -d icp deploy popd - rust-vetkeys-password-manager-with-metadata-rust-linux: + password-manager-with-metadata-rust-linux: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 @@ -46,7 +46,7 @@ jobs: icp network start -d icp deploy popd - rust-vetkeys-password-manager-with-metadata-motoko-darwin: + password-manager-with-metadata-motoko-darwin: runs-on: macos-15 steps: - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 @@ -62,7 +62,7 @@ jobs: icp network start -d icp deploy popd - rust-vetkeys-password-manager-with-metadata-motoko-linux: + password-manager-with-metadata-motoko-linux: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte index 81520766d..e79948f44 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte @@ -71,7 +71,7 @@ v.owner.compareTo(parentVaultOwnerPrincipal) === "eq" && v.name === parentVaultName, ); - const me = (await $auth.client.getIdentity()).getPrincipal(); + const me = $auth.principal; if ( parentVaultOwnerPrincipal.compareTo(me) !== "eq" && (!vault || @@ -152,7 +152,7 @@ }); await refreshVaults( - (await $auth.client.getIdentity()).getPrincipal(), + $auth.principal, $auth.passwordManager, ).catch((e) => showError(e as Error, "Could not refresh passwords.")); @@ -185,7 +185,7 @@ }); await refreshVaults( - (await $auth.client.getIdentity()).getPrincipal(), + $auth.principal, $auth.passwordManager, ) .catch((e) => showError(e as Error, "Could not refresh passwords.")) @@ -228,22 +228,19 @@ tagsInput = tags.join(", "); } - void $auth.client.getIdentity().then((identity) => { - const myPrincipal = identity.getPrincipal(); - if ( - parentVaultOwnerPrincipal.compareTo(myPrincipal) === - "eq" - ) { - accessRights = { ReadWriteManage: null }; - } else { - const foundAccessRights = targetVault.users.find( - (u) => u[0].compareTo(myPrincipal) === "eq", - ); - if (foundAccessRights) { - accessRights = foundAccessRights[1]; - } + const myPrincipal = $auth.principal; + if ( + parentVaultOwnerPrincipal.compareTo(myPrincipal) === "eq" + ) { + accessRights = { ReadWriteManage: null }; + } else { + const foundAccessRights = targetVault.users.find( + (u) => u[0].compareTo(myPrincipal) === "eq", + ); + if (foundAccessRights) { + accessRights = foundAccessRights[1]; } - }); + } editor = new Editor({ modules: { placeholder: placeholder("Start typing..."), diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte index cd8928058..c671bb0fe 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte @@ -41,15 +41,11 @@ if (vault) { editedVault = { ...vault }; - void $auth.client.getIdentity().then((identity) => { - const me = identity.getPrincipal(); - canManage = - vault.owner.compareTo(me) === "eq" || - "ReadWriteManage" in - vault.users.find( - (u) => u[0].compareTo(me) === "eq", - ); - }); + const me = $auth.principal; + canManage = + vault.owner.compareTo(me) === "eq" || + "ReadWriteManage" in + vault.users.find((u) => u[0].compareTo(me) === "eq"); } } } diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte index acaad7751..85d4047e1 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte @@ -10,14 +10,10 @@ import { Principal } from "@icp-sdk/core/principal"; let creating = false; - let vaultOwner = Principal.anonymous().toText(); - $: if ($auth.state === "initialized") { - void $auth.client - .getIdentity() - .then((identity) => { - vaultOwner = identity.getPrincipal().toText(); - }); - } + $: vaultOwner = + $auth.state === "initialized" + ? $auth.principal.toText() + : Principal.anonymous().toText(); let vaultName = ""; let passwordName = ""; let url: string = ""; @@ -85,7 +81,7 @@ // refresh passwords in the background refreshVaults( - (await $auth.client.getIdentity()).getPrincipal(), + $auth.principal, $auth.passwordManager, ).catch((e: Error) => showError(e, "Could not refresh passwords.")); } diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte index 25af700f3..cd8d81bf3 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte @@ -63,7 +63,7 @@ adding = false; } await refreshVaults( - (await $auth.client.getIdentity()).getPrincipal(), + $auth.principal, $auth.passwordManager, ).catch((e: Error) => showError(e, "Could not refresh vaults.")); } @@ -93,7 +93,7 @@ removing = false; } await refreshVaults( - (await $auth.client.getIdentity()).getPrincipal(), + $auth.principal, $auth.passwordManager, ).catch((e: Error) => showError(e, "Could not refresh vaults.")); } diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte index ae585203f..94a1e1fe5 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte @@ -9,15 +9,6 @@ import Disclaimer from "./Disclaimer.svelte"; import { Principal } from "@icp-sdk/core/principal"; import { link } from "svelte-spa-router"; - - let principalText = Principal.anonymous().toText(); - $: if ($auth.state === "initialized") { - void $auth.client - .getIdentity() - .then((identity) => { - principalText = identity.getPrincipal().toText(); - }); - }
    @@ -42,7 +33,9 @@
    My Principal:
    - {principalText} + {$auth.state === "initialized" + ? $auth.principal.toText() + : Principal.anonymous().toText()}
      { - const me = identity.getPrincipal(); - if (vault.owner.compareTo(me) === "eq") { - accessRights = { ReadWriteManage: null }; - } else { - const foundRights = vault.users.find( - (user) => user[0].compareTo(me) === "eq", - ); - accessRights = foundRights - ? foundRights[1] - : { Read: null }; - } - }); + const me = $auth.principal; + if (vault.owner.compareTo(me) === "eq") { + accessRights = { ReadWriteManage: null }; + } else { + const foundRights = vault.users.find( + (user) => user[0].compareTo(me) === "eq", + ); + accessRights = foundRights ? foundRights[1] : { Read: null }; + } } } diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts index f169873f0..a2a042a09 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts @@ -1,6 +1,7 @@ import { get, writable } from "svelte/store"; import { AuthClient } from "@icp-sdk/auth/client"; import { DelegationIdentity } from "@icp-sdk/core/identity"; +import type { Principal } from "@icp-sdk/core/principal"; import { replace } from "svelte-spa-router"; import { PasswordManager, @@ -19,6 +20,7 @@ export type AuthState = state: "initialized"; passwordManager: PasswordManager; client: AuthClient; + principal: Principal; } | { state: "error"; @@ -84,14 +86,14 @@ export async function authenticate(client: AuthClient) { void handleSessionTimeout(client); try { - const passwordManager = await createPasswordManager({ - identity: await client.getIdentity(), - }); + const identity = await client.getIdentity(); + const passwordManager = await createPasswordManager({ identity }); auth.update(() => ({ state: "initialized", passwordManager, client, + principal: identity.getPrincipal(), })); } catch (e) { auth.update(() => ({ diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts index ee81f6e7f..cdce44db7 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts @@ -120,22 +120,17 @@ auth.subscribe((auth) => { void (async () => { try { await refreshVaults( - (await auth.client.getIdentity()).getPrincipal(), + auth.principal, auth.passwordManager, ).catch((e) => showError(e as Error, "Could not poll vaults.")); vaultPollerHandle = setInterval(() => { - void auth.client - .getIdentity() - .then((identity) => - refreshVaults( - identity.getPrincipal(), - auth.passwordManager, - ), - ) - .catch((e) => - showError(e as Error, "Could not poll vaults."), - ); + void refreshVaults( + auth.principal, + auth.passwordManager, + ).catch((e) => + showError(e as Error, "Could not poll vaults."), + ); }, 3000); } catch { vaultsStore.set({ From 5ef5027926de9902614d46361e6779a0149b3260 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 1 Jun 2026 11:56:26 +0200 Subject: [PATCH 08/31] chore(password_manager_with_metadata): rename CI jobs to language-first format Job keys now follow {language}-{example}-{platform} convention: - rust-password-manager-with-metadata-{darwin,linux} - motoko-password-manager-with-metadata-{darwin,linux} Co-Authored-By: Claude Sonnet 4.6 --- ...-password-manager-with-metadata-example.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml b/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml index 524f0f67a..f7e12e972 100644 --- a/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml +++ b/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml @@ -8,13 +8,13 @@ on: - rust/vetkeys/password_manager_with_metadata/** - .github/workflows/provision-darwin.sh - .github/workflows/provision-linux.sh - - .github/workflows/password-manager-with-metadata-example.yml + - .github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml - .ic-commit concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: - password-manager-with-metadata-rust-darwin: + rust-password-manager-with-metadata-darwin: runs-on: macos-15 steps: - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 @@ -24,13 +24,13 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: bash .github/workflows/pre-download-launcher.sh - - name: Rust vetkeys password-manager-with-metadata Rust Darwin + - name: Password Manager with Metadata Rust Darwin run: | pushd rust/vetkeys/password_manager_with_metadata/rust icp network start -d icp deploy popd - password-manager-with-metadata-rust-linux: + rust-password-manager-with-metadata-linux: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 @@ -40,13 +40,13 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: bash .github/workflows/pre-download-launcher.sh - - name: Rust vetkeys password-manager-with-metadata Rust Linux + - name: Password Manager with Metadata Rust Linux run: | pushd rust/vetkeys/password_manager_with_metadata/rust icp network start -d icp deploy popd - password-manager-with-metadata-motoko-darwin: + motoko-password-manager-with-metadata-darwin: runs-on: macos-15 steps: - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 @@ -56,13 +56,13 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: bash .github/workflows/pre-download-launcher.sh - - name: Rust vetkeys password-manager-with-metadata Motoko Darwin + - name: Password Manager with Metadata Motoko Darwin run: | pushd rust/vetkeys/password_manager_with_metadata/motoko icp network start -d icp deploy popd - password-manager-with-metadata-motoko-linux: + motoko-password-manager-with-metadata-linux: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 @@ -72,7 +72,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: bash .github/workflows/pre-download-launcher.sh - - name: Rust vetkeys password-manager-with-metadata Motoko Linux + - name: Password Manager with Metadata Motoko Linux run: | pushd rust/vetkeys/password_manager_with_metadata/motoko icp network start -d From d1bf7a12067667dec63c694b96e967e79504e00b Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 1 Jun 2026 12:18:08 +0200 Subject: [PATCH 09/31] fix(password_manager_with_metadata): format vaults.ts for prettier printWidth 80 refreshVaults(auth.principal, auth.passwordManager) fits on one line; move .catch callback to the next line per prettier's formatting rules. Co-Authored-By: Claude Sonnet 4.6 --- .../frontend/src/store/vaults.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts index cdce44db7..f56f5dcb8 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts @@ -119,10 +119,9 @@ auth.subscribe((auth) => { void (async () => { try { - await refreshVaults( - auth.principal, - auth.passwordManager, - ).catch((e) => showError(e as Error, "Could not poll vaults.")); + await refreshVaults(auth.principal, auth.passwordManager).catch( + (e) => showError(e as Error, "Could not poll vaults."), + ); vaultPollerHandle = setInterval(() => { void refreshVaults( From f470fb9136ec96d82b4b87047df5883956ee5895 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 1 Jun 2026 12:38:20 +0200 Subject: [PATCH 10/31] fix(password_manager_with_metadata): format refreshVaults calls for printWidth 80 refreshVaults(principal, passwordManager) fits on one line (70 chars) so prettier collapses it. Move .catch callback to the next line. Applied to EditPassword, SharingEditor, and NewPassword components. Co-Authored-By: Claude Sonnet 4.6 --- .../frontend/src/components/EditPassword.svelte | 16 +++++----------- .../frontend/src/components/NewPassword.svelte | 7 +++---- .../frontend/src/components/SharingEditor.svelte | 14 ++++++-------- 3 files changed, 14 insertions(+), 23 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte index e79948f44..0f5b9d586 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte @@ -151,10 +151,9 @@ message: "Password saved successfully", }); - await refreshVaults( - $auth.principal, - $auth.passwordManager, - ).catch((e) => showError(e as Error, "Could not refresh passwords.")); + await refreshVaults($auth.principal, $auth.passwordManager).catch( + (e) => showError(e as Error, "Could not refresh passwords."), + ); if (move) { void replace( @@ -184,10 +183,7 @@ showError(e as Error, "Could not delete password."); }); - await refreshVaults( - $auth.principal, - $auth.passwordManager, - ) + await refreshVaults($auth.principal, $auth.passwordManager) .catch((e) => showError(e as Error, "Could not refresh passwords.")) .finally(() => { addNotification({ @@ -229,9 +225,7 @@ } const myPrincipal = $auth.principal; - if ( - parentVaultOwnerPrincipal.compareTo(myPrincipal) === "eq" - ) { + if (parentVaultOwnerPrincipal.compareTo(myPrincipal) === "eq") { accessRights = { ReadWriteManage: null }; } else { const foundAccessRights = targetVault.users.find( diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte index 85d4047e1..bac9f7fe8 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte @@ -80,10 +80,9 @@ }); // refresh passwords in the background - refreshVaults( - $auth.principal, - $auth.passwordManager, - ).catch((e: Error) => showError(e, "Could not refresh passwords.")); + refreshVaults($auth.principal, $auth.passwordManager).catch( + (e: Error) => showError(e, "Could not refresh passwords."), + ); } function saveDraft() { diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte index cd8d81bf3..f3fe7b565 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte @@ -62,10 +62,9 @@ } finally { adding = false; } - await refreshVaults( - $auth.principal, - $auth.passwordManager, - ).catch((e: Error) => showError(e, "Could not refresh vaults.")); + await refreshVaults($auth.principal, $auth.passwordManager).catch( + (e: Error) => showError(e, "Could not refresh vaults."), + ); } async function remove(sharing: Principal) { @@ -92,10 +91,9 @@ } finally { removing = false; } - await refreshVaults( - $auth.principal, - $auth.passwordManager, - ).catch((e: Error) => showError(e, "Could not refresh vaults.")); + await refreshVaults($auth.principal, $auth.passwordManager).catch( + (e: Error) => showError(e, "Could not refresh vaults."), + ); } function onKeyPress(e: KeyboardEvent) { From 0f72e1692239547e9c9b46d7a34042dc20849b2c Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 1 Jun 2026 12:52:08 +0200 Subject: [PATCH 11/31] fix(password_manager_with_metadata): format .catch arrow for printWidth 80 For untyped (e) => callbacks with short enough lines, prettier places the arrow on the same line as .catch( rather than on the next line. Co-Authored-By: Claude Sonnet 4.6 --- .../frontend/src/components/EditPassword.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte index 0f5b9d586..2f250d92e 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte @@ -151,8 +151,8 @@ message: "Password saved successfully", }); - await refreshVaults($auth.principal, $auth.passwordManager).catch( - (e) => showError(e as Error, "Could not refresh passwords."), + await refreshVaults($auth.principal, $auth.passwordManager).catch((e) => + showError(e as Error, "Could not refresh passwords."), ); if (move) { From 77410365612267069b9f4a78ffe809ff131a5bb0 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 1 Jun 2026 13:09:58 +0200 Subject: [PATCH 12/31] fix(password_manager_with_metadata): use let for vaultOwner in NewPassword $: reactive declaration conflicts with bind:value two-way binding (svelte/no-reactive-reassign). Use let initialization with $auth.principal which is available synchronously now that principal is in auth state. Co-Authored-By: Claude Sonnet 4.6 --- .../frontend/src/components/NewPassword.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte index bac9f7fe8..4dc1a099e 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte @@ -10,7 +10,7 @@ import { Principal } from "@icp-sdk/core/principal"; let creating = false; - $: vaultOwner = + let vaultOwner = $auth.state === "initialized" ? $auth.principal.toText() : Principal.anonymous().toText(); From ea60754ead44955357a0a8979c447f28e61c6e6f Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 1 Jun 2026 13:57:15 +0200 Subject: [PATCH 13/31] fix: use key_1 (not test_key_1), npx --yes, console.error in basic_ibe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change test_key_1 → key_1 in all icp.yaml: icp-cli 0.2.7 only provisions vetkd:Bls12_381_G2:key_1 (TestThresholdKeys subnet with test_key_1 is not yet supported by icp-cli). The vetkeys library itself uses key_1 in its own icp.yaml files. - Add --yes flag to npx @icp-sdk/bindgen to suppress the install confirmation prompt during local builds - basic_ibe: wrap event listeners in try-catch so errors are caught and shown (not silent unhandled rejections); add console.error() alongside alert() so errors are visible and copyable in DevTools Co-Authored-By: Claude Sonnet 4.6 --- .../frontend/scripts/gen_bindings.sh | 2 +- rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml | 2 +- rust/vetkeys/password_manager_with_metadata/rust/icp.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/scripts/gen_bindings.sh b/rust/vetkeys/password_manager_with_metadata/frontend/scripts/gen_bindings.sh index 4c66807c8..624b67835 100755 --- a/rust/vetkeys/password_manager_with_metadata/frontend/scripts/gen_bindings.sh +++ b/rust/vetkeys/password_manager_with_metadata/frontend/scripts/gen_bindings.sh @@ -12,6 +12,6 @@ fi cd "$SCRIPT_DIR/../.." rm -rf frontend/src/declarations/password_manager_with_metadata mkdir -p frontend/src/declarations/password_manager_with_metadata -npx @icp-sdk/bindgen --did-file rust/backend/backend.did \ +npx --yes @icp-sdk/bindgen --did-file rust/backend/backend.did \ --out-dir frontend/src/declarations/password_manager_with_metadata \ --declarations-flat --force diff --git a/rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml b/rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml index c19cfbed6..b88153419 100644 --- a/rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml +++ b/rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml @@ -6,7 +6,7 @@ canisters: main: backend/src/Main.mo init_args: type: text - value: "(\"test_key_1\")" + value: "(\"key_1\")" - name: www recipe: diff --git a/rust/vetkeys/password_manager_with_metadata/rust/icp.yaml b/rust/vetkeys/password_manager_with_metadata/rust/icp.yaml index 9d753e1a4..0874a46e3 100644 --- a/rust/vetkeys/password_manager_with_metadata/rust/icp.yaml +++ b/rust/vetkeys/password_manager_with_metadata/rust/icp.yaml @@ -7,7 +7,7 @@ canisters: candid: backend/backend.did init_args: type: text - value: "(\"test_key_1\")" + value: "(\"key_1\")" - name: www recipe: From 8d0f864dba4dde88bc8379fa083f6de6e04827dd Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 1 Jun 2026 15:09:04 +0200 Subject: [PATCH 14/31] docs: update vetkeys doc links to docs.internetcomputer.org/concepts/vetkeys Co-Authored-By: Claude Sonnet 4.6 --- rust/vetkeys/password_manager_with_metadata/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/vetkeys/password_manager_with_metadata/README.md b/rust/vetkeys/password_manager_with_metadata/README.md index 4809ff5b8..58f1dbe72 100644 --- a/rust/vetkeys/password_manager_with_metadata/README.md +++ b/rust/vetkeys/password_manager_with_metadata/README.md @@ -26,7 +26,7 @@ This version of the application extends the basic password manager by supporting ### (Optionally) Choose a Different Master Key -This example uses `test_key_1` by default. To use a different [available master key](https://docs.internetcomputer.org/building-apps/network-features/vetkeys/api#available-master-keys), change the `init_args` value in `icp.yaml` to the desired key before running `icp deploy` in the next step. +This example uses `test_key_1` by default. To use a different [available master key](https://docs.internetcomputer.org/concepts/vetkeys/#api-overview), change the `init_args` value in `icp.yaml` to the desired key before running `icp deploy` in the next step. ### Deploy the Canisters Locally From f0871b39202de8157bde7adf9f1a0d2832b32bb0 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Mon, 1 Jun 2026 17:46:02 +0200 Subject: [PATCH 15/31] docs: add folder structure, fix dev command and cd instructions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Folder Structure section explaining rust/, motoko/, frontend/ layout and why backends are in subdirectories (shared frontend via symlinks) - Replace vague "from the X folder" wording with explicit `cd X` commands - Fix `npm run dev` → `npm run dev:rust` / `npm run dev:motoko` (bare `dev` script intentionally errors with a "specify a backend" message) - Add `cd frontend` before all dev commands (package.json is in frontend/, not at the example root) Co-Authored-By: Claude Sonnet 4.6 --- .../password_manager_with_metadata/README.md | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/README.md b/rust/vetkeys/password_manager_with_metadata/README.md index 58f1dbe72..719685c3a 100644 --- a/rust/vetkeys/password_manager_with_metadata/README.md +++ b/rust/vetkeys/password_manager_with_metadata/README.md @@ -28,19 +28,37 @@ This version of the application extends the basic password manager by supporting This example uses `test_key_1` by default. To use a different [available master key](https://docs.internetcomputer.org/concepts/vetkeys/#api-overview), change the `init_args` value in `icp.yaml` to the desired key before running `icp deploy` in the next step. +### Folder Structure + +This example provides both a **Rust** and a **Motoko** backend, sharing a common `frontend/`: + +``` +password_manager_with_metadata/ +├── frontend/ ← shared frontend (symlinked into rust/ and motoko/) +├── motoko/ ← Motoko backend + icp.yaml +└── rust/ ← Rust backend + icp.yaml +``` + ### Deploy the Canisters Locally -If you want to deploy this project locally with a Motoko backend, then run: +Deploy with the **Motoko** backend: ```bash +cd motoko icp network start -d && icp deploy ``` -from the `motoko` folder. -To use the Rust backend instead of Motoko, run the same command in the `rust` folder. +Or deploy with the **Rust** backend: +```bash +cd rust +icp network start -d && icp deploy +``` To run the frontend in development mode with hot reloading (after running `icp deploy`): ```bash -npm run dev +cd frontend +npm run dev:motoko # if you deployed the Motoko backend +# or +npm run dev:rust # if you deployed the Rust backend ``` When you are done testing, stop the local network to free up resources and unblock the default port for other projects: From c46a8ef84dfa0af3eab4f3eb9f9a426b85f64063 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 2 Jun 2026 14:04:59 +0200 Subject: [PATCH 16/31] fix: simplify rootKey; use /authorize (not /#authorize) and https://id.ai - rootKey: pass rootKey: canisterEnv?.IC_ROOT_KEY directly; undefined is fine when on mainnet (HttpAgent ignores it) - identityProvider local: use /authorize path instead of /#authorize hash - identityProvider production: use https://id.ai instead of undefined Co-Authored-By: Claude Sonnet 4.6 --- .../frontend/src/lib/password_manager.ts | 4 +--- .../password_manager_with_metadata/frontend/src/store/auth.ts | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/password_manager.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/password_manager.ts index 64f58bc4b..fe216a638 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/password_manager.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/password_manager.ts @@ -165,9 +165,7 @@ export async function createPasswordManager(agentOptions?: { const agent = await HttpAgent.create({ ...agentOptions, host: window.location.origin, - ...(canisterEnv?.IC_ROOT_KEY - ? { rootKey: canisterEnv.IC_ROOT_KEY } - : {}), + rootKey: canisterEnv?.IC_ROOT_KEY, }); const encryptedMaps = createEncryptedMaps(agent); diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts index a2a042a09..ec401e371 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts @@ -37,8 +37,8 @@ async function initAuth() { window.location.hostname.endsWith(".localhost"); const client = new AuthClient({ identityProvider: isLocalEnv - ? "http://id.ai.localhost:8000/#authorize" - : undefined, + ? "http://id.ai.localhost:8000/authorize" + : "https://id.ai", }); if (client.isAuthenticated()) { await authenticate(client); From cd5baa8bc2b9ee338434e175e87b342bbda2f82b Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 2 Jun 2026 14:06:20 +0200 Subject: [PATCH 17/31] fix: use https://id.ai/authorize for production identityProvider Co-Authored-By: Claude Sonnet 4.6 --- .../password_manager_with_metadata/frontend/src/store/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts index ec401e371..0d2b5d5bc 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts @@ -38,7 +38,7 @@ async function initAuth() { const client = new AuthClient({ identityProvider: isLocalEnv ? "http://id.ai.localhost:8000/authorize" - : "https://id.ai", + : "https://id.ai/authorize", }); if (client.isAuthenticated()) { await authenticate(client); From b220d6f239f71136ecc22c54532c4ff775b34981 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 2 Jun 2026 16:16:31 +0200 Subject: [PATCH 18/31] chore: migrate Motoko backend from base to core 2.5.0 Co-Authored-By: Claude Sonnet 4.6 --- .../motoko/backend/src/Main.mo | 28 +++++++++---------- .../motoko/mops.toml | 4 +-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo b/rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo index 6cfa11b37..782448deb 100644 --- a/rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo +++ b/rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo @@ -1,14 +1,14 @@ -import Principal "mo:base/Principal"; -import Blob "mo:base/Blob"; -import Buffer "mo:base/Buffer"; -import Array "mo:base/Array"; -import OrderedMap "mo:base/OrderedMap"; -import MotokoResult "mo:base/Result"; -import Text "mo:base/Text"; -import Time "mo:base/Time"; -import Nat64 "mo:base/Nat64"; -import Int "mo:base/Int"; -import Debug "mo:base/Debug"; +import Principal "mo:core/Principal"; +import Blob "mo:core/Blob"; +import List "mo:core/List"; +import Array "mo:core/Array"; +import OrderedMap "mo:core/pure/Map"; +import MotokoResult "mo:core/Result"; +import Text "mo:core/Text"; +import Time "mo:core/Time"; +import Nat64 "mo:core/Nat64"; +import Int "mo:core/Int"; +import Debug "mo:core/Debug"; import VetKeys "mo:ic-vetkeys"; persistent actor class (keyName : Text) { @@ -116,7 +116,7 @@ persistent actor class (keyName : Text) { switch (encryptedMaps.getEncryptedValuesForMap(caller, mapId)) { case (#err(msg)) { #Err(msg) }; case (#ok(mapValues)) { - let results = Buffer.Buffer<(ByteBuf, ByteBuf, PasswordMetadata)>(0); + let results = List.empty<(ByteBuf, ByteBuf, PasswordMetadata)>(); for ((key, encryptedValue) in mapValues.vals()) { let metadataKey = (map_owner, map_name.inner, key); @@ -125,12 +125,12 @@ persistent actor class (keyName : Text) { Debug.trap("bug: inconsistent state: no metadata for key"); }; case (?metadataValue) { - results.add(({ inner = key }, { inner = encryptedValue }, metadataValue)); + List.add(results, ({ inner = key }, { inner = encryptedValue }, metadataValue)); }; }; }; - #Ok(Buffer.toArray(results)); + #Ok(List.toArray(results)); }; }; }; diff --git a/rust/vetkeys/password_manager_with_metadata/motoko/mops.toml b/rust/vetkeys/password_manager_with_metadata/motoko/mops.toml index e3638d183..670c65a28 100644 --- a/rust/vetkeys/password_manager_with_metadata/motoko/mops.toml +++ b/rust/vetkeys/password_manager_with_metadata/motoko/mops.toml @@ -1,6 +1,6 @@ [toolchain] -moc = "1.5.1" +moc = "1.9.0" [dependencies] -base = "0.14.9" +core = "2.5.0" ic-vetkeys = "0.5.0" \ No newline at end of file From 53ffb06668b901ab8e0ce3f698ebaac9696d832c Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 2 Jun 2026 16:57:51 +0200 Subject: [PATCH 19/31] =?UTF-8?q?fix:=20correct=20base=E2=86=92core=20API?= =?UTF-8?q?=20issues=20in=20Motoko=20backends?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit password_manager_with_metadata: pure/Map has no Make factory — pass compareMetadataKeys directly to each operation; fix .vals() → .values() encrypted_notes Hex.mo: migrate mo:base imports to mo:core; fix Array.init(n,0)→Array.repeat(0,n), Array.freeze→Array.fromVarArray, Iter.range(0,15)→Iter.range(0,16) (core range is exclusive) Co-Authored-By: Claude Sonnet 4.6 --- .../motoko/backend/src/Main.mo | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo b/rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo index 782448deb..e35b5fd4a 100644 --- a/rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo +++ b/rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo @@ -34,8 +34,7 @@ persistent actor class (keyName : Text) { ownerCompare; }; }; - transient let metadataMapOps = OrderedMap.Make(compareMetadataKeys); - var metadata : OrderedMap.Map = metadataMapOps.empty(); + var metadata : OrderedMap.Map = OrderedMap.empty(); // Types public type PasswordMetadata = { @@ -118,9 +117,9 @@ persistent actor class (keyName : Text) { case (#ok(mapValues)) { let results = List.empty<(ByteBuf, ByteBuf, PasswordMetadata)>(); - for ((key, encryptedValue) in mapValues.vals()) { + for ((key, encryptedValue) in mapValues.values()) { let metadataKey = (map_owner, map_name.inner, key); - switch (metadataMapOps.get(metadata, metadataKey)) { + switch (OrderedMap.get(metadata, compareMetadataKeys,metadataKey)) { case (null) { Debug.trap("bug: inconsistent state: no metadata for key"); }; @@ -156,7 +155,7 @@ persistent actor class (keyName : Text) { case (#err(msg)) { #Err(msg) }; case (#ok(optPrevValue)) { let metadataKey = (map_owner, map_name.inner, map_key.inner); - let prevMetadata = metadataMapOps.get(metadata, metadataKey); + let prevMetadata = OrderedMap.get(metadata, compareMetadataKeys,metadataKey); let metadataValue = switch (prevMetadata) { case (null) { @@ -167,7 +166,7 @@ persistent actor class (keyName : Text) { }; }; - metadata := metadataMapOps.put(metadata, metadataKey, metadataValue); + metadata := OrderedMap.put(metadata, compareMetadataKeys,metadataKey, metadataValue); switch (optPrevValue, prevMetadata) { case (null, null) { #Ok(null) }; @@ -194,9 +193,9 @@ persistent actor class (keyName : Text) { case (#err(msg)) { #Err(msg) }; case (#ok(optPrevValue)) { let metadataKey = (map_owner, map_name.inner, map_key.inner); - let prevMetadata = metadataMapOps.get(metadata, metadataKey); + let prevMetadata = OrderedMap.get(metadata, compareMetadataKeys,metadataKey); - metadata := metadataMapOps.delete(metadata, metadataKey); + metadata := OrderedMap.delete(metadata, compareMetadataKeys,metadataKey); switch (optPrevValue, prevMetadata) { case (null, null) { #Ok(null) }; From 041002316bdd4c10b7018f6bb1d0a49cafec8395 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Tue, 2 Jun 2026 17:14:14 +0200 Subject: [PATCH 20/31] fix: correct pure/Map and VarArray API usage in Motoko backends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit password_manager_with_metadata: - Debug.trap → Runtime.trap (trap moved to Runtime module) - OrderedMap.put → OrderedMap.add (add returns Map directly, no tuple) - OrderedMap.delete → OrderedMap.remove (remove returns Map directly) encrypted_notes Hex.mo: - Array.repeat returns immutable [T]; use VarArray.repeat for [var T] - Iter.range moved to Nat.range in core Co-Authored-By: Claude Sonnet 4.6 --- .../motoko/backend/src/Main.mo | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo b/rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo index e35b5fd4a..b13b3f6a0 100644 --- a/rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo +++ b/rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo @@ -9,6 +9,7 @@ import Time "mo:core/Time"; import Nat64 "mo:core/Nat64"; import Int "mo:core/Int"; import Debug "mo:core/Debug"; +import Runtime "mo:core/Runtime"; import VetKeys "mo:ic-vetkeys"; persistent actor class (keyName : Text) { @@ -121,7 +122,7 @@ persistent actor class (keyName : Text) { let metadataKey = (map_owner, map_name.inner, key); switch (OrderedMap.get(metadata, compareMetadataKeys,metadataKey)) { case (null) { - Debug.trap("bug: inconsistent state: no metadata for key"); + Runtime.trap("bug: inconsistent state: no metadata for key"); }; case (?metadataValue) { List.add(results, ({ inner = key }, { inner = encryptedValue }, metadataValue)); @@ -166,15 +167,15 @@ persistent actor class (keyName : Text) { }; }; - metadata := OrderedMap.put(metadata, compareMetadataKeys,metadataKey, metadataValue); + metadata := OrderedMap.add(metadata, compareMetadataKeys,metadataKey, metadataValue); switch (optPrevValue, prevMetadata) { case (null, null) { #Ok(null) }; case (null, ?_) { - Debug.trap("bug: inconsistent state: no previous value but some metadata"); + Runtime.trap("bug: inconsistent state: no previous value but some metadata"); }; case (?_, null) { - Debug.trap("bug: inconsistent state: some previous value but no metadata"); + Runtime.trap("bug: inconsistent state: some previous value but no metadata"); }; case (?prevValue, ?m) { #Ok(?({ inner = prevValue }, m)) }; }; @@ -195,15 +196,15 @@ persistent actor class (keyName : Text) { let metadataKey = (map_owner, map_name.inner, map_key.inner); let prevMetadata = OrderedMap.get(metadata, compareMetadataKeys,metadataKey); - metadata := OrderedMap.delete(metadata, compareMetadataKeys,metadataKey); + metadata := OrderedMap.remove(metadata, compareMetadataKeys,metadataKey); switch (optPrevValue, prevMetadata) { case (null, null) { #Ok(null) }; case (null, ?_) { - Debug.trap("bug: inconsistent state: no previous value but some metadata"); + Runtime.trap("bug: inconsistent state: no previous value but some metadata"); }; case (?_, null) { - Debug.trap("bug: inconsistent state: some previous value but no metadata"); + Runtime.trap("bug: inconsistent state: some previous value but no metadata"); }; case (?prevValue, ?m) { #Ok(?({ inner = prevValue }, m)) }; }; From cc16908728d56ffdc8cc8dba38623387d4b54a1e Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 3 Jun 2026 11:21:23 +0200 Subject: [PATCH 21/31] fix: switch back to test_key_1 (supported again in latest icp-cli-network-launcher) Co-Authored-By: Claude Sonnet 4.6 --- rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml | 2 +- rust/vetkeys/password_manager_with_metadata/rust/icp.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml b/rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml index b88153419..c19cfbed6 100644 --- a/rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml +++ b/rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml @@ -6,7 +6,7 @@ canisters: main: backend/src/Main.mo init_args: type: text - value: "(\"key_1\")" + value: "(\"test_key_1\")" - name: www recipe: diff --git a/rust/vetkeys/password_manager_with_metadata/rust/icp.yaml b/rust/vetkeys/password_manager_with_metadata/rust/icp.yaml index 0874a46e3..9d753e1a4 100644 --- a/rust/vetkeys/password_manager_with_metadata/rust/icp.yaml +++ b/rust/vetkeys/password_manager_with_metadata/rust/icp.yaml @@ -7,7 +7,7 @@ canisters: candid: backend/backend.did init_args: type: text - value: "(\"key_1\")" + value: "(\"test_key_1\")" - name: www recipe: From d908e53b107c01710f443c19db417362f7c255f1 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 3 Jun 2026 11:44:24 +0200 Subject: [PATCH 22/31] ci: switch to icp-dev-env container images, Ubuntu only Replace provision scripts + macOS + Linux dual-platform setup with single Ubuntu job using icp-dev-env Docker images: - ghcr.io/dfinity/icp-dev-env-rust:0.1.0 for Rust backends - ghcr.io/dfinity/icp-dev-env-motoko:0.1.0 for Motoko backends Eliminates: provision-darwin/linux.sh, pre-download-launcher.sh, actions/setup-node, cargo install candid-extractor, ICP_CLI_GITHUB_TOKEN, and macOS runners. Consistent with hello_world and who_am_i examples. Co-Authored-By: Claude Sonnet 4.6 --- ...password-manager-with-metadata-example.yml | 78 ++++--------------- 1 file changed, 15 insertions(+), 63 deletions(-) diff --git a/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml b/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml index f7e12e972..5cc34e10e 100644 --- a/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml +++ b/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml @@ -1,4 +1,5 @@ name: rust-vetkeys-password-manager-with-metadata + on: push: branches: @@ -6,75 +7,26 @@ on: pull_request: paths: - rust/vetkeys/password_manager_with_metadata/** - - .github/workflows/provision-darwin.sh - - .github/workflows/provision-linux.sh - .github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml - - .ic-commit + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + jobs: - rust-password-manager-with-metadata-darwin: - runs-on: macos-15 - steps: - - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 - - name: Provision Darwin - run: bash .github/workflows/provision-darwin.sh - - name: Pre-download network launcher - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: bash .github/workflows/pre-download-launcher.sh - - name: Password Manager with Metadata Rust Darwin - run: | - pushd rust/vetkeys/password_manager_with_metadata/rust - icp network start -d - icp deploy - popd - rust-password-manager-with-metadata-linux: + rust-password-manager-with-metadata: runs-on: ubuntu-24.04 + container: ghcr.io/dfinity/icp-dev-env-rust:0.1.0 steps: - - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 - - name: Provision Linux - run: bash .github/workflows/provision-linux.sh - - name: Pre-download network launcher - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: bash .github/workflows/pre-download-launcher.sh - - name: Password Manager with Metadata Rust Linux - run: | - pushd rust/vetkeys/password_manager_with_metadata/rust - icp network start -d - icp deploy - popd - motoko-password-manager-with-metadata-darwin: - runs-on: macos-15 - steps: - - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 - - name: Provision Darwin - run: bash .github/workflows/provision-darwin.sh - - name: Pre-download network launcher - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: bash .github/workflows/pre-download-launcher.sh - - name: Password Manager with Metadata Motoko Darwin - run: | - pushd rust/vetkeys/password_manager_with_metadata/motoko - icp network start -d - icp deploy - popd - motoko-password-manager-with-metadata-linux: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Deploy Password Manager With Metadata Rust + working-directory: rust/vetkeys/password_manager_with_metadata/rust + run: icp network start -d && icp deploy + motoko-password-manager-with-metadata: runs-on: ubuntu-24.04 + container: ghcr.io/dfinity/icp-dev-env-motoko:0.1.0 steps: - - uses: actions/checkout@50fbc622fc4ef5163becd7fab6573eac35f8462e # v1.2.0 - - name: Provision Linux - run: bash .github/workflows/provision-linux.sh - - name: Pre-download network launcher - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: bash .github/workflows/pre-download-launcher.sh - - name: Password Manager with Metadata Motoko Linux - run: | - pushd rust/vetkeys/password_manager_with_metadata/motoko - icp network start -d - icp deploy - popd + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Deploy Password Manager With Metadata Motoko + working-directory: rust/vetkeys/password_manager_with_metadata/motoko + run: icp network start -d && icp deploy From 1917c28d5de0d64e1ecd8b9ca63cad82e91192cb Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 3 Jun 2026 12:04:38 +0200 Subject: [PATCH 23/31] ci: rename workflow files to vetkeys-{example}.yml convention Follows hello_world/who_am_i naming (no language prefix, no -example suffix), keeping vetkeys- namespace prefix for grouping. Co-Authored-By: Claude Sonnet 4.6 --- ...a-example.yml => vetkeys-password-manager-with-metadata.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{rust-vetkeys-password-manager-with-metadata-example.yml => vetkeys-password-manager-with-metadata.yml} (92%) diff --git a/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml b/.github/workflows/vetkeys-password-manager-with-metadata.yml similarity index 92% rename from .github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml rename to .github/workflows/vetkeys-password-manager-with-metadata.yml index 5cc34e10e..159e92f34 100644 --- a/.github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml +++ b/.github/workflows/vetkeys-password-manager-with-metadata.yml @@ -7,7 +7,7 @@ on: pull_request: paths: - rust/vetkeys/password_manager_with_metadata/** - - .github/workflows/rust-vetkeys-password-manager-with-metadata-example.yml + - .github/workflows/vetkeys-password-manager-with-metadata.yml concurrency: group: ${{ github.workflow }}-${{ github.ref }} From 16baa89ca03c7e364df39ad4be09779de0a26b2a Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 3 Jun 2026 12:08:43 +0200 Subject: [PATCH 24/31] ci: fix workflow name and simplify job IDs to rust/motoko only Workflow name: vetkeys-{example} (no rust- prefix) Job IDs: rust and motoko (example name not needed in job context) Co-Authored-By: Claude Sonnet 4.6 --- .../workflows/vetkeys-password-manager-with-metadata.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/vetkeys-password-manager-with-metadata.yml b/.github/workflows/vetkeys-password-manager-with-metadata.yml index 159e92f34..7acba4b8e 100644 --- a/.github/workflows/vetkeys-password-manager-with-metadata.yml +++ b/.github/workflows/vetkeys-password-manager-with-metadata.yml @@ -1,4 +1,4 @@ -name: rust-vetkeys-password-manager-with-metadata +name: vetkeys-password-manager-with-metadata on: push: @@ -14,7 +14,7 @@ concurrency: cancel-in-progress: true jobs: - rust-password-manager-with-metadata: + rust: runs-on: ubuntu-24.04 container: ghcr.io/dfinity/icp-dev-env-rust:0.1.0 steps: @@ -22,7 +22,7 @@ jobs: - name: Deploy Password Manager With Metadata Rust working-directory: rust/vetkeys/password_manager_with_metadata/rust run: icp network start -d && icp deploy - motoko-password-manager-with-metadata: + motoko: runs-on: ubuntu-24.04 container: ghcr.io/dfinity/icp-dev-env-motoko:0.1.0 steps: From 5f7c275a54ae8703131be99ec33c8a19b50737f7 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 3 Jun 2026 12:16:09 +0200 Subject: [PATCH 25/31] ci: pass ICP_CLI_GITHUB_TOKEN to avoid network launcher rate limiting The icp-dev-env container downloads the network launcher from GitHub on first run. Without authentication this hits the 60 req/hr unauthenticated API limit. Pass GITHUB_TOKEN so downloads use the authenticated 5000 req/hr limit. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/vetkeys-password-manager-with-metadata.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/vetkeys-password-manager-with-metadata.yml b/.github/workflows/vetkeys-password-manager-with-metadata.yml index 7acba4b8e..95dfa5679 100644 --- a/.github/workflows/vetkeys-password-manager-with-metadata.yml +++ b/.github/workflows/vetkeys-password-manager-with-metadata.yml @@ -17,6 +17,8 @@ jobs: rust: runs-on: ubuntu-24.04 container: ghcr.io/dfinity/icp-dev-env-rust:0.1.0 + env: + ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Deploy Password Manager With Metadata Rust @@ -25,6 +27,8 @@ jobs: motoko: runs-on: ubuntu-24.04 container: ghcr.io/dfinity/icp-dev-env-motoko:0.1.0 + env: + ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Deploy Password Manager With Metadata Motoko From 58186fd77e92972692cedb4df8a5054ac550e2a8 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 3 Jun 2026 13:18:17 +0200 Subject: [PATCH 26/31] fix: use LocalStorage only on localhost as workaround for IDB race condition Workaround for https://github.com/dfinity/icp-js-auth/issues/120 Co-Authored-By: Claude Sonnet 4.6 --- .../frontend/src/store/auth.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts index 0d2b5d5bc..a2ed71968 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts @@ -1,5 +1,5 @@ import { get, writable } from "svelte/store"; -import { AuthClient } from "@icp-sdk/auth/client"; +import { AuthClient, LocalStorage } from "@icp-sdk/auth/client"; import { DelegationIdentity } from "@icp-sdk/core/identity"; import type { Principal } from "@icp-sdk/core/principal"; import { replace } from "svelte-spa-router"; @@ -35,10 +35,15 @@ async function initAuth() { const isLocalEnv = window.location.hostname === "localhost" || window.location.hostname.endsWith(".localhost"); + // Workaround for https://github.com/dfinity/icp-js-auth/issues/120 + // IdbStorage has a race condition on localhost dev servers. LocalStorage + // avoids IDB on local but uses plain string storage (less secure), so + // production deployments keep the default secure IdbStorage + ECDSA key. const client = new AuthClient({ identityProvider: isLocalEnv ? "http://id.ai.localhost:8000/authorize" : "https://id.ai/authorize", + ...(isLocalEnv ? { storage: new LocalStorage(), keyType: "Ed25519" as const } : {}), }); if (client.isAuthenticated()) { await authenticate(client); From 5b1c29e41c7bfc4777df90453c1df3b27c2c6b81 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 3 Jun 2026 13:33:45 +0200 Subject: [PATCH 27/31] fix(password_manager_with_metadata): format LocalStorage spread for printWidth 80 Co-Authored-By: Claude Sonnet 4.6 --- .../password_manager_with_metadata/frontend/src/store/auth.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts index a2ed71968..1f54fb574 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts @@ -43,7 +43,9 @@ async function initAuth() { identityProvider: isLocalEnv ? "http://id.ai.localhost:8000/authorize" : "https://id.ai/authorize", - ...(isLocalEnv ? { storage: new LocalStorage(), keyType: "Ed25519" as const } : {}), + ...(isLocalEnv + ? { storage: new LocalStorage(), keyType: "Ed25519" as const } + : {}), }); if (client.isAuthenticated()) { await authenticate(client); From ed045b305cff047b38b085ccad294df80ef83ffd Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 3 Jun 2026 15:26:05 +0200 Subject: [PATCH 28/31] fix(rust/backend): add post_upgrade hook to reinitialize ENCRYPTED_MAPS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ENCRYPTED_MAPS is a heap thread_local that resets to None after a canister upgrade (init is not called on upgrade, only post_upgrade). Store the key_name in a StableCell (persists across upgrades) and add a post_upgrade hook that reinitializes ENCRYPTED_MAPS from it — same pattern used in basic_ibe and basic_timelock_ibe backends. Co-Authored-By: Claude Sonnet 4.6 --- .../rust/backend/src/lib.rs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs b/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs index a846367d9..f972a2331 100644 --- a/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs +++ b/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs @@ -1,10 +1,10 @@ use candid::{CandidType, Principal}; use ic_cdk_management_canister::{VetKDCurve, VetKDKeyId}; -use ic_cdk::{init, query, update}; +use ic_cdk::{init, post_upgrade, query, update}; use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory}; use ic_stable_structures::storable::Blob; use ic_stable_structures::{storable::Bound, Storable}; -use ic_stable_structures::{BTreeMap as StableBTreeMap, DefaultMemoryImpl}; +use ic_stable_structures::{BTreeMap as StableBTreeMap, Cell as StableCell, DefaultMemoryImpl}; use ic_vetkeys::encrypted_maps::{EncryptedMaps, VetKey, VetKeyVerificationKey}; use ic_vetkeys::types::{AccessRights, ByteBuf, EncryptedMapValue, TransportKey}; use serde::{Deserialize, Serialize}; @@ -79,10 +79,26 @@ thread_local! { static METADATA: RefCell = RefCell::new(StableBTreeMap::new( MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(4))), )); + static KEY_NAME: RefCell> = + RefCell::new(StableCell::init( + MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(5))), + String::new(), + ).expect("failed to initialize key name")); } #[init] fn init(key_name: String) { + KEY_NAME.with_borrow_mut(|k| k.set(key_name.clone()).expect("failed to set key name")); + init_encrypted_maps(key_name); +} + +#[post_upgrade] +fn post_upgrade() { + let key_name = KEY_NAME.with_borrow(|k| k.get().clone()); + init_encrypted_maps(key_name); +} + +fn init_encrypted_maps(key_name: String) { let key_id = VetKDKeyId { curve: VetKDCurve::Bls12_381_G2, name: key_name, From 859c7008a9510065bfc59235d59cb937d5218c29 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 3 Jun 2026 15:28:35 +0200 Subject: [PATCH 29/31] fix(rust/backend): remove .expect() incompatible with ic-stable-structures 0.6.8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit StableCell::init() and set() in ic-stable-structures 0.6.8 return values directly, not Result — .expect() does not apply. Co-Authored-By: Claude Sonnet 4.6 --- .../password_manager_with_metadata/rust/backend/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs b/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs index f972a2331..6955e0b8e 100644 --- a/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs +++ b/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs @@ -83,12 +83,12 @@ thread_local! { RefCell::new(StableCell::init( MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(5))), String::new(), - ).expect("failed to initialize key name")); + )); } #[init] fn init(key_name: String) { - KEY_NAME.with_borrow_mut(|k| k.set(key_name.clone()).expect("failed to set key name")); + KEY_NAME.with_borrow_mut(|k| { k.set(key_name.clone()); }); init_encrypted_maps(key_name); } From 18740b67ec1d77ae6eaf2fa1f9a7d7094281fc51 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 3 Jun 2026 15:48:05 +0200 Subject: [PATCH 30/31] chore: replace dapp with app in backend domain separator and UI Rename domain separator strings and disclaimer text. Requires --mode reinstall on next deploy (changing domain separator changes key derivation, so old encrypted data would be inaccessible anyway). Co-Authored-By: Claude Sonnet 4.6 --- .../password_manager_with_metadata/rust/backend/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs b/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs index 6955e0b8e..334d0509b 100644 --- a/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs +++ b/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs @@ -105,7 +105,7 @@ fn init_encrypted_maps(key_name: String) { }; ENCRYPTED_MAPS.with_borrow_mut(|encrypted_maps| { encrypted_maps.replace(EncryptedMaps::init( - "password_manager_dapp", + "password_manager_app", key_id, MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(0))), MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(1))), From 381451bfb82fca860979a8ccfd888641d7aacfec Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Wed, 3 Jun 2026 15:54:53 +0200 Subject: [PATCH 31/31] chore: replace dapp with app in disclaimer UI Co-Authored-By: Claude Sonnet 4.6 --- .../frontend/src/components/DisclaimerCopy.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/DisclaimerCopy.svelte b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/DisclaimerCopy.svelte index 336ffde7d..7cf597760 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/src/components/DisclaimerCopy.svelte +++ b/rust/vetkeys/password_manager_with_metadata/frontend/src/components/DisclaimerCopy.svelte @@ -1,5 +1,5 @@ -Disclaimer: This sample dapp is intended exclusively for experimental -purpose. You are advised not to use this dapp for storing your critical data such +Disclaimer: This sample app is intended exclusively for experimental +purpose. You are advised not to use this app for storing your critical data such as keys or passwords.