From cf53bec9c950c77d09b8da6b90945c8d4ad87552 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 16 Jul 2026 18:05:01 +0200 Subject: [PATCH 1/7] chore(vetkeys): split password_manager_with_metadata into self-contained motoko + rust examples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same split as the earlier vetkeys examples (#1443–#1446): move the Motoko variant to motoko/vetkeys/password_manager_with_metadata and flatten the Rust variant to rust/vetkeys/password_manager_with_metadata, each self-contained with its own frontend copy (previously a shared Svelte frontend symlinked into motoko/ and rust/ subdirs). This example is a hybrid: a custom backend (metadata methods) accessed via generated bindings, PLUS the @icp-sdk/vetkeys Encrypted Maps client for the crypto ops. Because the SDK client calls the Encrypted Maps interface snake_case, the backend interface is kept snake_case (no camelCase), but the custom actor still needs bindgen. - Frontend (Svelte): per-language copy (identical); bindings via the @icp-sdk/bindgen Vite plugin from the committed backend/backend.did (replaces scripts/gen_bindings.sh); declaration imports repointed to ../bindings/…; canister renamed to `backend` (env key PUBLIC_CANISTER_ID:backend); dropped eslint/prettier + the bogus `save` dep; kept the @dfinity/vetkeys -> @icp-sdk/vetkeys vite alias; root workspace package.json so `npm run dev` runs from the example root. - Motoko: moc 1.11.0 + --default-persistent-actors (dropped `persistent`); committed backend/backend.did. - Rust: unchanged; relocated (the .did was already backend/backend.did). - READMEs split per language (linked prerequisites); test.sh added (query method called with --query); CI split into motoko + rust jobs. CODEOWNERS unchanged: /motoko/vetkeys/ and /rust/vetkeys/ both map to @dfinity/core-protocol. Verified: Motoko deploy + test.sh + a value survives an upgrade; Rust cargo build + deploy + test.sh; both frontends build/serve. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...vetkeys-password-manager-with-metadata.yml | 28 +- .../password_manager_with_metadata/README.md | 76 ++++ .../backend/app.mo | 2 +- .../backend/backend.did | 79 ++++ .../frontend/.gitignore | 3 + .../frontend/index.html | 14 + .../frontend/jsconfig.json | 38 ++ .../frontend/package.json | 35 ++ .../frontend/public/.ic-assets.json5 | 10 + ...wered-by-crypto_label-stripe-dark-text.png | Bin 0 -> 6265 bytes ...ered-by-crypto_label-stripe-white-text.png | Bin 0 -> 7672 bytes ...owered-by-crypto_transparent-dark-text.png | Bin 0 -> 12190 bytes ...wered-by-crypto_transparent-white-text.png | Bin 0 -> 11664 bytes .../frontend/public/vite.svg | 1 + .../frontend/src/App.svelte | 13 + .../frontend/src/app.css | 3 + .../frontend/src/assets/svelte.svg | 1 + .../frontend/src/components/Disclaimer.svelte | 26 ++ .../src/components/DisclaimerCopy.svelte | 5 + .../src/components/EditPassword.svelte | 356 ++++++++++++++++++ .../frontend/src/components/EditVault.svelte | 92 +++++ .../frontend/src/components/Header.svelte | 27 ++ .../frontend/src/components/Hero.svelte | 60 +++ .../src/components/LayoutAuthenticated.svelte | 29 ++ .../src/components/NewPassword.svelte | 146 +++++++ .../src/components/Notifications.svelte | 22 ++ .../frontend/src/components/Password.svelte | 107 ++++++ .../src/components/PasswordEditor.svelte | 73 ++++ .../frontend/src/components/Passwords.svelte | 79 ++++ .../src/components/SharingEditor.svelte | 222 +++++++++++ .../src/components/SidebarLayout.svelte | 84 +++++ .../frontend/src/components/Spinner.svelte | 5 + .../frontend/src/components/Vault.svelte | 150 ++++++++ .../frontend/src/components/Vaults.svelte | 90 +++++ .../frontend/src/lib/encrypted_maps.ts | 22 ++ .../frontend/src/lib/init.ts | 0 .../frontend/src/lib/password.ts | 31 ++ .../frontend/src/lib/password_manager.ts | 182 +++++++++ .../frontend/src/lib/sleep.ts | 3 + .../frontend/src/lib/vault.ts | 31 ++ .../frontend/src/main.ts | 11 + .../frontend/src/store/auth.ts | 130 +++++++ .../frontend/src/store/draft.ts | 36 ++ .../frontend/src/store/notifications.ts | 30 ++ .../frontend/src/store/vaults.ts | 147 ++++++++ .../frontend/src/vite-env.d.ts | 2 + .../frontend/svelte.config.js | 7 + .../frontend/tailwind.config.mjs | 9 + .../frontend/tsconfig.json | 24 ++ .../frontend/vite.config.js | 67 ++++ .../password_manager_with_metadata}/icp.yaml | 9 +- .../password_manager_with_metadata/mops.toml | 16 + .../package.json | 13 + .../password_manager_with_metadata/test.sh | 14 + .../{rust => }/Cargo.toml | 0 .../password_manager_with_metadata/README.md | 79 ++-- .../{rust => }/backend/Cargo.toml | 0 .../{rust => }/backend/Makefile | 0 .../{rust => }/backend/backend.did | 0 .../{rust => }/backend/src/lib.rs | 0 .../frontend/.gitignore | 3 + .../frontend/.prettierignore | 6 - .../frontend/.prettierrc | 5 - .../frontend/eslint.config.mjs | 55 --- .../frontend/package.json | 83 ++-- .../frontend/scripts/gen_bindings.sh | 17 - .../frontend/src/lib/encrypted_maps.ts | 6 +- .../frontend/src/lib/password.ts | 2 +- .../frontend/src/lib/password_manager.ts | 8 +- .../frontend/vite.config.js | 70 ++-- .../{rust => }/icp.yaml | 9 +- .../motoko/frontend | 1 - .../motoko/mops.toml | 8 - .../package.json | 13 + .../{rust => }/rust-toolchain.toml | 0 .../rust/frontend | 1 - .../password_manager_with_metadata/test.sh | 14 + 77 files changed, 2786 insertions(+), 254 deletions(-) create mode 100644 motoko/vetkeys/password_manager_with_metadata/README.md rename rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo => motoko/vetkeys/password_manager_with_metadata/backend/app.mo (99%) create mode 100644 motoko/vetkeys/password_manager_with_metadata/backend/backend.did create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/.gitignore create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/index.html create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/jsconfig.json create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/package.json create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/public/.ic-assets.json5 create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/public/img/ic-badge-powered-by-crypto_label-stripe-dark-text.png create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/public/img/ic-badge-powered-by-crypto_label-stripe-white-text.png create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/public/img/ic-badge-powered-by-crypto_transparent-dark-text.png create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/public/img/ic-badge-powered-by-crypto_transparent-white-text.png create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/public/vite.svg create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/App.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/app.css create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/assets/svelte.svg create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Disclaimer.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/DisclaimerCopy.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Header.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Hero.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/LayoutAuthenticated.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Notifications.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Password.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/PasswordEditor.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Passwords.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Spinner.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Vault.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Vaults.svelte create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/encrypted_maps.ts create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/init.ts create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/password.ts create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/password_manager.ts create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/sleep.ts create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/vault.ts create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/main.ts create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/store/draft.ts create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/store/notifications.ts create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/src/vite-env.d.ts create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/svelte.config.js create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/tailwind.config.mjs create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/tsconfig.json create mode 100644 motoko/vetkeys/password_manager_with_metadata/frontend/vite.config.js rename {rust/vetkeys/password_manager_with_metadata/motoko => motoko/vetkeys/password_manager_with_metadata}/icp.yaml (60%) create mode 100644 motoko/vetkeys/password_manager_with_metadata/mops.toml create mode 100644 motoko/vetkeys/password_manager_with_metadata/package.json create mode 100755 motoko/vetkeys/password_manager_with_metadata/test.sh rename rust/vetkeys/password_manager_with_metadata/{rust => }/Cargo.toml (100%) rename rust/vetkeys/password_manager_with_metadata/{rust => }/backend/Cargo.toml (100%) rename rust/vetkeys/password_manager_with_metadata/{rust => }/backend/Makefile (100%) rename rust/vetkeys/password_manager_with_metadata/{rust => }/backend/backend.did (100%) rename rust/vetkeys/password_manager_with_metadata/{rust => }/backend/src/lib.rs (100%) create mode 100644 rust/vetkeys/password_manager_with_metadata/frontend/.gitignore delete mode 100644 rust/vetkeys/password_manager_with_metadata/frontend/.prettierignore delete mode 100644 rust/vetkeys/password_manager_with_metadata/frontend/.prettierrc delete mode 100644 rust/vetkeys/password_manager_with_metadata/frontend/eslint.config.mjs delete mode 100755 rust/vetkeys/password_manager_with_metadata/frontend/scripts/gen_bindings.sh rename rust/vetkeys/password_manager_with_metadata/{rust => }/icp.yaml (69%) delete mode 120000 rust/vetkeys/password_manager_with_metadata/motoko/frontend delete mode 100644 rust/vetkeys/password_manager_with_metadata/motoko/mops.toml create mode 100644 rust/vetkeys/password_manager_with_metadata/package.json rename rust/vetkeys/password_manager_with_metadata/{rust => }/rust-toolchain.toml (100%) delete mode 120000 rust/vetkeys/password_manager_with_metadata/rust/frontend create mode 100755 rust/vetkeys/password_manager_with_metadata/test.sh diff --git a/.github/workflows/vetkeys-password-manager-with-metadata.yml b/.github/workflows/vetkeys-password-manager-with-metadata.yml index 79beb304da..2fddde62c0 100644 --- a/.github/workflows/vetkeys-password-manager-with-metadata.yml +++ b/.github/workflows/vetkeys-password-manager-with-metadata.yml @@ -6,6 +6,7 @@ on: - master pull_request: paths: + - motoko/vetkeys/password_manager_with_metadata/** - rust/vetkeys/password_manager_with_metadata/** - .github/workflows/vetkeys-password-manager-with-metadata.yml @@ -14,23 +15,30 @@ concurrency: cancel-in-progress: true jobs: - rust: + motoko: runs-on: ubuntu-24.04 - container: ghcr.io/dfinity/icp-dev-env-rust:1.0.1 + container: ghcr.io/dfinity/icp-dev-env-motoko:1.0.1 env: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - 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: + - name: Deploy and test + working-directory: motoko/vetkeys/password_manager_with_metadata + run: | + icp network start -d + icp deploy + bash test.sh + + rust: runs-on: ubuntu-24.04 - container: ghcr.io/dfinity/icp-dev-env-motoko:1.0.1 + container: ghcr.io/dfinity/icp-dev-env-rust:1.0.1 env: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - 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 + - name: Deploy and test + working-directory: rust/vetkeys/password_manager_with_metadata + run: | + icp network start -d + icp deploy + bash test.sh diff --git a/motoko/vetkeys/password_manager_with_metadata/README.md b/motoko/vetkeys/password_manager_with_metadata/README.md new file mode 100644 index 0000000000..e853876c80 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/README.md @@ -0,0 +1,76 @@ +# VetKey Password Manager with Metadata (Motoko) + +[View this sample's code on GitHub](https://github.com/dfinity/examples/tree/master/motoko/vetkeys/password_manager_with_metadata) + +Also available in: [Rust](../../../rust/vetkeys/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**. + +This version extends the basic password manager by supporting unencrypted metadata, such as URLs and tags, alongside encrypted passwords. The goal is to demonstrate how to make atomic updates to the Encrypted Maps canister, storing both encrypted and unencrypted data in a single update call. + +## Features + +- **Secure Password Storage**: Uses VetKey to encrypt passwords before storing them in Encrypted Maps. +- **Vault-Based Organization**: Users can create multiple vaults, each containing multiple passwords. +- **Access Control**: Vaults can be shared with other users via their **Internet Identity Principal**. +- **Atomic Updates**: Stores encrypted passwords along with unencrypted metadata in a single update call. + +## Build and deploy from the command line + +### Prerequisites + +- Install [Node.js](https://nodejs.org/en/download/) +- Install [icp-cli](https://cli.internetcomputer.org): `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm` +- Install [ic-mops](https://mops.one): `npm install -g ic-mops` + +### (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/concepts/vetkeys/#api-overview), change the `init_args` value in `icp.yaml` before deploying. + +### Install + +```bash +git clone https://github.com/dfinity/examples +cd examples/motoko/vetkeys/password_manager_with_metadata +``` + +### Deploy + +```bash +icp network start -d +icp deploy +``` + +Open the frontend URL printed by `icp deploy`. + +To run the frontend in development mode with hot reloading (after `icp deploy`): + +```bash +npm run dev +``` + +When done, stop the local network to free up the port for other projects: + +```bash +icp network stop +``` + +## Example components + +### Backend (`backend/`) + +An **Encrypted Maps**-enabled Motoko canister that stores encrypted passwords together with unencrypted metadata (URLs, tags) in atomic update calls. + +### Frontend (`frontend/`) + +A **Svelte** application for managing vaults and passwords. It uses the `@icp-sdk/vetkeys` Encrypted Maps client for the crypto operations and a canister actor (bindings generated from `backend/backend.did` by the `@icp-sdk/bindgen` Vite plugin) for the metadata methods. + +## Limitations + +This example dapp does not implement key rotation, which is strongly recommended in a production environment. Key rotation involves periodically changing encryption keys and re-encrypting data to enhance security. In a production dapp, key rotation would be useful to limit the impact of a potential key compromise, or to limit access when users are added to or removed from sharing. + +## Additional resources + +- **[Basic Password Manager](../../../rust/vetkeys/password_manager)** — a simpler example without metadata. +- **[What are VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** — more information about VetKeys and VetKD. +- [Security best practices](https://docs.internetcomputer.org/guides/security/overview) diff --git a/rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo b/motoko/vetkeys/password_manager_with_metadata/backend/app.mo similarity index 99% rename from rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo rename to motoko/vetkeys/password_manager_with_metadata/backend/app.mo index b13b3f6a0f..15d58dcca3 100644 --- a/rust/vetkeys/password_manager_with_metadata/motoko/backend/src/Main.mo +++ b/motoko/vetkeys/password_manager_with_metadata/backend/app.mo @@ -12,7 +12,7 @@ import Debug "mo:core/Debug"; import Runtime "mo:core/Runtime"; import VetKeys "mo:ic-vetkeys"; -persistent actor class (keyName : Text) { +actor class (keyName : Text) { // Global state let encryptedMapsState = VetKeys.EncryptedMaps.newEncryptedMapsState({ curve = #bls12_381_g2; name = keyName }, "password_manager_example_dapp"); diff --git a/motoko/vetkeys/password_manager_with_metadata/backend/backend.did b/motoko/vetkeys/password_manager_with_metadata/backend/backend.did new file mode 100644 index 0000000000..9371d69b44 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/backend/backend.did @@ -0,0 +1,79 @@ +type _anon_class_15_1 = + service { + get_accessible_shared_map_names: () -> + (vec record { + principal; + ByteBuf; + }) query; + get_encrypted_values_for_map_with_metadata: (map_owner: principal, + map_name: ByteBuf) -> (Result_4) query; + get_encrypted_vetkey: (map_owner: principal, map_name: ByteBuf, + transport_key: ByteBuf) -> (Result_3); + get_owned_non_empty_map_names: () -> (vec ByteBuf) query; + get_shared_user_access_for_map: (map_owner: principal, map_name: + ByteBuf) -> (Result_2) query; + get_user_rights: (map_owner: principal, map_name: ByteBuf, user: + principal) -> (Result) query; + get_vetkey_verification_key: () -> (ByteBuf); + insert_encrypted_value_with_metadata: (map_owner: principal, map_name: + ByteBuf, map_key: ByteBuf, value: ByteBuf, tags: vec text, url: text) -> + (Result_1); + remove_encrypted_value_with_metadata: (map_owner: principal, map_name: + ByteBuf, map_key: ByteBuf) -> (Result_1); + remove_user: (map_owner: principal, map_name: ByteBuf, user: principal) -> + (Result); + set_user_rights: (map_owner: principal, map_name: ByteBuf, user: + principal, access_rights: AccessRights) -> (Result); + }; +type Result_4 = + variant { + Err: text; + Ok: vec record { + ByteBuf; + ByteBuf; + PasswordMetadata; + }; + }; +type Result_3 = + variant { + Err: text; + Ok: ByteBuf; + }; +type Result_2 = + variant { + Err: text; + Ok: vec record { + principal; + AccessRights; + }; + }; +type Result_1 = + variant { + Err: text; + Ok: opt record { + ByteBuf; + PasswordMetadata; + }; + }; +type Result = + variant { + Err: text; + Ok: opt AccessRights; + }; +type PasswordMetadata = + record { + creation_date: nat64; + last_modification_date: nat64; + last_modified_principal: principal; + number_of_modifications: nat64; + tags: vec text; + url: text; + }; +type ByteBuf = record {inner: blob;}; +type AccessRights = + variant { + Read; + ReadWrite; + ReadWriteManage; + }; +service : (keyName: text) -> _anon_class_15_1 diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/.gitignore b/motoko/vetkeys/password_manager_with_metadata/frontend/.gitignore new file mode 100644 index 0000000000..061e2e66e1 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +src/bindings/ diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/index.html b/motoko/vetkeys/password_manager_with_metadata/frontend/index.html new file mode 100644 index 0000000000..d880f186ed --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + Password Manager with Metadata based on vetKeys + + +
+ + + + diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/jsconfig.json b/motoko/vetkeys/password_manager_with_metadata/frontend/jsconfig.json new file mode 100644 index 0000000000..cdcb3c8dbf --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/jsconfig.json @@ -0,0 +1,38 @@ +{ + "compilerOptions": { + "moduleResolution": "bundler", + "target": "ESNext", + "module": "ESNext", + /** + * svelte-preprocess cannot figure out whether you have + * a value or a type, so tell TypeScript to enforce using + * `import type` instead of `import` for Types. + */ + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + /** + * To have warnings / errors of the Svelte compiler at the + * correct position, enable source maps by default. + */ + "sourceMap": true, + "esModuleInterop": true, + "skipLibCheck": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable this if you'd like to use dynamic types. + */ + "checkJs": true + }, + /** + * Use global.d.ts instead of compilerOptions.types + * to avoid limiting type declarations. + */ + "include": [ + "src/**/*.d.ts", + "src/**/*.js", + "src/**/*.svelte", + "src/main.ts", + "src/main.ts" + ] +} diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/package.json b/motoko/vetkeys/password_manager_with_metadata/frontend/package.json new file mode 100644 index 0000000000..adaf711ac4 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/package.json @@ -0,0 +1,35 @@ +{ + "name": "frontend", + "private": true, + "type": "module", + "scripts": { + "prebuild": "npm i --include=dev", + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@icp-sdk/bindgen": "~0.2.2", + "@rollup/plugin-typescript": "^12.1.2", + "@tailwindcss/postcss": "^4.0.6", + "@tailwindcss/vite": "^4.0.0", + "@tsconfig/svelte": "^5.0.4", + "@typewriter/delta": "^1.2.4", + "autoprefixer": "^10.4.20", + "rollup-plugin-css-only": "^4.5.2", + "tslib": "^2.8.1", + "vite": "^5.4.21" + }, + "dependencies": { + "@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", + "svelte": "^4.2.19", + "svelte-icons": "^2.1.0", + "svelte-spa-router": "^4.0.1", + "tailwindcss": "^3.0.17", + "typewriter-editor": "^0.9.4" + } +} diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/public/.ic-assets.json5 b/motoko/vetkeys/password_manager_with_metadata/frontend/public/.ic-assets.json5 new file mode 100644 index 0000000000..a57140a5e5 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/public/.ic-assets.json5 @@ -0,0 +1,10 @@ +[ + { + match: "**/*", + security_policy: "hardened", + headers: { + "Content-Security-Policy": "default-src 'self';script-src 'self';connect-src 'self' http://localhost:* https://icp0.io https://*.icp0.io https://icp-api.io;img-src 'self' data:;style-src * 'unsafe-inline';object-src 'none';base-uri 'self';frame-ancestors 'none';form-action 'self';upgrade-insecure-requests;", + }, + allow_raw_access: false + }, +] diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/public/img/ic-badge-powered-by-crypto_label-stripe-dark-text.png b/motoko/vetkeys/password_manager_with_metadata/frontend/public/img/ic-badge-powered-by-crypto_label-stripe-dark-text.png new file mode 100644 index 0000000000000000000000000000000000000000..1a227a2b059107873415a8fa6d61c45908d52756 GIT binary patch literal 6265 zcmV-<7>4JGP);? zU+kXbOTp~vd$xO6WfohkXBC(<%+*|yK?*2!9<=3VjkFV4eY zFc>~ySTGn2hBbmsFElT^qJB`wuyGDFwE|i2d4S2&`>$zZz$~_Qu=;x!4+b_`;K}K+ zY*9_tgsWSygyRkPjYiGga`=Cb!eB5MJ|GyuXfPPogq=_S{Gi3Tt1T4!8K7|F!lL-r z`TD^CaFKdB&S49sSVvbdl30|YG2Ftp8`jvCM}O+UU@#aqI7ToU42D&OtcqK%sLhC7 z2LnKK6i>bV*3zUuw!MQAocqkRP7qkrLN>DgbCwlWiJ}`iv@DV^tXUilafYmmHQ}dj z_lQTU0e8#8KbeHVU@&Z8j9@ev46DYDV++h`)aFEYFT)@qYvYB+$mx4N<`)Te_TKBL zJA5kq#k&!aNF;$6LzYDnh<2|XZg1~}tykdfk8Z`ekAE2NG_ODbIvh*;PT!?BDdx@l zt`k;8w7NfdKsH26ipJ%w6z#$){GNx+q6?a+39&W=bj703SRK);JNb7%dw5DGE~{Rn zu4E_AZ@*~luB~{!J|_g>aPJsJ^p0EPm=E~HeOsUOeQ5CZW5ce!$(j%Xkqhx?#bX)m zXcCM)-?;lnyJ0XG3>yd=27|#cK!)CV*WLE*A4Fo2cdd#;kyVlQZElU|h(+S`J=^`4 z?z(1#gXhjd7Jl5FPo5c}u z!X5LcPI*{ zG8)B=l=Dt>-jB{FF)Z8!($|2?N@q*Bq^>c##}mlEKPKGajB9(sO%@DW<{71-=C8^r z;ihupKNy8S5~Zad`CP|PKAla(WosC5`I?QcoA;kL7}E1F@A)w$?7k?BiY!+?(#xJ2 zN73uc{LN!NJx6#eQylYGGK&%^CS0Vbb81-FkLr#1TTV6&*o;XG&yYoV@ahmtBM&-aIdlNnGuvH@4ZrCh|3q1sWGf9rxX{}VLr0U2}5>WrQ#vI-cqkw zrrwjJmH$&w_`JN4$a;uKYC{;}`_`$eT1ajaB=laVm2(FDJl1)yi$K?GA*=(`y3~VS zTb4u%VP2^56>0k=488f1RNtogO?syNq?Jb>VO!dUej16i_v|3@pIhF$p+fJ7mvKI? zjnJt>=2DKY&;UHcEg&oeSo5av&n`){Fq`5aaJ9ZopEfa=eX!lsp< zyn9z2Lpl>0X~=umbNPb@{%!P+c09)KxZyFr|At5TeZsY_f0Tdr`bYTZuX|(;6`?Nb zoll)v*m?X_p5Yds`}`mBv-e)xb#LBx-SYnOv3sv;p(1>GxcOfD_kVrm zj^E5L%-q5+%!=*oEu7CdI*mbU^pTs^@av-YStga!wsJm;7WB*8Lcw$u4G&a*_X&pl z_svnxw~)^q%C{#D2A9W0V93wU!v*8Z%ZCMI*GnpHUsyN8@8Leb(+Ir9<$=?a+aYVWH0fgzJG*OyejqvkWnFqSp zo{hpbmFq&eZf>_-4ixv1XrCeef}TeD7Ybcde(6mh{;Hzz@;Um@w*;s#gyn`Q@tla# zDa$C~3U?OiU-T+%gyoZvU)c;{+Q$<7Q@TBbWsvUY2;#4;7UHz&<~_xCD&hKvQ?5^6 z>3fvlvit+R<@hAhts-=9uJYT9{5KI>8_7{#lT~aUCHdI*|8p-@FEnORS`{17tZ188 zE}z{lY9Sg&teQQSVQFms>1~Jc_5V18>(9P|72_9SjeY2e|7)Wn*4TXKz3<%gxGT^%hXA-jml>>n7Q@xCqO#2YncN z>#C}F3CFVTZ62$W$5}A+)}?A&t=6To&d$plmJ^?>*QCF+wm%CpIJhb2${HXfvK?3J zz!t*vyI6hN!fjK2Q#xdO?xVLZFH#1)gkweD(|#RD(EHM+oir6XrJPH%JhT))bRm}D zrwzTY7xj898(J=IWF*+zm8_D+# z{iJu<#mf6Dg|IBn?H3ls&j`!nf$JU#?iZHDF9M}ZSRMC9O4LvA(^BGUWyGagpd4lY zfRdh0-!VdM06nZNl%A8(eiLhtk6x?P0SuN-%6>np<%kL#Q_`YZeFqi(gm`b{j!hZ3%C6>Ufc2;(#q&w`MsB`h`1GOx#!2eFsF8U zX43%OCBc^vFswbY9;JHG#m2=){OlR@@s#}{-LLOw7~;CrM`%Ute{bmczG&+qyUuQE z#;1-2J&0m;oQ!WG;hcKyL7-Z7*P|W3lu>nAGfOazD*H*5vT8yl6Q+-JPAFlV6hCU0 z#hz%(6#plbct#OF*u6@4A9^?mt_%#J*;Za(hv5NTTjC+UZwJ+JBG*FvLC{9F81v98 zsAi&lA9~%yY3Yz;nbfI3Q+J1p;xNQz&Cl`cBF+P9Xk?`|2~?WQGw8~%pIu7)vl;%{ z%D#h=2(j3t<#WO%bhsiKEgu7hL?if;mT3H+tv60x`NYeUSOqLwUO)U0@BaT7PD@Z& zS7PmP5$6ZJzYLcK{q^23s?kE+2Kdm2KG2RRX>w5=r_V85S~$vn);mCL*mi`&Qhd$$ zvOd)5xcm^!YWtH=+Y_~{FvRf;>V0PD?OK07<6y95aj&xP*ZVFs7wxNJ&_l8qV#*&9^)Ui7J(*s9+f7{kG5B$mP&9}B}O7ugb@&1vo zjqbShTStoi?`a(Z$We>o-_V+AVV$6{yi|9}S;16ajbQyrs`n^e9`vAlNot>nqQb#N zTvyTi*hYC6s>&9)^M&*5zKc2NRrDl8#!@0)4JEExA}I~BVU^ZH)e1=u*-^q)5nLS<%TNb# z9#J?FRBe>UM>Pg%$?(zJ-Qz%SCZfy-2|bTC2E!oBnzVFCu_n{)YJ{aq%u{|(qIlX= zON%NNU91kP@t&q4x}-ayKn#UqY;6pRG#1)4gY$=fcj1@2zB2tU*&aEVRXab!rWXdD z4vWj@D2FYDVlevd4^5;rR4T!6cMKzmMq1mNcmKgvF!tC(9h4@JS^gk8XUD+;i$p(v3^j!B$xZ5ceqFo%xaL- z&FMoQdS3Ld%9RC(-6&s|N>S}Zu55YmvC@KD!~n4tsA+MKT~{`dpLSo_r)QDX{^_>; zNl;zwAYMC?(#4{4ReNM+5#Q%o^nPT$U-41BEJ__H$DGpd-SVW}=h0Q1hip`l<{-U6 zV2B%MKPO(*f~ZcYX)F6)w56L-Es$#kW#%|~$Fr>`>NurA=Yy;4k78|6E1|3b({zp+ z#s%mvHcZdY*NA&Hpz`BJZVIbkNvT%*@eF^e6*;Q`$fV8#uWMPPv5Qu;q)zuZntyYx z>tH}EUegteu6peD=YEDiKk-aRT7Ry%;T2V+_4LR%P+~+@&NI3H5!>ni$Ct)}!Q{41XSa+v&7-No}KvqV1EX zgxQPU@!lS)LFH&Ha^ofAOS*44oM*ktfRAcrQR+Z-oUbEK+Ng#)c7)9O{VyFuSXf!~ zTj#4lZQuwWy#-DEGEhy)Yl-~YB^at#TxF1!gDK@$(Xl*vILiK7zW{VS`CJ)#*Zh)D zWi{0oMi;e9Y8)FE${+RPrvF}Lmz)#*Zli()ykP~Wx+0NB;~1lT>XxooUtqq=8`nHI z5m*Ub_(G}e|M#EZkALudb_4?wjql9d#B7W2iI;f{r6T=)4OinE*k81P95ie3L9*6( zi0`TDq#hrGuQM-2e8hQ10)WPovOYibo9ftRwC`ucr?!7hB-hQ&=$i6)8|B)Nq}3bN z^O^a|e!uu;h|{>LX`qQa6{Y8K+AB3|ZNY zr&9FcEF`$1qCw42Y6x{3pSgr<>yDwBQNk=@<&r5rsv*ULbm~C$Hr5{15=a%DEL-I0 z{#CK4GciZSLvO=VNjzM_P#IP8hD2lkJKBoQ`KVYKUw#3zL$DU*g5Aqw7^!>g?C}om zJLF^iQLSj}3DqZaeXeZpjA}Wp`Vmr9R7=6s?=H2EP<5>5D8e*S{Vtyi)dMU#ud@`| z6ng9C7ozy4qBzyIpZpcApCxUqT;6RK>xYNle5Cu7_pGkrxYkB2VvuDq(I}=0#yv!DV&XbW)C~8S0??wH?i{O{|;^?IRqwFoo*K zu_Dq*5j6-gS_aBvx;OQCJLsJ-LVk*lBbBHPPYbI@CuP43W!}>^;x*O?hTe%kgopg) zG;(o$wtAyvF^tmO?si1g`wIFx>0y4F!DvA3!Opn!K57} zO@2z)HsXh)D-_x}=Vx=mHnDQ-jf*%#J!HqtGS1KEoI8Y-m7=t?dZ>UohM(;$^G$ zA}&v-FyH~-mtEvjyoQ6cCZk+phsHB@XHIjg*|6(#|IGes{L}uIv_#df*u#z;yhmT~ zjDAavNMcd_w}0Hi*#^CT>qXCR57Ng5x(M@*;ya0A_)1!eieQ

zT4!T4l(=`W*G2Q>gY~OnF5j*hTzr5M_H@cBZv# z%XQ12O4hwoe7f&hfBBQWQwQX63kHM1Fkl|P)*au(akw7+AL-@w84*u}5y#pbwaF3R z^^@XwT?oa-($a*`g%15*7KlQv!S4R(E_z5yWpj&nEsII<2+`Qi6pa!KJ`XUNCiX7O z+~inRZJPH9S4zg4{4)G)aHkNApB9GArQI*?-(FPE8w>`+C5;h`27_T$`1WtPb3>dv z%d@39mIWhqZG;nH0@}`t- zM6oO;vE%p|*McLz@Fc?;aY38!cHlGs$k%eOzVyih{nsUV6F}!8o4SOX$Ez;sP}SLb>!&S@@%~5A2WBYi@mi~C$RaxtNpKf zD8SCA7e*1-hec!z1I36%gTb(|sk*F=!C)|?a*yx&4Q`K!Zt2->*EFP6(TaqkA{MEu zT8hP}&$s`JHX63***4!@R4fuN-MM|1F9jnamVu2iq;6;<77Yf&M#oqf4F3>zFH7!3x)n(+dAkkwIdHF$7fNC>rh5I}5Ela|G30q&?LI-KOfc_#or zQnxoxZS`H3S14H7K4hXTdjDee&5o#IehBrVe7E~R*Jr?^9Lf>UVmU_aiU z@AJpAGpBR2cel5*Gq(0)Lhj!+ z+Y1Sbe{cVQKdmwUMqFHQP4e;Y_3W840V)m-PNHOYy|S?CFP}l=B_bp#+@!lpanv&C z1nNWE=;y6}@VFFxt2j@eULe?H9c|}CG_EdM`YuUq8<1H%2w>Z!HSxi5yJ`vxu0SVy zkuFP1ODij@de!quS2Fk3@y`lW!UM<&Id0a3=Y)1ZY5zq=TnYoWl)H7V5QNZ2kd0T7K{}A5kL_=a<vMI9yb2_Fqb&(NsV&@lQd4BIBR(g2| z8d-pm#QB3>3tah`0Hd}OEw0A=^}>2}UvrOYtt9TE`7@_~t3UOwOs@RJB^;yS)xg8$ z%|@iLVm2X$1TzieY*GDHJ_ipIQEw!3R0?AZPb6f|5xigad<1}%k`p`}hULEc@4-Vb zRVjG5xqZ?6q_0n~pSVbmfW2Eoz`m{LQ1sZE4Wv|P&j9u%9grP~z;psg0uZ^~lV>}g zd|sheWrt1JOq7k3=7%n(-xbZ0AfI+_^Lh@xNyDDu1_KLN>j%yDQDEPWgWr?>^`{uw zq;Mh%p@)K;sMJwLaOL9n_X#@h;~nc>WXZlRdb+pQdQR*RZY4m&$LVEvA+$!TS<_mwd~d7IYlbw! zV_rO2DiZZSA42V)8e8O2mbxcZBx{}G#OJ7H4Dl1?vS&z)9wVT^%M0DC5x`g%!ltY( z|NS_?rGv;GW_(IaQ>1xS-4)kO-kU`Yud+$^IJTJQgpk;;+Ds$U&@(k}Mr;kv9Ecje z$O%*3j~`c`vh$CvmJIRwJX%e8G18E^xSUxgq3kI2XkPh30>^Wr1d|(n8zG5ne=JN^ z4SRzUQ~F9Y@+2q&GI|FR$%iNl8$F5Ni*+?M>Guj|h$5pgF}Z(E0d9=e?cg|+@qaCI4!^TCRKr|P zJv)GaA%sbHpy7I`d6RQty-(Akmo*JZJ-+t_75aQv-OsG%Cd;p;QbxyKVb(Oh&e9j= z!LhTqxK}9`-z4y&FzAsl@ucl)5seT?< z40d~WaqVuwK`g}N*A*!spV7tWRdJ|c&zzXsvOhg;RrFbPiZ_T`6v~=s$eESK{*=R~BT>6a z-)kMQ!8Jk0NADoxwKVSLUlKv-K~y7;`8KbwmM_~JOPSz=A*r9`!Gi$?MxP|uAkhq_ z6x7u5r~3g%qGK%+s2_?`cGN^ug0OLo-ngdQ@+L-Ukt+o*?}rw;qzA@lG^;EQ{%}+i zdw0*29M9)cW5?kvZoz3WBg(}4Vcl(QC>Bdf91J#A2d}!O3^U4a2hcN&B0ET$CXAp^ zchmR*+`Znt+wnG*FB|!C4WiD9Y<*Sno><=$3*#8q&iE}=^c?2DP^V}QlxKCt1nv>F z_|7xpHOcMl|-#3&L;uFS!ip1I0^Ip=P&oe3+1)C90NThMz$RR5aU_m7wM zzOPyH$4n8aPpM(VXr=o@8<65V}a`EB)rZL!O(Yxjv`jOUDp1b;Jq1w?e` z7y_)uOMcMkzsYA!d9^1`UZBZdT@{Qsa`994cfcrWZN;zz)2<`Mn8(D$?}Ams$w+yN zpc3zUKai2GERQLIHxN*f|Fn*=7o}eQ#H6;*QT`cYF(Q^QF7}R|Je9NAs&^Y6n2bs%xb~CAefi9g=SU zRuq_gWc~JN2zX6U)^BFQPQqFbY_1t_QwK2k#6=c8v0s}GDDEK;gGl-YVIwIS^iku;Xc85`l zy7AC_bn>DCb+T8Gm_u=}LL|trT zBP?QE%6vo5*Kv&8`t42ljyR@lwR6RBoK`RFaF*R{$8@_YLXzTh4VW1YsC^z?8Xk3P zAnYI9x}Uzr_-GMvBC{0f<<=FfVzc3|z5>jPn7qH%LaDWko6vo33!A}MHJxX;!HN00 zgwTQ4Bvkr~79$Fgs;hjKHS6q|Ph7+po?D}-C*&KuveEi)b0>Y-5>M}SX?U~Wk%XDZ z&WPfMZ2DL{P3m-$8toQH&4f+E)9O<`$g(=E__}g85)g(3P1K=$Ji+dBc7Yy- zlVSXB7RguR0;$cmm;|!7jlI#dmL&}H|4yG&G`W7W$u3Cy91Jk?xF2Ipt3^&4>cx>3 zCjiUmoqCV7ar1~nbhwTL;K{5pl*M|rwn9JCmhD}td|VeF>Lt1M>O1qu4E<}Ie{yb{ zrz>-JH_;2Gie<)|ZU!t*cv3w`$iQJ|#Ii-5VFD;0!T??R+lsoKa19ZyOeJM>%d zp0=yQQ1gxNq5h9!&upQu`-#-Io5MQ88p?ka}8{fgvlX?)LE&k}=gFqBTS#m|*S z&h+mq`I|VjZCqMEqqAZ?j!4Ss3M-DWG=}I6BP&(_FWG>KZ*m5|VzCr5-fXszMHc0Q z-^#~vn>m1}NOkmKP?gL!{#mA+1f1mZ?9086@M(=%vXTNwV#I`3HqMp#G=HkWrNp1M z6I}xMFT5)?y%6rTm~1|rjDKIr7sJgF)@lvV?~jVEAaja#u*a9C z4HJM7^JbKWjETHpdgDwtR@Z=?nP25xXn1BmX0g`UZL&TC&(|vbp>}jCdQl9H52IeS z1?XvE;bvVdrZd4#fvAGRi&rF!3-}OvIhGNUvWD{*^WdFdAP4x0$`{O!yw@CoPq5Ka ze6fgAKgAGr7iqDUR`bIw$a_VszYC5BM35={cK8N?jTnn2p}Jq1B65X^e4*))p17jS zI*2=%)DuB%{ig4|=_uR^4;{1G(MBuT8EtaopYiR^bZQSYE>y*tlFKjn>HdOG1`v2~ zj9j5}oa-A3c6yU>%v)GYO0mx4I$|y}LNa~5)D)UxQh8ciRmtiqP0yK1nVwMo)=1?l z9(wLry(T9|SyW^InmuMt1WW$=JG&x2L$)_3P;t2=tnLw%=WKL~h~#Wf(^$WaGI zA&O0gUlX}IQ#AKEmIqVg*?-n78C0g^9;SNgN>jrAcwL7U*@Kh3<+U%gB}MDp^r=mN z5-3lgc`CxX3N*W&N&w_1qUoixQk}RveU&TOe26xLYv3Q)H|%b3c?1KpOgYwgWMGip zyg1Q?H)%TW3bp7L(KL~HYuU(*A4%!H^OT#iOQ6YHW@GlkaI9HvWQ6r?mb^Dmj3`=G4A7wv;UI=KPvQt40V# zV`_-O2edv%Zfy=V$_3tXd@*BQO;A!KaSGjGc3 z)AI>4^l2{^VVhf3cQ@ES%Vwo|RWxyg|E~GuGEFfxDrup&AzjR^no2BDJ{$*4V)Uf< z8LZzcpRsrPAIO{tDNt>gJ@wz#r%!V4hTM3QMHl=egIko3+tiUefO^SIMI;hmbMUMr zti+&PQ>?;i*_uned^7mCl=e%TTIt|AH8J5Fm7wz2JgZT)WVyXN8<8mn)qCF8`)|1< zhg9eGyZ;$YPQpoocKiIs@Dl7VRMguKPoiEsy@Toa3pDXJq+p13|f!28LA zb^3B(4Pe44WOV{Hz`3RnA?##QcBOh$h{1Hn!g{BJ`z1buOe=(Erb4bV@5gl-t57b` zT->d}akgj;m2aHdD8CeK8eH-2t|aI}EKSB5AVb&vfKf8i-SgUmQnTQ=@Z*52DQsUp z%CG?*dc=%vZyBOaR7R}Ov>$OOn&HPbCrml-YaB_q&-pn$Oa%@kqc-|?M%3PBt(4(y zMy#Mh5;s@c-S;a)k(Q*78roHVSGYqbc5`+}?sc9M^ln7DS?t`4uZB&oCX6UdeiPVD z+l@OX_pt#z`c&8p_I$8H>^*GG9BhBV`{q;C%ms{f9(6F;jK(i0CRbpKv(>oxY-!NG zQOv|RTE^rj$^oVIP&n3)D}7ab0=J1a$F%QsgMYg-&w?y@A2`4zwg`C?{nG|SWpnMP z8%JyvFM^@bp09u?9u@Fe*U040b zqKqrvQim%=1y!P&m*m%{-vm^!W-(wzdkjoPDyr9G>(MxhD#vYDp1n|Z%MxsHw3F8n zjOBfY27$3azO&X-e2-W;766%4k^)tXim`%9mSuO#QI|XV>buC!eE&T${|)jkY`V}z zceai0rSq>Qxund=@XeFy2(<%|%yr?U!wMC_3R$SEOI$r$K};tlVQh$-o-0k0@}_Mg z_NzVM&AK?TO_zm&N=AaCxyQ{02epH6y} z5Xr7Hq6NaGl9pF~aGr{$1;^e$=vz7TjJ9q8F*q4Ib#V@$j?ML;v*ciUa_^5IcO(AY z&XC8CDu$DDHlsnA?zxjb(DZ==fBp7=emUj53~qItV4a@g>Hx4xXj4cd%f}c#L5Q#u z3+4c4b`S64=(ti}PKkpzA}J;B%Mr^@(NoS$gZ7IP6}p3Fpc5@5Q4q{(8?{W1F!h4f z9(zqp|AFrr%_C>6Mwi@Igrhjj8T@o2P&-X$B=)(0JsD8n$s`wWeausjy*uM|;2V9s z+C}e*?&y@D{Krli*A7O~MQd|1SQ)FVv;GqS(WH)={Cp#0*Ql;7c0Kjbih744tJ>0M z%H~)U8f_dTF!Jf3#X?6TA|?w>Wp^}HD}?Pk2qxIpCT!EA@h8xq)rQ~1qM$MKe7kv* zpzGiGB^tT{m4$g=szR7^a z;Z3~4Fsz3k(cF>JK@O~O^?i!1xQHn-5F5zU2E#djREtOD+KP>Zd#nv&@6p}i8CcD` z1i9Cx7FPUpI#}C@CCm+U3C@Ij2sD}nYuv^7;V#j`duw#-IUDAM2S7M4u^OOX;LQF8bu#3m>Ay^Dh`S$sbH&PJpEC??MlgY3yX5AuaQCP16??!NV z!aw_dSWe?T+D>EIwNv-c@Dq$`$)KUX;zUDKIkxjn3dPsKGa+0cv1hzoej7offQs8( zUx8Nnahej-!$Vi0G8!}89$?$^g8K2SoBRZIbYEkk)K9FLPyKeOjcYM#4BI#BQ z<9xApl4+eW)u5f=$%w4U2N|8&B`+9gn9-GFW!;1yFE^KgH+ujxCE_if4H@C`(SvuV zRmo0@tHL*(F^ca!{(BuzmpGld8ww8eT{@6}0O6MITuOI|;JM|nL#QUrY{^FKLXIX1 zlO@{6ogjCuz2AJxq^y=hQ+)Rt7Dc*<)!n|3n_Sa=DPO9jW}pn{JWg{Qfn5vIjeJ8h zvNg1-z>lD`wt{Othx7%^hOF1#O1B;Z^ZO@68A3Ntd0bY87{LM86ZX=^tq)2Z z8dsHj%L|6zlE-fXt=vxxuqtT9^KyVETIXsm7T#tn2%xkY4-@a7=;K^vS!xpBja2k_ z2$QRNIIO|*N*k)le2guYvG5?#MW^huw_@bViXFP?pP|6*nW$d;dR zrxh>?Zz>dCMojk`kegh_@@$N{(i0~Y4Qhaa&9o4?LbBKx$EE8JUX*n_XVQX$RTR}G(adIKC)v3bvGAx=ThI;Lt# zIBWTo|K*o)Dd@tWwns>DVq1L<9e))asD6~g`WP|%ngF_yEucK*Z-x%S_L_z-fnAX> zy*i@McWCbtXU2Xo#*UwthB-O0yhtmGaa2BAoiWHn)t#jwB*l-=fOAmej_3Sl>k&e+ zk&-aJ7hg&%0ZW`RdD-yy$^X#;<@03`J^ffw;E!*`VlQ8YLxe588sypeHs}h;nE4B9 zZ4fUI-%}Ls$$Zv?p>-`c@Y8xd!b0(<(+Yl7e_%~ z!Z@rmhxnT_H8RVWA6UCEI*HbKK#7Z+WF4O-r4;u({>Mzv|37B3|Bw(iwZ#P?457LG zwt=ul@1F`yj+BZ05;Qz0p?fj?l#GAY05n%>C`(TSadyy#;tnm5-sPiE<1VXXQMK=G$vTgYP~(QIs|z6) z1UAL1RIDfIeS0V#m8#Ex@#kq~Pc(W0pnx)jj;u=dLUpc1uDy=K{-Zo8aglE{H{Vi@ z&y4kg@Qf>mNwi+VV@`#Tk*@H@1ZlOGNfj7hIz5Yu*W z)79tnKT7n9`U!(Ijede&>~el@y`w;Vihdm(HH9G^{3GG^iu38R$3Xf%J%9o!MG|p> z_Tgtv-j8Ge$^^_FDDBaR{Xb*|_&;Pum3K*ZEqTt0Eg>NotuT%w-~GJlH2$p~*^g^D z<{}U?xCe?qL5I(Yx)yc1#PnIpe*$gCgB8QGYR2R}Vgo$X+uOUnkMV!Hu!J2~ztr)E zrq%Co?@B=d(jRT=QzAKJIt>SQaQvS5x(-{(F_W2gSRUR4b;gem)B`SAlfcA_R8JPR k5>+ca(Es=%(OZCZ_*S7t&NA}<0giOF*Z=?k literal 0 HcmV?d00001 diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/public/img/ic-badge-powered-by-crypto_transparent-dark-text.png b/motoko/vetkeys/password_manager_with_metadata/frontend/public/img/ic-badge-powered-by-crypto_transparent-dark-text.png new file mode 100644 index 0000000000000000000000000000000000000000..90029c464bf6080f78d19b48c0aea12073448600 GIT binary patch literal 12190 zcmX9^WmFu^5(N_6-5nMW?z;FA++BjZyL)hVmmtC2J!o)uf@E=bf8={Vrl+Q>`c79( zpPoMV+z3T^DP#l!1PBNSWRSGDG6V$V$cOz64(6ksR}x$NxWPL}YdJ$e;86d&AVJDh z7ayIF&dO4v5Y>}JhaV50&4F@22#C5k#1|uI2nc3&kT_7)1M)P-H=bzNb=hF*43bPd z5;5Wd?KcIORyio$$-x051%tOwzfIZkO3I^Fe8{BOuqII^yEu`b_AiEMLOX8+N5TvF zx*El(6Mc8^>30keHVQV4qw1H<*;ut|Y1UZz$PgiW5jFs%^pMgY*np0lN$*$IId=YM z&u2%nP1N+Y$AiPev%^E~^V?$*Nc=BPsA&?7tmGo=QCUJ9KsZQc8Ewe?ExE9$ga=g2 zX|nou9@;nB#H0B3c(l>BNw>TB&C(*-L52xC1rw-4S3*WiHH>lHGOgB5Mot8cG$DsE z)uWvL>yUBrY}`^_vOe|<>v%l)!}^4bS~h1C+bB57JK28Kx1`U-XZ}Qag2FjlH_h

B-^J2-2d7W%HLj zUiX<9yDsx)vrKUhKF_k$!^)3IfMJTP;k}o6p(L{L8tE_zyya5j;Ap?Xoo}lTsTliI}TC9lF7wmg93&n1remsmY?Oa;iTbP`TDZF z6m2{0+S%ZtTLM8>X_4$|X-VNct}&{5EuP%e@#x8ppKO?Oy)31a*7T>i#80zT{275%$nEIjf1eUL4e<$9uF7i($@*~t$Tb>eUcjCNFU$xJ z3}Z$aWKIc>2G2|G=QLcJLOEs2Echkh1JTINq5G;^pp7zEpY z8DMk15xrw+dH?gIf978iMgJEP7EuQ^`{|!yq?>$AZyo;;i?oZDv7)0T@^4&pFb8I4 zL%@GF2eZ*O3*jki(Dh>e@}L_@ku#)Jh*pcGT)M zC2`T>GA2l)(~aG(|BN(n18u3j#dOj8?jUZ3}3FRH=( zyvGa;GB8ZZ)K=~RWg`oe=QM92m1gjoL*Ou*fXZ!{tV%WGQu2h~1VIH%*HkPoQu5yY)vrR^ zc)?(Gs`Tn5nf1%Sk^_X1Tpv&%$Rq-k?oq@l-SoO)zUQ=OBD1YS&~~PhDrf5rIAa<( zXa|29aDjLApONL;tcCShzut>NOizXggRrc)7kIrA6>c2;n$j`cnL}x=Xd>6DU36xj zd|5aL*?;P!s@;X{A%iCRO<$s@b&*8Sd7bB66O!QkKBy)3eH{uZZw}mogv5@w{CUC# z;RlaQCM^Wvu90Ot6;T~~Bb3FlCG+Ok+BqJB0frmXxmVjfz#mPoA?3v41En? z*1!mGa4K7@WC!Aku(O^w)m;8=qE5z4cu*|!7QZ2T41PD)z-^Kj`c||%adZHQV^P;e z;J5q(f=e7~`#9~FvENX|HF|8G>2?@WDlAjQ523NAdas^g7kCr9-|kizCsy>#>2UF))G8{QUD_!BF>&>UEgVB zjMwUn*G7Gq(+g)R%*D*eVA)P~a5nwCw@d1TBlnbXJV!1Acoi#_;3ha

kGJ?@15| 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 literal 0 HcmV?d00001 diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/public/vite.svg b/motoko/vetkeys/password_manager_with_metadata/frontend/public/vite.svg new file mode 100644 index 0000000000..e7b8dfb1b2 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/App.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/App.svelte new file mode 100644 index 0000000000..ed34e5e20d --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/App.svelte @@ -0,0 +1,13 @@ + + +{#if $auth.state === "initialized"} + +{:else} + +{/if} + diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/app.css b/motoko/vetkeys/password_manager_with_metadata/frontend/src/app.css new file mode 100644 index 0000000000..b5c61c9567 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/app.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/assets/svelte.svg b/motoko/vetkeys/password_manager_with_metadata/frontend/src/assets/svelte.svg new file mode 100644 index 0000000000..c5e08481f8 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Disclaimer.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Disclaimer.svelte new file mode 100644 index 0000000000..e4065d217b --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Disclaimer.svelte @@ -0,0 +1,26 @@ + + +{#if !isDismissed} +

+

+ +

+ + +
+{/if} diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/DisclaimerCopy.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/DisclaimerCopy.svelte new file mode 100644 index 0000000000..7cf5977604 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/DisclaimerCopy.svelte @@ -0,0 +1,5 @@ + + +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. diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte new file mode 100644 index 0000000000..2f250d92ea --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/EditPassword.svelte @@ -0,0 +1,356 @@ + + +{#if parentVaultName.length > 0} +
+ Edit password + +
+
+ {#if $vaultsStore.state === "loaded"} +
+ + + + + +
+ +
+ Created: {new Date( + Number(originalPassword.metadata.creation_date) / 1000000, + )} +
+
+ Last modified: {new Date( + Number(originalPassword.metadata.last_modification_date) / + 1000000, + )} +
+
+ Number of modifications: {originalPassword.metadata + .number_of_modifications} +
+
+ Last modification by: {originalPassword.metadata.last_modified_principal.toText()} +
+ + Back + + + +
+ {:else if $vaultsStore.state === "loading"} + Loading password... + {/if} +
+{:else} +
+ Edit password +
+
+ {#if $vaultsStore.state === "loading"} + + Loading password... + {:else if $vaultsStore.state === "loaded"} +
Could not find password.
+ {/if} +
+{/if} diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte new file mode 100644 index 0000000000..c671bb0fe9 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/EditVault.svelte @@ -0,0 +1,92 @@ + + +{#if editedVault} +
+ Edit vault + +
+
+ {#if $vaultsStore.state === "loaded"} +
+ + {:else if $vaultsStore.state === "loading"} + Loading vaults... + {/if} +
+{:else} +
+ Edit vault +
+
+ {#if $vaultsStore.state === "loading"} + + Loading vault... + {:else if $vaultsStore.state === "loaded"} +
Could not find vault.
+ {/if} +
+{/if} diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Header.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Header.svelte new file mode 100644 index 0000000000..77e83eb954 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Header.svelte @@ -0,0 +1,27 @@ + + + diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Hero.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Hero.svelte new file mode 100644 index 0000000000..9b58a6ca3b --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Hero.svelte @@ -0,0 +1,60 @@ + + +
+
+
+

+ Password Manager +

+

+ Your private passwords on the Internet Computer. +

+

+ A safe place to store your personal lists, thoughts, ideas or + passphrases and much more... +

+ + {#if auth.state === "initializing-auth"} +
+ + Initializing... +
+ {:else if auth.state === "anonymous"} + + {:else if auth.state === "error"} +
An error occurred.
+ {/if} + +
+ +
+
+
+
+ + Powered by the Internet Computer +
+
diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/LayoutAuthenticated.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/LayoutAuthenticated.svelte new file mode 100644 index 0000000000..090735f212 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/LayoutAuthenticated.svelte @@ -0,0 +1,29 @@ + + + + + diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte new file mode 100644 index 0000000000..4dc1a099e3 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/NewPassword.svelte @@ -0,0 +1,146 @@ + + + + +
+ New password +
+ +
+ +
+ + + + + +
+ + +
diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Notifications.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Notifications.svelte new file mode 100644 index 0000000000..800cc748fd --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Notifications.svelte @@ -0,0 +1,22 @@ + + +
+ {#each $notifications as n (n.id)} +
+

{n.message}

+
+ {/each} +
diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Password.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Password.svelte new file mode 100644 index 0000000000..0bfe573317 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Password.svelte @@ -0,0 +1,107 @@ + + +
+ + Password: {password.passwordName} + + + {#if $vaultsStore.state === "loaded" && $vaultsStore.list.length > 0} + New password + {/if} + +
+ +
+ {#if $vaultsStore.state === "loading"} + + Loading password... + {:else if $vaultsStore.state === "loaded"} + {#if password.parentVaultName === ""} +
+ There is no such password in this vault. +
+ + {:else} +
+
+

+ {password.passwordName}: "{password.content}" +

+
+
+ {/if} +
+ + {/if} +
diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/PasswordEditor.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/PasswordEditor.svelte new file mode 100644 index 0000000000..e8fa33191c --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/PasswordEditor.svelte @@ -0,0 +1,73 @@ + + + +
+ + + + +
+
+ +
+ + diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Passwords.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Passwords.svelte new file mode 100644 index 0000000000..f5355ea706 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Passwords.svelte @@ -0,0 +1,79 @@ + + +
+ Your passwords + + {#if $vaultsStore.state === "loaded" && $vaultsStore.list.length > 0} + New Password + {/if} + +
+
+ {#if $vaultsStore.state === "loading"} + + Loading passwords... + {:else if $vaultsStore.state === "loaded"} + {#if $vaultsStore.list.length > 0} +
+ +
+ +
+ {#each filteredVaults as vault (vault.name)} + {#each Array.from(vault.passwords.map((password) => password[1])) as password ((password.owner, password.parentVaultName, password.passwordName))} + + {/each} + {/each} +
+ {:else} +
You don't have any notes.
+ + {/if} + {:else if $vaultsStore.state === "error"} +
Could not load passwords.
+ {/if} +
diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte new file mode 100644 index 0000000000..f3fe7b5654 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/SharingEditor.svelte @@ -0,0 +1,222 @@ + + +

Users

+{#if canManage} +

+ Add users by their principal to allow them viewing or editing the vault. +

+{:else} +

+ This vault is shared with you. It is + owned by + {editedVault.owner}. +

+

Users with whom the vault is shared:

+{/if} +
+ {#each editedVault.users as sharing (sharing[0].toText())} + + {/each} + + + +
diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte new file mode 100644 index 0000000000..94a1e1fe5a --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/SidebarLayout.svelte @@ -0,0 +1,84 @@ + + +
+ +
+
+ +
+ +
+
+
+
diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Spinner.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Spinner.svelte new file mode 100644 index 0000000000..74904b535a --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Spinner.svelte @@ -0,0 +1,5 @@ + + + diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Vault.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Vault.svelte new file mode 100644 index 0000000000..3783517611 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Vault.svelte @@ -0,0 +1,150 @@ + + +
+ + + + + Vault: {vault.name} + + + {#if $vaultsStore.state === "loaded" && $vaultsStore.list.length > 0} + New password + {/if} + +
+ +
+ {#if $vaultsStore.state === "loading"} + + Loading vault... + {:else if $vaultsStore.state === "loaded"} +
+

+ {vaultSummary} +

+
+
+ + +
+ +
+

Passwords

+
+ {#if vault.passwords.length === 0} +
+ You don't have any passwords in this vault. +
+ + {:else} +
+ {#each vault.passwords as password ((password[1].owner, password[1].parentVaultName, password[1].passwordName))} + +
+

+ {password[1].passwordName}: "{password[1] + .content}" +

+
+
+ {/each} +
+ {/if} +
+ + {/if} +
diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Vaults.svelte b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Vaults.svelte new file mode 100644 index 0000000000..f7f42947cb --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/components/Vaults.svelte @@ -0,0 +1,90 @@ + + +
+ Your vaults + + {#if $vaultsStore.state === "loaded" && $vaultsStore.list.length > 0} + New password + {/if} + +
+
+ {#if $vaultsStore.state === "loading"} + + Loading vaults... + {:else if $vaultsStore.state === "loaded"} + {#if $vaultsStore.list.length > 0} +
+ +
+ +
+ {#each filteredVaults as vault ([vault.owner, vault.name])} + +
+

+ "{vault.name}" owned by {vault.owner} +

+
+
+ {/each} +
+ {:else} +
+ You don't have any vaults. +
+ + {/if} + {:else if $vaultsStore.state === "error"} +
Could not load vaults.
+ {/if} +
diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/encrypted_maps.ts b/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/encrypted_maps.ts new file mode 100644 index 0000000000..6fc0287456 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/encrypted_maps.ts @@ -0,0 +1,22 @@ +import { HttpAgent } from "@icp-sdk/core/agent"; +import { + DefaultEncryptedMapsClient, + EncryptedMaps, +} from "@icp-sdk/vetkeys/encrypted_maps"; +import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"; + +const canisterEnv = safeGetCanisterEnv<{ + "PUBLIC_CANISTER_ID:backend": string; +}>(); + +export function createEncryptedMaps(agent: HttpAgent): EncryptedMaps { + const canisterId = + canisterEnv?.["PUBLIC_CANISTER_ID:backend"]; + if (!canisterId) { + throw new Error( + "Canister ID for backend is not set", + ); + } + + return new EncryptedMaps(new DefaultEncryptedMapsClient(agent, canisterId)); +} diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/init.ts b/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/init.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/password.ts b/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/password.ts new file mode 100644 index 0000000000..84ad07680c --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/password.ts @@ -0,0 +1,31 @@ +import type { Principal } from "@icp-sdk/core/principal"; +import type { PasswordMetadata } from "../bindings/declarations/backend.did"; + +export interface PasswordModel { + owner: Principal; + parentVaultName: string; + passwordName: string; + content: string; + metadata: PasswordMetadata; +} + +export function passwordFromContent( + owner: Principal, + parentVaultName: string, + passwordName: string, + content: string, + metadata: PasswordMetadata, +): PasswordModel { + return { + owner, + parentVaultName, + passwordName, + content, + metadata, + }; +} + +export function summarize(password: PasswordModel, maxLength = 50) { + const text = password.content.replace(/<[^>]+>/, ""); + return text.slice(0, maxLength) + (text.length > maxLength ? "..." : ""); +} diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/password_manager.ts b/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/password_manager.ts new file mode 100644 index 0000000000..131eacd51d --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/password_manager.ts @@ -0,0 +1,182 @@ +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 "../bindings/declarations/backend.did"; +import { createEncryptedMaps } from "./encrypted_maps"; +import { passwordFromContent, type PasswordModel } from "../lib/password"; +import { vaultFromContent, type VaultModel } from "../lib/vault"; + +const canisterEnv = safeGetCanisterEnv<{ + "PUBLIC_CANISTER_ID:backend": string; +}>(); + +export class PasswordManager { + /// The actor class representing the full interface of the canister. + private readonly canisterClient: ActorSubclass<_SERVICE>; + // TODO: inaccessible API are get, instert and remove + readonly encryptedMaps: EncryptedMaps; + + constructor( + canisterClient: ActorSubclass<_SERVICE>, + encryptedMaps: EncryptedMaps, + ) { + this.canisterClient = canisterClient; + this.encryptedMaps = encryptedMaps; + } + + async setPassword( + owner: Principal, + vault: string, + passwordName: string, + password: Uint8Array, + tags: string[], + url: string, + ): Promise<{ Ok: null } | { Err: string }> { + const encryptedPassword = await this.encryptedMaps.encryptFor( + owner, + new TextEncoder().encode(vault), + new TextEncoder().encode(passwordName), + password, + ); + const maybeError = + await this.canisterClient.insert_encrypted_value_with_metadata( + owner, + stringToBytebuf(vault), + stringToBytebuf(passwordName), + { inner: encryptedPassword }, + tags, + url, + ); + if ("Err" in maybeError) { + return maybeError; + } else { + return { Ok: null }; + } + } + + async getDecryptedVaults(owner: Principal): Promise { + const vaultsSharedWithMe = + await this.encryptedMaps.getAccessibleSharedMapNames(); + const vaultsOwnedByMeResult = + await this.encryptedMaps.getOwnedNonEmptyMapNames(); + + const vaultIds = new Array<[Principal, Uint8Array]>(); + for (const vaultName of vaultsOwnedByMeResult) { + vaultIds.push([owner, vaultName]); + } + for (const [otherOwner, vaultName] of vaultsSharedWithMe) { + vaultIds.push([otherOwner, vaultName]); + } + + const vaults = []; + + for (const [otherOwner, vaultName] of vaultIds) { + const result = + await this.canisterClient.get_encrypted_values_for_map_with_metadata( + otherOwner, + { inner: vaultName }, + ); + if ("Err" in result) { + throw new Error(result.Err); + } + + const passwords = new Array<[string, PasswordModel]>(); + const vaultNameString = new TextDecoder().decode(vaultName); + for (const [ + passwordNameBytebuf, + encryptedData, + passwordMetadata, + ] of result.Ok) { + const passwordNameBytes = Uint8Array.from( + passwordNameBytebuf.inner, + ); + const passwordNameString = new TextDecoder().decode( + passwordNameBytes, + ); + const data = await this.encryptedMaps.decryptFor( + otherOwner, + vaultName, + passwordNameBytes, + Uint8Array.from(encryptedData.inner), + ); + + const passwordContent = new TextDecoder().decode(data); + const password = passwordFromContent( + otherOwner, + vaultNameString, + passwordNameString, + passwordContent, + passwordMetadata, + ); + passwords.push([passwordNameString, password]); + } + + const usersResult = await this.encryptedMaps + .getSharedUserAccessForMap(otherOwner, vaultName) + .catch(() => []); + + vaults.push( + vaultFromContent( + otherOwner, + vaultNameString, + passwords, + usersResult, + ), + ); + } + + return vaults; + } + + async removePassword( + owner: Principal, + vault: string, + passwordName: string, + ): Promise<{ Ok: null } | { Err: string }> { + const maybeError = + await this.canisterClient.remove_encrypted_value_with_metadata( + owner, + stringToBytebuf(vault), + stringToBytebuf(passwordName), + ); + if ("Err" in maybeError) { + return maybeError; + } else { + return { Ok: null }; + } + } +} + +export async function createPasswordManager(agentOptions?: { + identity?: HttpAgent["config"]["identity"]; +}): Promise { + const canisterId = + canisterEnv?.["PUBLIC_CANISTER_ID:backend"]; + if (!canisterId) { + throw new Error( + "Canister ID for backend is not set", + ); + } + + const agent = await HttpAgent.create({ + ...agentOptions, + host: window.location.origin, + rootKey: canisterEnv?.IC_ROOT_KEY, + }); + + const encryptedMaps = createEncryptedMaps(agent); + const canisterClient = Actor.createActor<_SERVICE>(idlFactory, { + agent, + canisterId, + }); + + return new PasswordManager(canisterClient, encryptedMaps); +} + +function stringToBytebuf(str: string): { inner: Uint8Array } { + return { inner: new TextEncoder().encode(str) }; +} diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/sleep.ts b/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/sleep.ts new file mode 100644 index 0000000000..38caca0c10 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/sleep.ts @@ -0,0 +1,3 @@ +export function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/vault.ts b/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/vault.ts new file mode 100644 index 0000000000..49d302a8d5 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/lib/vault.ts @@ -0,0 +1,31 @@ +import type { Principal } from "@icp-sdk/core/principal"; +import type { PasswordModel } from "./password"; +import type { AccessRights } from "@icp-sdk/vetkeys/encrypted_maps"; + +export interface VaultModel { + owner: Principal; + name: string; + passwords: Array<[string, PasswordModel]>; + users: Array<[Principal, AccessRights]>; +} + +export function vaultFromContent( + owner: Principal, + name: string, + passwords: Array<[string, PasswordModel]>, + users: Array<[Principal, AccessRights]>, +): VaultModel { + return { owner, name, passwords, users }; +} + +export function summarize(vault: VaultModel, maxLength = 1500) { + let text = + "Owner: " + + vault.owner.toString() + + ", " + + vault.users.length + + " users"; + text += ", " + vault.passwords.length + " passwords.\n"; + + return text.slice(0, maxLength) + (text.length > maxLength ? "..." : ""); +} diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/main.ts b/motoko/vetkeys/password_manager_with_metadata/frontend/src/main.ts new file mode 100644 index 0000000000..336504f610 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/main.ts @@ -0,0 +1,11 @@ +import "./app.css"; +import App from "./App.svelte"; + +const init = () => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const app = new App({ + target: document.body, + }); +}; + +init(); diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts b/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts new file mode 100644 index 0000000000..1f54fb574e --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/auth.ts @@ -0,0 +1,130 @@ +import { get, writable } from "svelte/store"; +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"; +import { + PasswordManager, + createPasswordManager, +} from "../lib/password_manager.js"; + +export type AuthState = + | { + state: "initializing-auth"; + } + | { + state: "anonymous"; + client: AuthClient; + } + | { + state: "initialized"; + passwordManager: PasswordManager; + client: AuthClient; + principal: Principal; + } + | { + state: "error"; + error: string; + }; + +export const auth = writable({ + state: "initializing-auth", +}); + +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); + } else { + auth.update(() => ({ + state: "anonymous", + client, + })); + } +} + +void initAuth(); + +export function login() { + const currentAuth = get(auth); + + if (currentAuth.state === "anonymous") { + 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); + } + })(); + } +} + +export async function logout() { + const currentAuth = get(auth); + + if (currentAuth.state === "initialized") { + await currentAuth.client.signOut(); + auth.update(() => ({ + state: "anonymous", + client: currentAuth.client, + })); + await replace("/"); + } +} + +export async function authenticate(client: AuthClient) { + void handleSessionTimeout(client); + + try { + const identity = await client.getIdentity(); + const passwordManager = await createPasswordManager({ identity }); + + auth.update(() => ({ + state: "initialized", + passwordManager, + client, + principal: identity.getPrincipal(), + })); + } catch (e) { + auth.update(() => ({ + state: "error", + error: (e as Error).message || "An error occurred", + })); + } +} + +// set a timer when the II session will expire and log the user out +async function handleSessionTimeout(client: AuthClient) { + try { + const identity = await client.getIdentity(); + if (!(identity instanceof DelegationIdentity)) return; + + 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(); + }, expirationMs - Date.now()); + } catch { + console.error("Could not handle delegation expiry."); + } +} diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/draft.ts b/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/draft.ts new file mode 100644 index 0000000000..85a8ec2e40 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/draft.ts @@ -0,0 +1,36 @@ +import { writable } from "svelte/store"; +import { auth } from "./auth"; + +interface DraftModel { + content: string; +} + +let initialDraft: DraftModel = { + content: "", +}; + +try { + const getDraft = localStorage.getItem("draft"); + if (getDraft) { + const savedDraft = JSON.parse(getDraft) as DraftModel; + if ("content" in savedDraft && "tags" in savedDraft) { + initialDraft = savedDraft; + } + } else { + throw new Error("Draft not found"); + } +} catch { + // ignore error +} + +export const draft = writable(initialDraft); + +draft.subscribe((draft) => { + localStorage.setItem("draft", JSON.stringify(draft)); +}); + +auth.subscribe(($auth) => { + if ($auth.state === "anonymous") { + draft.set(initialDraft); + } +}); diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/notifications.ts b/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/notifications.ts new file mode 100644 index 0000000000..d876301531 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/notifications.ts @@ -0,0 +1,30 @@ +import { writable } from "svelte/store"; + +export interface Notification { + type: "error" | "info" | "success"; + message: string; + id: number; +} + +export type NewNotification = Omit; + +let nextId = 0; + +export const notifications = writable([]); + +export function addNotification(notification: NewNotification, timeout = 2000) { + const id = nextId++; + + notifications.update(($n) => [...$n, { ...notification, id }]); + + setTimeout(() => { + notifications.update(($n) => $n.filter((n) => n.id != id)); + }, timeout); +} + +export function showError(e: Error, message: string): never { + addNotification({ type: "error", message }); + console.error(e); + console.error(e.stack); + throw e; +} diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts b/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts new file mode 100644 index 0000000000..f56f5dcb8d --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/store/vaults.ts @@ -0,0 +1,147 @@ +import { writable } from "svelte/store"; +import { type PasswordModel } from "../lib/password"; +import { type VaultModel } from "../lib/vault"; +import { auth } from "./auth"; +import { showError } from "./notifications"; +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< + | { + state: "uninitialized"; + } + | { + state: "loading"; + } + | { + state: "loaded"; + list: VaultModel[]; + } + | { + state: "error"; + } +>({ state: "uninitialized" }); + +let vaultPollerHandle: ReturnType | null; + +function updateVaults(vaults: VaultModel[]) { + vaultsStore.set({ + state: "loaded", + list: vaults, + }); +} + +export async function refreshVaults( + owner: Principal, + passwordManager: PasswordManager, +) { + updateVaults(await passwordManager.getDecryptedVaults(owner)); +} + +export async function setPassword( + parentVaultOwner: Principal, + parentVaultName: string, + passwordName: string, + password: string, + url: string, + tags: string[], + passwordManager: PasswordManager, +) { + const result = await passwordManager.setPassword( + parentVaultOwner, + parentVaultName, + passwordName, + new TextEncoder().encode(password), + tags, + url, + ); + if ("Err" in result) { + throw new Error(result.Err); + } +} + +export async function removePassword( + password: PasswordModel, + passwordManager: PasswordManager, +) { + const result = await passwordManager.removePassword( + password.owner, + password.parentVaultName, + password.passwordName, + ); + if ("Err" in result) { + throw new Error(result.Err); + } +} + +export async function addUser( + owner: Principal, + vaultName: string, + user: Principal, + userRights: AccessRights, + passwordManager: PasswordManager, +) { + await passwordManager.encryptedMaps.setUserRights( + owner, + new TextEncoder().encode(vaultName), + user, + userRights, + ); +} + +export async function removeUser( + owner: Principal, + vaultName: string, + user: Principal, + passwordManager: PasswordManager, +) { + await passwordManager.encryptedMaps.removeUser( + owner, + new TextEncoder().encode(vaultName), + user, + ); +} + +auth.subscribe((auth) => { + if (!auth) { + throw Error("$auth is undefined"); + } + if (auth.state === "initialized") { + if (vaultPollerHandle !== null) { + clearInterval(vaultPollerHandle); + vaultPollerHandle = null; + } + + vaultsStore.set({ + state: "loading", + }); + + void (async () => { + try { + await refreshVaults(auth.principal, auth.passwordManager).catch( + (e) => showError(e as Error, "Could not poll vaults."), + ); + + vaultPollerHandle = setInterval(() => { + void refreshVaults( + auth.principal, + auth.passwordManager, + ).catch((e) => + showError(e as Error, "Could not poll vaults."), + ); + }, 3000); + } catch { + vaultsStore.set({ + state: "error", + }); + } + })(); + } else if (auth.state === "anonymous" && vaultPollerHandle !== null) { + clearInterval(vaultPollerHandle); + vaultPollerHandle = null; + vaultsStore.set({ + state: "uninitialized", + }); + } +}); diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/src/vite-env.d.ts b/motoko/vetkeys/password_manager_with_metadata/frontend/src/vite-env.d.ts new file mode 100644 index 0000000000..4078e7476a --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/svelte.config.js b/motoko/vetkeys/password_manager_with_metadata/frontend/svelte.config.js new file mode 100644 index 0000000000..9a3adfbed9 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/svelte.config.js @@ -0,0 +1,7 @@ +import { vitePreprocess } from "@sveltejs/vite-plugin-svelte"; + +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), +}; diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/tailwind.config.mjs b/motoko/vetkeys/password_manager_with_metadata/frontend/tailwind.config.mjs new file mode 100644 index 0000000000..18a39dbc2f --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/tailwind.config.mjs @@ -0,0 +1,9 @@ +import daisyui from "daisyui"; + +export default { + content: ["./index.html", "./src/**/*.{svelte,js,ts,jsx,tsx}"], + theme: { + extend: {}, + }, + plugins: [daisyui], +}; diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/tsconfig.json b/motoko/vetkeys/password_manager_with_metadata/frontend/tsconfig.json new file mode 100644 index 0000000000..ba869ee473 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ES2022", + "lib": [ + "ES2022", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "noUncheckedSideEffectImports": true + }, + "include": [ + "src" + ] +} diff --git a/motoko/vetkeys/password_manager_with_metadata/frontend/vite.config.js b/motoko/vetkeys/password_manager_with_metadata/frontend/vite.config.js new file mode 100644 index 0000000000..169539f3bb --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/frontend/vite.config.js @@ -0,0 +1,67 @@ +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import tailwindcss from "tailwindcss"; +import autoprefixer from "autoprefixer"; +import css from "rollup-plugin-css-only"; +import { icpBindgen } from "@icp-sdk/bindgen/plugins/vite"; +import { execSync } from "child_process"; + +function getDevServerConfig() { + try { + const canisterId = execSync("icp canister status backend -e local -i", { + encoding: "utf-8", + stdio: "pipe", + }).trim(); + const networkStatus = JSON.parse( + execSync("icp network status --json", { + encoding: "utf-8", + stdio: "pipe", + }) + ); + return { + headers: { + "Set-Cookie": `ic_env=${encodeURIComponent( + `ic_root_key=${networkStatus.root_key}&PUBLIC_CANISTER_ID:backend=${canisterId}` + )}; SameSite=Lax;`, + }, + proxy: { + "/api": { target: "http://127.0.0.1:8000", changeOrigin: true }, + }, + }; + } catch {} + + throw new Error( + "No local network running. Start with:\n icp network start -d && icp deploy" + ); +} + +export default defineConfig(({ command }) => ({ + base: "./", + plugins: [ + svelte(), + css({ output: "bundle.css" }), + icpBindgen({ + didFile: "../backend/backend.did", + outDir: "./src/bindings", + }), + ], + css: { + postcss: { + plugins: [autoprefixer(), tailwindcss()], + }, + }, + build: { + sourcemap: true, + rollupOptions: { + output: { + inlineDynamicImports: true, + }, + }, + }, + resolve: { + alias: { + "@dfinity/vetkeys": "@icp-sdk/vetkeys", + }, + }, + server: command === "serve" ? getDevServerConfig() : undefined, +})); diff --git a/rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml b/motoko/vetkeys/password_manager_with_metadata/icp.yaml similarity index 60% rename from rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml rename to motoko/vetkeys/password_manager_with_metadata/icp.yaml index 09bb4252c5..ba5979ac3f 100644 --- a/rust/vetkeys/password_manager_with_metadata/motoko/icp.yaml +++ b/motoko/vetkeys/password_manager_with_metadata/icp.yaml @@ -1,18 +1,19 @@ canisters: - - name: password_manager_with_metadata + - name: backend recipe: type: "@dfinity/motoko@v5.0.0" init_args: type: text value: "(\"test_key_1\")" - - name: www + - name: frontend recipe: type: "@dfinity/asset-canister@v2.2.1" configuration: - dir: dist + dir: frontend/dist build: - - cd frontend && npm i --include=dev && npm run build && cd - && rm -rf dist; mv frontend/dist ./ + - npm install --prefix frontend + - npm run build --prefix frontend networks: - name: local diff --git a/motoko/vetkeys/password_manager_with_metadata/mops.toml b/motoko/vetkeys/password_manager_with_metadata/mops.toml new file mode 100644 index 0000000000..5dae136934 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/mops.toml @@ -0,0 +1,16 @@ +[toolchain] +moc = "1.11.0" + +[dependencies] +core = "2.5.0" +ic-vetkeys = "0.5.0" + +[moc] +# M0236: use context dot notation +# M0237: redundant explicit implicit arguments +# M0223: redundant type instantiation +args = ["--default-persistent-actors", "-W=M0236,M0237,M0223"] + +[canisters.backend] +main = "backend/app.mo" +candid = "backend/backend.did" diff --git a/motoko/vetkeys/password_manager_with_metadata/package.json b/motoko/vetkeys/password_manager_with_metadata/package.json new file mode 100644 index 0000000000..a874aee1a8 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/package.json @@ -0,0 +1,13 @@ +{ + "name": "password_manager_with_metadata", + "private": true, + "type": "module", + "scripts": { + "build": "npm run build --workspaces --if-present", + "prebuild": "npm run prebuild --workspaces --if-present", + "dev": "npm run dev --workspaces --if-present" + }, + "workspaces": [ + "frontend" + ] +} diff --git a/motoko/vetkeys/password_manager_with_metadata/test.sh b/motoko/vetkeys/password_manager_with_metadata/test.sh new file mode 100755 index 0000000000..b7d60d69e5 --- /dev/null +++ b/motoko/vetkeys/password_manager_with_metadata/test.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -e + +echo "--- Testing password_manager_with_metadata: vetkey verification key ---" +result=$(icp canister call backend get_vetkey_verification_key '()') && \ + echo "$result" | head -1 && \ + echo "$result" | grep -q 'blob' && \ + echo "PASS" || (echo "FAIL" && exit 1) + +echo "--- Testing password_manager_with_metadata: owned map names (query) ---" +result=$(icp canister call backend get_owned_non_empty_map_names '()' --query) && \ + echo "$result" && \ + echo "$result" | grep -q 'vec' && \ + echo "PASS" || (echo "FAIL" && exit 1) diff --git a/rust/vetkeys/password_manager_with_metadata/rust/Cargo.toml b/rust/vetkeys/password_manager_with_metadata/Cargo.toml similarity index 100% rename from rust/vetkeys/password_manager_with_metadata/rust/Cargo.toml rename to rust/vetkeys/password_manager_with_metadata/Cargo.toml diff --git a/rust/vetkeys/password_manager_with_metadata/README.md b/rust/vetkeys/password_manager_with_metadata/README.md index 719685c3a1..ac6bcdd53b 100644 --- a/rust/vetkeys/password_manager_with_metadata/README.md +++ b/rust/vetkeys/password_manager_with_metadata/README.md @@ -1,14 +1,12 @@ -# VetKey Password Manager with Metadata +# VetKey Password Manager with Metadata (Rust) - +[View this sample's code on GitHub](https://github.com/dfinity/examples/tree/master/rust/vetkeys/password_manager_with_metadata) + +Also available in: [Motoko](../../../motoko/vetkeys/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**. -This version of the application extends the basic password manager by supporting unencrypted metadata, such as URLs and tags, alongside encrypted passwords. The goal is to demonstrate how to make atomic updates to the Encrypted Maps canister, storing both encrypted and unencrypted data in a single update call. +This version extends the basic password manager by supporting unencrypted metadata, such as URLs and tags, alongside encrypted passwords. The goal is to demonstrate how to make atomic updates to the Encrypted Maps canister, storing both encrypted and unencrypted data in a single update call. ## Features @@ -17,71 +15,62 @@ This version of the application extends the basic password manager by supporting - **Access Control**: Vaults can be shared with other users via their **Internet Identity Principal**. - **Atomic Updates**: Stores encrypted passwords along with unencrypted metadata in a single update call. -## Setup +## Build and deploy from the command line ### Prerequisites -- [ICP CLI](https://cli.internetcomputer.org) -- [npm](https://www.npmjs.com/package/npm) - -### (Optionally) Choose a Different Master Key +- Install [Node.js](https://nodejs.org/en/download/) +- Install [icp-cli](https://cli.internetcomputer.org): `npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm` +- Install the [Rust toolchain](https://www.rust-lang.org/tools/install), then add the WASM target: `rustup target add wasm32-unknown-unknown` -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. +### (Optionally) choose a different master key -### Folder Structure +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` before deploying. -This example provides both a **Rust** and a **Motoko** backend, sharing a common `frontend/`: +### Install -``` -password_manager_with_metadata/ -├── frontend/ ← shared frontend (symlinked into rust/ and motoko/) -├── motoko/ ← Motoko backend + icp.yaml -└── rust/ ← Rust backend + icp.yaml +```bash +git clone https://github.com/dfinity/examples +cd examples/rust/vetkeys/password_manager_with_metadata ``` -### Deploy the Canisters Locally +### Deploy -Deploy with the **Motoko** backend: ```bash -cd motoko -icp network start -d && icp deploy +icp network start -d +icp deploy ``` -Or deploy with the **Rust** backend: -```bash -cd rust -icp network start -d && icp deploy -``` +Open the frontend URL printed by `icp deploy`. + +To run the frontend in development mode with hot reloading (after `icp deploy`): -To run the frontend in development mode with hot reloading (after running `icp deploy`): ```bash -cd frontend -npm run dev:motoko # if you deployed the Motoko backend -# or -npm run dev:rust # if you deployed the Rust backend +npm run dev ``` -When you are done testing, stop the local network to free up resources and unblock the default port for other projects: +When done, stop the local network to free up the port for other projects: + ```bash icp network stop ``` -## Example Components +## Example components -### Backend +### Backend (`backend/`) -The backend consists of an **Encrypted Maps**-enabled canister that securely stores passwords. +An **Encrypted Maps**-enabled Rust canister that stores encrypted passwords together with unencrypted metadata (URLs, tags) in atomic update calls. -### Frontend +### Frontend (`frontend/`) -The frontend is a **Svelte** application providing a user-friendly interface for managing vaults and passwords. +A **Svelte** application for managing vaults and passwords. It uses the `@icp-sdk/vetkeys` Encrypted Maps client for the crypto operations and a canister actor (bindings generated from `backend/backend.did` by the `@icp-sdk/bindgen` Vite plugin) for the metadata methods. ## Limitations -This example dapp does not implement key rotation, which is strongly recommended in a production environment. -Key rotation involves periodically changing encryption keys and re-encrypting data to enhance security. -In a production dapp, key rotation would be useful to limit the impact of potential key compromise if a malicious party gains access to a key, or to limit access when users are added or removed from note sharing. +This example dapp does not implement key rotation, which is strongly recommended in a production environment. Key rotation involves periodically changing encryption keys and re-encrypting data to enhance security. In a production dapp, key rotation would be useful to limit the impact of a potential key compromise, or to limit access when users are added to or removed from sharing. -## Additional Resources +## Additional resources -- **[Basic Password Manager](../password_manager/)** - If you want a simpler example without metadata. +- **[Basic Password Manager](../password_manager)** — a simpler example without metadata. +- **[What are VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** — more information about VetKeys and VetKD. +- [Security best practices](https://docs.internetcomputer.org/guides/security/overview) diff --git a/rust/vetkeys/password_manager_with_metadata/rust/backend/Cargo.toml b/rust/vetkeys/password_manager_with_metadata/backend/Cargo.toml similarity index 100% rename from rust/vetkeys/password_manager_with_metadata/rust/backend/Cargo.toml rename to rust/vetkeys/password_manager_with_metadata/backend/Cargo.toml diff --git a/rust/vetkeys/password_manager_with_metadata/rust/backend/Makefile b/rust/vetkeys/password_manager_with_metadata/backend/Makefile similarity index 100% rename from rust/vetkeys/password_manager_with_metadata/rust/backend/Makefile rename to rust/vetkeys/password_manager_with_metadata/backend/Makefile diff --git a/rust/vetkeys/password_manager_with_metadata/rust/backend/backend.did b/rust/vetkeys/password_manager_with_metadata/backend/backend.did similarity index 100% rename from rust/vetkeys/password_manager_with_metadata/rust/backend/backend.did rename to rust/vetkeys/password_manager_with_metadata/backend/backend.did diff --git a/rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs b/rust/vetkeys/password_manager_with_metadata/backend/src/lib.rs similarity index 100% rename from rust/vetkeys/password_manager_with_metadata/rust/backend/src/lib.rs rename to rust/vetkeys/password_manager_with_metadata/backend/src/lib.rs diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/.gitignore b/rust/vetkeys/password_manager_with_metadata/frontend/.gitignore new file mode 100644 index 0000000000..061e2e66e1 --- /dev/null +++ b/rust/vetkeys/password_manager_with_metadata/frontend/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +src/bindings/ diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/.prettierignore b/rust/vetkeys/password_manager_with_metadata/frontend/.prettierignore deleted file mode 100644 index 187eec9533..0000000000 --- a/rust/vetkeys/password_manager_with_metadata/frontend/.prettierignore +++ /dev/null @@ -1,6 +0,0 @@ -# Ignore artifacts: -build -coverage -dist -README.md -**/declarations/ diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/.prettierrc b/rust/vetkeys/password_manager_with_metadata/frontend/.prettierrc deleted file mode 100644 index 2b9bd83ee0..0000000000 --- a/rust/vetkeys/password_manager_with_metadata/frontend/.prettierrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "plugins": ["prettier-plugin-svelte"], - "tabWidth": 4, - "overrides": [{ "files": ["*.svelte"], "options": { "parser": "svelte" } }] -} diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/eslint.config.mjs b/rust/vetkeys/password_manager_with_metadata/frontend/eslint.config.mjs deleted file mode 100644 index 1606408854..0000000000 --- a/rust/vetkeys/password_manager_with_metadata/frontend/eslint.config.mjs +++ /dev/null @@ -1,55 +0,0 @@ -// @ts-check - -import eslint from "@eslint/js"; -import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; -import globals from "globals"; -import tseslint from "typescript-eslint"; -import svelteConfig from "./svelte.config.js"; -import svelte from "eslint-plugin-svelte"; - -export default tseslint.config( - eslint.configs.recommended, - tseslint.configs.recommendedTypeChecked, - ...svelte.configs.recommended, - eslintPluginPrettierRecommended, - { - languageOptions: { - parserOptions: { - projectService: { - defaultProject: "./tsconfig.json", - }, - tsconfigRootDir: import.meta.dirname, - }, - globals: { - ...globals.browser, - ...globals.es2020, - }, - }, - }, - { - files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"], - languageOptions: { - parserOptions: { - projectService: true, - extraFileExtensions: [".svelte"], - parser: tseslint.parser, - svelteConfig, - }, - }, - }, - { - ignores: [ - "dist/", - "src/declarations", - "*.config.js", - "*.config.cjs", - "*.config.mjs", - ], - }, - { - rules: { - "@typescript-eslint/no-unsafe-argument": "off", - "@typescript-eslint/no-unsafe-member-access": "off", - }, - }, -); diff --git a/rust/vetkeys/password_manager_with_metadata/frontend/package.json b/rust/vetkeys/password_manager_with_metadata/frontend/package.json index a50afea070..adaf711ac4 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/package.json +++ b/rust/vetkeys/password_manager_with_metadata/frontend/package.json @@ -1,52 +1,35 @@ { - "name": "password-manager-with-metadata-frontend", - "version": "0.1.0", - "license": "Apache-2.0", - "type": "module", - "scripts": { - "build": "npm run build:bindings && vite build", - "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 .", - "prettier-check": "prettier --check .", - "preview": "vite preview" - }, - "devDependencies": { - "@eslint/js": "^9.22.0", - "@rollup/plugin-typescript": "^12.1.2", - "@tailwindcss/postcss": "^4.0.6", - "@tailwindcss/vite": "^4.0.0", - "@tsconfig/svelte": "^5.0.4", - "@typewriter/delta": "^1.2.4", - "autoprefixer": "^10.4.20", - "eslint": "^9.22.0", - "eslint-config-prettier": "^10.1.5", - "eslint-plugin-prettier": "^5.2.5", - "eslint-plugin-svelte": "^3.3.3", - "globals": "^16.0.0", - "prettier": "3.5.3", - "prettier-plugin-svelte": "^3.3.3", - "rollup-plugin-css-only": "^4.5.2", - "tslib": "^2.8.1", - "typescript-eslint": "^8.26.1", - "vite": "^5.4.21", - "vite-plugin-compression": "^0.5.1", - "vite-plugin-eslint": "^1.8.1" - }, - "dependencies": { - "@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", - "svelte": "^4.2.19", - "svelte-icons": "^2.1.0", - "svelte-spa-router": "^4.0.1", - "tailwindcss": "^3.0.17", - "typewriter-editor": "^0.9.4" - } + "name": "frontend", + "private": true, + "type": "module", + "scripts": { + "prebuild": "npm i --include=dev", + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@icp-sdk/bindgen": "~0.2.2", + "@rollup/plugin-typescript": "^12.1.2", + "@tailwindcss/postcss": "^4.0.6", + "@tailwindcss/vite": "^4.0.0", + "@tsconfig/svelte": "^5.0.4", + "@typewriter/delta": "^1.2.4", + "autoprefixer": "^10.4.20", + "rollup-plugin-css-only": "^4.5.2", + "tslib": "^2.8.1", + "vite": "^5.4.21" + }, + "dependencies": { + "@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", + "svelte": "^4.2.19", + "svelte-icons": "^2.1.0", + "svelte-spa-router": "^4.0.1", + "tailwindcss": "^3.0.17", + "typewriter-editor": "^0.9.4" + } } 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 deleted file mode 100755 index 624b678351..0000000000 --- a/rust/vetkeys/password_manager_with_metadata/frontend/scripts/gen_bindings.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -# Bindings are always generated from the Rust backend since both backends -# expose the same Candid interface. - -# 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 -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/frontend/src/lib/encrypted_maps.ts b/rust/vetkeys/password_manager_with_metadata/frontend/src/lib/encrypted_maps.ts index e464c683c8..6fc0287456 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 @@ -6,15 +6,15 @@ import { import { safeGetCanisterEnv } from "@icp-sdk/core/agent/canister-env"; const canisterEnv = safeGetCanisterEnv<{ - "PUBLIC_CANISTER_ID:password_manager_with_metadata": string; + "PUBLIC_CANISTER_ID:backend": string; }>(); export function createEncryptedMaps(agent: HttpAgent): EncryptedMaps { const canisterId = - canisterEnv?.["PUBLIC_CANISTER_ID:password_manager_with_metadata"]; + canisterEnv?.["PUBLIC_CANISTER_ID:backend"]; if (!canisterId) { throw new Error( - "Canister ID for password_manager_with_metadata is not set", + "Canister ID for backend is not set", ); } 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 ff7d78287a..84ad07680c 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 "@icp-sdk/core/principal"; -import type { PasswordMetadata } from "../declarations/password_manager_with_metadata/backend.did"; +import type { PasswordMetadata } from "../bindings/declarations/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 fe216a6383..131eacd51d 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 @@ -5,13 +5,13 @@ import { EncryptedMaps } from "@icp-sdk/vetkeys/encrypted_maps"; import { idlFactory, type _SERVICE, -} from "../declarations/password_manager_with_metadata/backend.did"; +} from "../bindings/declarations/backend.did"; import { createEncryptedMaps } from "./encrypted_maps"; 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; + "PUBLIC_CANISTER_ID:backend": string; }>(); export class PasswordManager { @@ -155,10 +155,10 @@ export async function createPasswordManager(agentOptions?: { identity?: HttpAgent["config"]["identity"]; }): Promise { const canisterId = - canisterEnv?.["PUBLIC_CANISTER_ID:password_manager_with_metadata"]; + canisterEnv?.["PUBLIC_CANISTER_ID:backend"]; if (!canisterId) { throw new Error( - "Canister ID for password_manager_with_metadata is not defined", + "Canister ID for backend is not set", ); } 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 ca53dc054a..169539f3bb 100644 --- a/rust/vetkeys/password_manager_with_metadata/frontend/vite.config.js +++ b/rust/vetkeys/password_manager_with_metadata/frontend/vite.config.js @@ -1,56 +1,49 @@ import { defineConfig } from "vite"; import { svelte } from "@sveltejs/vite-plugin-svelte"; -import eslint from "vite-plugin-eslint"; import tailwindcss from "tailwindcss"; import autoprefixer from "autoprefixer"; import css from "rollup-plugin-css-only"; -import viteCompression from "vite-plugin-compression"; +import { icpBindgen } from "@icp-sdk/bindgen/plugins/vite"; 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`.", + try { + const canisterId = execSync("icp canister status backend -e local -i", { + encoding: "utf-8", + stdio: "pipe", + }).trim(); + const networkStatus = JSON.parse( + execSync("icp network status --json", { + encoding: "utf-8", + stdio: "pipe", + }) ); - } + return { + headers: { + "Set-Cookie": `ic_env=${encodeURIComponent( + `ic_root_key=${networkStatus.root_key}&PUBLIC_CANISTER_ID:backend=${canisterId}` + )}; SameSite=Lax;`, + }, + proxy: { + "/api": { target: "http://127.0.0.1:8000", changeOrigin: true }, + }, + }; + } catch {} - const networkStatus = JSON.parse( - execSync( - `icp network status -e ${environment} --json --project-root-override ../${backend}`, - { encoding: "utf-8" }, - ), + throw new Error( + "No local network running. Start with:\n icp network start -d && icp deploy" ); - 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(({ command }) => ({ + base: "./", plugins: [ svelte(), css({ output: "bundle.css" }), - eslint(), - viteCompression(), + icpBindgen({ + didFile: "../backend/backend.did", + outDir: "./src/bindings", + }), ], css: { postcss: { @@ -58,18 +51,17 @@ export default defineConfig(({ command }) => ({ }, }, build: { + sourcemap: true, rollupOptions: { output: { inlineDynamicImports: true, }, }, - sourcemap: true, }, - root: "./", resolve: { alias: { "@dfinity/vetkeys": "@icp-sdk/vetkeys", }, }, - ...(command === "serve" ? { server: getDevServerConfig() } : {}), + server: command === "serve" ? getDevServerConfig() : undefined, })); diff --git a/rust/vetkeys/password_manager_with_metadata/rust/icp.yaml b/rust/vetkeys/password_manager_with_metadata/icp.yaml similarity index 69% rename from rust/vetkeys/password_manager_with_metadata/rust/icp.yaml rename to rust/vetkeys/password_manager_with_metadata/icp.yaml index 332199d440..6692c31378 100644 --- a/rust/vetkeys/password_manager_with_metadata/rust/icp.yaml +++ b/rust/vetkeys/password_manager_with_metadata/icp.yaml @@ -1,5 +1,5 @@ canisters: - - name: password_manager_with_metadata + - name: backend recipe: type: "@dfinity/rust@v3.2.0" configuration: @@ -9,13 +9,14 @@ canisters: type: text value: "(\"test_key_1\")" - - name: www + - name: frontend recipe: type: "@dfinity/asset-canister@v2.2.1" configuration: - dir: dist + dir: frontend/dist build: - - cd frontend && npm i --include=dev && npm run build && cd - && rm -rf dist; mv frontend/dist ./ + - npm install --prefix frontend + - npm run build --prefix frontend networks: - name: local diff --git a/rust/vetkeys/password_manager_with_metadata/motoko/frontend b/rust/vetkeys/password_manager_with_metadata/motoko/frontend deleted file mode 120000 index af288785f3..0000000000 --- a/rust/vetkeys/password_manager_with_metadata/motoko/frontend +++ /dev/null @@ -1 +0,0 @@ -../frontend \ No newline at end of file diff --git a/rust/vetkeys/password_manager_with_metadata/motoko/mops.toml b/rust/vetkeys/password_manager_with_metadata/motoko/mops.toml deleted file mode 100644 index dff61c8753..0000000000 --- a/rust/vetkeys/password_manager_with_metadata/motoko/mops.toml +++ /dev/null @@ -1,8 +0,0 @@ -[toolchain] -moc = "1.9.0" - -[dependencies] -core = "2.5.0" -ic-vetkeys = "0.5.0" -[canisters.password_manager_with_metadata] -main = "backend/src/Main.mo" diff --git a/rust/vetkeys/password_manager_with_metadata/package.json b/rust/vetkeys/password_manager_with_metadata/package.json new file mode 100644 index 0000000000..a874aee1a8 --- /dev/null +++ b/rust/vetkeys/password_manager_with_metadata/package.json @@ -0,0 +1,13 @@ +{ + "name": "password_manager_with_metadata", + "private": true, + "type": "module", + "scripts": { + "build": "npm run build --workspaces --if-present", + "prebuild": "npm run prebuild --workspaces --if-present", + "dev": "npm run dev --workspaces --if-present" + }, + "workspaces": [ + "frontend" + ] +} diff --git a/rust/vetkeys/password_manager_with_metadata/rust/rust-toolchain.toml b/rust/vetkeys/password_manager_with_metadata/rust-toolchain.toml similarity index 100% rename from rust/vetkeys/password_manager_with_metadata/rust/rust-toolchain.toml rename to rust/vetkeys/password_manager_with_metadata/rust-toolchain.toml diff --git a/rust/vetkeys/password_manager_with_metadata/rust/frontend b/rust/vetkeys/password_manager_with_metadata/rust/frontend deleted file mode 120000 index af288785f3..0000000000 --- a/rust/vetkeys/password_manager_with_metadata/rust/frontend +++ /dev/null @@ -1 +0,0 @@ -../frontend \ No newline at end of file diff --git a/rust/vetkeys/password_manager_with_metadata/test.sh b/rust/vetkeys/password_manager_with_metadata/test.sh new file mode 100755 index 0000000000..b7d60d69e5 --- /dev/null +++ b/rust/vetkeys/password_manager_with_metadata/test.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -e + +echo "--- Testing password_manager_with_metadata: vetkey verification key ---" +result=$(icp canister call backend get_vetkey_verification_key '()') && \ + echo "$result" | head -1 && \ + echo "$result" | grep -q 'blob' && \ + echo "PASS" || (echo "FAIL" && exit 1) + +echo "--- Testing password_manager_with_metadata: owned map names (query) ---" +result=$(icp canister call backend get_owned_non_empty_map_names '()' --query) && \ + echo "$result" && \ + echo "$result" | grep -q 'vec' && \ + echo "PASS" || (echo "FAIL" && exit 1) From 4d83bd29a0257d0e9f1cc313cf309b5dee017cd0 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 16 Jul 2026 18:18:18 +0200 Subject: [PATCH 2/7] docs(vetkeys/password_manager_with_metadata): explain snake_case interface + note the mixin Same rationale as password_manager: the Encrypted Maps methods must stay snake_case because the @icp-sdk/vetkeys Encrypted Maps client calls them by those exact names, and the custom metadata methods follow suit for a consistent interface. Document this in app.mo and the Motoko README, and point to the upstream Motoko actor mixin (dfinity/vetkeys#405). Co-Authored-By: Claude Opus 4.8 (1M context) --- motoko/vetkeys/password_manager_with_metadata/README.md | 2 ++ .../vetkeys/password_manager_with_metadata/backend/app.mo | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/motoko/vetkeys/password_manager_with_metadata/README.md b/motoko/vetkeys/password_manager_with_metadata/README.md index e853876c80..52c85e9ca9 100644 --- a/motoko/vetkeys/password_manager_with_metadata/README.md +++ b/motoko/vetkeys/password_manager_with_metadata/README.md @@ -61,6 +61,8 @@ icp network stop An **Encrypted Maps**-enabled Motoko canister that stores encrypted passwords together with unencrypted metadata (URLs, tags) in atomic update calls. +> **Note on naming.** The backend methods are snake_case (rather than the usual Motoko camelCase). The standard Encrypted Maps methods are called by these exact names by the `@icp-sdk/vetkeys` Encrypted Maps client, and the custom metadata methods follow the same convention — renaming them would break the frontend. An upstream Motoko actor mixin that generates the Encrypted Maps endpoint set automatically is in progress ([dfinity/vetkeys#405](https://github.com/dfinity/vetkeys/pull/405)). + ### Frontend (`frontend/`) A **Svelte** application for managing vaults and passwords. It uses the `@icp-sdk/vetkeys` Encrypted Maps client for the crypto operations and a canister actor (bindings generated from `backend/backend.did` by the `@icp-sdk/bindgen` Vite plugin) for the metadata methods. diff --git a/motoko/vetkeys/password_manager_with_metadata/backend/app.mo b/motoko/vetkeys/password_manager_with_metadata/backend/app.mo index 15d58dcca3..2c67082b4d 100644 --- a/motoko/vetkeys/password_manager_with_metadata/backend/app.mo +++ b/motoko/vetkeys/password_manager_with_metadata/backend/app.mo @@ -12,6 +12,14 @@ import Debug "mo:core/Debug"; import Runtime "mo:core/Runtime"; import VetKeys "mo:ic-vetkeys"; +// This canister combines the Encrypted Maps interface with extra metadata-aware +// methods. Its public methods are intentionally snake_case (not the usual Motoko +// camelCase): the standard Encrypted Maps methods are called by these exact names +// by the `@icp-sdk/vetkeys` Encrypted Maps client, and the custom metadata +// methods follow the same convention for a consistent interface — renaming to +// camelCase would break the frontend. An upstream Motoko actor mixin that +// generates the Encrypted Maps endpoint set automatically is in progress +// (https://github.com/dfinity/vetkeys/pull/405). actor class (keyName : Text) { // Global state From 388e0e6345e2a0e46d0b4ae68c188396c41c9bcd Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Thu, 16 Jul 2026 18:22:17 +0200 Subject: [PATCH 3/7] docs(vetkeys/password_manager_with_metadata): note the upstream Rust EncryptedMaps macro Acknowledge the WIP Rust macro ic_vetkeys::export_encrypted_maps_canister! (dfinity/vetkeys#404) that generates a plain Encrypted Maps canister; this example hand-writes a custom canister to add the metadata methods on top. Co-Authored-By: Claude Opus 4.8 (1M context) --- rust/vetkeys/password_manager_with_metadata/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/vetkeys/password_manager_with_metadata/README.md b/rust/vetkeys/password_manager_with_metadata/README.md index ac6bcdd53b..1aba65f667 100644 --- a/rust/vetkeys/password_manager_with_metadata/README.md +++ b/rust/vetkeys/password_manager_with_metadata/README.md @@ -61,6 +61,8 @@ icp network stop An **Encrypted Maps**-enabled Rust canister that stores encrypted passwords together with unencrypted metadata (URLs, tags) in atomic update calls. +> **Note.** A plain Encrypted Maps canister can be generated by an upstream Rust macro — `ic_vetkeys::export_encrypted_maps_canister!(...)` — that is in progress ([dfinity/vetkeys#404](https://github.com/dfinity/vetkeys/pull/404)). This example instead hand-writes a custom canister so it can add the metadata-aware methods on top of the Encrypted Maps interface. + ### Frontend (`frontend/`) A **Svelte** application for managing vaults and passwords. It uses the `@icp-sdk/vetkeys` Encrypted Maps client for the crypto operations and a canister actor (bindings generated from `backend/backend.did` by the `@icp-sdk/bindgen` Vite plugin) for the metadata methods. From 33e962013d4e8321f836836c400f31d4c985925a Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 17 Jul 2026 10:17:04 +0200 Subject: [PATCH 4/7] chore(vetkeys/password_manager_with_metadata): rust v3.3.0, package `backend`, drop Makefile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump the Rust recipe to @dfinity/rust@v3.3.0 and rename the Cargo package to `backend` (drops the custom `package:` in the recipe config). Drop backend/Makefile and add a README "Updating the Candid interface" section using `icp build backend && candid-extractor … > backend/backend.did`. Verified: rust deploy + test.sh pass. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../password_manager_with_metadata/README.md | 8 ++++++++ .../backend/Cargo.toml | 2 +- .../backend/Makefile | 15 --------------- .../password_manager_with_metadata/icp.yaml | 3 +-- 4 files changed, 10 insertions(+), 18 deletions(-) delete mode 100644 rust/vetkeys/password_manager_with_metadata/backend/Makefile diff --git a/rust/vetkeys/password_manager_with_metadata/README.md b/rust/vetkeys/password_manager_with_metadata/README.md index 1aba65f667..d7dd2ffb87 100644 --- a/rust/vetkeys/password_manager_with_metadata/README.md +++ b/rust/vetkeys/password_manager_with_metadata/README.md @@ -67,6 +67,14 @@ An **Encrypted Maps**-enabled Rust canister that stores encrypted passwords toge A **Svelte** application for managing vaults and passwords. It uses the `@icp-sdk/vetkeys` Encrypted Maps client for the crypto operations and a canister actor (bindings generated from `backend/backend.did` by the `@icp-sdk/bindgen` Vite plugin) for the metadata methods. +## Updating the Candid interface + +`backend/backend.did` defines the backend's public interface; the frontend bindings are generated from it during the build. If you change the backend's public API, regenerate it: + +```bash +icp build backend && candid-extractor target/wasm32-unknown-unknown/release/backend.wasm > backend/backend.did +``` + ## Limitations This example dapp does not implement key rotation, which is strongly recommended in a production environment. Key rotation involves periodically changing encryption keys and re-encrypting data to enhance security. In a production dapp, key rotation would be useful to limit the impact of a potential key compromise, or to limit access when users are added to or removed from sharing. diff --git a/rust/vetkeys/password_manager_with_metadata/backend/Cargo.toml b/rust/vetkeys/password_manager_with_metadata/backend/Cargo.toml index 964961938e..3480734795 100644 --- a/rust/vetkeys/password_manager_with_metadata/backend/Cargo.toml +++ b/rust/vetkeys/password_manager_with_metadata/backend/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ic-vetkd-example-password-manager-with-metadata-backend" +name = "backend" authors = ["DFINITY Stiftung"] version = "0.1.0" edition = "2021" diff --git a/rust/vetkeys/password_manager_with_metadata/backend/Makefile b/rust/vetkeys/password_manager_with_metadata/backend/Makefile deleted file mode 100644 index fadd943554..0000000000 --- a/rust/vetkeys/password_manager_with_metadata/backend/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -.PHONY: compile-wasm -.SILENT: compile-wasm -compile-wasm: - cargo build --release --target wasm32-unknown-unknown - -.PHONY: extract-candid -.SILENT: extract-candid -extract-candid: compile-wasm - candid-extractor ../target/wasm32-unknown-unknown/release/ic_vetkd_example_password_manager_with_metadata_backend.wasm > backend.did - -.PHONY: clean -.SILENT: clean -clean: - cargo clean - rm -rf ../.icp \ No newline at end of file diff --git a/rust/vetkeys/password_manager_with_metadata/icp.yaml b/rust/vetkeys/password_manager_with_metadata/icp.yaml index 6692c31378..3b7759d854 100644 --- a/rust/vetkeys/password_manager_with_metadata/icp.yaml +++ b/rust/vetkeys/password_manager_with_metadata/icp.yaml @@ -1,9 +1,8 @@ canisters: - name: backend recipe: - type: "@dfinity/rust@v3.2.0" + type: "@dfinity/rust@v3.3.0" configuration: - package: ic-vetkd-example-password-manager-with-metadata-backend candid: backend/backend.did init_args: type: text From cf070a5be21d8136b4e1a759c9fd03b3d97d6f29 Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 17 Jul 2026 10:47:41 +0200 Subject: [PATCH 5/7] chore(vetkeys/password_manager_with_metadata): drop test.sh, deploy-only CI The example never had a test.sh on master; drop the ones added during the split and run deploy-only CI (the deploy verifies build + install). Persistence is unchanged (flag-based). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../vetkeys-password-manager-with-metadata.yml | 6 ++---- .../vetkeys/password_manager_with_metadata/test.sh | 14 -------------- .../vetkeys/password_manager_with_metadata/test.sh | 14 -------------- 3 files changed, 2 insertions(+), 32 deletions(-) delete mode 100755 motoko/vetkeys/password_manager_with_metadata/test.sh delete mode 100755 rust/vetkeys/password_manager_with_metadata/test.sh diff --git a/.github/workflows/vetkeys-password-manager-with-metadata.yml b/.github/workflows/vetkeys-password-manager-with-metadata.yml index 2fddde62c0..18c13e3440 100644 --- a/.github/workflows/vetkeys-password-manager-with-metadata.yml +++ b/.github/workflows/vetkeys-password-manager-with-metadata.yml @@ -22,12 +22,11 @@ jobs: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Deploy and test + - name: Deploy working-directory: motoko/vetkeys/password_manager_with_metadata run: | icp network start -d icp deploy - bash test.sh rust: runs-on: ubuntu-24.04 @@ -36,9 +35,8 @@ jobs: ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - - name: Deploy and test + - name: Deploy working-directory: rust/vetkeys/password_manager_with_metadata run: | icp network start -d icp deploy - bash test.sh diff --git a/motoko/vetkeys/password_manager_with_metadata/test.sh b/motoko/vetkeys/password_manager_with_metadata/test.sh deleted file mode 100755 index b7d60d69e5..0000000000 --- a/motoko/vetkeys/password_manager_with_metadata/test.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -e - -echo "--- Testing password_manager_with_metadata: vetkey verification key ---" -result=$(icp canister call backend get_vetkey_verification_key '()') && \ - echo "$result" | head -1 && \ - echo "$result" | grep -q 'blob' && \ - echo "PASS" || (echo "FAIL" && exit 1) - -echo "--- Testing password_manager_with_metadata: owned map names (query) ---" -result=$(icp canister call backend get_owned_non_empty_map_names '()' --query) && \ - echo "$result" && \ - echo "$result" | grep -q 'vec' && \ - echo "PASS" || (echo "FAIL" && exit 1) diff --git a/rust/vetkeys/password_manager_with_metadata/test.sh b/rust/vetkeys/password_manager_with_metadata/test.sh deleted file mode 100755 index b7d60d69e5..0000000000 --- a/rust/vetkeys/password_manager_with_metadata/test.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -e - -echo "--- Testing password_manager_with_metadata: vetkey verification key ---" -result=$(icp canister call backend get_vetkey_verification_key '()') && \ - echo "$result" | head -1 && \ - echo "$result" | grep -q 'blob' && \ - echo "PASS" || (echo "FAIL" && exit 1) - -echo "--- Testing password_manager_with_metadata: owned map names (query) ---" -result=$(icp canister call backend get_owned_non_empty_map_names '()' --query) && \ - echo "$result" && \ - echo "$result" | grep -q 'vec' && \ - echo "PASS" || (echo "FAIL" && exit 1) From 7acfefb8bde85f4911afe46a18162aa05fccb48d Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 17 Jul 2026 12:36:07 +0200 Subject: [PATCH 6/7] docs(vetkeys/password_manager_with_metadata): drop "dapp", fix security link Replace "dapp" with "app" in the READMEs and normalize the security best-practices link to https://docs.internetcomputer.org/guides/security/overview/. The functional vetKD domain-separator string ("password_manager_example_dapp") is left unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- motoko/vetkeys/password_manager_with_metadata/README.md | 4 ++-- rust/vetkeys/password_manager_with_metadata/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/motoko/vetkeys/password_manager_with_metadata/README.md b/motoko/vetkeys/password_manager_with_metadata/README.md index 52c85e9ca9..66497b738d 100644 --- a/motoko/vetkeys/password_manager_with_metadata/README.md +++ b/motoko/vetkeys/password_manager_with_metadata/README.md @@ -69,10 +69,10 @@ A **Svelte** application for managing vaults and passwords. It uses the `@icp-sd ## Limitations -This example dapp does not implement key rotation, which is strongly recommended in a production environment. Key rotation involves periodically changing encryption keys and re-encrypting data to enhance security. In a production dapp, key rotation would be useful to limit the impact of a potential key compromise, or to limit access when users are added to or removed from sharing. +This example app does not implement key rotation, which is strongly recommended in a production environment. Key rotation involves periodically changing encryption keys and re-encrypting data to enhance security. In a production app, key rotation would be useful to limit the impact of a potential key compromise, or to limit access when users are added to or removed from sharing. ## Additional resources - **[Basic Password Manager](../../../rust/vetkeys/password_manager)** — a simpler example without metadata. - **[What are VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** — more information about VetKeys and VetKD. -- [Security best practices](https://docs.internetcomputer.org/guides/security/overview) +- [Security best practices](https://docs.internetcomputer.org/guides/security/overview/) diff --git a/rust/vetkeys/password_manager_with_metadata/README.md b/rust/vetkeys/password_manager_with_metadata/README.md index d7dd2ffb87..cd06c10aa5 100644 --- a/rust/vetkeys/password_manager_with_metadata/README.md +++ b/rust/vetkeys/password_manager_with_metadata/README.md @@ -77,10 +77,10 @@ icp build backend && candid-extractor target/wasm32-unknown-unknown/release/back ## Limitations -This example dapp does not implement key rotation, which is strongly recommended in a production environment. Key rotation involves periodically changing encryption keys and re-encrypting data to enhance security. In a production dapp, key rotation would be useful to limit the impact of a potential key compromise, or to limit access when users are added to or removed from sharing. +This example app does not implement key rotation, which is strongly recommended in a production environment. Key rotation involves periodically changing encryption keys and re-encrypting data to enhance security. In a production app, key rotation would be useful to limit the impact of a potential key compromise, or to limit access when users are added to or removed from sharing. ## Additional resources - **[Basic Password Manager](../password_manager)** — a simpler example without metadata. - **[What are VetKeys](https://docs.internetcomputer.org/concepts/vetkeys)** — more information about VetKeys and VetKD. -- [Security best practices](https://docs.internetcomputer.org/guides/security/overview) +- [Security best practices](https://docs.internetcomputer.org/guides/security/overview/) From f2262f103e07cb1f01ddaa15348c9f93159bf49d Mon Sep 17 00:00:00 2001 From: Marco Walz Date: Fri, 17 Jul 2026 16:06:59 +0200 Subject: [PATCH 7/7] refactor(vetkeys/password_manager_with_metadata): drop "dapp" from Motoko domain separator Rename the EncryptedMaps vetKD domain separator "password_manager_example_dapp" -> "password_manager_example_app" in the Motoko backend. Fresh example, no previously derived keys to stay compatible with. Backend-only: the frontend (DefaultEncryptedMapsClient) does not reference the separator. Co-Authored-By: Claude Opus 4.8 (1M context) --- motoko/vetkeys/password_manager_with_metadata/backend/app.mo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/motoko/vetkeys/password_manager_with_metadata/backend/app.mo b/motoko/vetkeys/password_manager_with_metadata/backend/app.mo index 2c67082b4d..938bf276dc 100644 --- a/motoko/vetkeys/password_manager_with_metadata/backend/app.mo +++ b/motoko/vetkeys/password_manager_with_metadata/backend/app.mo @@ -23,7 +23,7 @@ import VetKeys "mo:ic-vetkeys"; actor class (keyName : Text) { // Global state - let encryptedMapsState = VetKeys.EncryptedMaps.newEncryptedMapsState({ curve = #bls12_381_g2; name = keyName }, "password_manager_example_dapp"); + let encryptedMapsState = VetKeys.EncryptedMaps.newEncryptedMapsState({ curve = #bls12_381_g2; name = keyName }, "password_manager_example_app"); transient let encryptedMaps = VetKeys.EncryptedMaps.EncryptedMaps(encryptedMapsState, VetKeys.accessRightsOperations()); func compareMetadataKeys(a : MetadataKey, b : MetadataKey) : {