Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2f0b063
chore(vetkeys): migrate password_manager to icp-cli and @icp-sdk/vetkeys
marc0olo May 29, 2026
577b4a7
fix(ci): update password_manager to ic-cdk 0.20.1 and @icp-sdk/auth@7…
marc0olo May 30, 2026
1d4105d
fix(password_manager): fix motoko frontend path in icp.yaml
marc0olo Jun 1, 2026
63443e1
chore(password_manager): rename CI job keys to remove rust-vetkeys- p…
marc0olo Jun 1, 2026
1d0d9c5
chore(password_manager): rename CI jobs to language-first format
marc0olo Jun 1, 2026
ab827f2
fix: use key_1 (not test_key_1), npx --yes, console.error in basic_ibe
marc0olo Jun 1, 2026
6843c42
docs: update vetkeys doc links to docs.internetcomputer.org/concepts/…
marc0olo Jun 1, 2026
f3a4a60
docs: add folder structure, fix dev command and cd instructions
marc0olo Jun 1, 2026
9436e20
fix: simplify rootKey; use /authorize (not /#authorize) and https://i…
marc0olo Jun 2, 2026
800922b
fix: use https://id.ai/authorize for production identityProvider
marc0olo Jun 2, 2026
eb1d299
chore: migrate Motoko backend from base to core 2.5.0
marc0olo Jun 2, 2026
e8e02e2
fix: switch back to test_key_1 (supported again in latest icp-cli-net…
marc0olo Jun 3, 2026
92bef7b
ci: switch to icp-dev-env container images, Ubuntu only
marc0olo Jun 3, 2026
5eb8f0b
ci: rename workflow files to vetkeys-{example}.yml convention
marc0olo Jun 3, 2026
834d010
ci: fix workflow name and simplify job IDs to rust/motoko only
marc0olo Jun 3, 2026
6c13b39
ci: pass ICP_CLI_GITHUB_TOKEN to avoid network launcher rate limiting
marc0olo Jun 3, 2026
5cdc779
fix: use LocalStorage only on localhost as workaround for IDB race co…
marc0olo Jun 3, 2026
7e68747
fix: format LocalStorage spread for printWidth 80
marc0olo Jun 3, 2026
5ad95cb
fix(password_manager): add principal to auth state, fix getIdentity c…
marc0olo Jun 3, 2026
97a746f
fix(rust/backend): add post_upgrade hook to reinitialize ENCRYPTED_MAPS
marc0olo Jun 3, 2026
32fd7e0
fix(rust/backend): remove .expect() incompatible with ic-stable-struc…
marc0olo Jun 3, 2026
541848e
refactor(password_manager): move icp.yaml to motoko/, remove duplicat…
marc0olo Jun 3, 2026
cf20674
chore: replace dapp with app in backend domain separator and UI
marc0olo Jun 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/vetkeys-password-manager.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: vetkeys-password-manager

on:
push:
branches:
- master
pull_request:
paths:
- rust/vetkeys/password_manager/**
- .github/workflows/vetkeys-password-manager.yml

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
rust:
runs-on: ubuntu-24.04
container: ghcr.io/dfinity/icp-dev-env-rust:0.1.0
env:
ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Deploy Password Manager Rust
working-directory: rust/vetkeys/password_manager/rust
run: icp network start -d && icp deploy
motoko:
runs-on: ubuntu-24.04
container: ghcr.io/dfinity/icp-dev-env-motoko:0.1.0
env:
ICP_CLI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
- name: Deploy Password Manager Motoko
working-directory: rust/vetkeys/password_manager/motoko
run: icp network start -d && icp deploy
53 changes: 39 additions & 14 deletions rust/vetkeys/password_manager/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# VetKey Password Manager

<!-- TODO: re-enable once icp.ninja supports icp-cli (currently requires dfx)
| Motoko backend | [![](https://icp.ninja/assets/open.svg)](http://icp.ninja/editor?g=https://github.com/dfinity/examples/tree/master/rust/vetkeys/password_manager/motoko)|
| --- | --- |
| Rust backend | [![](https://icp.ninja/assets/open.svg)](http://icp.ninja/editor?g=https://github.com/dfinity/examples/tree/master/rust/vetkeys/password_manager/rust) |
-->

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**.

Expand All @@ -16,38 +18,61 @@ The **VetKey Password Manager** is an example application demonstrating how to u

### Prerequisites

- [Local Internet Computer dev environment](https://internetcomputer.org/docs/building-apps/getting-started/install)
- [ICP CLI](https://cli.internetcomputer.org)
- [npm](https://www.npmjs.com/package/npm)

### (Optionally) Choose a Different Master Key

This example uses `test_key_1` by default. To use a different [available master key](https://internetcomputer.org/docs/building-apps/network-features/vetkeys/api#available-master-keys), change the `"init_arg": "(\"test_key_1\")"` line in `dfx.json` to the desired key before running `dfx deploy` in the next step.
This example uses `test_key_1` by default. To use a different [available master key](https://docs.internetcomputer.org/concepts/vetkeys/#api-overview), change the `init_args` value in `icp.yaml` to the desired key before running `icp deploy` in the next step.

### Folder Structure

This example provides both a **Rust** and a **Motoko** backend, sharing a common `frontend/`:

```
password_manager/
├── frontend/ ← shared frontend (symlinked into rust/ and motoko/)
├── motoko/ ← Motoko backend + icp.yaml
└── rust/ ← Rust backend + icp.yaml
```

### Deploy the Canisters Locally
If you want to deploy this project locally with a Motoko backend, then run:

Deploy with the **Motoko** backend:
```bash
cd motoko
icp network start -d && icp deploy
```

Or deploy with the **Rust** backend:
```bash
dfx start --background && dfx deploy
cd rust
icp network start -d && icp deploy
```
from the `motoko` folder.

To use the Rust backend instead of Motoko, run the same command in the `rust` folder.
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
```

When you are done testing, stop the local network to free up resources and unblock the default port for other projects:
```bash
icp network stop
```

## Running the Project
## Example Components

### Backend

The backend consists of an **Encrypted Maps**-enabled canister that securely stores passwords. It is automatically deployed with `dfx deploy`.
The backend consists of an **Encrypted Maps**-enabled canister that securely stores passwords.

### Frontend

The frontend is a **Svelte** application providing a user-friendly interface for managing vaults and passwords.

To run the frontend in development mode with hot reloading:

```bash
npm run dev
```

## Limitations

This example dapp does not implement key rotation, which is strongly recommended in a production environment.
Expand Down
16 changes: 7 additions & 9 deletions rust/vetkeys/password_manager/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"dev": "printf '\\nNo backend specified. Use one of:\\n\\n npm run dev:motoko\\n npm run dev:rust\\n\\n' && exit 1",
"dev:motoko": "BACKEND=motoko vite",
"dev:rust": "BACKEND=rust vite",
"build": "vite build",
"lint": "eslint",
"prettier": "prettier --write .",
Expand All @@ -23,16 +25,12 @@
"svelte": "^4.2.19",
"tslib": "^2.8.1",
"typescript-eslint": "^8.35.1",
"vite": "^5.4.21",
"vite-plugin-environment": "^1.1.3"
"vite": "^5.4.21"
},
"dependencies": {
"@dfinity/agent": "^2.3.0",
"@dfinity/auth-client": "^2.3.0",
"@dfinity/candid": "^2.3.0",
"@dfinity/identity": "^2.3.0",
"@dfinity/principal": "^2.3.0",
"@dfinity/vetkeys": "^0.3.0",
"@icp-sdk/auth": "^7.1.0",
"@icp-sdk/core": "^5.4.0",
"@icp-sdk/vetkeys": "^0.5.0-beta.0",
"@popperjs/core": "^2.11.8",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@tailwindcss/postcss": "^4.0.6",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts"></script>

<strong>Disclaimer:</strong> This sample dapp is intended exclusively for experimental
purpose. You are advised not to use this dapp for storing your critical data such
<strong>Disclaimer:</strong> 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.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
import { auth } from "../store/auth";
import Spinner from "./Spinner.svelte";
import { onDestroy } from "svelte";
import { Principal } from "@dfinity/principal";
import type { AccessRights } from "@dfinity/vetkeys/encrypted_maps";
import { Principal } from "@icp-sdk/core/principal";
import type { AccessRights } from "@icp-sdk/vetkeys/encrypted_maps";

export let currentRoute = "";
const unsubscribe = location.subscribe((value) => {
Expand Down Expand Up @@ -64,7 +64,7 @@
v.owner.compareTo(editedPassword.owner) === "eq" &&
v.name === editedPassword.parentVaultName,
);
const me = $auth.client.getIdentity().getPrincipal();
const me = $auth.principal;
const accessRights =
vault && vault.users.find((u) => u[0].compareTo(me) === "eq");
const authorized = accessRights && "Read" in accessRights[1];
Expand Down Expand Up @@ -185,8 +185,8 @@
) {
const split = currentRoute.split("/");
vaultOwner = split[split.length - 3];
const parentVaultName = split[split.length - 2];
const passwordName = split[split.length - 1];
const parentVaultName = decodeURIComponent(split[split.length - 2]);
const passwordName = decodeURIComponent(split[split.length - 1]);
const searchedForPassword = $vaultsStore.list
.find(
(v) =>
Expand All @@ -199,7 +199,7 @@
editedPassword = { ...searchedForPassword[1] };
}

const myPrincipal = $auth.client.getIdentity().getPrincipal();
const myPrincipal = $auth.principal;

if (editedPassword.owner.compareTo(myPrincipal) === "eq") {
accessRights = { ReadWriteManage: null };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@
import Trash from "svelte-icons/fa/FaTrash.svelte";
import { auth } from "../store/auth";
import Spinner from "./Spinner.svelte";
import { location } from "svelte-spa-router";
import { Principal } from "@icp-sdk/core/principal";
import { onDestroy } from "svelte";

export let currentRoute = "";
let currentRoute = "";
const unsubscribeCurrentRoute = location.subscribe((value) => {
currentRoute = value;
});
onDestroy(unsubscribeCurrentRoute);

let editedVault: VaultModel;
let updating = false;
Expand All @@ -21,15 +28,21 @@
if (
$auth.state === "initialized" &&
$vaultsStore.state === "loaded" &&
!editedVault
!editedVault &&
currentRoute.split("/").length > 2
) {
const split = currentRoute.split("/");
const vaultOwner = Principal.fromText(split[split.length - 2]);
const vaultName = decodeURIComponent(split[split.length - 1]);
const vault = $vaultsStore.list.find(
(vault) => vault.name === currentRoute,
(v) =>
v.owner.compareTo(vaultOwner) === "eq" &&
v.name === vaultName,
);

if (vault) {
editedVault = { ...vault };
const me = $auth.client.getIdentity().getPrincipal();
const me = $auth.principal;
if (vault.owner.compareTo(me) === "eq") {
canManage = true;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
import { addNotification, showError } from "../store/notifications";
import Header from "./Header.svelte";
import PasswordEditor from "./PasswordEditor.svelte";
import { Principal } from "@dfinity/principal";
import { Principal } from "@icp-sdk/core/principal";

let creating = false;
let vaultOwner =
$auth.state === "initialized"
? $auth.client.getIdentity().getPrincipal().toText()
? $auth.principal.toText()
: Principal.anonymous().toText();
let vaultName = "";
let passwordName = "";
Expand All @@ -30,6 +30,14 @@
return;
}

if (vaultName.trim() === "" || passwordName.trim() === "") {
addNotification({
type: "error",
message: "Vault name and password name must not be empty.",
});
return;
}

creating = true;

await addPassword(
Expand Down Expand Up @@ -102,7 +110,9 @@
<PasswordEditor {editor} class="mb-3" disabled={creating} />
<button
class="btn mt-6 btn-primary {creating ? 'loading' : ''}"
disabled={creating}
disabled={creating ||
vaultName.trim() === "" ||
passwordName.trim() === ""}
on:click={add}>{creating ? "Adding..." : "Add password"}</button
>
</main>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { type PasswordModel, summarize } from "../lib/password";
import { link, location } from "svelte-spa-router";
import { vaultsStore } from "../store/vaults";
import { Principal } from "@dfinity/principal";
import { Principal } from "@icp-sdk/core/principal";
import { onDestroy } from "svelte";
import Spinner from "./Spinner.svelte";
import Header from "./Header.svelte";
Expand Down Expand Up @@ -30,15 +30,15 @@
) {
const split = currentRoute.split("/");
const vaultOwner = Principal.fromText(split[split.length - 3]);
const parentVaultName = split[split.length - 2];
const passwordName = split[split.length - 1];
const parentVaultName = decodeURIComponent(split[split.length - 2]);
const passwordName = decodeURIComponent(split[split.length - 1]);
const searchedForPassword = $vaultsStore.list
.find(
(v) =>
v.owner.compareTo(vaultOwner) === "eq" &&
v.name === parentVaultName,
)
.passwords.find((p) => p[0] === passwordName);
?.passwords.find((p) => p[0] === passwordName);
if (searchedForPassword) {
password = searchedForPassword[1];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
vaultsStore,
} from "../store/vaults";
import { addNotification, showError } from "../store/notifications";
import { Principal } from "@dfinity/principal";
import type { AccessRights } from "@dfinity/vetkeys/encrypted_maps";
import { Principal } from "@icp-sdk/core/principal";
import type { AccessRights } from "@icp-sdk/vetkeys/encrypted_maps";
export let editedVault: VaultModel;
export let canManage = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import GoDatabase from "svelte-icons/go/GoDatabase.svelte";
import FaDoorOpen from "svelte-icons/fa/FaDoorOpen.svelte";
import Disclaimer from "./Disclaimer.svelte";
import { Principal } from "@dfinity/principal";
import { Principal } from "@icp-sdk/core/principal";
import { link } from "svelte-spa-router";
</script>

Expand All @@ -31,7 +31,7 @@
<div class="pl-4">My Principal:</div>
<div class="pl-4">
{$auth.state === "initialized"
? $auth.client.getIdentity().getPrincipal().toText()
? $auth.principal.toText()
: Principal.anonymous().toText()}
</div>
</div>
Expand Down
Loading
Loading