From e87914045f3e2d068e6ea3472adf19d82d9b6c21 Mon Sep 17 00:00:00 2001 From: Andrea Cerulli Date: Mon, 20 Apr 2026 17:06:26 +0200 Subject: [PATCH 1/3] fix: replace broken symlinks with actual canister source The password_manager example had symlinks to the vetkeys repo's shared backend canisters. Copy the actual source (rust and motoko) to make the example self-contained. Also replace the rust-toolchain.toml symlink with the actual file. --- .../motoko/backend/.prettierrc | 8 + .../password_manager/motoko/backend/Makefile | 14 + .../password_manager/motoko/backend/README.md | 7 + .../password_manager/motoko/backend/icp.yaml | 7 + .../password_manager/motoko/backend/mops.toml | 16 + .../motoko/backend/src/Main.mo | 221 +++++++++++++ .../password_manager/rust/backend/Cargo.toml | 29 ++ .../password_manager/rust/backend/Makefile | 22 ++ .../password_manager/rust/backend/README.md | 7 + .../ic_vetkeys_encrypted_maps_canister.did | 41 +++ .../password_manager/rust/backend/src/lib.rs | 298 ++++++++++++++++++ .../password_manager/rust/rust-toolchain.toml | 4 + 12 files changed, 674 insertions(+) create mode 100644 rust/vetkeys/password_manager/motoko/backend/.prettierrc create mode 100644 rust/vetkeys/password_manager/motoko/backend/Makefile create mode 100644 rust/vetkeys/password_manager/motoko/backend/README.md create mode 100644 rust/vetkeys/password_manager/motoko/backend/icp.yaml create mode 100644 rust/vetkeys/password_manager/motoko/backend/mops.toml create mode 100644 rust/vetkeys/password_manager/motoko/backend/src/Main.mo create mode 100644 rust/vetkeys/password_manager/rust/backend/Cargo.toml create mode 100644 rust/vetkeys/password_manager/rust/backend/Makefile create mode 100644 rust/vetkeys/password_manager/rust/backend/README.md create mode 100644 rust/vetkeys/password_manager/rust/backend/ic_vetkeys_encrypted_maps_canister.did create mode 100644 rust/vetkeys/password_manager/rust/backend/src/lib.rs create mode 100644 rust/vetkeys/password_manager/rust/rust-toolchain.toml diff --git a/rust/vetkeys/password_manager/motoko/backend/.prettierrc b/rust/vetkeys/password_manager/motoko/backend/.prettierrc new file mode 100644 index 000000000..a42bc32c3 --- /dev/null +++ b/rust/vetkeys/password_manager/motoko/backend/.prettierrc @@ -0,0 +1,8 @@ +{ + "overrides": [{ + "files": "*.mo", + "options": { + "tabWidth": 4 + } + }] + } \ No newline at end of file diff --git a/rust/vetkeys/password_manager/motoko/backend/Makefile b/rust/vetkeys/password_manager/motoko/backend/Makefile new file mode 100644 index 000000000..ffaeb9642 --- /dev/null +++ b/rust/vetkeys/password_manager/motoko/backend/Makefile @@ -0,0 +1,14 @@ +PWD:=$(shell pwd) + +.PHONY: compile-wasm +.SILENT: compile-wasm +compile-wasm: + icp build + +# Test the APIs of this canister using the respective Rust canister tests. +# This has the advantage that the tests are consistent (less room for bugs by having only one implementation of the tests) and the checked expected behavior is consistent across Rust and Motoko. +.PHONY: test +.SILENT: test +test: compile-wasm + @echo "Testing Motoko canister WASM: $(PWD)/.icp/cache/artifacts/ic_vetkeys_encrypted_maps_canister" + CUSTOM_WASM_PATH=$(PWD)/.icp/cache/artifacts/ic_vetkeys_encrypted_maps_canister cargo test -p ic-vetkeys-encrypted-maps-canister diff --git a/rust/vetkeys/password_manager/motoko/backend/README.md b/rust/vetkeys/password_manager/motoko/backend/README.md new file mode 100644 index 000000000..71f0ac924 --- /dev/null +++ b/rust/vetkeys/password_manager/motoko/backend/README.md @@ -0,0 +1,7 @@ +# ic-vetkeys-encrypted-maps-canister + +The canister implemented in this folder directly exposes the methods of the encrypted maps. +This is useful for: + +1. running canister tests +2. implementing dapps that only require encrypted maps \ No newline at end of file diff --git a/rust/vetkeys/password_manager/motoko/backend/icp.yaml b/rust/vetkeys/password_manager/motoko/backend/icp.yaml new file mode 100644 index 000000000..b4101fe9c --- /dev/null +++ b/rust/vetkeys/password_manager/motoko/backend/icp.yaml @@ -0,0 +1,7 @@ +canisters: + - name: ic_vetkeys_encrypted_maps_canister + recipe: + type: "@dfinity/motoko@v4.1.0" + configuration: + main: src/Main.mo + args: --enhanced-orthogonal-persistence diff --git a/rust/vetkeys/password_manager/motoko/backend/mops.toml b/rust/vetkeys/password_manager/motoko/backend/mops.toml new file mode 100644 index 000000000..8cc38c25d --- /dev/null +++ b/rust/vetkeys/password_manager/motoko/backend/mops.toml @@ -0,0 +1,16 @@ +[toolchain] +moc = "1.5.0" + +[package] +name = "ic-vetkeys-encrypted-maps-canister" +version = "0.1.0" +repository = "https://github.com/dfinity/vetkeys/backend/mo/canisters/ic_vetkeys_encrypted_maps_canister" +keywords = [ + "vetkeys,vetkd,encryption,privacy,signature,BLS,key ", + "derivation,IBE" +] +license = "Apache-2.0" + +[dependencies] +base = "0.14.6" +ic-vetkeys = "0.4.0" diff --git a/rust/vetkeys/password_manager/motoko/backend/src/Main.mo b/rust/vetkeys/password_manager/motoko/backend/src/Main.mo new file mode 100644 index 000000000..ad0c468bf --- /dev/null +++ b/rust/vetkeys/password_manager/motoko/backend/src/Main.mo @@ -0,0 +1,221 @@ +import IcVetkeys "mo:ic-vetkeys"; +import Types "mo:ic-vetkeys/Types"; +import Principal "mo:base/Principal"; +import Text "mo:base/Text"; +import Blob "mo:base/Blob"; +import Result "mo:base/Result"; +import Array "mo:base/Array"; + +persistent actor class (keyName : Text) { + let encryptedMapsState = IcVetkeys.EncryptedMaps.newEncryptedMapsState({ curve = #bls12_381_g2; name = keyName }, "password_manager_example_dapp"); + transient let encryptedMaps = IcVetkeys.EncryptedMaps.EncryptedMaps(encryptedMapsState, Types.accessRightsOperations()); + + /// In this canister, we use the `ByteBuf` type to represent blobs. The reason is that we want to be consistent with the Rust canister implementation. + /// Unfortunately, the `Blob` type cannot be serialized/deserialized in the current Rust implementation efficiently without nesting it in another type. + public type ByteBuf = { inner : Blob }; + + public type EncryptedMapData = { + map_owner : Principal; + map_name : ByteBuf; + keyvals : [(ByteBuf, ByteBuf)]; + access_control : [(Principal, Types.AccessRights)]; + }; + + /// The result type compatible with Rust's `Result`. + public type Result = { + #Ok : Ok; + #Err : Err; + }; + + public query (msg) func get_accessible_shared_map_names() : async [(Principal, ByteBuf)] { + Array.map<(Principal, Blob), (Principal, ByteBuf)>( + encryptedMaps.getAccessibleSharedMapNames(msg.caller), + + func((principal, blob) : (Principal, Blob)) { + (principal, { inner = blob }); + }, + ); + }; + + public query (msg) func get_shared_user_access_for_map( + map_owner : Principal, + map_name : ByteBuf, + ) : async Result<[(Principal, Types.AccessRights)], Text> { + convertResult(encryptedMaps.getSharedUserAccessForMap(msg.caller, (map_owner, map_name.inner))); + }; + + public query (msg) func get_encrypted_values_for_map( + map_owner : Principal, + map_name : ByteBuf, + ) : async Result<[(ByteBuf, ByteBuf)], Text> { + let result = encryptedMaps.getEncryptedValuesForMap(msg.caller, (map_owner, map_name.inner)); + switch (result) { + case (#err(e)) { #Err(e) }; + case (#ok(values)) { + #Ok( + Array.map<(Blob, Blob), (ByteBuf, ByteBuf)>( + values, + func((blob1, blob2) : (Blob, Blob)) { + ({ inner = blob1 }, { inner = blob2 }); + }, + ) + ); + }; + }; + }; + + public query (msg) func get_all_accessible_encrypted_values() : async [((Principal, ByteBuf), [(ByteBuf, ByteBuf)])] { + Array.map<((Principal, Blob), [(Blob, Blob)]), ((Principal, ByteBuf), [(ByteBuf, ByteBuf)])>( + encryptedMaps.getAllAccessibleEncryptedValues(msg.caller), + func(((owner, map_name), values) : ((Principal, Blob), [(Blob, Blob)])) { + ( + (owner, { inner = map_name }), + Array.map<(Blob, Blob), (ByteBuf, ByteBuf)>( + values, + func((blob1, blob2) : (Blob, Blob)) { + ({ inner = blob1 }, { inner = blob2 }); + }, + ), + ); + }, + ); + }; + + public query (msg) func get_all_accessible_encrypted_maps() : async [EncryptedMapData] { + Array.map, EncryptedMapData>( + encryptedMaps.getAllAccessibleEncryptedMaps(msg.caller), + func(map : IcVetkeys.EncryptedMaps.EncryptedMapData) : EncryptedMapData { + { + map_owner = map.map_owner; + map_name = { inner = map.map_name }; + keyvals = Array.map<(Blob, Blob), (ByteBuf, ByteBuf)>( + map.keyvals, + func((blob1, blob2) : (Blob, Blob)) { + ({ inner = blob1 }, { inner = blob2 }); + }, + ); + access_control = map.access_control; + }; + }, + ); + }; + + public query (msg) func get_encrypted_value( + map_owner : Principal, + map_name : ByteBuf, + map_key : ByteBuf, + ) : async Result { + let result = encryptedMaps.getEncryptedValue(msg.caller, (map_owner, map_name.inner), map_key.inner); + switch (result) { + case (#err(e)) { #Err(e) }; + case (#ok(null)) { #Ok(null) }; + case (#ok(?blob)) { #Ok(?{ inner = blob }) }; + }; + }; + + public shared (msg) func remove_map_values( + map_owner : Principal, + map_name : ByteBuf, + ) : async Result<[ByteBuf], Text> { + let result = encryptedMaps.removeMapValues(msg.caller, (map_owner, map_name.inner)); + switch (result) { + case (#err(e)) { #Err(e) }; + case (#ok(values)) { + #Ok( + Array.map( + values, + func(blob : Blob) : ByteBuf { + { inner = blob }; + }, + ) + ); + }; + }; + }; + + public query (msg) func get_owned_non_empty_map_names() : async [ByteBuf] { + Array.map( + encryptedMaps.getOwnedNonEmptyMapNames(msg.caller), + func(blob : Blob) : ByteBuf { + { inner = blob }; + }, + ); + }; + + public shared (msg) func insert_encrypted_value( + map_owner : Principal, + map_name : ByteBuf, + map_key : ByteBuf, + value : ByteBuf, + ) : async Result { + let result = encryptedMaps.insertEncryptedValue(msg.caller, (map_owner, map_name.inner), map_key.inner, value.inner); + switch (result) { + case (#err(e)) { #Err(e) }; + case (#ok(null)) { #Ok(null) }; + case (#ok(?blob)) { #Ok(?{ inner = blob }) }; + }; + }; + + public shared (msg) func remove_encrypted_value( + map_owner : Principal, + map_name : ByteBuf, + map_key : ByteBuf, + ) : async Result { + let result = encryptedMaps.removeEncryptedValue(msg.caller, (map_owner, map_name.inner), map_key.inner); + switch (result) { + case (#err(e)) { #Err(e) }; + case (#ok(null)) { #Ok(null) }; + case (#ok(?blob)) { #Ok(?{ inner = blob }) }; + }; + }; + + public shared func get_vetkey_verification_key() : async ByteBuf { + let inner = await encryptedMaps.getVetkeyVerificationKey(); + { inner }; + }; + + public shared (msg) func get_encrypted_vetkey( + map_owner : Principal, + map_name : ByteBuf, + transport_key : ByteBuf, + ) : async Result { + let result = await encryptedMaps.getEncryptedVetkey(msg.caller, (map_owner, map_name.inner), transport_key.inner); + switch (result) { + case (#err(e)) { #Err(e) }; + case (#ok(vetkey)) { #Ok({ inner = vetkey }) }; + }; + }; + + public query (msg) func get_user_rights( + map_owner : Principal, + map_name : ByteBuf, + user : Principal, + ) : async Result { + convertResult(encryptedMaps.getUserRights(msg.caller, (map_owner, map_name.inner), user)); + }; + + public shared (msg) func set_user_rights( + map_owner : Principal, + map_name : ByteBuf, + user : Principal, + access_rights : Types.AccessRights, + ) : async Result { + convertResult(encryptedMaps.setUserRights(msg.caller, (map_owner, map_name.inner), user, access_rights)); + }; + + public shared (msg) func remove_user( + map_owner : Principal, + map_name : ByteBuf, + user : Principal, + ) : async Result { + convertResult(encryptedMaps.removeUser(msg.caller, (map_owner, map_name.inner), user)); + }; + + /// Convert to the result type compatible with Rust's `Result` + private func convertResult(result : Result.Result) : Result { + switch (result) { + case (#err(e)) { #Err(e) }; + case (#ok(o)) { #Ok(o) }; + }; + }; +}; diff --git a/rust/vetkeys/password_manager/rust/backend/Cargo.toml b/rust/vetkeys/password_manager/rust/backend/Cargo.toml new file mode 100644 index 000000000..cb662f05f --- /dev/null +++ b/rust/vetkeys/password_manager/rust/backend/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "ic-vetkeys-encrypted-maps-canister" +authors = ["DFINITY Stiftung"] +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" +description = "# Basic Identity Based Encryption" +repository = "https://github.com/dfinity/vetkeys" +rust-version = "1.85.0" + +[lib] +path = "src/lib.rs" +crate-type = ["cdylib"] + +[dependencies] +candid = "0.10.2" +ic-cdk = { workspace = true } +ic-dummy-getrandom-for-wasm = "0.1.0" +ic-stable-structures = { workspace = true } +ic-vetkeys = { workspace = true } +serde = "1.0.217" + +[dev-dependencies] +assert_matches = "1.5.0" +pocket-ic = "9.0.0" +rand = "0.8.5" +rand_chacha = "0.3.1" +reqwest = "0.12.12" +strum = "0.27.1" diff --git a/rust/vetkeys/password_manager/rust/backend/Makefile b/rust/vetkeys/password_manager/rust/backend/Makefile new file mode 100644 index 000000000..b6dc7595e --- /dev/null +++ b/rust/vetkeys/password_manager/rust/backend/Makefile @@ -0,0 +1,22 @@ +ROOT_DIR := $(shell git rev-parse --show-toplevel) + +.PHONY: compile-wasm +.SILENT: compile-wasm +compile-wasm: + cargo build --release --target wasm32-unknown-unknown + +.PHONY: test +.SILENT: test +test: compile-wasm + cargo test -p ic-vetkeys-encrypted-maps-canister + +.PHONY: extract-candid +.SILENT: extract-candid +extract-candid: compile-wasm + candid-extractor $(ROOT_DIR)/target/wasm32-unknown-unknown/release/ic_vetkeys_encrypted_maps_canister.wasm > ic_vetkeys_encrypted_maps_canister.did + +.PHONY: clean +.SILENT: clean +clean: + cargo clean + rm -rf .icp/cache \ No newline at end of file diff --git a/rust/vetkeys/password_manager/rust/backend/README.md b/rust/vetkeys/password_manager/rust/backend/README.md new file mode 100644 index 000000000..71f0ac924 --- /dev/null +++ b/rust/vetkeys/password_manager/rust/backend/README.md @@ -0,0 +1,7 @@ +# ic-vetkeys-encrypted-maps-canister + +The canister implemented in this folder directly exposes the methods of the encrypted maps. +This is useful for: + +1. running canister tests +2. implementing dapps that only require encrypted maps \ No newline at end of file diff --git a/rust/vetkeys/password_manager/rust/backend/ic_vetkeys_encrypted_maps_canister.did b/rust/vetkeys/password_manager/rust/backend/ic_vetkeys_encrypted_maps_canister.did new file mode 100644 index 000000000..f30499fd1 --- /dev/null +++ b/rust/vetkeys/password_manager/rust/backend/ic_vetkeys_encrypted_maps_canister.did @@ -0,0 +1,41 @@ +type AccessRights = variant { Read; ReadWrite; ReadWriteManage }; +type ByteBuf = record { inner : blob }; +type EncryptedMapData = record { + access_control : vec record { principal; AccessRights }; + keyvals : vec record { ByteBuf; ByteBuf }; + map_name : ByteBuf; + map_owner : principal; +}; +type Result = variant { Ok : opt ByteBuf; Err : text }; +type Result_1 = variant { Ok : vec record { ByteBuf; ByteBuf }; Err : text }; +type Result_2 = variant { Ok : ByteBuf; Err : text }; +type Result_3 = variant { + Ok : vec record { principal; AccessRights }; + Err : text; +}; +type Result_4 = variant { Ok : opt AccessRights; Err : text }; +type Result_5 = variant { Ok : vec ByteBuf; Err : text }; +service : (text) -> { + get_accessible_shared_map_names : () -> ( + vec record { principal; ByteBuf }, + ) query; + get_all_accessible_encrypted_maps : () -> (vec EncryptedMapData) query; + get_all_accessible_encrypted_values : () -> ( + vec record { + record { principal; ByteBuf }; + vec record { ByteBuf; ByteBuf }; + }, + ) query; + get_encrypted_value : (principal, ByteBuf, ByteBuf) -> (Result) query; + get_encrypted_values_for_map : (principal, ByteBuf) -> (Result_1) query; + get_encrypted_vetkey : (principal, ByteBuf, ByteBuf) -> (Result_2); + get_owned_non_empty_map_names : () -> (vec ByteBuf) query; + get_shared_user_access_for_map : (principal, ByteBuf) -> (Result_3) query; + get_user_rights : (principal, ByteBuf, principal) -> (Result_4) query; + get_vetkey_verification_key : () -> (ByteBuf); + insert_encrypted_value : (principal, ByteBuf, ByteBuf, ByteBuf) -> (Result); + remove_encrypted_value : (principal, ByteBuf, ByteBuf) -> (Result); + remove_map_values : (principal, ByteBuf) -> (Result_5); + remove_user : (principal, ByteBuf, principal) -> (Result_4); + set_user_rights : (principal, ByteBuf, principal, AccessRights) -> (Result_4); +} diff --git a/rust/vetkeys/password_manager/rust/backend/src/lib.rs b/rust/vetkeys/password_manager/rust/backend/src/lib.rs new file mode 100644 index 000000000..ee4034817 --- /dev/null +++ b/rust/vetkeys/password_manager/rust/backend/src/lib.rs @@ -0,0 +1,298 @@ +use std::cell::RefCell; + +use candid::Principal; +use ic_cdk::management_canister::{VetKDCurve, VetKDKeyId}; +use ic_cdk::{init, query, update}; +use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory}; +use ic_stable_structures::storable::Blob; +use ic_stable_structures::DefaultMemoryImpl; +use ic_vetkeys::encrypted_maps::{EncryptedMapData, EncryptedMaps, VetKey, VetKeyVerificationKey}; +use ic_vetkeys::types::{AccessRights, ByteBuf, EncryptedMapValue, TransportKey}; + +type Memory = VirtualMemory; +type MapId = (Principal, ByteBuf); + +thread_local! { + static MEMORY_MANAGER: RefCell> = + RefCell::new(MemoryManager::init(DefaultMemoryImpl::default())); + static ENCRYPTED_MAPS: RefCell>> = + const { RefCell::new(None) }; +} + +#[init] +fn init(key_name: String) { + let key_id = VetKDKeyId { + curve: VetKDCurve::Bls12_381_G2, + name: key_name, + }; + ENCRYPTED_MAPS.with_borrow_mut(|encrypted_maps| { + encrypted_maps.replace(EncryptedMaps::init( + "encrypted_maps_dapp", + key_id, + id_to_memory(0), + id_to_memory(1), + id_to_memory(2), + id_to_memory(3), + )) + }); +} + +#[query] +fn get_accessible_shared_map_names() -> Vec<(Principal, ByteBuf)> { + ENCRYPTED_MAPS.with_borrow(|encrypted_maps| { + encrypted_maps + .as_ref() + .unwrap() + .get_accessible_shared_map_names(ic_cdk::api::msg_caller()) + .into_iter() + .map(|map_id| (map_id.0, ByteBuf::from(map_id.1.as_ref().to_vec()))) + .collect() + }) +} + +#[query] +fn get_shared_user_access_for_map( + key_owner: Principal, + key_name: ByteBuf, +) -> Result, String> { + let key_name = bytebuf_to_blob(key_name)?; + let key_id = (key_owner, key_name); + ENCRYPTED_MAPS.with_borrow(|encrypted_maps| { + encrypted_maps + .as_ref() + .unwrap() + .get_shared_user_access_for_map(ic_cdk::api::msg_caller(), key_id) + }) +} + +#[query] +fn get_encrypted_values_for_map( + map_owner: Principal, + map_name: ByteBuf, +) -> Result, String> { + let map_name = bytebuf_to_blob(map_name)?; + let map_id = (map_owner, map_name); + let result = ENCRYPTED_MAPS.with_borrow(|encrypted_maps| { + encrypted_maps + .as_ref() + .unwrap() + .get_encrypted_values_for_map(ic_cdk::api::msg_caller(), map_id) + }); + result.map(|map_values| { + map_values + .into_iter() + .map(|(key, value)| (ByteBuf::from(key.as_slice().to_vec()), value)) + .collect() + }) +} + +#[query] +fn get_all_accessible_encrypted_values() -> Vec<(MapId, Vec<(ByteBuf, EncryptedMapValue)>)> { + ENCRYPTED_MAPS + .with_borrow(|encrypted_maps| { + encrypted_maps + .as_ref() + .unwrap() + .get_all_accessible_encrypted_values(ic_cdk::api::msg_caller()) + }) + .into_iter() + .map(|((owner, map_name), encrypted_values)| { + ( + (owner, ByteBuf::from(map_name.as_ref().to_vec())), + encrypted_values + .into_iter() + .map(|(key, value)| (ByteBuf::from(key.as_ref().to_vec()), value)) + .collect(), + ) + }) + .collect() +} + +#[query] +fn get_all_accessible_encrypted_maps() -> Vec> { + ENCRYPTED_MAPS.with_borrow(|encrypted_maps| { + encrypted_maps + .as_ref() + .unwrap() + .get_all_accessible_encrypted_maps(ic_cdk::api::msg_caller()) + }) +} + +#[query] +fn get_encrypted_value( + map_owner: Principal, + map_name: ByteBuf, + map_key: ByteBuf, +) -> Result, String> { + let map_name = bytebuf_to_blob(map_name)?; + let map_id = (map_owner, map_name); + ENCRYPTED_MAPS.with_borrow(|encrypted_maps| { + encrypted_maps.as_ref().unwrap().get_encrypted_value( + ic_cdk::api::msg_caller(), + map_id, + bytebuf_to_blob(map_key)?, + ) + }) +} + +#[update] +fn remove_map_values( + map_owner: Principal, + map_name: ByteBuf, +) -> Result, String> { + let map_name = bytebuf_to_blob(map_name)?; + let map_id = (map_owner, map_name); + let result = ENCRYPTED_MAPS.with_borrow_mut(|encrypted_maps| { + encrypted_maps + .as_mut() + .unwrap() + .remove_map_values(ic_cdk::api::msg_caller(), map_id) + }); + result.map(|removed| { + removed + .into_iter() + .map(|key| ByteBuf::from(key.as_ref().to_vec())) + .collect() + }) +} + +#[query] +fn get_owned_non_empty_map_names() -> Vec { + ENCRYPTED_MAPS.with_borrow(|encrypted_maps| { + encrypted_maps + .as_ref() + .unwrap() + .get_owned_non_empty_map_names(ic_cdk::api::msg_caller()) + .into_iter() + .map(|map_name| ByteBuf::from(map_name.as_slice().to_vec())) + .collect() + }) +} + +#[update] +fn insert_encrypted_value( + map_owner: Principal, + map_name: ByteBuf, + map_key: ByteBuf, + value: EncryptedMapValue, +) -> Result, String> { + let map_name = bytebuf_to_blob(map_name)?; + let map_id = (map_owner, map_name); + ENCRYPTED_MAPS.with_borrow_mut(|encrypted_maps| { + encrypted_maps.as_mut().unwrap().insert_encrypted_value( + ic_cdk::api::msg_caller(), + map_id, + bytebuf_to_blob(map_key)?, + value, + ) + }) +} + +#[update] +fn remove_encrypted_value( + map_owner: Principal, + map_name: ByteBuf, + map_key: ByteBuf, +) -> Result, String> { + let map_name = bytebuf_to_blob(map_name)?; + let map_id = (map_owner, map_name); + ENCRYPTED_MAPS.with_borrow_mut(|encrypted_maps| { + encrypted_maps.as_mut().unwrap().remove_encrypted_value( + ic_cdk::api::msg_caller(), + map_id, + bytebuf_to_blob(map_key)?, + ) + }) +} + +#[update] +async fn get_vetkey_verification_key() -> VetKeyVerificationKey { + ENCRYPTED_MAPS + .with_borrow(|encrypted_maps| { + encrypted_maps + .as_ref() + .unwrap() + .get_vetkey_verification_key() + }) + .await +} + +#[update] +async fn get_encrypted_vetkey( + map_owner: Principal, + map_name: ByteBuf, + transport_key: TransportKey, +) -> Result { + let map_name = bytebuf_to_blob(map_name)?; + let map_id = (map_owner, map_name); + Ok(ENCRYPTED_MAPS + .with_borrow(|encrypted_maps| { + encrypted_maps.as_ref().unwrap().get_encrypted_vetkey( + ic_cdk::api::msg_caller(), + map_id, + transport_key, + ) + })? + .await) +} + +#[query] +fn get_user_rights( + map_owner: Principal, + map_name: ByteBuf, + user: Principal, +) -> Result, String> { + let map_name = bytebuf_to_blob(map_name)?; + let map_id = (map_owner, map_name); + ENCRYPTED_MAPS.with_borrow(|encrypted_maps| { + encrypted_maps + .as_ref() + .unwrap() + .get_user_rights(ic_cdk::api::msg_caller(), map_id, user) + }) +} + +#[update] +fn set_user_rights( + map_owner: Principal, + map_name: ByteBuf, + user: Principal, + access_rights: AccessRights, +) -> Result, String> { + let map_name = bytebuf_to_blob(map_name)?; + let map_id = (map_owner, map_name); + ENCRYPTED_MAPS.with_borrow_mut(|encrypted_maps| { + encrypted_maps.as_mut().unwrap().set_user_rights( + ic_cdk::api::msg_caller(), + map_id, + user, + access_rights, + ) + }) +} + +#[update] +fn remove_user( + map_owner: Principal, + map_name: ByteBuf, + user: Principal, +) -> Result, String> { + let map_name = bytebuf_to_blob(map_name)?; + let map_id = (map_owner, map_name); + ENCRYPTED_MAPS.with_borrow_mut(|encrypted_maps| { + encrypted_maps + .as_mut() + .unwrap() + .remove_user(ic_cdk::api::msg_caller(), map_id, user) + }) +} + +fn bytebuf_to_blob(buf: ByteBuf) -> Result, String> { + Blob::try_from(buf.as_ref()).map_err(|_| "too large input".to_string()) +} + +fn id_to_memory(id: u8) -> Memory { + MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(id))) +} + +ic_cdk::export_candid!(); diff --git a/rust/vetkeys/password_manager/rust/rust-toolchain.toml b/rust/vetkeys/password_manager/rust/rust-toolchain.toml new file mode 100644 index 000000000..2a2058b04 --- /dev/null +++ b/rust/vetkeys/password_manager/rust/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.88.0" +targets = ["wasm32-unknown-unknown"] +profile = "default" \ No newline at end of file From 83fb0c260314fcea836b895bb802cce2e476ac94 Mon Sep 17 00:00:00 2001 From: Andrea Cerulli Date: Mon, 20 Apr 2026 17:16:25 +0200 Subject: [PATCH 2/3] fix: restore missing README, dfx.json, Cargo.toml, and symlinks for password_manager --- rust/vetkeys/password_manager/README.md | 59 +++++++++++++++++++ rust/vetkeys/password_manager/motoko/dfx.json | 37 ++++++++++++ rust/vetkeys/password_manager/motoko/frontend | 1 + .../vetkeys/password_manager/motoko/mops.toml | 13 ++++ rust/vetkeys/password_manager/rust/Cargo.toml | 13 ++++ rust/vetkeys/password_manager/rust/dfx.json | 31 ++++++++++ rust/vetkeys/password_manager/rust/frontend | 1 + 7 files changed, 155 insertions(+) create mode 100644 rust/vetkeys/password_manager/README.md create mode 100644 rust/vetkeys/password_manager/motoko/dfx.json create mode 120000 rust/vetkeys/password_manager/motoko/frontend create mode 100644 rust/vetkeys/password_manager/motoko/mops.toml create mode 100644 rust/vetkeys/password_manager/rust/Cargo.toml create mode 100644 rust/vetkeys/password_manager/rust/dfx.json create mode 120000 rust/vetkeys/password_manager/rust/frontend diff --git a/rust/vetkeys/password_manager/README.md b/rust/vetkeys/password_manager/README.md new file mode 100644 index 000000000..d44ee8663 --- /dev/null +++ b/rust/vetkeys/password_manager/README.md @@ -0,0 +1,59 @@ +# VetKey Password Manager + +| 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**. + +## 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**. + +## Setup + +### Prerequisites + +- [Local Internet Computer dev environment](https://internetcomputer.org/docs/building-apps/getting-started/install) +- [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. + +### Deploy the Canisters Locally +If you want to deploy this project locally with a Motoko backend, then run: +```bash +dfx start --background && dfx deploy +``` +from the `motoko` folder. + +To use the Rust backend instead of Motoko, run the same command in the `rust` folder. + +## Running the Project + +### Backend + +The backend consists of an **Encrypted Maps**-enabled canister that securely stores passwords. It is automatically deployed with `dfx deploy`. + +### 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. +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. + +## Additional Resources + +- **[Password Manager with Metadata](../password_manager_with_metadata/)** - If you need to store additional metadata alongside passwords. diff --git a/rust/vetkeys/password_manager/motoko/dfx.json b/rust/vetkeys/password_manager/motoko/dfx.json new file mode 100644 index 000000000..3118676e7 --- /dev/null +++ b/rust/vetkeys/password_manager/motoko/dfx.json @@ -0,0 +1,37 @@ +{ + "canisters": { + "ic_vetkeys_encrypted_maps_canister": { + "main": "backend/src/Main.mo", + "type": "motoko", + "args": "--enhanced-orthogonal-persistence", + "init_arg": "(\"test_key_1\")" + }, + "internet-identity": { + "candid": "https://github.com/dfinity/internet-identity/releases/download/release-2026-03-16/internet_identity.did", + "type": "custom", + "specified_id": "rdmx6-jaaaa-aaaaa-aaadq-cai", + "remote": { + "id": { + "ic": "rdmx6-jaaaa-aaaaa-aaadq-cai" + } + }, + "wasm": "https://github.com/dfinity/internet-identity/releases/download/release-2026-03-16/internet_identity_dev.wasm.gz" + }, + "www": { + "dependencies": ["ic_vetkeys_encrypted_maps_canister", "internet-identity"], + "build": ["cd frontend && npm i --include=dev && npm run build && cd - && rm -r dist > /dev/null 2>&1; mv frontend/dist ./"], + "frontend": { + "entrypoint": "dist/index.html" + }, + "source": ["dist/"], + "type": "assets", + "output_env_file": "frontend/.env" + } + }, + "defaults": { + "build": { + "packtool": "npx ic-mops sources", + "args": "" + } + } +} diff --git a/rust/vetkeys/password_manager/motoko/frontend b/rust/vetkeys/password_manager/motoko/frontend new file mode 120000 index 000000000..af288785f --- /dev/null +++ b/rust/vetkeys/password_manager/motoko/frontend @@ -0,0 +1 @@ +../frontend \ No newline at end of file diff --git a/rust/vetkeys/password_manager/motoko/mops.toml b/rust/vetkeys/password_manager/motoko/mops.toml new file mode 100644 index 000000000..593456675 --- /dev/null +++ b/rust/vetkeys/password_manager/motoko/mops.toml @@ -0,0 +1,13 @@ +[package] +name = "ic-vetkeys-encrypted-maps-canister" +version = "0.1.0" +repository = "https://github.com/dfinity/vetkeys/examples/password_manager/motoko" +keywords = [ + "vetkeys,vetkd,encryption,privacy,signature,BLS,key ", + "derivation,IBE" +] +license = "Apache-2.0" + +[dependencies] +base = "0.14.6" +ic-vetkeys = "0.4.0" diff --git a/rust/vetkeys/password_manager/rust/Cargo.toml b/rust/vetkeys/password_manager/rust/Cargo.toml new file mode 100644 index 000000000..0169acf9f --- /dev/null +++ b/rust/vetkeys/password_manager/rust/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] +members = ["backend"] +resolver = "2" + +[workspace.dependencies] +ic-cdk = "0.19.0" +ic-stable-structures = "0.7.0" +ic-vetkeys = "0.6.0" + +[profile.release] +lto = true +opt-level = 'z' +panic = 'abort' \ No newline at end of file diff --git a/rust/vetkeys/password_manager/rust/dfx.json b/rust/vetkeys/password_manager/rust/dfx.json new file mode 100644 index 000000000..356bdb3f5 --- /dev/null +++ b/rust/vetkeys/password_manager/rust/dfx.json @@ -0,0 +1,31 @@ +{ + "canisters": { + "ic_vetkeys_encrypted_maps_canister": { + "candid": "backend/ic_vetkeys_encrypted_maps_canister.did", + "package": "ic-vetkeys-encrypted-maps-canister", + "type": "rust", + "init_arg": "(\"test_key_1\")" + }, + "internet-identity": { + "candid": "https://github.com/dfinity/internet-identity/releases/download/release-2026-03-16/internet_identity.did", + "type": "custom", + "specified_id": "rdmx6-jaaaa-aaaaa-aaadq-cai", + "remote": { + "id": { + "ic": "rdmx6-jaaaa-aaaaa-aaadq-cai" + } + }, + "wasm": "https://github.com/dfinity/internet-identity/releases/download/release-2026-03-16/internet_identity_dev.wasm.gz" + }, + "www": { + "dependencies": ["ic_vetkeys_encrypted_maps_canister", "internet-identity"], + "build": ["cd frontend && npm i --include=dev && npm run build && cd - && rm -r dist > /dev/null 2>&1; mv frontend/dist ./"], + "frontend": { + "entrypoint": "dist/index.html" + }, + "source": ["dist/"], + "type": "assets", + "output_env_file": "frontend/.env" + } + } +} diff --git a/rust/vetkeys/password_manager/rust/frontend b/rust/vetkeys/password_manager/rust/frontend new file mode 120000 index 000000000..af288785f --- /dev/null +++ b/rust/vetkeys/password_manager/rust/frontend @@ -0,0 +1 @@ +../frontend \ No newline at end of file From dfb06717ccb43ed7497816893cafc28d4997cb1c Mon Sep 17 00:00:00 2001 From: Andrea Cerulli Date: Mon, 20 Apr 2026 17:30:28 +0200 Subject: [PATCH 3/3] fix: add missing frontend directory and Cargo.lock for password_manager --- .../password_manager/frontend/.prettierignore | 7 + .../password_manager/frontend/.prettierrc | 5 + .../password_manager/frontend/README.md | 17 + .../frontend/eslint.config.mjs | 56 ++++ .../password_manager/frontend/index.html | 14 + .../password_manager/frontend/package.json | 47 +++ .../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 .../password_manager/frontend/public/vite.svg | 1 + .../password_manager/frontend/src/App.svelte | 13 + .../password_manager/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 | 309 ++++++++++++++++++ .../frontend/src/components/EditVault.svelte | 86 +++++ .../frontend/src/components/Header.svelte | 27 ++ .../frontend/src/components/Hero.svelte | 60 ++++ .../src/components/LayoutAuthenticated.svelte | 29 ++ .../src/components/NewPassword.svelte | 108 ++++++ .../src/components/Notifications.svelte | 22 ++ .../frontend/src/components/Password.svelte | 106 ++++++ .../src/components/PasswordEditor.svelte | 68 ++++ .../frontend/src/components/Passwords.svelte | 79 +++++ .../src/components/SharingEditor.svelte | 218 ++++++++++++ .../src/components/SidebarLayout.svelte | 81 +++++ .../frontend/src/components/Spinner.svelte | 5 + .../frontend/src/components/Vault.svelte | 143 ++++++++ .../frontend/src/components/Vaults.svelte | 90 +++++ .../frontend/src/lib/encrypted_maps.ts | 43 +++ .../password_manager/frontend/src/lib/init.ts | 1 + .../frontend/src/lib/password.ts | 56 ++++ .../frontend/src/lib/sleep.ts | 3 + .../frontend/src/lib/vault.ts | 60 ++++ .../password_manager/frontend/src/main.ts | 8 + .../frontend/src/store/auth.ts | 116 +++++++ .../frontend/src/store/draft.ts | 36 ++ .../frontend/src/store/notifications.ts | 30 ++ .../frontend/src/store/vaults.ts | 166 ++++++++++ .../frontend/src/vite-env.d.ts | 2 + .../frontend/svelte.config.js | 7 + .../frontend/tailwind.config.cjs | 6 + .../password_manager/frontend/tsconfig.json | 25 ++ .../password_manager/frontend/vite.config.ts | 44 +++ 47 files changed, 2239 insertions(+) create mode 100644 rust/vetkeys/password_manager/frontend/.prettierignore create mode 100644 rust/vetkeys/password_manager/frontend/.prettierrc create mode 100644 rust/vetkeys/password_manager/frontend/README.md create mode 100644 rust/vetkeys/password_manager/frontend/eslint.config.mjs create mode 100644 rust/vetkeys/password_manager/frontend/index.html create mode 100644 rust/vetkeys/password_manager/frontend/package.json create mode 100644 rust/vetkeys/password_manager/frontend/public/.ic-assets.json5 create mode 100644 rust/vetkeys/password_manager/frontend/public/img/ic-badge-powered-by-crypto_label-stripe-dark-text.png create mode 100644 rust/vetkeys/password_manager/frontend/public/img/ic-badge-powered-by-crypto_label-stripe-white-text.png create mode 100644 rust/vetkeys/password_manager/frontend/public/img/ic-badge-powered-by-crypto_transparent-dark-text.png create mode 100644 rust/vetkeys/password_manager/frontend/public/img/ic-badge-powered-by-crypto_transparent-white-text.png create mode 100644 rust/vetkeys/password_manager/frontend/public/vite.svg create mode 100644 rust/vetkeys/password_manager/frontend/src/App.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/app.css create mode 100644 rust/vetkeys/password_manager/frontend/src/assets/svelte.svg create mode 100644 rust/vetkeys/password_manager/frontend/src/components/Disclaimer.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/DisclaimerCopy.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/EditPassword.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/EditVault.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/Header.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/Hero.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/LayoutAuthenticated.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/NewPassword.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/Notifications.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/Password.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/PasswordEditor.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/Passwords.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/SharingEditor.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/SidebarLayout.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/Spinner.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/Vault.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/components/Vaults.svelte create mode 100644 rust/vetkeys/password_manager/frontend/src/lib/encrypted_maps.ts create mode 100644 rust/vetkeys/password_manager/frontend/src/lib/init.ts create mode 100644 rust/vetkeys/password_manager/frontend/src/lib/password.ts create mode 100644 rust/vetkeys/password_manager/frontend/src/lib/sleep.ts create mode 100644 rust/vetkeys/password_manager/frontend/src/lib/vault.ts create mode 100644 rust/vetkeys/password_manager/frontend/src/main.ts create mode 100644 rust/vetkeys/password_manager/frontend/src/store/auth.ts create mode 100644 rust/vetkeys/password_manager/frontend/src/store/draft.ts create mode 100644 rust/vetkeys/password_manager/frontend/src/store/notifications.ts create mode 100644 rust/vetkeys/password_manager/frontend/src/store/vaults.ts create mode 100644 rust/vetkeys/password_manager/frontend/src/vite-env.d.ts create mode 100644 rust/vetkeys/password_manager/frontend/svelte.config.js create mode 100644 rust/vetkeys/password_manager/frontend/tailwind.config.cjs create mode 100644 rust/vetkeys/password_manager/frontend/tsconfig.json create mode 100644 rust/vetkeys/password_manager/frontend/vite.config.ts diff --git a/rust/vetkeys/password_manager/frontend/.prettierignore b/rust/vetkeys/password_manager/frontend/.prettierignore new file mode 100644 index 000000000..987c28939 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/.prettierignore @@ -0,0 +1,7 @@ +# Ignore artifacts: +build +coverage +dist +README.md +**/declarations/ + diff --git a/rust/vetkeys/password_manager/frontend/.prettierrc b/rust/vetkeys/password_manager/frontend/.prettierrc new file mode 100644 index 000000000..2b9bd83ee --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/.prettierrc @@ -0,0 +1,5 @@ +{ + "plugins": ["prettier-plugin-svelte"], + "tabWidth": 4, + "overrides": [{ "files": ["*.svelte"], "options": { "parser": "svelte" } }] +} diff --git a/rust/vetkeys/password_manager/frontend/README.md b/rust/vetkeys/password_manager/frontend/README.md new file mode 100644 index 000000000..134a1abb0 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/README.md @@ -0,0 +1,17 @@ +# VetKD Password Manager frontend +Uses the defaults provided by the devkit to implement a VetKD-based password +manager. This utilizes the encrypted maps canister example to realize the +password manager, i.e., there is no dedicated canister implementation, only the +frontend implementation that uses all defaults from the SDK. + +## Step 1: Deploy `encrypted_maps_example` canister and the internet identity canister. + +## Step 2: Tell `frontend` what canisters to communicate with, so the following environment variables must be defined. For a local deployment, one can run `deploy_locally.sh` from that folder. +* `CANISTER_ID_IC_VETKEYS_ENCRYPTED_MAPS_CANISTER` + +## Step 3: Deploy frontend. This returns a link that can be used to access the frontend from the asset canister. +```shell +dfx deploy www +``` +Note: if this returns a URL with the IP `0.0.0.0` and the fronetned does not +work, a potential fix is to replace it with `localhost`. \ No newline at end of file diff --git a/rust/vetkeys/password_manager/frontend/eslint.config.mjs b/rust/vetkeys/password_manager/frontend/eslint.config.mjs new file mode 100644 index 000000000..96aee8e5c --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/eslint.config.mjs @@ -0,0 +1,56 @@ +// @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", + "*.config.ts", + ], + }, + { + rules: { + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + }, + }, +); diff --git a/rust/vetkeys/password_manager/frontend/index.html b/rust/vetkeys/password_manager/frontend/index.html new file mode 100644 index 000000000..89d2ddc2a --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/index.html @@ -0,0 +1,14 @@ + + + + + + + VetKeys Password Manager + + +
+ + + + diff --git a/rust/vetkeys/password_manager/frontend/package.json b/rust/vetkeys/password_manager/frontend/package.json new file mode 100644 index 000000000..2c2bd3812 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/package.json @@ -0,0 +1,47 @@ +{ + "name": "password-manager-frontend", + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint", + "prettier": "prettier --write .", + "prettier-check": "prettier --check .", + "preview": "vite preview" + }, + "devDependencies": { + "@rollup/plugin-typescript": "^12.1.2", + "@tsconfig/svelte": "^5.0.4", + "@typewriter/delta": "^1.2.4", + "daisyui": "^4.12.23", + "eslint-config-prettier": "^10.1.5", + "eslint-plugin-prettier": "^5.2.6", + "eslint-plugin-svelte": "^3.5.1", + "globals": "^16.0.0", + "prettier-plugin-svelte": "^3.4.0", + "svelte": "^4.2.19", + "tslib": "^2.8.1", + "typescript-eslint": "^8.35.1", + "vite": "^5.4.21", + "vite-plugin-environment": "^1.1.3" + }, + "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", + "@popperjs/core": "^2.11.8", + "@sveltejs/vite-plugin-svelte": "^3.0.2", + "@tailwindcss/postcss": "^4.0.6", + "@tailwindcss/vite": "^4.0.0", + "autoprefixer": "^10.4.20", + "rollup-plugin-css-only": "^4.5.2", + "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/frontend/public/.ic-assets.json5 b/rust/vetkeys/password_manager/frontend/public/.ic-assets.json5 new file mode 100644 index 000000000..a57140a5e --- /dev/null +++ b/rust/vetkeys/password_manager/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/rust/vetkeys/password_manager/frontend/public/img/ic-badge-powered-by-crypto_label-stripe-dark-text.png b/rust/vetkeys/password_manager/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 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 k5>+ca(Es=%(OZCZ_*S7t&NA}<0giOF*Z=?k literal 0 HcmV?d00001 diff --git a/rust/vetkeys/password_manager/frontend/public/img/ic-badge-powered-by-crypto_transparent-dark-text.png b/rust/vetkeys/password_manager/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| zB<8aU-(WM@;#rIA1nu%}ogTr=JcT%&!%hF=K18d?{gh%JrGE3y*;<Uw2WKO%>u(a4Az7s%MYL4fMfwVj5=U98Uq(ZVmt?I#@#(feGrAOL$Cv{SZXAKGloqmXV5{%h37xI6g{kS+YKZS-RAqNm&41h6Gvbs&*hTQ$(q-uVyORCz$=UZFI)Cj(v76wp zJ7gj*)LLv+=GKg?fDlJDY#?YUh<9kcS9%WMy}TR=Jc>Vme~QAi%iER5`u^;HoK^cS z?2EbiT7{duSQbL*YxPSL=P-@R6R3qHfek%g7Zg2YFMPkNXYl$Iyn`xyOHR&h^}sSx z{W&Wte&!qZB){-$xchD}H9a7;Gksy}$%x3TCxCPQ1*~`zw^V4|vH3{a!(jI~rsa6u z%+Qe!Krzatkw8y1S=^(S?V-#x4P79U^DpMG$7wgfD5?44V*&LvY-LtQppK1k8r|$W z9b*{Jn|C*nwcAYII4R_!KMJqI)icbtX4t=S9F}oBDe5~QU|a2sbp#Kz7XNDII|14{ z1bEVlB?7j7<=_ehoDD@(i*%JIhT#2;yE0&X{u!Taz>B#FcE`yy=2X+*3DRmAaDU0Z zrWFhX9ghL#=Is1*(9Am0Rhjy?LEp;)Sc@jEv*d<3g97X3xt3gq*~s@cJD(i z$zY4i@%xr}X&5&CvErC}vdj(j_$X)dU>4@H2Gr=;Hw1l3GzsQjl~~a<+WnkT{hdG(ov8LyJXoFINW5k_$(7D*_=h5o1YswkrzOR4eDMJlP^J36Q_ zk4@if5t;@pD;$ruNz~V51#|_mqOEceRFMzR5uFNjv6B}3(&Ot*P3QDn@3cPFo2yp!=k-h%G7ynUn`TbaFgC@x+UQsm?lC5B!yu^9^OU!2$!uu+oSws8 znobuxQGl5X&K%l`hJ(IGV*0Q9^h#Q3lU5Q%C~5((sdSG`>$FfsRdd!*=gkAt>>S81 z3%FfrN`dOle9~oXr&z2NIWLcDRqaIGI-Tr!ocD%y_u6tE(CV}xPg&GHMaxQs*90ZL z;h30QK>vMpFO2|hh_MSJ`4nOFH{n_>GbcR_`%Zc%{czTZ4Gmb$d%RYYP)oR(L5hW| zUlz*Yq{`5SM6QlTF)O=vKuG*#{*w%?3`QihO8V-$wl;IO1hR~KQx z_HnXXVp<#NR$&r2n?9Mfs~#|`94bzxmcwJ?^Lv`l_pD2E6y6wmkAuUVhtAZGaAVg_ zUtOKx1a+e3{G>uu+q%d#>r9D)m6d@p|w5p&yd|h6$qZdWN+jbm5`URM_jztUf+^h2E=L+s;Ml%~P zKCSs>i+&HnIk6ECkr3*7`xUMbCvZWdwwxI+0-s}RZAQ#KTu*@in#=M`w{BFn3mT2C&1={?ct-pX-sDX{!& z!bBkDIFrIcLH^iRlT-0^*cx@+RrIIOu+3JXvHb099!RN(f7%9jfeXb}lBa$BrX1VG zX0Ot9RtccC+em3_1V7m-qbn)S9ZTNTYXyag9IHc~zJMn$HrrxBs75%1FkKXX!*lS!>XmF!R@J9tquCRd-20G^V~ z{&^Z-jYcqVWl1*6Hf3u+U$9Kq$09q;JI7%>CVFurxqk6Jkfd1fOt&k)QK~|9M%Y?? z;Wc)MTuSGY@O{>nH&p-$aCPsRv}5->6>t_A+Ht3Mj(NCbb%~@CkyCl8Q{5owOdKL) zh6VR;7U5q#gX7cEpLS0RDG?f?a}P(-fXFuzdqnr?EL0S11pC&__E<}lS+MlRUmi16uAM*XFzv7X17wV?}$5! zmBeNf-TU33IqN==&ONBiS^YgX1 zpkg<1mzi@)6At&OqzYq>Np$W{S*T2(H?A=6YFu*iSk_Of3|_bQs(;k(TDJ_ysciKL zX_U#BgNc>qt=jK=Zg-|k6Y|#sVl43llHINFe5YRnlf&vCtAfs+N3U5LGYwX3xG@5# z1j;W_*@q8xj|1uuM$`K&rNeS$MW2^zIY!n|9?Q~OV>7MQ$Uz;q&EsHC{Py$U@gdit zz2ItMaJEXQID2OJT}JS)sN~P@AzEf+_#Y*RGRIE5Y2?crD@%HrbsH__4k+Ga48lpo zb{2YF?Oc9%;>C(=(`Ugvz$%AMnKmGVz`dry{7A&7KxPw1HA1yMt5~Xx+s~|*(j&1-PQ#Sh`9;-`g?UkqwT)p{LMvY&;~wk_J49||!4c+QnLq<}4!8Vu~{ z#uAhqiwXKKXtNB~mKJbN+Xr1W44s$#=G?9oxq8T5Xf$`$777Kju|!IGsAtBW2kabBkeziD7$5l_Fbzv~Z_eJqRw< zf@XD@Ud&}8`-kua4NvWhJDS*d09ej;G2R%2gJYAw7G^|6bYi1EnLImGNstG0d-L^d zxG>~MXc&p;^L)ALGVr5$@1loSBNLKnR+qbe<-r`oIodZOHHy{JK9Dj!o2QEcbrwJ2%>ZdAC-7D?z` zLkLnsTx;QQul&?j^;%=%37?9y@c5`@ zY25N3B>3H24IeD0ZGz@o%GPtz?5qESIdR^W#pWVV1wsK?I08CBn5m6eBrG;yhtkMc5je4M*f)K3 ze_53kpL5ShxVgEuHt>eg_grFf0Q;l)HeX14b_l4th-ZYu@V z(o8I&xcz{Y4)=3atWnnJ=k-uT+#vOBi;e4F)*3bX$cyndts5Qg#(2OaL$QKC6Wvco z6-EuMtB^Gn-h3b4PFsL9@$sGeqYL{MY*2VC7Q)P>!n2jW zniFVo9_z3OV{)a)yV=f9Y{8P-7lGx5t*p){A0;S_7#(id4xyDj4vPV&hMJR`R}$q4 z$2dMt`w@tZWaJa>G8+q|;OzIu67!b<%gy4p-&n^qk{ZfqP?Zdc+de8JlVm+k9jiM3 z=XA|=yT1a+MKr`2xINbXB8gMb$0%fn-%MtantB&+y8O#S5(B(^kW&aFbVweDKk(W* zLWczf$u+`6d|=~~{>9+@P)Nutu>YCxSN9*~Q=R^yw$+2|gal%bxLH@IDa+;jB`c?} zGfO|4#Ir})hQ@YF8`UY$gHbx&HPD^Z2@AawMKF(8tNZH8be4VLC(8EiBry1{@R5C` zUDhj*tG#^c%TbJ9pxHdOyJ*0}5_-Bsl~QVthdYY{xn-!azB|!r$K#lqhtKxC(UyU3 zn0&$L#6ot>r@bl_(xEU(5?QnHg3Qc$^(8$itWLL{q87{S77y=NHuWp`1E|WD96lx6 z#ReW5?B3#t{?Lq@?bnM`P(wg-0{i?l2ci-!7T20NsatCgx?WnVMlsXg!Ao zxmJK^Jkq7Kw0__?Bi~JsGJ~p7*>}twc$A5!rzS@Znwa(|ZvpR<)uxJFCY7L)(i2bc zy54Dq^RKZ`#C<8tk8j=oB?;iIZ@Jb?Ow8%9jK(a2+UrAX9bdC<31jJB^P3CR3zAH3 z%F|Jl8~`Zqw8fXfCw8hL(lj?GSV*(+nGe_>grz$zckUBoc8eh}YuE~}zKmVr3qfoW zstXhM3>Ol`i8|?$(@F27JY)5Nks{1^AnxyMm-^aa=2L~vc zOr9r;3hI8Yp!e>r-4jhMYnRKL&-oK4E%Nfiq_I}T8|^$JpBJbwESFe`+KY<>BzTrPYd${Kd>7EbF*e|MGN;1@_IxfxZ;V8JZgtun$fNw&(U~t5XB7iUw0ET~jQrP=(VwWj;1A5VbhzvoOIu|H6sldFPxVXE zc0?Kas8U&dNn&#oy6H$}8?$nAovVBD9R$jsiGF10%hM~U-$J80JW-d2S59Yf@0-Pg zrS0R+x+MYXuM3EJhxhvJ0*LjC6NC*IiKH)2RvLgoH~c>9GHjX|ItBGtME@`D1NL?= zvzB-m>GiF7Ay1gkmh>z1hCQQIr=hae$W^jcOU$DJy$?>a(?-FcaS=LblwQ$$nf@DqO&*=I;-# z5`_gJcxLMCCtHi{nwyn{udWOptYtp2zayZ{&INBSaWy=1QahJsH-FkUG=i=^rEP*|P<1WF z-uG2zu2V^Ht-*ZD@_1xkn$o4u9O1ZxC!)ed;h1A32AOx;)9tHj%=K&uS=_fyyBE3N ze%^L>fy&HzbY65ob}t+CA8CLCOyyw(HW`r~L9JjGP4AQH6?%l;dVbOh?Bvm6NUurt zO7lMMv+TgQJ4Na4`g3ystj_ijg6qC{NU2P?gv~S`u+k*Mwne_Dj_}2NTVlAc#WXob zMz5ek&xzD==t=`>Crt>6NI$wQRA2y1|4b+9%)7tFvy=nQneNF#Q^yZQw4^3&m!b5y z7s-+aU?8>XP*}aLzc^#io1wi*0GNe-{TduoLQIG}>)7RG%>|Gr8_W>Qh1z3Dge zv}#x47YaZ z;zztv6ia;ByxX~Kk9|4cZ1Jzo^3-u%SCWXXWG3~v8bq*3yqzQE4SYmU?Ihc+aWfJB zfgYO|uod(6C0F&J4Q7q*1_o48YZAAf2o|?#)ITO`MLn$9#7v($kfyO8r$V|s8#J>h zGPHbe>i<3e7)TKM^T#{0O~SU`_bHvI{?oA+tu>iZ2~ zxWi$cJn4vkrZ`eb{YaHT&`-bEy}ZXNCxlYm>2QOY*uG3~dJhbn%O?dmnuKoS3jdPN z<=IMTn@HpoX#pOzPU~Z>I5ObRmt}|*PB{-aTv{*}AbxdM$pFweh7{R#0IxpSY?N@ca!FuN%{|Pfb5;>g- zCtn#YaXjhKOq~-+d<5$8H~=H_K~hcUHP2nBzLf;qYz;k`3eP^aTF&!ol|#QI89R0{ zZ;o&KdnFA+wV|+Flj8EsY-2MWIlN>Rqa-#~a?~l@n04vfYC4T%iDJ9PJR>i%eSYfQ zRw^l0oXv~#gY&Xnhv+h1_uauxrZ!0Nn`b4mF@`JS@WSP}|;J=Qs1AW8He*6fc*f~$m%Q<)2J7C6K&8P-^v$#j`VCJWb{Nm|a%T;1wm zuQ_3&Eke33`bPL$`&n7u=I)~i_;1cu%UyK3UC9b|V4r0#gMFtI`%5owJbCpX0Ml-pRJB3HEn4_(d8X4V=zEDZjIouyrKU+;+C?<|->n7LH$$nmP# zeWnmvRJAjJSDKk|9J8AwNTT0*A+v_^^=4;BKMD}|y(aUB4G#(5*^X|Cl8AG|`;^bB zHOB?t2EN{ObLuh?uAAeDHZ1o!3;4W+p#o@VW{EcyU7qSKan`!a0p|P zI_P+5kMCvrJ(@%XUoYk6HL~zxzFDa{BuAHtbA4{KW`p^1bWYpU<*FHAwjfC>cQv|D zi+H|>MMZmgn}3C<@NWOmdcU>(-hb@?Q57p{wQU}@|Gg66sC5?7;3UlW8@12!v1Ps) zF_-JP%b$ghV`|XsBmV)2b04}Ir5`Jze4P9F>H<+Q4qEo3bT+&D?VRul<=9)fac&Gm zf;H}sN7Q>&QICvC8)=amku7ctXDx-RbPP^R zkh2!#vy7-iGNW9lVX8(+Klk}}JDUk3j7GA90&r5Hl%ZyRXBzY&EQ}zbv#x16(N{Kl zC9`*B%34RcU3BU%`5(}bjLlH*ZA7kaAt1Dxwt<{sW7MePRCaJYFhfn?S_ULqB|53? z(Czafd|<4;44uuhc<+vxk5Oi|0p)JsWwap zM?r0IX_ra-9IfE@JEXXY4-s@ZqdS1gEQ7dK+qaE*L@1C`==U>}9g!lk*E>l{L!6(o zq5(Jvs9^7<&)9TQO?ZA)nb1-p8VrBbtp#gDGTS<%H*DOTA6gIIFAXi+!#5Hp_>q5N zBdt-+y)D6@O?Bvf&7f!Ugyp*}&a~H!AUm zIEGDAei@K|;L4O$_&spZF~i)SW=*Qr)A$~O6%czY1(8@Xs{V_ahllOgJo_@ z(hf0KXPH#GSUh_;lc`R>Wz|fvOo3M8Uo~ph(Pf671{uBSPbm+<4`^e--PWjv5Wo#$^ z%WbabZH})Yhrq?JqgGn4K=zIyw1Xap;;=9xl8bF?*2}iy6~(h33NV~(G1H%FeV^|V zUsX}KMAZ&F`7lPP!T>FU>f6=2ZS9FY?_&rh?zyv3$k(8IyuXg;n_Z5XnHlVBpbkef zW&5d#vH0F0uUMFKQA|F%QoFHcy=XI`%vS-m9c;#233lf-fK}08`9m#semD2g)O|-p z+>U6KDNg9aTj#TNoKl-^YkU*S!IONRxmC5+m;{o0#3I2$gBzKVg29pqvOq5fv21rX zCF|6trB^c_XSwm?jB12Y#S?fZRja{4GY_|5+x7Z5tEt2=S2mA6bKxoEZlZvW^d&rn zOH1F^Wjb{+{e4@QC|{DtIJ78ZM7PioOPe-wp2KzZhG3=qsMuUjd95$Q3MMQ5#}dLC zjntaU&|%mog&f58TZav#`Tf@0iJu?p9-X*n6aj!S^J3qdG2+1N%@Ku8*=5aQ5JJIySIwvvzw$y zXudtdf;2}w|4Ll$_{0IW574iS!9yk0BM9v0LFo2mdJ)l7VeobOWfcqZr=l`bn~vo+ zGwIBQq%qb=Q4&b>d*iILb(xBao9YW7OFBlpKPMezl)d|^_|0y~qHI*hejX*~gB3*h zn7OR`vZZ=1zOQ)1uz}|5i^GC+IQyHJknd70?Y2eVcy&>iF7brYqCc27mnghbwdjVZ zqE+>BxlT{;#QFs0BOA06-~7X)LL}+19Qi#_>Zm)kea56)1J+Jxcc0 z>o4x&PbE0kKzv!a) zBWGMutZ+^(-hVnW8;OudKr#)qE{)%RZgxEBFJ9bZu z!a!C6|1*e{?aV4w^Xi!IRhw04e&W6hbK zhZKzcUp6gtn6N?ycZK~%jpg!1U`;4G^}VLsfY`w=m1VrC0;o{QxhiL)CVtqW@v?FX zAexvWRL{QE3>eyMK0x2(G-b}M2Y=X*;h2da3d63g(e8M0`B^$IH?vK{>AaAaD;glp zk$$V>c;*<=OH$ zofg$TRnxcx)L!;^ZW4T2Hn}@D^IJg=cU;H2RQ>n!aYs-^N_19H3r0s0}F>2n5@LRpiB4emW-Ed52M&p_Aw+ifxVVpFmV#PD)^vay zKO$$iuD3a+a;?mS6k!UEIAbC$Ym*A{%s5Hlq`xgpCD-w){+{IMD$|ZbZgV_IH|>UM z4fz=OGcZhC6Yw`@(pvrD$5FKo;E%vEAY$_ILmAA80^)S|9CiI|j|ey33w;bMCw!AT zLil67j4|;F)9!4Z^T-$!62o%K1D{mPNPYVal3eBaho{EZkQ5czb?-8a1Kuf*&Fgb2 zNE>~Vr&bmB_2(1tg72m>6q<&~i$FLt zXdwNzF|@OKK)dTAZz*ztihq@A2le%&g+clcgB#n4$hAf<$TX1D#;qP}qV2J`ITQ{i o&Y1RTBV+IE?26RSvx0Zjk=WFz}JjwlN&c=Y2t0Z0Yj>?X+T+sD2RZ8HN+vj8bO1BF;L2gi>P^mpXYcx5-o#Pel~iGL?mPC zjj2#rp*xYuF_r-7nC%3BDacHv{_N)@SXy$C#U1++x_mN8FoyVXxa$Dy2$B;M#Qcc! zfeg$v1?$noZ-Mho^Uo;TbQJ;_V@SPCCdw^XUnw~vvzDL~1mHIW1b&0gas@mddPq>8 z-bMyDhgWac8V}bm`IumktEzsgUi16IeBXqPhTU2282ig4_wKw52?{lvVs;wg!Li8L z-|C*fkFiqpyvJY8<=JL-iXCp~q6+$Pfn_nkVsXzvN4#+zy2`T6ayVf^)dSpU@6gq2 zovRW9?$m8I*t$0XJ7iF%vn|JLN-?Z~Y#B=Spo%xcB^LJKCT7+fp=t4{4q5^n=^wM) zGII_>9d|&tId{YF%>(0jUvQ88kI_@J@8-cDpZfb|jqhB#^ zU5Q`Jm>Q$Qfau&F5W!6wN94)k208YA*1-2s??g|Q!}Do)rd!jj#8K5a6Z^E)J>J$g zM?gemSS*ys3B-EEy+JB+2F!(|Zc6GGhdH0I+r_RAL@lb+iZH8=U!YLlj^|Y~>o9iy ztyfO=!NEy+uG>47<+vG_t&cCc8EJiAfNJ6L`^p#Vb>oq~1XqO{vT^NWH0s-|Z|~F& zKavc$cDfsgK`Nrv4?z|v8Ii+; zKBZ$WCV5Gun!k#{4+Y-Y^`c7{qb|7?OJ077{3KWfu;~T_P$J^7FN^YDck+aS;3o%^ zgk(<>V+k6es&X<-e@BKf9(+wHCY1t?E6jKR%i-eEWk-s+CfweEOeR(C>6EOvN@`TmXpX1(#q=aKm#(M& zdoyW7Cy2rMhV{~fH38qS)4|V=hbPBdLC@OnW1mgBtC_8(eTv@ey|Qv{`iBx+sR8eW z9~EY+Un8;meL}|V$wk+OjG%Z;9O1fvY2w)sq_Y8z3rmI3hGzx=19)X&PP$p1NBB)SK8Mnt*y97IB zUf-m}9foyrtalgq*0%z=J)+^yS*~8IxRej032YOCDgHTAI=_0-?2+*?xR0~!&gWhl zbc0puRY~?G{Nq&ty_jL8OKgaegQqAx zPJ7nP#`p3E=HJKwR`d+{=_CDs@gML{4iQxN24cpY>(YjR|MJ`KyK7rVkC6N~HEEV4 z63=1*^v?ltHvRRdT)5BP9RPVM+w1m%i!lC|fAP3?(e+9Y|J+H@7XZQwGGYIktRp~P z8qx~r#DB)uJzi5ph3f3*<9~d?L)B^(=5u6x^#8(X+EfVDsfp%D;Xffn$t8?gCxWOD z{zbt_uqGi_#Qn=E;X9FQJ<%M+lk`6T>~C>F$p`8{mH)s|Ikb6}b6mHe?c#e+%t4oe zdu6yLW(m=(`Z%;J=n;_v`cTnA!=B$h4uX;{BPH^g{ElK}5zmORZvhsz zvc)wM!o>=V5-_?uJQC4ScPxIPASH)ne1aDn<$o`3096=#bVALgQ#B}A^jfEmj@&Dc zyV|twhkSKOesUl5z>qoj?RKq&eyk^A)J%GLp6_ESoIBbf2_P_0lNX;YJ6%6L4iqCzYyn`b&u%LIREhGCr5<9SnHF+BOrCy1xA?wRm>epKAsJ9k z65@wF;YCA3DBx7iMfP&-A#0gb#m9%JpV!%W(CFzz7^q^|KE8AKoGo`apn)@54yhbI z@-y;L8*4psq-vkj{%~+QdnFdW)kYX~z;R|Wwi3xX%`(26<)X8Ny$p)tQjfU{&`uyWf+1mN%|n(#6VUklc* zUQe>(-bYwX-p96vxyW&e$~R%5m$AjYPIex`%V%1@(~y6k5`-GpYomlKl#*vS>tz(N zj7jNUekxOrzcx|_(<#C%U7ie5?85#P?)fI>F$G#Lnbfib(*`dW1WvSzTgd~33pvKN zD|;}*3HKZdWdPunQ%;KMHq<+RFa39<%nb*a5uDl#Z0C)|7|O_7l7vwyM7sn}+rUNc zB2S7SiI)afHaLLDf^TCG^iQWWzYH<+^S|0T02)sr5EE|ML_ntre89*vJ#3@Yn3izX zFU|R5p^~cv)M&YqQ~6i)wCBq2UOHI=vsF4t;v%EI+iR*2n7Amf=zLF0)E_rW^GkpQ z?Aa!|7K$BIK3R3~H3-u1SyZs-az-*nxwiMiQ+z0Jh_d9ZH;m>*Nv&_HJCXUk8EQrnx_h?sI@)=e42{)-JKidmtMG;y)K-d zzmIXXeUCgP{+|m&QGoo6xIy~!swylqwi359j5;nwAyGZu8r3o^?U&i&6Nlkxz)wh% zNlpmVb)t-u?EXm}wU4`JlW$gOkEV0?lC)Pd*_F`biovCAVtI;u*X1Vrp_;$n&kE*! ztgN}zaAgRJrKymOL|krV8tkl-R(@2OMVh>}!OeD`;~scijQjfS0GYS;y|Uw8Leqh5h_h5nb8W>Y z^xH>2ec`F5ej>lLY5-{&u}=i9;C)jiU3-}=6d5o!LYI^1Yy-Wvbh z<;rMO+(yGpctfC}XoTEkDgUU2@01E&`|)TbPD%YOZrXubtX+jg9mVpYq_wssly@_qC2)oX#R~~YC!*7u*7Z}S8VsRiC&#m-GvEkp) zzKTR@x@8Iy28wgl^fIa-`=k04hFh0F3{E!x48V&-E?9tZ#iQThT4i|R%wnDY`-FMSO&C_cuz6vZX4m7 zyL!R~|Bgy^;rCCjf*{FEvb4Ws7Br(5e4M~`^+ez~t~t5O9c;+YL~&5$118M{ znlKU_K};rc*zgKMP38JkCFBMB`gG1EW?-nOd64%>SGCyq@G%2_6o3%d<-w84REuvq z*3PCfZyh9GcYHfxZ?LD+22$Z>6`P}{R^6ZNntCHc^`Udt$jcK z@Iwq0kHrogmK<658ejx9Sjdf|^PnlB{#;7G~}vjG`fV#mz!}m_pDt(GE|h;^}4=* z-|uu?ho?-^HOc@f!r}^ss7GB0TEY+sWj`X4TM)nRY5Pa#hn#_5@Y{RQrG-x!nLf-` zl3}9}f9>Bn(BX^!2k_dsq4J(`+TtmV%dACk+EC8YZ(Q_Fk!wfp#s5#MzUlJYfwud3 zZtH_sP{FTsGfJ)9+R>C_cd2X4j9F4Q?=Q;+*p+(d2fwv_KM)b)6fD^AHS}zUUDvL; z!PMbH?-@fcAFYOH-jFskLo%s9$DK2Kf>t7trN{vzKe$gdU4s5E1uWKmzN8v z=8<({qh*j=*K~)=0wpV0sGIshMNXC5#FFds-fdo@%wxU=K)JY@tSX;o6^q8dM(Obz zU5VH3bmSEIQG%xRbNz7`x+da{=Fn=CmJ>dYw#tT!;#)g{nM0z!HHjFZvUr=kV0=8Ibj|S5>Cu3|wXN$2r z1Z(V7pANj3Vi?(|UU>M_nlV=igEWW*)<*mQwbobW_ZqR6$JJ?L zis2Zv_**ZV{j@jTyJ+txt2A`N|GT!}&$j_spaTDEnA=KD zzHr1u0CuKZ17FExS*>K~Pq}CQ4zct3MNj!bE+mE{;VcMxdz$=ZUPlX>{y%UxKppcV zgel6RW}-zB(xE*95gYlQ^RV7ToIJ0nM<@_A&!tyO4Y^DGI1g#9Ws{kD6)S~hu(%yE zG=;r6Dpe$sB7$UI6_ESKE|t_iEVlpcdN;fjLAFLL*Nzn#qZhg)&6i8Bn{TT8jQl>s z=xKJb_UnaaK#9h(5W-Ee33n%xLnWcUS@X*xg2Er$QKd<->=h?KK7O=R!h0vG$MXcMc3(Q%)BoUJ6z|Xus{h zzbelZbV>oQRl*OLPaM0!83Pz<#Oh>rs^3C6kGf(8+}osajiwd1_P?M4eMEgbQaINc zyDjqBOi@;_1OEz+6FG(SH=DT}?VvRe`(}MeS;t9M^!X1}SFRDcg%O$|y{0hvoi?Dt zKv^gK?)qqGx@oqaJ`vuk3W?;;D zu-zW2yyA5kaADv)MZmU0=gLi#Tf5N!A)rHwE2<~poC+h&)x#c!0S*`v@-M+eKs%Lw`8Nx9XX zUP)jvhu7izw?pXZ!nf6L-^Mxbcy!k7H<~@~9?dq*v_ueQET;ttHz}0NF5GFsX4tjo z(?uC)nPYYlH=+V>GHO#}e8$gFaj6Dq2`lk1?5f$fl zOZl6v97*&+6x|+lJN@U@{|ezovv*#I5wsejJf@y<;WZoVlUj?6_K>``K(wfloFF%Y7lr(CHOGdhYsTmd>_@ zG9({L2gwToW!`EH@+H!r`g;3#0HM)KS`mz>bUD(&UQw5t!8Z+Z=Vqs0h=V$&y2KT? z&+*C-LV=vSJOOWVRtvmjFs|%84&##D%ZB0%JX-|_H+pQ(F}i%eiTH8n;Di0X`K)NC z=W%SzlOd%Z_?D{#M=78qMMp^47w0)20#4Rasvbeg?cOq-}!Ptpip}G99nXaZHIluawAX zZWL?*)7g1lE0&kwl$%CLdT8dzONW;Bi~nomeiX*?NwiwVXmtv&l1rE0LZ5QI#r-J( zx$0?pula>W?h_bmsi9F~Oi4z3+r;)UyFIlbPW}3h7%F2a@x$HJjG8n`mp4BHqTHw> zGQ$^)b7je5WRpK{1994Q!;tA}1}w^3;WD z`7T4qkWQ2{l^&TrEvAx5+;WhYK?OFSGwz*oZPh(;9Pk{4Q{ zzw0jM>xlsfGZ_qh&4kc}EM;kkmM|d#y}AmS=h!&U3*W*_U%`9Leeqqk%cd8|ne^5I zLXHRSHilm0w_+*66?&MG$d}^C6O$LlA}hgpY3Q05o9~Z)*>gSJ%_A? zEh&01>?uiaz4Quk13F=fs0)vmFZwH|SySPFpW)<`88Tt3QewiE5xPBir{#yb%{Zcr z5wab@9DfHMn=>X!zg|YHJ}Z7~k))Emz6o7CzZZgZrD9NW3H=h`gb$*38@$&4mB}VQ zcO4r>U_pb2*Vq6l#dN31U@#RL(Y1G)5VLGVFk*t`Te+%ySn`j?=DObBpm&KQe2c9m zr#}HCrck6|u0)L9tZujtolKtvrwef)OW0&Ue~!65#48fYj&P=W?<~2(v<4b1S%uR` z?}}Gd&d+lb1Qy`$5~;b}>ET=-Z2it{UhcOo-Oih+>o%(3FWKE6VMDA_QK0K>S3TKU z)g;%|ZRdBrRh!|ce8s!qMQ(~wo+V=i>2adKah4Ehw(R^Y9^ba~<&6I8Dh$V^loUdL z2uq<^_o}uvjN-!pfyUEw-eT_K(U_{;T4vx{PEMG_o?spE&3(Hc70HZ|v7m-XO{tMN zm8c^+%tZgbJs;k2L#ouvU&ZN1jH7-83Dqa)^kK`9TsjHQX(9e4k|~ zFQxJ98aKSSjW(>3aX=MTl*Dl#kE`y+vU2WSR0eDE+ghD}g2~{Mi_l9#0lW0uMo!eY zDECwbOWg4o5kFg0EVseO;N@5Oeq5!#gs_Yx9VPv)XS&CyytmG)b6tmEd!sHVIBJUT zXpPck%$C*hjLOKF&snoGPHFcvI=dnW75=B%%WTe$d9Oz-!*iQUnrW0HJ z&(A(q%;NMJubz6(G*(D`WNKJ@z;z3eyF*3W!NuWA3q_6j#wcD`mlkOkb9JO3zuI0| zQdr2IKppXr_~C(FnWa(eZ7Gah_71GbIo5y_zHvxL~C&>KqnW z`ran;rp{C5->0rb-6DTlSTr3ep^swh`IP^zH*%l+noM=a5#j6tTad-vY-(V-y0`gy zziv+A2I>jVz12Wqt8Rm|j14+4dwvEr#e10UakQi1meaSUW6WKv-^<^PZdXoq0OxHqHnq^feq&&W6e)f2ASxe?uty zT(I_)5oAbSnH~H#)zoJikN&{Q_Sf}$YVT~;0pdmi4yMd^^-l*_o#+k)H|{+wr$la- zins1hy(fLshwSOwfb8o)ZkVTIx=iycR763CtQiup^5cv{k^r3$lbYyFxs^EuLVa#tf)oS z2(pU~{}cngMl7*cf@3)I@*uY28?$0bgfp&G*D29<2nyVx|UK`J%IslDI>22tlZh z+UM()L5oS+;JTM^OCsyOL`kHS)`lx1)SmvvzuV5Ef|9!uw!k_Mb(>b!EI&CkUArRB zC*CQ_|7{*Bn&=dNzqi<5*wmjEqrDgvLzPhrd90lT<4H)TxY5ImxrjH+1K4iMWT~>= zwnzz-KG!!O8;-|-RRAvc0DI_6x_YrJs*??LT0vPsb3bG1^`- zG=n_X4ftQK7|;;GFk^pdkK`X|)6K?xe37seW+#QWbCV+|R6)*U$F+>m4^T z-=Fo}&GJ2}`VH9ZjoEcj1lv3~(_!w9Xw z>V3oeWwp!}?-;ZqJZi($^XjSEIUI#TsC8ksrLphv%?xAx6gJPR9pL-J_hgGQp;qY` z!F-^o39tgc*mq6x>vG6ncViFbtiMR}ZuzRSH`}uSp*5fxk6fvrpZo}w0nv`_j+y-| zb}c>}^X=QoGc&USB){Ho^oqX)#|6UGM+|IV07Ea=+?E}*-T}?4MD?*qw~%ozMIz3~ zMW_is?{eyUT&2QGmq0?CYlS)v2wa;oQV*^%1q1sIkBg%QwepS*?j2!+$+N}Jkk=)Y z+h)x&&8!q6Y-M`}?Qwl*#y$7Bn#4>C#E#ClL5g|(sq+`mJZT3r>IDkK7ZO zb&>V-zf_zoCu_zhe5!vRF8}h`&FklS+!UR?a}T1ha+*olsukswK|%WCUU?>2+n?TDT;S0H zA>UpwP|2p^$k&I;Z6ucDOsXm(+r&q*Y^ePB0YNaR3;MW|*4u5wdLFGDSxfBwm#Tsa zB`zgjz1B^^30v&Js1I8eAxvLe+?$&)K*31g+V;J5outZc_}!Q~5FL8A`MQKB&L9(~ zH(77HJk8Iz6a&a=R)LmgOdY%1acQu*)U=cq4^^X(x>Jdo+zf=;FG=#Z8#BRh5$({N zmjQG3P4&+`^k^7%_k(%IwC__zJBv2(cCA;a!NScmZ(I$-!{{YWoS(`+^UjxYmanlK zJfd{qe3oDSC4wYZj5Ke|9!OW-vi!|8cV}2t&z5@QZtrn&%MNi?KUmPWV+@<-~9m>vX9T3{8dEz2cA!1VBpMS%u8LkOWl^c z&lA3%Rnj&p^27=ud|y|S3D7GMuSUf(*6DmmlQ?gSX$OI5=9E>NQ&!2|0U*+DjRL_J2+ZmT3>jsog#qsD3^#ol-7);A-wj&inQ=fjY%;qPNp4YWG`%Ahz3n#so*M(X8M6AnxDpHyR zcXThqmH~EU$}K&oAF+(97L2NgJ$#z>c>k0Z7JsebFW>=SL}k;2 z^OZ_j@=DhZH~657D&<(3X)uTaMz_3L&4ooqGyI<&1aE~NPy8Rhd>A;sP%-yRabp;) zlq*yz;-^LVknofIrd-3Ta=e^!=odl{yathdLD~s2y4DrqS#^?NmYrDuh`p1af$>az zwK$caS;gA_mUkXTc2}m3!m1YpmDet?V7z&;9ezEZ;!E_yt|N-!HOl!_ZZ6{8iKBU4 z9`?OCiciZ3?}VB7Wa+)~%_-met7gq9MGYiiOdo91BrOdDjlr)u(hZi(6;uI>Csr7o;@!derDhSL=SQBr-1mTpJ1 z+JHt!;SQ$IyvI`1KlPoxEcRtaJ`=(s9{$Q2$ZqrIVVQYtWyoW9=AqVich{yFI*OtW zUq#-P65x2xWVjJsJL-MMEDgbrh~pU6kYbzhnXH~PTV8eDCh9n&x|=SK>rdj(E7lYb zv;7*pCt-}&7px>Vh_CGgMD^3G?*>&i4l@pE9TR&W|kM8WLS ziL0&2_FYBHiq!Ghs-MlQGkIY~Atp*StFQa5C6l|=&Nzo$C%S@Y3Zwu(i*n zOy;S6@gk;z4MlU3?cO;k6O@%M?>G~DZ~|WGaUO9*FWb!ApuLJ5lFFu}xCAfa!{NNL zu^pwMW&~QW@ennmn!FlfgD5I}FJ~?{-&ZQB*A^Bs6UpV@!bp}WrE7#65Nw>N5~npZCQsbwc46V=V!yLsAz^hbk2n@_ON z)8HfiJqK$Uga|>jmxpVXsT>VC%P#`UA~a|1Itdg|f2&Ks3OT6+$KK9BjJrdfk7p5~ z@J;I|+c+__c4;qR&X?l%#{a_R;^Dq9csuo{Fer|D-tl? z6=G;AOY>ub-EHg+Kda)T&7z~&U6j=fIrby08R1>MF^R~1=s4u_=Y`;XzP*OBa`4E= zT=jVtKI8SfNzh)^I*gGCn7C>!R17Udx(P@1ROV%}alZkhAS}5rF9Q>uoKv-dpoZj2 z*kI3)=`6V)DVF&gswwDVn%;+&kmKQwpA^L2-V!V}P{5+_Pxn&D75rY})zDX;)_D?t ze3WE^@kbG%x3w^sgxSQJAp#D6lEp4Ac%d3lxf?@_S7-jq>8w&lQEzs}JK#Ly6@kg$ z98VG|^P7R^)pzC>IJVVzs1oUOY-A45Y0OYTBZ~Y|z^C@)2%bVvl22Kfz)xNQg4v&! zyXP!`1aN*`&e_1K*dj2begZ(yco=06s`5T&7Wa+i^E!Md#*n-GQHP*qe|gTI+v zN_(UiY>!oiI97etxD8a_i^q=x`MZtQs|&dklYz#jZF_HP!7W=u z7U|l6MD{uzVME~1Gpj{W@F~V&`6hc0wb_}Wn_NF&hs4O{c=o0IVuiv6hw@Xvqtm+} z&)Zp$g#1tEbB3I(w@9)5pMZx`{+$9`=AV=YnI#S+!y%Fw|K*?HCxjT6Y#1r=`+o-f zyp8C2WW~Y10pnIf7>{IA10wXq&mbtH-l5hFtp89o99eVpcoqr&8PNpa0+neJSpES+ zfg56XUm5?Otcbi9(f>}%>pyNyeo3?Bq?CDX=}+81CCi8rfjQ!T+M_`ziHT*KrcSum$Jw6RA1My<+`!O^^sJoUsr_1Q5s+L~dtN0Iwwdjb2Aup@@)IC* zsZxv_Ml$nf(F!=0lnz_{FH;-rd>Qd>amot2R&P|Gr0nSf0rwGOIj|2^#HTivE=UPpfBcYIH|O{gf~0J zIJ|l=t~^}+KdeTEG$}bYJ>D3{iVp5@+Ew3IrkkGrGTldYj>_kjFiORL=u?`>=E;u- zj8zqfmnAHqK~ouM4W;X)E+_znzfw0u^QhNVRQ!lN=Nd7Uq?$7P`JwTp+C!D7^nuwo zuJ$+XRnyjFF;_$_9W@o4uYV$peF?4Zb{P|w$r6DXD0cy{n#QPrfvde6UMcTs1*GkiZ71|JTIXkNI&86Z+6uT|Bk{#DUYv!NV7W7Jz62DLhtbZc62 zAioVcQiGCJ4^_k;-HsuUt7d%R8$S|UZP+lMFgE;Z-S1aVMVx`dJbG(uR&0Ee=Dx=EU=Ee=Mt*Z9 zXR|oI^Z+mo@lmFvc}ODPNv&BhNxcouN=(!pJ$d`mGdhg5=bhHm6>p%eYh=3bPIsrS zzq)-Gy7Ef4GX%R~QSs_5i@O5Bd~1{dPhEDibL{5ESR;KM?PZX|zvOfd#RmkN?29`T zx+)G#1_6F$=D7j~Pt|tZB^>NWs_HYarU$^sm!SDE=pbABrZHUIC5F>IKPHD0|5_go z&Sp13|BP@(muQU-$Pu#7G5o0s`~&8WHkL}C3z&>11?rQplO!?y@hjuFw5E#y8Z6W`YIJ{Z;6bierk-^{Lx#U;AA`4mut$w&ajYefwM{|D#-#f1O> literal 0 HcmV?d00001 diff --git a/rust/vetkeys/password_manager/frontend/public/vite.svg b/rust/vetkeys/password_manager/frontend/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/rust/vetkeys/password_manager/frontend/src/App.svelte b/rust/vetkeys/password_manager/frontend/src/App.svelte new file mode 100644 index 000000000..ed34e5e20 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/App.svelte @@ -0,0 +1,13 @@ + + +{#if $auth.state === "initialized"} + +{:else} + +{/if} + diff --git a/rust/vetkeys/password_manager/frontend/src/app.css b/rust/vetkeys/password_manager/frontend/src/app.css new file mode 100644 index 000000000..b5c61c956 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/app.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/rust/vetkeys/password_manager/frontend/src/assets/svelte.svg b/rust/vetkeys/password_manager/frontend/src/assets/svelte.svg new file mode 100644 index 000000000..c5e08481f --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/rust/vetkeys/password_manager/frontend/src/components/Disclaimer.svelte b/rust/vetkeys/password_manager/frontend/src/components/Disclaimer.svelte new file mode 100644 index 000000000..2ded84f8f --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/components/Disclaimer.svelte @@ -0,0 +1,26 @@ + + +{#if !isDismissed} +

+

+ +

+ + +
+{/if} diff --git a/rust/vetkeys/password_manager/frontend/src/components/DisclaimerCopy.svelte b/rust/vetkeys/password_manager/frontend/src/components/DisclaimerCopy.svelte new file mode 100644 index 000000000..336ffde7d --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/components/DisclaimerCopy.svelte @@ -0,0 +1,5 @@ + + +Disclaimer: This sample dapp is intended exclusively for experimental +purpose. You are advised not to use this dapp for storing your critical data such +as keys or passwords. diff --git a/rust/vetkeys/password_manager/frontend/src/components/EditPassword.svelte b/rust/vetkeys/password_manager/frontend/src/components/EditPassword.svelte new file mode 100644 index 000000000..73cc65f46 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/components/EditPassword.svelte @@ -0,0 +1,309 @@ + + +{#if editedPassword.parentVaultName.length > 0} +
+ Edit password + +
+
+ {#if $vaultsStore.state === "loaded"} +
+ + + +
+ + + + 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/rust/vetkeys/password_manager/frontend/src/components/EditVault.svelte b/rust/vetkeys/password_manager/frontend/src/components/EditVault.svelte new file mode 100644 index 000000000..5c156fcc8 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/components/EditVault.svelte @@ -0,0 +1,86 @@ + + +{#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/rust/vetkeys/password_manager/frontend/src/components/Header.svelte b/rust/vetkeys/password_manager/frontend/src/components/Header.svelte new file mode 100644 index 000000000..9245e48b6 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/components/Header.svelte @@ -0,0 +1,27 @@ + + + diff --git a/rust/vetkeys/password_manager/frontend/src/components/Hero.svelte b/rust/vetkeys/password_manager/frontend/src/components/Hero.svelte new file mode 100644 index 000000000..3f7ba7b22 --- /dev/null +++ b/rust/vetkeys/password_manager/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/rust/vetkeys/password_manager/frontend/src/components/LayoutAuthenticated.svelte b/rust/vetkeys/password_manager/frontend/src/components/LayoutAuthenticated.svelte new file mode 100644 index 000000000..090735f21 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/components/LayoutAuthenticated.svelte @@ -0,0 +1,29 @@ + + + + + diff --git a/rust/vetkeys/password_manager/frontend/src/components/NewPassword.svelte b/rust/vetkeys/password_manager/frontend/src/components/NewPassword.svelte new file mode 100644 index 000000000..7270bc0bb --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/components/NewPassword.svelte @@ -0,0 +1,108 @@ + + + + +
+ New password +
+ +
+ +
+ + + +
+ + +
diff --git a/rust/vetkeys/password_manager/frontend/src/components/Notifications.svelte b/rust/vetkeys/password_manager/frontend/src/components/Notifications.svelte new file mode 100644 index 000000000..af1b01eb5 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/components/Notifications.svelte @@ -0,0 +1,22 @@ + + +
+ {#each $notifications as n (n.id)} +
+

{n.message}

+
+ {/each} +
diff --git a/rust/vetkeys/password_manager/frontend/src/components/Password.svelte b/rust/vetkeys/password_manager/frontend/src/components/Password.svelte new file mode 100644 index 000000000..9b30c30a2 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/components/Password.svelte @@ -0,0 +1,106 @@ + + +
+ + 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/rust/vetkeys/password_manager/frontend/src/components/PasswordEditor.svelte b/rust/vetkeys/password_manager/frontend/src/components/PasswordEditor.svelte new file mode 100644 index 000000000..a27c1ba12 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/components/PasswordEditor.svelte @@ -0,0 +1,68 @@ + + + +
+ + + + +
+
+ +
+ + diff --git a/rust/vetkeys/password_manager/frontend/src/components/Passwords.svelte b/rust/vetkeys/password_manager/frontend/src/components/Passwords.svelte new file mode 100644 index 000000000..70e7f30b3 --- /dev/null +++ b/rust/vetkeys/password_manager/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)) as password (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/rust/vetkeys/password_manager/frontend/src/components/SharingEditor.svelte b/rust/vetkeys/password_manager/frontend/src/components/SharingEditor.svelte new file mode 100644 index 000000000..53d6b644f --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/components/SharingEditor.svelte @@ -0,0 +1,218 @@ + + +

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/rust/vetkeys/password_manager/frontend/src/components/SidebarLayout.svelte b/rust/vetkeys/password_manager/frontend/src/components/SidebarLayout.svelte new file mode 100644 index 000000000..94959e666 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/components/SidebarLayout.svelte @@ -0,0 +1,81 @@ + + +
+ +
+
+ +
+ +
+
+
+
diff --git a/rust/vetkeys/password_manager/frontend/src/components/Spinner.svelte b/rust/vetkeys/password_manager/frontend/src/components/Spinner.svelte new file mode 100644 index 000000000..cc26ba251 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/components/Spinner.svelte @@ -0,0 +1,5 @@ + + + diff --git a/rust/vetkeys/password_manager/frontend/src/components/Vault.svelte b/rust/vetkeys/password_manager/frontend/src/components/Vault.svelte new file mode 100644 index 000000000..5c578a5fa --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/components/Vault.svelte @@ -0,0 +1,143 @@ + + +
+ + + + + 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/rust/vetkeys/password_manager/frontend/src/components/Vaults.svelte b/rust/vetkeys/password_manager/frontend/src/components/Vaults.svelte new file mode 100644 index 000000000..fc99e9b3f --- /dev/null +++ b/rust/vetkeys/password_manager/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.toText()} +

+
+
+ {/each} +
+ {:else} +
+ You don't have any vaults. +
+ + {/if} + {:else if $vaultsStore.state === "error"} +
Could not load vaults.
+ {/if} +
diff --git a/rust/vetkeys/password_manager/frontend/src/lib/encrypted_maps.ts b/rust/vetkeys/password_manager/frontend/src/lib/encrypted_maps.ts new file mode 100644 index 000000000..2b2c7e071 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/lib/encrypted_maps.ts @@ -0,0 +1,43 @@ +import "./init.ts"; +import { HttpAgent, type HttpAgentOptions } from "@dfinity/agent"; +import { + DefaultEncryptedMapsClient, + EncryptedMaps, +} from "@dfinity/vetkeys/encrypted_maps"; + +export async function createEncryptedMaps( + agentOptions?: HttpAgentOptions, +): Promise { + const host = + process.env.DFX_NETWORK === "ic" + ? `https://${process.env.CANISTER_ID_IC_VETKEYS_ENCRYPTED_MAPS_CANISTER}.ic0.app` + : "http://localhost:4943"; + const hostOptions = { host }; + + if (!agentOptions) { + agentOptions = hostOptions; + } else { + agentOptions.host = hostOptions.host; + } + + const agent = await HttpAgent.create({ ...agentOptions }); + // Fetch root key for certificate validation during development + if (process.env.NODE_ENV !== "production") { + console.log(`Dev environment - fetching root key...`); + + agent.fetchRootKey().catch((err) => { + console.warn( + "Unable to fetch root key. Check to ensure that your local replica is running", + ); + console.error(err); + }); + } + + // Creates an actor with using the candid interface and the HttpAgent + return new EncryptedMaps( + new DefaultEncryptedMapsClient( + agent, + process.env.CANISTER_ID_IC_VETKEYS_ENCRYPTED_MAPS_CANISTER, + ), + ); +} diff --git a/rust/vetkeys/password_manager/frontend/src/lib/init.ts b/rust/vetkeys/password_manager/frontend/src/lib/init.ts new file mode 100644 index 000000000..062c8af94 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/lib/init.ts @@ -0,0 +1 @@ +window.global ||= window; diff --git a/rust/vetkeys/password_manager/frontend/src/lib/password.ts b/rust/vetkeys/password_manager/frontend/src/lib/password.ts new file mode 100644 index 000000000..e37c46204 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/lib/password.ts @@ -0,0 +1,56 @@ +import type { Principal } from "@dfinity/principal"; + +export interface PasswordModel { + owner: Principal; + parentVaultName: string; + passwordName: string; + content: string; +} + +export function passwordFromContent( + owner: Principal, + parentVaultName: string, + passwordName: string, + content: string, +): PasswordModel { + return { + owner, + parentVaultName, + passwordName, + content, + }; +} + +export function summarize(note: PasswordModel, maxLength = 50) { + const div = document.createElement("div"); + div.innerHTML = note.content; + + let text = div.innerText; + const title = extractTitleFromDomEl(div); + if (title) { + text = text.replace(title, ""); + } + + return text.slice(0, maxLength) + (text.length > maxLength ? "..." : ""); +} + +function extractTitleFromDomEl(el: HTMLElement): string { + const title = el.querySelector("h1"); + if (title) { + return title.innerText; + } + + const blockElements = el.querySelectorAll("h1,h2,p,li"); + for (const el of blockElements) { + if (el.textContent && el.textContent.trim().length > 0) { + return el.textContent.trim(); + } + } + return ""; +} + +export function extractTitle(html: string) { + const div = document.createElement("div"); + div.innerHTML = html; + return extractTitleFromDomEl(div); +} diff --git a/rust/vetkeys/password_manager/frontend/src/lib/sleep.ts b/rust/vetkeys/password_manager/frontend/src/lib/sleep.ts new file mode 100644 index 000000000..38caca0c1 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/lib/sleep.ts @@ -0,0 +1,3 @@ +export function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/rust/vetkeys/password_manager/frontend/src/lib/vault.ts b/rust/vetkeys/password_manager/frontend/src/lib/vault.ts new file mode 100644 index 000000000..02dc5b712 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/lib/vault.ts @@ -0,0 +1,60 @@ +import type { Principal } from "@dfinity/principal"; +import type { PasswordModel } from "./password"; +import type { AccessRights } from "@dfinity/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) { + const div = document.createElement("div"); + + div.innerHTML += + "Owner: " + + vault.owner.toString() + + ", " + + vault.users.length + + " users"; + div.innerHTML += ", " + vault.passwords.length + " passwords.\n"; + + let text = div.innerText; + const title = extractTitleFromDomEl(div); + if (title) { + text = text.replace(title, ""); + } + + return text.slice(0, maxLength) + (text.length > maxLength ? "..." : ""); +} + +function extractTitleFromDomEl(el: HTMLElement): string { + const title = el.querySelector("h1"); + if (title) { + return title.innerText; + } + + const blockElements = el.querySelectorAll("h1,h2,p,li"); + for (const el of blockElements) { + if (el.textContent && el.textContent?.trim().length > 0) { + return el.textContent.trim(); + } + } + return ""; +} + +export function extractTitle(html: string) { + const div = document.createElement("div"); + div.innerHTML = html; + return extractTitleFromDomEl(div); +} diff --git a/rust/vetkeys/password_manager/frontend/src/main.ts b/rust/vetkeys/password_manager/frontend/src/main.ts new file mode 100644 index 000000000..ff634fcc2 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/main.ts @@ -0,0 +1,8 @@ +import "./app.css"; +import App from "./App.svelte"; + +const app = new App({ + target: document.body, +}); + +export default app; diff --git a/rust/vetkeys/password_manager/frontend/src/store/auth.ts b/rust/vetkeys/password_manager/frontend/src/store/auth.ts new file mode 100644 index 000000000..ec6547c8b --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/store/auth.ts @@ -0,0 +1,116 @@ +import "../lib/init.ts"; +import { get, writable } from "svelte/store"; +import { AuthClient } from "@dfinity/auth-client"; +import type { JsonnableDelegationChain } from "@dfinity/identity/lib/cjs/identity/delegation"; +import { replace } from "svelte-spa-router"; +import { createEncryptedMaps } from "../lib/encrypted_maps.js"; +import { EncryptedMaps } from "@dfinity/vetkeys/encrypted_maps"; + +export type AuthState = + | { + state: "initializing-auth"; + } + | { + state: "anonymous"; + client: AuthClient; + } + | { + state: "initialized"; + encryptedMaps: EncryptedMaps; + client: AuthClient; + } + | { + state: "error"; + error: string; + }; + +export const auth = writable({ + state: "initializing-auth", +}); + +async function initAuth() { + const client = await AuthClient.create(); + if (await client.isAuthenticated()) { + void authenticate(client); + } else { + auth.update(() => ({ + state: "anonymous", + client, + })); + } +} + +void initAuth(); + +export function login() { + const currentAuth = get(auth); + + if (currentAuth.state === "anonymous") { + void currentAuth.client.login({ + maxTimeToLive: BigInt(1800) * BigInt(1_000_000_000), + identityProvider: + process.env.DFX_NETWORK === "ic" + ? "https://identity.ic0.app/#authorize" + : `http://rdmx6-jaaaa-aaaaa-aaadq-cai.localhost:4943/#authorize`, + onSuccess: () => authenticate(currentAuth.client), + }); + } +} + +export async function logout() { + const currentAuth = get(auth); + + if (currentAuth.state === "initialized") { + await currentAuth.client.logout(); + auth.update(() => ({ + state: "anonymous", + client: currentAuth.client, + })); + void replace("/"); + } +} + +export async function authenticate(client: AuthClient) { + handleSessionTimeout(); + + try { + const encryptedMaps = await createEncryptedMaps({ + identity: client.getIdentity(), + }); + + auth.update(() => ({ + state: "initialized", + encryptedMaps, + client, + })); + } 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 +function handleSessionTimeout() { + // upon login the localstorage items may not be set, wait for next tick + setTimeout(() => { + try { + const delegation = JSON.parse( + window.localStorage.getItem("ic-delegation") || "{}", + ) as JsonnableDelegationChain; + + const expirationTimeMs = + Number.parseInt( + delegation.delegations[0].delegation.expiration, + 16, + ) / 1000000; + + setTimeout(() => { + void logout(); + }, expirationTimeMs - Date.now()); + } catch { + console.error("Could not handle delegation expiry."); + } + }); +} diff --git a/rust/vetkeys/password_manager/frontend/src/store/draft.ts b/rust/vetkeys/password_manager/frontend/src/store/draft.ts new file mode 100644 index 000000000..05835819e --- /dev/null +++ b/rust/vetkeys/password_manager/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: DraftModel = 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/rust/vetkeys/password_manager/frontend/src/store/notifications.ts b/rust/vetkeys/password_manager/frontend/src/store/notifications.ts new file mode 100644 index 000000000..d87630153 --- /dev/null +++ b/rust/vetkeys/password_manager/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/rust/vetkeys/password_manager/frontend/src/store/vaults.ts b/rust/vetkeys/password_manager/frontend/src/store/vaults.ts new file mode 100644 index 000000000..0abfadf65 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/store/vaults.ts @@ -0,0 +1,166 @@ +import { writable } from "svelte/store"; +import { passwordFromContent, type PasswordModel } from "../lib/password"; +import { vaultFromContent, type VaultModel } from "../lib/vault"; +import { auth } from "./auth"; +import { showError } from "./notifications"; +import { + type AccessRights, + EncryptedMaps, +} from "@dfinity/vetkeys/encrypted_maps"; +import type { Principal } from "@dfinity/principal"; + +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(encryptedMaps: EncryptedMaps) { + const allMaps = await encryptedMaps.getAllAccessibleMaps(); + const vaults = allMaps.map((mapData) => { + const vaultName = new TextDecoder().decode(mapData.mapName); + const passwords = new Array<[string, PasswordModel]>(); + for (const [passwordNameBytes, data] of mapData.keyvals) { + const passwordName = new TextDecoder().decode(passwordNameBytes); + const passwordContent = new TextDecoder().decode( + Uint8Array.from(data), + ); + const password = passwordFromContent( + mapData.mapOwner, + vaultName, + passwordName, + passwordContent, + ); + passwords.push([passwordName, password]); + } + return vaultFromContent( + mapData.mapOwner, + vaultName, + passwords, + mapData.accessControl, + ); + }); + + updateVaults(vaults); +} + +export async function addPassword( + password: PasswordModel, + encryptedMaps: EncryptedMaps, +) { + await encryptedMaps.setValue( + password.owner, + new TextEncoder().encode(password.parentVaultName), + new TextEncoder().encode(password.passwordName), + new TextEncoder().encode(password.content), + ); +} + +export async function removePassword( + password: PasswordModel, + encryptedMaps: EncryptedMaps, +) { + await encryptedMaps.removeEncryptedValue( + password.owner, + new TextEncoder().encode(password.parentVaultName), + new TextEncoder().encode(password.passwordName), + ); +} + +export async function updatePassword( + password: PasswordModel, + encryptedMaps: EncryptedMaps, +) { + await encryptedMaps.setValue( + password.owner, + new TextEncoder().encode(password.parentVaultName), + new TextEncoder().encode(password.passwordName), + new TextEncoder().encode(password.content), + ); +} + +export async function addUser( + owner: Principal, + vaultName: string, + user: Principal, + userRights: AccessRights, + encryptedMaps: EncryptedMaps, +) { + await encryptedMaps.setUserRights( + owner, + new TextEncoder().encode(vaultName), + user, + userRights, + ); +} + +export async function removeUser( + owner: Principal, + vaultName: string, + user: Principal, + encryptedMaps: EncryptedMaps, +) { + await encryptedMaps.removeUser( + owner, + new TextEncoder().encode(vaultName), + user, + ); +} + +auth.subscribe((auth) => { + void (async () => { + if (auth && auth.state === "initialized") { + if (vaultPollerHandle !== null) { + clearInterval(vaultPollerHandle); + vaultPollerHandle = null; + } + + vaultsStore.set({ + state: "loading", + }); + try { + await refreshVaults(auth.encryptedMaps).catch((e: Error) => + showError(e, "Could not poll vaults."), + ); + + vaultPollerHandle = setInterval(() => { + void (async () => { + await refreshVaults(auth.encryptedMaps).catch( + (e: Error) => + showError(e, "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/rust/vetkeys/password_manager/frontend/src/vite-env.d.ts b/rust/vetkeys/password_manager/frontend/src/vite-env.d.ts new file mode 100644 index 000000000..4078e7476 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/src/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/rust/vetkeys/password_manager/frontend/svelte.config.js b/rust/vetkeys/password_manager/frontend/svelte.config.js new file mode 100644 index 000000000..b0683fd24 --- /dev/null +++ b/rust/vetkeys/password_manager/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/rust/vetkeys/password_manager/frontend/tailwind.config.cjs b/rust/vetkeys/password_manager/frontend/tailwind.config.cjs new file mode 100644 index 000000000..4bcec2567 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/tailwind.config.cjs @@ -0,0 +1,6 @@ +import daisyui from "daisyui"; + +export default { + content: ["./index.html", "./src/**/*.{svelte,js,ts,jsx,tsx}"], + plugins: [daisyui], +}; diff --git a/rust/vetkeys/password_manager/frontend/tsconfig.json b/rust/vetkeys/password_manager/frontend/tsconfig.json new file mode 100644 index 000000000..2f9865d46 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ES2020", + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "noUncheckedSideEffectImports": true + }, + "include": [ + "src" + ] + } + \ No newline at end of file diff --git a/rust/vetkeys/password_manager/frontend/vite.config.ts b/rust/vetkeys/password_manager/frontend/vite.config.ts new file mode 100644 index 000000000..811003329 --- /dev/null +++ b/rust/vetkeys/password_manager/frontend/vite.config.ts @@ -0,0 +1,44 @@ +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 typescript from '@rollup/plugin-typescript'; +import environment from 'vite-plugin-environment'; +import path from 'path'; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + svelte(), + css({ output: "bundle.css" }), + typescript({ + inlineSources: true, + }), + environment("all", { prefix: "CANISTER_" }), + environment("all", { prefix: "DFX_" }), + ], + css: { + postcss: { + plugins: [autoprefixer(), tailwindcss()], + } + }, + build: { + sourcemap: true, + rollupOptions: { + output: { + inlineDynamicImports: true, + }, + }, + }, + resolve: { + alias: { + 'ic_vetkeys': path.resolve(__dirname, '../../../frontend/ic_vetkeys/src'), + 'ic_vetkeys/encrypted_maps': path.resolve(__dirname, '../../../frontend/ic_vetkeys/src/encrypted_maps'), + } + }, + root: "./", + server: { + hmr: false + } +}) \ No newline at end of file