Compare commits

..

10 commits

Author SHA1 Message Date
2c1403c332 Merge pull request 'feature/7-add-wasm-api-in-sharenet-passport-for-creating-and-editing-passports' (#8) from feature/7-add-wasm-api-in-sharenet-passport-for-creating-and-editing-passports into main
Some checks failed
Sharenet Passport CI / test-native (push) Has been cancelled
Sharenet Passport CI / test-wasm-headless (push) Has been cancelled
Sharenet Passport CI / test-wasm-webdriver (push) Has been cancelled
Sharenet Passport CI / build-wasm (push) Has been cancelled
Sharenet Passport CI / lint (push) Has been cancelled
Reviewed-on: #8
2025-11-01 12:30:57 -04:00
7407f5ac09 Add default user profile id
Some checks failed
Sharenet Passport CI / test-native (push) Has been cancelled
Sharenet Passport CI / test-wasm-headless (push) Has been cancelled
Sharenet Passport CI / test-wasm-webdriver (push) Has been cancelled
Sharenet Passport CI / build-wasm (push) Has been cancelled
Sharenet Passport CI / lint (push) Has been cancelled
Sharenet Passport CI / test-native (pull_request) Has been cancelled
Sharenet Passport CI / test-wasm-headless (pull_request) Has been cancelled
Sharenet Passport CI / test-wasm-webdriver (pull_request) Has been cancelled
Sharenet Passport CI / build-wasm (pull_request) Has been cancelled
Sharenet Passport CI / lint (pull_request) Has been cancelled
2025-10-31 14:56:52 -04:00
bd4c3ac3ab Add lots more tests
Some checks are pending
Sharenet Passport CI / test-native (push) Waiting to run
Sharenet Passport CI / test-wasm-headless (push) Waiting to run
Sharenet Passport CI / test-wasm-webdriver (push) Waiting to run
Sharenet Passport CI / build-wasm (push) Waiting to run
Sharenet Passport CI / lint (push) Waiting to run
2025-10-31 00:17:16 -04:00
8d0d203182 Add global date_of_birth field to passport
Some checks are pending
Sharenet Passport CI / test-native (push) Waiting to run
Sharenet Passport CI / test-wasm-headless (push) Waiting to run
Sharenet Passport CI / test-wasm-webdriver (push) Waiting to run
Sharenet Passport CI / build-wasm (push) Waiting to run
Sharenet Passport CI / lint (push) Waiting to run
2025-10-30 14:51:30 -04:00
b0097ecb8b Add user profiles to import function
Some checks are pending
Sharenet Passport CI / test-native (push) Waiting to run
Sharenet Passport CI / test-wasm-headless (push) Waiting to run
Sharenet Passport CI / test-wasm-webdriver (push) Waiting to run
Sharenet Passport CI / build-wasm (push) Waiting to run
Sharenet Passport CI / lint (push) Waiting to run
2025-10-30 13:18:31 -04:00
181adee26c update exports
Some checks are pending
Sharenet Passport CI / test-native (push) Waiting to run
Sharenet Passport CI / test-wasm-headless (push) Waiting to run
Sharenet Passport CI / test-wasm-webdriver (push) Waiting to run
Sharenet Passport CI / build-wasm (push) Waiting to run
Sharenet Passport CI / lint (push) Waiting to run
2025-10-29 23:06:41 -04:00
8a39f36875 Add re-exports
Some checks are pending
Sharenet Passport CI / test-native (push) Waiting to run
Sharenet Passport CI / test-wasm-headless (push) Waiting to run
Sharenet Passport CI / test-wasm-webdriver (push) Waiting to run
Sharenet Passport CI / build-wasm (push) Waiting to run
Sharenet Passport CI / lint (push) Waiting to run
2025-10-29 22:43:42 -04:00
e7db02ab73 Add additional tests
Some checks are pending
Sharenet Passport CI / test-native (push) Waiting to run
Sharenet Passport CI / test-wasm-headless (push) Waiting to run
Sharenet Passport CI / test-wasm-webdriver (push) Waiting to run
Sharenet Passport CI / build-wasm (push) Waiting to run
Sharenet Passport CI / lint (push) Waiting to run
2025-10-29 20:36:20 -04:00
34b2a776fe bump version of library crate
Some checks failed
Sharenet Passport CI / test-native (push) Has been cancelled
Sharenet Passport CI / test-wasm-headless (push) Has been cancelled
Sharenet Passport CI / test-wasm-webdriver (push) Has been cancelled
Sharenet Passport CI / build-wasm (push) Has been cancelled
Sharenet Passport CI / lint (push) Has been cancelled
2025-10-28 00:19:56 -04:00
a2b7601b60 Add tests for passport update and create
Some checks are pending
Sharenet Passport CI / test-native (push) Waiting to run
Sharenet Passport CI / test-wasm-headless (push) Waiting to run
Sharenet Passport CI / test-wasm-webdriver (push) Waiting to run
Sharenet Passport CI / build-wasm (push) Waiting to run
Sharenet Passport CI / lint (push) Waiting to run
2025-10-28 00:18:33 -04:00
27 changed files with 5829 additions and 158 deletions

172
.forgejo/workflows/ci.yml Normal file
View file

@ -0,0 +1,172 @@
name: Sharenet Passport CI
on: [push, pull_request]
jobs:
test-native:
runs-on: [ci]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
components: rust-src
- name: Run native tests
run: |
cd libs/sharenet-passport
cargo test --verbose
test-wasm-headless:
runs-on: [ci]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
components: rust-src
- name: Install wasm-pack
run: |
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Install Firefox and geckodriver
run: |
# Install Firefox
apt-get install -y firefox-esr
# Install geckodriver
GECKODRIVER_VERSION=$(curl -s https://api.github.com/repos/mozilla/geckodriver/releases/latest | grep tag_name | cut -d '"' -f 4)
wget -q "https://github.com/mozilla/geckodriver/releases/download/${GECKODRIVER_VERSION}/geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz"
tar -xzf geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz
mv geckodriver /usr/local/bin/
chmod +x /usr/local/bin/geckodriver
- name: Run WASM headless tests
run: |
cd libs/sharenet-passport/tests/wasm-headless
wasm-pack test --headless --chrome --firefox --node
test-wasm-webdriver:
runs-on: [ci]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
components: rust-src
- name: Install wasm-pack
run: |
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Install browsers and drivers
run: |
# Install Chrome
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list
apt-get update
apt-get install -y google-chrome-stable
# Install ChromeDriver
CHROME_VERSION=$(google-chrome --version | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+')
CHROMEDRIVER_VERSION=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_${CHROME_VERSION%.*}")
wget -q "https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip"
unzip chromedriver_linux64.zip
mv chromedriver /usr/local/bin/
chmod +x /usr/local/bin/chromedriver
# Install Firefox
apt-get install -y firefox-esr
# Install geckodriver
GECKODRIVER_VERSION=$(curl -s https://api.github.com/repos/mozilla/geckodriver/releases/latest | grep tag_name | cut -d '"' -f 4)
wget -q "https://github.com/mozilla/geckodriver/releases/download/${GECKODRIVER_VERSION}/geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz"
tar -xzf geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz
mv geckodriver /usr/local/bin/
chmod +x /usr/local/bin/geckodriver
- name: Install Python for HTTP server
run: |
apt-get install -y python3
- name: Build WASM package for WebDriver tests
run: |
cd libs/sharenet-passport
wasm-pack build --target web --out-dir pkg
- name: Install Rust WebDriver dependencies
run: |
cd libs/sharenet-passport/tests/wasm-webdriver
cargo build
- name: Run WASM WebDriver tests
run: |
cd libs/sharenet-passport/tests/wasm-webdriver
# Build and run WebDriver tests
cargo run
build-wasm:
runs-on: [ci]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
components: rust-src
- name: Install wasm-pack
run: |
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
- name: Build WASM package
run: |
cd libs/sharenet-passport
wasm-pack build --target web --out-dir pkg
- name: Verify WASM build
run: |
cd libs/sharenet-passport/pkg
ls -la
file sharenet_passport_bg.wasm
lint:
runs-on: [ci]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
components: clippy, rustfmt
- name: Run clippy
run: |
cd libs/sharenet-passport
cargo clippy -- -D warnings
- name: Run rustfmt
run: |
cd libs/sharenet-passport
cargo fmt -- --check

151
Cargo.lock generated
View file

@ -139,16 +139,6 @@ version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "cc"
version = "1.2.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
@ -390,12 +380,6 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]]
name = "generic-array"
version = "0.14.9"
@ -535,9 +519,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
version = "0.3.81"
version = "0.3.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
dependencies = [
"once_cell",
"wasm-bindgen",
@ -555,28 +539,12 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "minicov"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b"
dependencies = [
"cc",
"walkdir",
]
[[package]]
name = "once_cell"
version = "1.21.3"
@ -734,15 +702,6 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "semver"
version = "1.0.27"
@ -759,6 +718,17 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_cbor"
version = "0.11.2"
@ -815,7 +785,7 @@ dependencies = [
[[package]]
name = "sharenet-passport"
version = "0.3.0"
version = "0.4.0"
dependencies = [
"async-trait",
"base64",
@ -831,13 +801,15 @@ dependencies = [
"rand",
"rand_core",
"serde",
"serde-wasm-bindgen",
"serde_cbor",
"serde_json",
"sha2",
"tempfile",
"thiserror",
"uuid",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test",
"web-time",
"zeroize",
]
@ -855,12 +827,6 @@ dependencies = [
"uuid",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signature"
version = "2.2.0"
@ -1005,16 +971,6 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
@ -1032,9 +988,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
version = "0.2.104"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
dependencies = [
"cfg-if",
"once_cell",
@ -1043,25 +999,11 @@ dependencies = [
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.54"
version = "0.4.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c"
checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0"
dependencies = [
"cfg-if",
"js-sys",
@ -1072,9 +1014,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.104"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -1082,55 +1024,31 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.104"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.104"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-bindgen-test"
version = "0.3.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e381134e148c1062f965a42ed1f5ee933eef2927c3f70d1812158f711d39865"
dependencies = [
"js-sys",
"minicov",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-bindgen-test-macro",
]
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b673bca3298fe582aeef8352330ecbad91849f85090805582400850f8270a2e8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "web-sys"
version = "0.3.81"
version = "0.3.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120"
checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1"
dependencies = [
"js-sys",
"wasm-bindgen",
@ -1146,15 +1064,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "windows-link"
version = "0.2.1"

View file

@ -3,6 +3,10 @@ members = [
"libs/sharenet-passport",
"sharenet-passport-cli"
]
exclude = [
"libs/sharenet-passport/tests/wasm-headless",
"libs/sharenet-passport/tests/wasm-webdriver"
]
resolver = "2"
[workspace.dependencies]

View file

@ -1,6 +1,6 @@
[package]
name = "sharenet-passport"
version = "0.3.0"
version = "0.4.0"
publish = ["sharenet-sh-forgejo"] # Set this to whichever Cargo registry you are publishing to
edition = "2021"
description = "Core library for Sharenet Passport creation and management"
@ -40,16 +40,18 @@ web-time = "1.1"
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
gloo-storage = "0.3"
wasm-bindgen = "0.2.105"
serde-wasm-bindgen = "0.6"
serde_json = "1.0"
# Native dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
getrandom = { version = "0.2", features = ["std"] }
uuid = { version = "1.10", features = ["v7", "rng"] }
# Dev dependencies for WASM testing
# Dev dependencies for testing
[dev-dependencies]
tempfile = "3.8"
wasm-bindgen-test = "0.3"
[lib]
crate-type = ["cdylib", "rlib"] # Support both native and WASM

View file

@ -1,2 +1,5 @@
pub mod use_cases;
pub mod error;
#[cfg(test)]
pub mod use_cases_test;

View file

@ -80,6 +80,8 @@ where
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
&passport.default_user_profile_id,
)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
@ -169,6 +171,8 @@ where
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
&passport.default_user_profile_id,
)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
@ -217,7 +221,7 @@ where
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to load file: {}", e.into())))?;
// Decrypt file
let (seed, public_key, private_key, user_profiles) = self
let (seed, public_key, private_key, user_profiles, date_of_birth, default_user_profile_id) = self
.file_encryptor
.decrypt(&passport_file, password)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to decrypt file: {}", e.into())))?;
@ -231,6 +235,8 @@ where
passport_file.univ_id.clone(),
);
passport.user_profiles = user_profiles;
passport.date_of_birth = date_of_birth;
passport.default_user_profile_id = default_user_profile_id;
// Re-encrypt and save if output path provided
if let Some(output_path) = output_path {
@ -243,6 +249,8 @@ where
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
&passport.default_user_profile_id,
)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to re-encrypt file: {}", e.into())))?;
@ -291,6 +299,8 @@ where
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
&passport.default_user_profile_id,
)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
@ -377,6 +387,8 @@ where
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
&passport.default_user_profile_id,
)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
@ -413,6 +425,7 @@ where
&self,
passport: &mut Passport,
id: Option<&str>,
hub_did: Option<String>,
identity: UserIdentity,
preferences: UserPreferences,
password: &str,
@ -428,10 +441,10 @@ where
let now = time::now_seconds()
.map_err(|e| ApplicationError::UseCaseError(format!("Time error: {}", e)))?;
// Use existing hub_did (cannot change hub_did via update)
// Use provided hub_did or keep existing
let profile = UserProfile {
id: existing_profile.id.clone(),
hub_did: existing_profile.hub_did.clone(),
hub_did: hub_did.or_else(|| existing_profile.hub_did.clone()),
identity,
preferences,
created_at: existing_profile.created_at,
@ -451,6 +464,8 @@ where
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
&passport.default_user_profile_id,
)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;
@ -506,6 +521,8 @@ where
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
&passport.default_user_profile_id,
)
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to encrypt file: {}", e.into())))?;

View file

@ -0,0 +1,533 @@
use tempfile::NamedTempFile;
use crate::application::use_cases::*;
use crate::domain::entities::*;
use crate::domain::traits::FileStorage;
use crate::infrastructure::*;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_user_profile_use_case() {
// Create a temporary file for testing
let temp_file = NamedTempFile::new().unwrap();
let file_path = temp_file.path().to_str().unwrap();
// Create a passport first
let create_use_case = CreatePassportUseCase::new(
Bip39MnemonicGenerator,
Ed25519KeyDeriver,
XChaCha20FileEncryptor,
FileSystemStorage,
);
let (mut passport, _) = create_use_case.execute("test-universe", "test-password", file_path)
.expect("Failed to create passport");
// Test creating a user profile
let create_profile_use_case = CreateUserProfileUseCase::new(
XChaCha20FileEncryptor,
FileSystemStorage,
);
let identity = UserIdentity {
handle: Some("testuser".to_string()),
display_name: Some("Test User".to_string()),
first_name: Some("Test".to_string()),
last_name: Some("User".to_string()),
email: Some("test@example.com".to_string()),
avatar_url: Some("https://example.com/avatar.png".to_string()),
bio: Some("Test bio".to_string()),
};
let preferences = UserPreferences {
theme: Some("dark".to_string()),
language: Some("en".to_string()),
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
};
let result = create_profile_use_case.execute(
&mut passport,
Some("h:example".to_string()),
identity,
preferences,
"test-password",
file_path,
);
assert!(result.is_ok());
// Verify the profile was added
assert_eq!(passport.user_profiles.len(), 2); // default + new profile
let hub_profile = passport.user_profile_for_hub("h:example");
assert!(hub_profile.is_some());
assert_eq!(hub_profile.unwrap().identity.handle, Some("testuser".to_string()));
}
#[test]
fn test_create_user_profile_duplicate_hub_did() {
// Create a temporary file for testing
let temp_file = NamedTempFile::new().unwrap();
let file_path = temp_file.path().to_str().unwrap();
// Create a passport first
let create_use_case = CreatePassportUseCase::new(
Bip39MnemonicGenerator,
Ed25519KeyDeriver,
XChaCha20FileEncryptor,
FileSystemStorage,
);
let (mut passport, _) = create_use_case.execute("test-universe", "test-password", file_path)
.expect("Failed to create passport");
// Create first profile
let create_profile_use_case = CreateUserProfileUseCase::new(
XChaCha20FileEncryptor,
FileSystemStorage,
);
let identity1 = UserIdentity {
handle: Some("user1".to_string()),
display_name: Some("User One".to_string()),
first_name: Some("User".to_string()),
last_name: Some("One".to_string()),
email: Some("user1@example.com".to_string()),
avatar_url: None,
bio: None,
};
let preferences1 = UserPreferences {
theme: Some("dark".to_string()),
language: Some("en".to_string()),
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
};
let result1 = create_profile_use_case.execute(
&mut passport,
Some("h:example".to_string()),
identity1,
preferences1,
"test-password",
file_path,
);
assert!(result1.is_ok());
// Try to create second profile with same hub DID (should fail)
let identity2 = UserIdentity {
handle: Some("user2".to_string()),
display_name: Some("User Two".to_string()),
first_name: Some("User".to_string()),
last_name: Some("Two".to_string()),
email: Some("user2@example.com".to_string()),
avatar_url: None,
bio: None,
};
let preferences2 = UserPreferences {
theme: Some("light".to_string()),
language: Some("es".to_string()),
notifications_enabled: false,
auto_sync: true,
show_date_of_birth: false,
};
let result2 = create_profile_use_case.execute(
&mut passport,
Some("h:example".to_string()),
identity2,
preferences2,
"test-password",
file_path,
);
assert!(result2.is_err());
}
#[test]
fn test_update_user_profile_use_case() {
// Create a temporary file for testing
let temp_file = NamedTempFile::new().unwrap();
let file_path = temp_file.path().to_str().unwrap();
// Create a passport first
let create_use_case = CreatePassportUseCase::new(
Bip39MnemonicGenerator,
Ed25519KeyDeriver,
XChaCha20FileEncryptor,
FileSystemStorage,
);
let (mut passport, _) = create_use_case.execute("test-universe", "test-password", file_path)
.expect("Failed to create passport");
// Create a user profile first
let create_profile_use_case = CreateUserProfileUseCase::new(
XChaCha20FileEncryptor,
FileSystemStorage,
);
let identity = UserIdentity {
handle: Some("testuser".to_string()),
display_name: Some("Test User".to_string()),
first_name: Some("Test".to_string()),
last_name: Some("User".to_string()),
email: Some("test@example.com".to_string()),
avatar_url: Some("https://example.com/avatar.png".to_string()),
bio: Some("Test bio".to_string()),
};
let preferences = UserPreferences {
theme: Some("dark".to_string()),
language: Some("en".to_string()),
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
};
create_profile_use_case.execute(
&mut passport,
Some("h:example".to_string()),
identity,
preferences,
"test-password",
file_path,
).expect("Failed to create profile");
// Get the profile ID
let profile_id = passport.user_profile_for_hub("h:example")
.expect("Profile should exist")
.id
.clone();
// Test updating the user profile
let update_profile_use_case = UpdateUserProfileUseCase::new(
XChaCha20FileEncryptor,
FileSystemStorage,
);
let updated_identity = UserIdentity {
handle: Some("updateduser".to_string()),
display_name: Some("Updated User".to_string()),
first_name: Some("Updated".to_string()),
last_name: Some("User".to_string()),
email: Some("updated@example.com".to_string()),
avatar_url: Some("https://example.com/new-avatar.png".to_string()),
bio: Some("Updated bio".to_string()),
};
let updated_preferences = UserPreferences {
theme: Some("light".to_string()),
language: Some("es".to_string()),
notifications_enabled: false,
auto_sync: true,
show_date_of_birth: false,
};
let result = update_profile_use_case.execute(
&mut passport,
Some(&profile_id),
Some("h:example".to_string()),
updated_identity,
updated_preferences,
"test-password",
file_path,
);
assert!(result.is_ok());
// Verify the profile was updated
let updated_profile = passport.user_profile_for_hub("h:example")
.expect("Profile should exist");
assert_eq!(updated_profile.identity.handle, Some("updateduser".to_string()));
assert_eq!(updated_profile.preferences.theme, Some("light".to_string()));
assert_eq!(updated_profile.preferences.language, Some("es".to_string()));
}
#[test]
fn test_update_user_profile_use_case_invalid_id() {
// Create a temporary file for testing
let temp_file = NamedTempFile::new().unwrap();
let file_path = temp_file.path().to_str().unwrap();
// Create a passport first
let create_use_case = CreatePassportUseCase::new(
Bip39MnemonicGenerator,
Ed25519KeyDeriver,
XChaCha20FileEncryptor,
FileSystemStorage,
);
let (mut passport, _) = create_use_case.execute("test-universe", "test-password", file_path)
.expect("Failed to create passport");
// Try to update non-existent profile (should fail)
let update_profile_use_case = UpdateUserProfileUseCase::new(
XChaCha20FileEncryptor,
FileSystemStorage,
);
let identity = UserIdentity {
handle: Some("testuser".to_string()),
display_name: Some("Test User".to_string()),
first_name: Some("Test".to_string()),
last_name: Some("User".to_string()),
email: Some("test@example.com".to_string()),
avatar_url: None,
bio: None,
};
let preferences = UserPreferences {
theme: Some("dark".to_string()),
language: Some("en".to_string()),
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
};
let result = update_profile_use_case.execute(
&mut passport,
Some("non-existent-id"),
Some("h:example".to_string()),
identity,
preferences,
"test-password",
file_path,
);
assert!(result.is_err());
}
#[test]
fn test_delete_user_profile_use_case() {
// Create a temporary file for testing
let temp_file = NamedTempFile::new().unwrap();
let file_path = temp_file.path().to_str().unwrap();
// Create a passport first
let create_use_case = CreatePassportUseCase::new(
Bip39MnemonicGenerator,
Ed25519KeyDeriver,
XChaCha20FileEncryptor,
FileSystemStorage,
);
let (mut passport, _) = create_use_case.execute("test-universe", "test-password", file_path)
.expect("Failed to create passport");
// Create a user profile first
let create_profile_use_case = CreateUserProfileUseCase::new(
XChaCha20FileEncryptor,
FileSystemStorage,
);
let identity = UserIdentity {
handle: Some("testuser".to_string()),
display_name: Some("Test User".to_string()),
first_name: Some("Test".to_string()),
last_name: Some("User".to_string()),
email: Some("test@example.com".to_string()),
avatar_url: None,
bio: None,
};
let preferences = UserPreferences {
theme: Some("dark".to_string()),
language: Some("en".to_string()),
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
};
create_profile_use_case.execute(
&mut passport,
Some("h:example".to_string()),
identity,
preferences,
"test-password",
file_path,
).expect("Failed to create profile");
// Get the profile ID
let profile_id = passport.user_profile_for_hub("h:example")
.expect("Profile should exist")
.id
.clone();
// Test deleting the user profile
let delete_profile_use_case = DeleteUserProfileUseCase::new(
XChaCha20FileEncryptor,
FileSystemStorage,
);
let result = delete_profile_use_case.execute(
&mut passport,
Some(&profile_id),
"test-password",
file_path,
);
assert!(result.is_ok());
// Verify the profile was deleted
assert_eq!(passport.user_profiles.len(), 1); // only default profile remains
let deleted_profile = passport.user_profile_for_hub("h:example");
assert!(deleted_profile.is_none());
}
#[test]
fn test_delete_user_profile_use_case_invalid_id() {
// Create a temporary file for testing
let temp_file = NamedTempFile::new().unwrap();
let file_path = temp_file.path().to_str().unwrap();
// Create a passport first
let create_use_case = CreatePassportUseCase::new(
Bip39MnemonicGenerator,
Ed25519KeyDeriver,
XChaCha20FileEncryptor,
FileSystemStorage,
);
let (mut passport, _) = create_use_case.execute("test-universe", "test-password", file_path)
.expect("Failed to create passport");
// Try to delete non-existent profile (should fail)
let delete_profile_use_case = DeleteUserProfileUseCase::new(
XChaCha20FileEncryptor,
FileSystemStorage,
);
let result = delete_profile_use_case.execute(
&mut passport,
Some("non-existent-id"),
"test-password",
file_path,
);
assert!(result.is_err());
}
#[test]
fn test_change_passport_password_workflow() {
// Create a temporary file for testing
let temp_file = NamedTempFile::new().unwrap();
let file_path = temp_file.path().to_str().unwrap();
// Create a passport with old password
let create_use_case = CreatePassportUseCase::new(
Bip39MnemonicGenerator,
Ed25519KeyDeriver,
XChaCha20FileEncryptor,
FileSystemStorage,
);
let (passport, _) = create_use_case.execute("test-universe", "old-password", file_path)
.expect("Failed to create passport");
// Export passport with new password (simulating password change)
let export_use_case = ExportPassportUseCase::new(
XChaCha20FileEncryptor,
FileSystemStorage,
);
let result = export_use_case.execute(&passport, "new-password", file_path);
assert!(result.is_ok());
// Verify we can import with new password
let import_use_case = ImportFromFileUseCase::new(
XChaCha20FileEncryptor,
FileSystemStorage,
);
let imported_passport = import_use_case.execute(file_path, "new-password", None)
.expect("Failed to import with new password");
// Verify the imported passport has the same DID
assert_eq!(passport.did.as_str(), imported_passport.did.as_str());
assert_eq!(passport.univ_id, imported_passport.univ_id);
}
#[test]
fn test_get_passport_metadata_functionality() {
// Create a temporary file for testing
let temp_file = NamedTempFile::new().unwrap();
let file_path = temp_file.path().to_str().unwrap();
// Create a passport
let create_use_case = CreatePassportUseCase::new(
Bip39MnemonicGenerator,
Ed25519KeyDeriver,
XChaCha20FileEncryptor,
FileSystemStorage,
);
let (passport, _) = create_use_case.execute("test-universe", "test-password", file_path)
.expect("Failed to create passport");
// Load file directly to get metadata
let file_storage = FileSystemStorage;
let passport_file = file_storage.load(file_path)
.expect("Failed to load passport file");
// Verify metadata fields
assert!(!passport_file.did.is_empty());
assert!(!passport_file.univ_id.is_empty());
assert!(!passport_file.public_key.is_empty());
assert!(!passport_file.enc_seed.is_empty());
assert!(!passport_file.salt.is_empty());
assert!(!passport_file.nonce.is_empty());
// Verify DID matches
assert_eq!(passport_file.did, passport.did.as_str());
assert_eq!(passport_file.univ_id, passport.univ_id);
}
#[test]
fn test_validate_passport_file_functionality() {
// Create a temporary file for testing
let temp_file = NamedTempFile::new().unwrap();
let file_path = temp_file.path().to_str().unwrap();
// Create a valid passport
let create_use_case = CreatePassportUseCase::new(
Bip39MnemonicGenerator,
Ed25519KeyDeriver,
XChaCha20FileEncryptor,
FileSystemStorage,
);
create_use_case.execute("test-universe", "test-password", file_path)
.expect("Failed to create passport");
// Load file directly to validate
let file_storage = FileSystemStorage;
let passport_file = file_storage.load(file_path)
.expect("Failed to load passport file");
// Validate the file structure
let is_valid = !passport_file.enc_seed.is_empty()
&& !passport_file.salt.is_empty()
&& !passport_file.nonce.is_empty()
&& !passport_file.public_key.is_empty()
&& !passport_file.did.is_empty()
&& !passport_file.univ_id.is_empty();
assert!(is_valid);
}
#[test]
fn test_validate_passport_file_invalid_file() {
// Try to load non-existent file (should fail)
let file_storage = FileSystemStorage;
let result = file_storage.load("/non/existent/path.spf");
assert!(result.is_err());
}
}

View file

@ -57,7 +57,7 @@ impl Did {
}
}
#[derive(Debug, Zeroize, ZeroizeOnDrop)]
#[derive(Debug, Zeroize, ZeroizeOnDrop, Serialize, Deserialize)]
pub struct Seed {
bytes: Vec<u8>,
}
@ -72,7 +72,7 @@ impl Seed {
}
}
#[derive(Debug)]
#[derive(Debug, Serialize, Deserialize)]
pub struct Passport {
pub seed: Seed,
pub public_key: PublicKey,
@ -80,6 +80,8 @@ pub struct Passport {
pub did: Did,
pub univ_id: String,
pub user_profiles: Vec<UserProfile>,
pub date_of_birth: Option<DateOfBirth>,
pub default_user_profile_id: Option<String>, // UUIDv7 of the default user profile
}
impl Passport {
@ -108,6 +110,7 @@ impl Passport {
language: None,
notifications_enabled: true,
auto_sync: true,
show_date_of_birth: false,
},
);
@ -117,7 +120,9 @@ impl Passport {
private_key,
did,
univ_id,
user_profiles: vec![default_profile],
user_profiles: vec![default_profile.clone()],
date_of_birth: None,
default_user_profile_id: Some(default_profile.id.clone()),
}
}
@ -138,7 +143,12 @@ impl Passport {
}
pub fn default_user_profile(&self) -> Option<&UserProfile> {
self.user_profiles.iter().find(|p| p.is_default())
if let Some(default_id) = &self.default_user_profile_id {
self.user_profile_by_id(default_id)
} else {
// Fallback to implicit detection for backward compatibility
self.user_profiles.iter().find(|p| p.is_default())
}
}
pub fn user_profile_for_hub(&self, hub_did: &str) -> Option<&UserProfile> {
@ -154,9 +164,12 @@ impl Passport {
}
pub fn add_user_profile(&mut self, profile: UserProfile) -> Result<(), String> {
// Ensure only one default profile
if profile.is_default() && self.default_user_profile().is_some() {
return Err("Default user profile already exists".to_string());
// If this is a default profile (no hub_did), set it as the default
if profile.hub_did.is_none() {
if self.default_user_profile_id.is_some() {
return Err("Default user profile already exists".to_string());
}
self.default_user_profile_id = Some(profile.id.clone());
}
// Ensure hub_did is unique
@ -222,7 +235,7 @@ impl Passport {
match index {
Some(idx) => {
// Check if this is the default profile
if self.user_profiles[idx].is_default() {
if self.default_user_profile_id.as_deref() == Some(profile_id) {
return Err("Cannot delete default user profile".to_string());
}
self.user_profiles.remove(idx);
@ -231,6 +244,23 @@ impl Passport {
None => Err("User profile not found".to_string()),
}
}
pub fn set_default_user_profile(&mut self, profile_id: &str) -> Result<(), String> {
// Verify the profile exists
if self.user_profile_by_id(profile_id).is_none() {
return Err("User profile not found".to_string());
}
// Verify the profile is a default profile (no hub_did)
if let Some(profile) = self.user_profile_by_id(profile_id) {
if profile.hub_did.is_some() {
return Err("Cannot set hub-specific profile as default".to_string());
}
}
self.default_user_profile_id = Some(profile_id.to_string());
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -250,6 +280,14 @@ pub struct UserPreferences {
pub language: Option<String>,
pub notifications_enabled: bool,
pub auto_sync: bool,
pub show_date_of_birth: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DateOfBirth {
pub month: u8,
pub day: u8,
pub year: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@ -298,4 +336,6 @@ pub struct PassportFile {
pub created_at: u64,
pub version: String,
pub enc_user_profiles: Vec<u8>, // Encrypted CBOR of Vec<UserProfile>
pub enc_date_of_birth: Vec<u8>, // Encrypted CBOR of Option<DateOfBirth>
pub enc_default_user_profile_id: Vec<u8>, // Encrypted CBOR of Option<String>
}

View file

@ -42,7 +42,7 @@ mod tests {
seed.zeroize();
// After zeroization, bytes should be empty (zeroize clears the vector)
assert_eq!(seed.as_bytes(), &[]);
assert_eq!(seed.as_bytes(), &[] as &[u8]);
}
#[test]
@ -53,7 +53,7 @@ mod tests {
private_key.zeroize();
// After zeroization, bytes should be empty (zeroize clears the vector)
assert_eq!(private_key.0, vec![]);
assert_eq!(private_key.0, vec![] as Vec<u8>);
}
#[test]
@ -75,6 +75,7 @@ mod tests {
language: Some("en".to_string()),
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
};
let profile = UserProfile {
@ -120,6 +121,7 @@ mod tests {
language: None,
notifications_enabled: true,
auto_sync: true,
show_date_of_birth: false,
},
created_at: 1234567890,
updated_at: 1234567890,
@ -162,6 +164,7 @@ mod tests {
language: None,
notifications_enabled: false,
auto_sync: true,
show_date_of_birth: false,
},
created_at: 1234567890,
updated_at: 1234567890,
@ -195,6 +198,7 @@ mod tests {
language: None,
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
},
created_at: 1234567890,
updated_at: 1234567890,
@ -223,6 +227,7 @@ mod tests {
language: None,
notifications_enabled: true,
auto_sync: false,
show_date_of_birth: false,
},
created_at: 1234567890,
updated_at: 1234567890,

View file

@ -26,13 +26,15 @@ pub trait FileEncryptor {
did: &Did,
univ_id: &str,
user_profiles: &[UserProfile],
date_of_birth: &Option<DateOfBirth>,
default_user_profile_id: &Option<String>,
) -> Result<PassportFile, Self::Error>;
fn decrypt(
&self,
file: &PassportFile,
password: &str,
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>), Self::Error>;
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>, Option<DateOfBirth>, Option<String>), Self::Error>;
}
pub trait FileStorage {

View file

@ -90,6 +90,8 @@ impl FileEncryptor for XChaCha20FileEncryptor {
did: &Did,
univ_id: &str,
user_profiles: &[UserProfile],
date_of_birth: &Option<DateOfBirth>,
default_user_profile_id: &Option<String>,
) -> Result<PassportFile, Self::Error> {
// Generate salt and nonce
let mut salt = [0u8; SALT_LENGTH];
@ -118,6 +120,20 @@ impl FileEncryptor for XChaCha20FileEncryptor {
.encrypt(&nonce, &*user_profiles_bytes)
.map_err(|e| DomainError::CryptographicError(format!("User profiles encryption failed: {}", e)))?;
// Serialize and encrypt date of birth
let date_of_birth_bytes = serde_cbor::to_vec(&date_of_birth)
.map_err(|e| DomainError::CryptographicError(format!("Failed to serialize date of birth: {}", e)))?;
let enc_date_of_birth = cipher
.encrypt(&nonce, &*date_of_birth_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Date of birth encryption failed: {}", e)))?;
// Serialize and encrypt default user profile ID
let default_user_profile_id_bytes = serde_cbor::to_vec(&default_user_profile_id)
.map_err(|e| DomainError::CryptographicError(format!("Failed to serialize default user profile ID: {}", e)))?;
let enc_default_user_profile_id = cipher
.encrypt(&nonce, &*default_user_profile_id_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Default user profile ID encryption failed: {}", e)))?;
// Get current timestamp
let created_at = SystemTime::now()
.duration_since(UNIX_EPOCH)
@ -136,6 +152,8 @@ impl FileEncryptor for XChaCha20FileEncryptor {
created_at,
version: "1.0.0".to_string(),
enc_user_profiles,
enc_date_of_birth,
enc_default_user_profile_id,
})
}
@ -143,7 +161,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
&self,
file: &PassportFile,
password: &str,
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>), Self::Error> {
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>, Option<DateOfBirth>, Option<String>), Self::Error> {
// Validate file format
validate_file_format(&file.kdf, &file.cipher)?;
@ -180,7 +198,21 @@ impl FileEncryptor for XChaCha20FileEncryptor {
let user_profiles: Vec<UserProfile> = serde_cbor::from_slice(&user_profiles_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize user profiles: {}", e)))?;
// Decrypt date of birth
let date_of_birth_bytes = cipher
.decrypt(&nonce, &*file.enc_date_of_birth)
.map_err(|e| DomainError::CryptographicError(format!("Date of birth decryption failed: {}", e)))?;
let date_of_birth: Option<DateOfBirth> = serde_cbor::from_slice(&date_of_birth_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize date of birth: {}", e)))?;
// Decrypt default user profile ID
let default_user_profile_id_bytes = cipher
.decrypt(&nonce, &*file.enc_default_user_profile_id)
.map_err(|e| DomainError::CryptographicError(format!("Default user profile ID decryption failed: {}", e)))?;
let default_user_profile_id: Option<String> = serde_cbor::from_slice(&default_user_profile_id_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize default user profile ID: {}", e)))?;
// Note: univ_id is stored in the PassportFile and will be used when creating the Passport
Ok((seed, public_key, private_key, user_profiles))
Ok((seed, public_key, private_key, user_profiles, date_of_birth, default_user_profile_id))
}
}

View file

@ -45,7 +45,7 @@ mod tests {
let password = "test-password";
// Encrypt
let encrypted_file = encryptor.encrypt(&seed, password, &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[]).unwrap();
let encrypted_file = encryptor.encrypt(&seed, password, &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[], &None, &None).unwrap();
// Verify file structure
assert_eq!(encrypted_file.kdf, "HKDF-SHA256");
@ -56,7 +56,7 @@ mod tests {
assert_eq!(encrypted_file.did, did.0);
// Decrypt
let (decrypted_seed, decrypted_public_key, _, _) = encryptor.decrypt(&encrypted_file, password).unwrap();
let (decrypted_seed, decrypted_public_key, _, _, _, _) = encryptor.decrypt(&encrypted_file, password).unwrap();
// Verify decryption
assert_eq!(decrypted_seed.as_bytes(), seed.as_bytes());
@ -72,7 +72,7 @@ mod tests {
let did = Did::new(&public_key);
// Encrypt with one password
let encrypted_file = encryptor.encrypt(&seed, "correct-password", &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[]).unwrap();
let encrypted_file = encryptor.encrypt(&seed, "correct-password", &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[], &None, &None).unwrap();
// Try to decrypt with wrong password
let result = encryptor.decrypt(&encrypted_file, "wrong-password");

View file

@ -91,6 +91,8 @@ impl FileEncryptor for XChaCha20FileEncryptor {
did: &Did,
univ_id: &str,
user_profiles: &[UserProfile],
date_of_birth: &Option<DateOfBirth>,
default_user_profile_id: &Option<String>,
) -> Result<PassportFile, Self::Error> {
// Generate salt and nonce using WASM-compatible RNG
let mut salt = [0u8; SALT_LENGTH];
@ -120,6 +122,20 @@ impl FileEncryptor for XChaCha20FileEncryptor {
.encrypt(&nonce, &*user_profiles_bytes)
.map_err(|e| DomainError::CryptographicError(format!("User profiles encryption failed: {}", e)))?;
// Serialize and encrypt date of birth
let date_of_birth_bytes = serde_cbor::to_vec(&date_of_birth)
.map_err(|e| DomainError::CryptographicError(format!("Failed to serialize date of birth: {}", e)))?;
let enc_date_of_birth = cipher
.encrypt(&nonce, &*date_of_birth_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Date of birth encryption failed: {}", e)))?;
// Serialize and encrypt default user profile ID
let default_user_profile_id_bytes = serde_cbor::to_vec(&default_user_profile_id)
.map_err(|e| DomainError::CryptographicError(format!("Failed to serialize default user profile ID: {}", e)))?;
let enc_default_user_profile_id = cipher
.encrypt(&nonce, &*default_user_profile_id_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Default user profile ID encryption failed: {}", e)))?;
// Get current timestamp using WASM-compatible time
let created_at = time::now_seconds()?;
@ -135,6 +151,8 @@ impl FileEncryptor for XChaCha20FileEncryptor {
created_at,
version: "1.0.0".to_string(),
enc_user_profiles,
enc_date_of_birth,
enc_default_user_profile_id,
})
}
@ -142,7 +160,7 @@ impl FileEncryptor for XChaCha20FileEncryptor {
&self,
file: &PassportFile,
password: &str,
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>), Self::Error> {
) -> Result<(Seed, PublicKey, PrivateKey, Vec<UserProfile>, Option<DateOfBirth>, Option<String>), Self::Error> {
// Validate file format
validate_file_format(&file.kdf, &file.cipher)?;
@ -179,7 +197,21 @@ impl FileEncryptor for XChaCha20FileEncryptor {
let user_profiles: Vec<UserProfile> = serde_cbor::from_slice(&user_profiles_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize user profiles: {}", e)))?;
// Decrypt date of birth
let date_of_birth_bytes = cipher
.decrypt(&nonce, &*file.enc_date_of_birth)
.map_err(|e| DomainError::CryptographicError(format!("Date of birth decryption failed: {}", e)))?;
let date_of_birth: Option<DateOfBirth> = serde_cbor::from_slice(&date_of_birth_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize date of birth: {}", e)))?;
// Decrypt default user profile ID
let default_user_profile_id_bytes = cipher
.decrypt(&nonce, &*file.enc_default_user_profile_id)
.map_err(|e| DomainError::CryptographicError(format!("Default user profile ID decryption failed: {}", e)))?;
let default_user_profile_id: Option<String> = serde_cbor::from_slice(&default_user_profile_id_bytes)
.map_err(|e| DomainError::CryptographicError(format!("Failed to deserialize default user profile ID: {}", e)))?;
// Note: univ_id is stored in the PassportFile and will be used when creating the Passport
Ok((seed, public_key, private_key, user_profiles))
Ok((seed, public_key, private_key, user_profiles, date_of_birth, default_user_profile_id))
}
}

View file

@ -45,7 +45,7 @@ mod tests {
let password = "test-password";
// Encrypt
let encrypted_file = encryptor.encrypt(&seed, password, &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[]).unwrap();
let encrypted_file = encryptor.encrypt(&seed, password, &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[], &None, &None).unwrap();
// Verify file structure
assert_eq!(encrypted_file.kdf, "HKDF-SHA256");
@ -56,7 +56,7 @@ mod tests {
assert_eq!(encrypted_file.did, did.0);
// Decrypt
let (decrypted_seed, decrypted_public_key, _, _) = encryptor.decrypt(&encrypted_file, password).unwrap();
let (decrypted_seed, decrypted_public_key, _, _, _, _) = encryptor.decrypt(&encrypted_file, password).unwrap();
// Verify decryption
assert_eq!(decrypted_seed.as_bytes(), seed.as_bytes());
@ -72,7 +72,7 @@ mod tests {
let did = Did::new(&public_key);
// Encrypt with one password
let encrypted_file = encryptor.encrypt(&seed, "correct-password", &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[]).unwrap();
let encrypted_file = encryptor.encrypt(&seed, "correct-password", &public_key, &did, "u:Test Universe:12345678-1234-1234-1234-123456789012", &[], &None, &None).unwrap();
// Try to decrypt with wrong password
let result = encryptor.decrypt(&encrypted_file, "wrong-password");

View file

@ -25,6 +25,8 @@ mod tests {
created_at: 1234567890,
version: "1.0.0".to_string(),
enc_user_profiles: vec![],
enc_date_of_birth: vec![],
enc_default_user_profile_id: vec![],
};
// Save the file

View file

@ -24,6 +24,8 @@ mod tests {
created_at: 1234567890,
version: "1.0.0".to_string(),
enc_user_profiles: vec![],
enc_date_of_birth: vec![],
enc_default_user_profile_id: vec![],
};
// Save the file

View file

@ -7,6 +7,31 @@ pub mod domain;
pub mod application;
pub mod infrastructure;
#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
pub mod wasm;
// Re-export WASM API functions when building for WASM target
#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
pub use wasm::{
create_passport,
import_from_recovery,
import_from_encrypted_data,
export_to_encrypted_data,
sign_message,
generate_recovery_phrase,
validate_recovery_phrase,
create_user_profile,
update_user_profile,
delete_user_profile,
change_passport_password,
get_passport_metadata,
validate_passport_file,
};
#[cfg(any(target_arch = "wasm32", feature = "force-wasm"))]
#[cfg(test)]
pub mod wasm_test;
// Public API surface
pub use domain::entities::{Passport, RecoveryPhrase, PassportFile, PublicKey, PrivateKey, Did, Seed};
pub use domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor, FileStorage};

View file

@ -0,0 +1,346 @@
//! Browser-specific WASM API for Sharenet Passport
//!
//! This module provides browser-compatible functions that work with in-memory data
//! and return encrypted data as bytes. The library is purely in-memory and does not
//! handle any I/O operations - the consumer must handle storage/retrieval.
use wasm_bindgen::prelude::*;
use crate::application::use_cases::{
SignCardUseCase,
};
use crate::infrastructure::{
Bip39MnemonicGenerator,
Ed25519KeyDeriver,
XChaCha20FileEncryptor,
};
use crate::domain::entities::{Passport, UserIdentity, UserPreferences, PassportFile, RecoveryPhrase, UserProfile, Did};
use crate::domain::traits::{MnemonicGenerator, KeyDeriver, FileEncryptor};
/// Create a new passport with the given universe ID and password
///
/// Returns a JSON string containing both the passport and recovery phrase
/// This function works entirely in memory and doesn't write to any storage.
#[wasm_bindgen]
pub fn create_passport(
univ_id: String,
_password: String,
) -> Result<JsValue, JsValue> {
// For WASM, we need to create a passport in memory without file operations
// This is a simplified version that creates the passport structure directly
let generator = Bip39MnemonicGenerator;
let key_deriver = Ed25519KeyDeriver;
match generator.generate() {
Ok(recovery_phrase) => {
match key_deriver.derive_from_mnemonic(&recovery_phrase, &univ_id) {
Ok(seed) => {
// Derive keys from seed
let (public_key, private_key) = key_deriver.derive_from_seed(&seed)
.map_err(|e| JsValue::from_str(&format!("Error deriving keys from seed: {}", e)))?;
// Create passport with default user profile
let passport = Passport::new(
seed,
public_key,
private_key,
univ_id,
);
let result = serde_wasm_bindgen::to_value(&serde_json::json!({
"passport": passport,
"recovery_phrase": recovery_phrase
})).map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(result)
}
Err(e) => Err(JsValue::from_str(&format!("Error deriving keys: {}", e))),
}
}
Err(e) => Err(JsValue::from_str(&format!("Error generating recovery phrase: {}", e))),
}
}
/// Import a passport from recovery phrase
/// Returns the imported passport as JSON
#[wasm_bindgen]
pub fn import_from_recovery(
univ_id: String,
recovery_words: Vec<String>,
_password: String,
) -> Result<JsValue, JsValue> {
let generator = Bip39MnemonicGenerator;
let key_deriver = Ed25519KeyDeriver;
// Validate recovery phrase
if let Err(_) = generator.validate(&recovery_words) {
return Err(JsValue::from_str("Invalid recovery phrase"));
}
// Reconstruct recovery phrase from words
let recovery_phrase = RecoveryPhrase::new(recovery_words);
// Derive keys from recovery phrase
match key_deriver.derive_from_mnemonic(&recovery_phrase, &univ_id) {
Ok(seed) => {
// Derive keys from seed
let (public_key, private_key) = key_deriver.derive_from_seed(&seed)
.map_err(|e| JsValue::from_str(&format!("Error deriving keys from seed: {}", e)))?;
// Create passport with default user profile
let passport = Passport::new(
seed,
public_key,
private_key,
univ_id,
);
let result = serde_wasm_bindgen::to_value(&passport)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(result)
}
Err(e) => Err(JsValue::from_str(&format!("Error deriving keys: {}", e))),
}
}
/// Load a passport from encrypted data (ArrayBuffer/Blob)
/// This accepts encrypted passport data as bytes and returns the decrypted passport
#[wasm_bindgen]
pub fn import_from_encrypted_data(
encrypted_data: Vec<u8>,
password: String,
) -> Result<JsValue, JsValue> {
// Deserialize the encrypted passport file
let passport_file: PassportFile = serde_cbor::from_slice(&encrypted_data)
.map_err(|e| JsValue::from_str(&format!("Failed to deserialize passport file: {}", e)))?;
// Decrypt the passport file using the password
let encryptor = XChaCha20FileEncryptor;
let (seed, public_key, private_key, user_profiles, date_of_birth, default_user_profile_id) = encryptor.decrypt(
&passport_file,
&password,
).map_err(|e| JsValue::from_str(&format!("Failed to decrypt passport: {}", e)))?;
// Create passport with decrypted user profiles instead of creating a new default one
let did = Did::new(&public_key);
let passport = Passport {
seed,
public_key,
private_key,
did,
univ_id: passport_file.univ_id,
user_profiles,
date_of_birth,
default_user_profile_id,
};
let result = serde_wasm_bindgen::to_value(&passport)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(result)
}
/// Export a passport to encrypted data (ArrayBuffer/Blob)
/// This returns encrypted passport data as bytes that can be downloaded or stored
#[wasm_bindgen]
pub fn export_to_encrypted_data(
passport_json: JsValue,
password: String,
) -> Result<Vec<u8>, JsValue> {
let passport: Passport = serde_wasm_bindgen::from_value(passport_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
let encryptor = XChaCha20FileEncryptor;
// Encrypt the passport data
let passport_file = encryptor.encrypt(
&passport.seed,
&password,
&passport.public_key,
&passport.did,
&passport.univ_id,
&passport.user_profiles,
&passport.date_of_birth,
&passport.default_user_profile_id,
).map_err(|e| JsValue::from_str(&format!("Failed to encrypt passport: {}", e)))?;
// Serialize to bytes for browser download
serde_cbor::to_vec(&passport_file)
.map_err(|e| JsValue::from_str(&format!("Failed to serialize passport file: {}", e)))
}
/// Sign a message with the passport's private key
#[wasm_bindgen]
pub fn sign_message(
passport_json: JsValue,
message: String,
) -> Result<Vec<u8>, JsValue> {
let passport: Passport = serde_wasm_bindgen::from_value(passport_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
let use_case = SignCardUseCase::new();
match use_case.execute(&passport, &message) {
Ok(signature) => Ok(signature),
Err(e) => Err(JsValue::from_str(&format!("Error signing message: {}", e))),
}
}
/// Generate a new recovery phrase
#[wasm_bindgen]
pub fn generate_recovery_phrase() -> Result<JsValue, JsValue> {
let generator = Bip39MnemonicGenerator;
match generator.generate() {
Ok(recovery_phrase) => {
let result = serde_wasm_bindgen::to_value(&recovery_phrase)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(result)
}
Err(e) => Err(JsValue::from_str(&format!("Error generating recovery phrase: {}", e))),
}
}
/// Validate a recovery phrase
#[wasm_bindgen]
pub fn validate_recovery_phrase(recovery_words: Vec<String>) -> Result<bool, JsValue> {
let generator = Bip39MnemonicGenerator;
match generator.validate(&recovery_words) {
Ok(()) => Ok(true),
Err(_) => Ok(false),
}
}
/// Create a new user profile for a passport
/// Returns the updated passport as JSON
#[wasm_bindgen]
pub fn create_user_profile(
passport_json: JsValue,
hub_did: Option<String>,
identity_json: JsValue,
preferences_json: JsValue,
) -> Result<JsValue, JsValue> {
let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
let identity: UserIdentity = serde_wasm_bindgen::from_value(identity_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
let preferences: UserPreferences = serde_wasm_bindgen::from_value(preferences_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
// Create new user profile and add to passport (in-memory operation)
let profile = UserProfile::new(hub_did, identity, preferences);
passport.add_user_profile(profile)
.map_err(|e| JsValue::from_str(&format!("Error adding user profile: {}", e)))?;
let result = serde_wasm_bindgen::to_value(&passport)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(result)
}
/// Update an existing user profile
/// Returns the updated passport as JSON
#[wasm_bindgen]
pub fn update_user_profile(
passport_json: JsValue,
profile_id: String,
identity_json: JsValue,
preferences_json: JsValue,
) -> Result<JsValue, JsValue> {
let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
let identity: UserIdentity = serde_wasm_bindgen::from_value(identity_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
let preferences: UserPreferences = serde_wasm_bindgen::from_value(preferences_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
// Update user profile directly in passport (in-memory operation)
let profile = UserProfile::new(None, identity, preferences);
passport.update_user_profile_by_id(&profile_id, profile)
.map_err(|e| JsValue::from_str(&format!("Error updating user profile: {}", e)))?;
let result = serde_wasm_bindgen::to_value(&passport)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(result)
}
/// Delete a user profile
/// Returns the updated passport as JSON
#[wasm_bindgen]
pub fn delete_user_profile(
passport_json: JsValue,
profile_id: String,
) -> Result<JsValue, JsValue> {
let mut passport: Passport = serde_wasm_bindgen::from_value(passport_json)
.map_err(|e| JsValue::from_str(&format!("Deserialization error: {}", e)))?;
// Delete user profile directly from passport (in-memory operation)
passport.remove_user_profile_by_id(&profile_id)
.map_err(|e| JsValue::from_str(&format!("Error deleting user profile: {}", e)))?;
let result = serde_wasm_bindgen::to_value(&passport)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(result)
}
/// Change passport password
/// Returns the updated passport as JSON
#[wasm_bindgen]
pub fn change_passport_password(
_passport_json: JsValue,
_old_password: String,
_new_password: String,
) -> Result<JsValue, JsValue> {
// Note: This function requires re-encryption which typically needs file operations
// In a browser environment, you'd need to handle this differently
// For now, we'll return an error indicating this operation isn't supported
Err(JsValue::from_str(
"Password change requires file operations which are not supported in browser environment. "
))
}
/// Get passport metadata from encrypted data
/// This can extract public metadata without full decryption
#[wasm_bindgen]
pub fn get_passport_metadata(
encrypted_data: Vec<u8>,
) -> Result<JsValue, JsValue> {
// Deserialize the encrypted passport file
let passport_file: PassportFile = serde_cbor::from_slice(&encrypted_data)
.map_err(|e| JsValue::from_str(&format!("Failed to deserialize passport file: {}", e)))?;
let metadata = serde_json::json!({
"did": passport_file.did,
"univ_id": passport_file.univ_id,
"public_key": hex::encode(&passport_file.public_key),
"created_at": passport_file.created_at,
"version": passport_file.version,
});
let result = serde_wasm_bindgen::to_value(&metadata)
.map_err(|e| JsValue::from_str(&format!("Serialization error: {}", e)))?;
Ok(result)
}
/// Validate passport file integrity from encrypted data
#[wasm_bindgen]
pub fn validate_passport_file(
encrypted_data: Vec<u8>,
) -> Result<bool, JsValue> {
match serde_cbor::from_slice::<PassportFile>(&encrypted_data) {
Ok(passport_file) => {
// Basic validation checks
let is_valid = !passport_file.enc_seed.is_empty()
&& !passport_file.salt.is_empty()
&& !passport_file.nonce.is_empty()
&& !passport_file.public_key.is_empty()
&& !passport_file.did.is_empty()
&& !passport_file.univ_id.is_empty();
Ok(is_valid)
}
Err(_) => Ok(false),
}
}

113
pkg/sharenet_passport.d.ts vendored Normal file
View file

@ -0,0 +1,113 @@
/* tslint:disable */
/* eslint-disable */
/**
* Create a new passport with the given universe ID and password
*
* Returns a JSON string containing both the passport and recovery phrase
* This function works entirely in memory and doesn't write to any storage.
*/
export function create_passport(univ_id: string, _password: string): any;
/**
* Import a passport from recovery phrase
* Returns the imported passport as JSON
*/
export function import_from_recovery(univ_id: string, recovery_words: string[], _password: string): any;
/**
* Load a passport from encrypted data (ArrayBuffer/Blob)
* This accepts encrypted passport data as bytes and returns the decrypted passport
*/
export function import_from_encrypted_data(encrypted_data: Uint8Array, password: string): any;
/**
* Export a passport to encrypted data (ArrayBuffer/Blob)
* This returns encrypted passport data as bytes that can be downloaded or stored
*/
export function export_to_encrypted_data(passport_json: any, password: string): Uint8Array;
/**
* Sign a message with the passport's private key
*/
export function sign_message(passport_json: any, message: string): Uint8Array;
/**
* Generate a new recovery phrase
*/
export function generate_recovery_phrase(): any;
/**
* Validate a recovery phrase
*/
export function validate_recovery_phrase(recovery_words: string[]): boolean;
/**
* Create a new user profile for a passport
* Returns the updated passport as JSON
*/
export function create_user_profile(passport_json: any, hub_did: string | null | undefined, identity_json: any, preferences_json: any): any;
/**
* Update an existing user profile
* Returns the updated passport as JSON
*/
export function update_user_profile(passport_json: any, profile_id: string, identity_json: any, preferences_json: any): any;
/**
* Delete a user profile
* Returns the updated passport as JSON
*/
export function delete_user_profile(passport_json: any, profile_id: string): any;
/**
* Change passport password
* Returns the updated passport as JSON
*/
export function change_passport_password(_passport_json: any, _old_password: string, _new_password: string): any;
/**
* Get passport metadata from encrypted data
* This can extract public metadata without full decryption
*/
export function get_passport_metadata(encrypted_data: Uint8Array): any;
/**
* Validate passport file integrity from encrypted data
*/
export function validate_passport_file(encrypted_data: Uint8Array): boolean;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly create_passport: (a: number, b: number, c: number, d: number) => [number, number, number];
readonly import_from_recovery: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
readonly import_from_encrypted_data: (a: number, b: number, c: number, d: number) => [number, number, number];
readonly export_to_encrypted_data: (a: any, b: number, c: number) => [number, number, number, number];
readonly sign_message: (a: any, b: number, c: number) => [number, number, number, number];
readonly generate_recovery_phrase: () => [number, number, number];
readonly validate_recovery_phrase: (a: number, b: number) => [number, number, number];
readonly create_user_profile: (a: any, b: number, c: number, d: any, e: any) => [number, number, number];
readonly update_user_profile: (a: any, b: number, c: number, d: any, e: any) => [number, number, number];
readonly delete_user_profile: (a: any, b: number, c: number) => [number, number, number];
readonly change_passport_password: (a: any, b: number, c: number, d: number, e: number) => [number, number, number];
readonly get_passport_metadata: (a: number, b: number) => [number, number, number];
readonly validate_passport_file: (a: number, b: number) => [number, number, number];
readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
readonly __wbindgen_exn_store: (a: number) => void;
readonly __externref_table_alloc: () => number;
readonly __wbindgen_externrefs: WebAssembly.Table;
readonly __externref_table_dealloc: (a: number) => void;
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
readonly __wbindgen_start: () => void;
}
export type SyncInitInput = BufferSource | WebAssembly.Module;
/**
* Instantiates the given `module`, which can either be bytes or
* a precompiled `WebAssembly.Module`.
*
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
*
* @returns {InitOutput}
*/
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
*
* @returns {Promise<InitOutput>}
*/
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;

832
pkg/sharenet_passport.js Normal file
View file

@ -0,0 +1,832 @@
let wasm;
let cachedUint8ArrayMemory0 = null;
function getUint8ArrayMemory0() {
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8ArrayMemory0;
}
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
const MAX_SAFARI_DECODE_BYTES = 2146435072;
let numBytesDecoded = 0;
function decodeText(ptr, len) {
numBytesDecoded += len;
if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
numBytesDecoded = len;
}
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
}
function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0;
return decodeText(ptr, len);
}
let WASM_VECTOR_LEN = 0;
const cachedTextEncoder = new TextEncoder();
if (!('encodeInto' in cachedTextEncoder)) {
cachedTextEncoder.encodeInto = function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
}
}
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length, 1) >>> 0;
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len, 1) >>> 0;
const mem = getUint8ArrayMemory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
const ret = cachedTextEncoder.encodeInto(arg, view);
offset += ret.written;
ptr = realloc(ptr, len, offset, 1) >>> 0;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
let cachedDataViewMemory0 = null;
function getDataViewMemory0() {
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
}
return cachedDataViewMemory0;
}
function isLikeNone(x) {
return x === undefined || x === null;
}
function debugString(val) {
// primitive types
const type = typeof val;
if (type == 'number' || type == 'boolean' || val == null) {
return `${val}`;
}
if (type == 'string') {
return `"${val}"`;
}
if (type == 'symbol') {
const description = val.description;
if (description == null) {
return 'Symbol';
} else {
return `Symbol(${description})`;
}
}
if (type == 'function') {
const name = val.name;
if (typeof name == 'string' && name.length > 0) {
return `Function(${name})`;
} else {
return 'Function';
}
}
// objects
if (Array.isArray(val)) {
const length = val.length;
let debug = '[';
if (length > 0) {
debug += debugString(val[0]);
}
for(let i = 1; i < length; i++) {
debug += ', ' + debugString(val[i]);
}
debug += ']';
return debug;
}
// Test for built-in
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
let className;
if (builtInMatches && builtInMatches.length > 1) {
className = builtInMatches[1];
} else {
// Failed to match the standard '[object ClassName]'
return toString.call(val);
}
if (className == 'Object') {
// we're a user defined class or Object
// JSON.stringify avoids problems with cycles, and is generally much
// easier than looping through ownProperties of `val`.
try {
return 'Object(' + JSON.stringify(val) + ')';
} catch (_) {
return 'Object';
}
}
// errors
if (val instanceof Error) {
return `${val.name}: ${val.message}\n${val.stack}`;
}
// TODO we could test for more things here, like `Set`s and `Map`s.
return className;
}
function addToExternrefTable0(obj) {
const idx = wasm.__externref_table_alloc();
wasm.__wbindgen_externrefs.set(idx, obj);
return idx;
}
function handleError(f, args) {
try {
return f.apply(this, args);
} catch (e) {
const idx = addToExternrefTable0(e);
wasm.__wbindgen_exn_store(idx);
}
}
function getArrayU8FromWasm0(ptr, len) {
ptr = ptr >>> 0;
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
}
function takeFromExternrefTable0(idx) {
const value = wasm.__wbindgen_externrefs.get(idx);
wasm.__externref_table_dealloc(idx);
return value;
}
/**
* Create a new passport with the given universe ID and password
*
* Returns a JSON string containing both the passport and recovery phrase
* This function works entirely in memory and doesn't write to any storage.
* @param {string} univ_id
* @param {string} _password
* @returns {any}
*/
export function create_passport(univ_id, _password) {
const ptr0 = passStringToWasm0(univ_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(_password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.create_passport(ptr0, len0, ptr1, len1);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
function passArrayJsValueToWasm0(array, malloc) {
const ptr = malloc(array.length * 4, 4) >>> 0;
for (let i = 0; i < array.length; i++) {
const add = addToExternrefTable0(array[i]);
getDataViewMemory0().setUint32(ptr + 4 * i, add, true);
}
WASM_VECTOR_LEN = array.length;
return ptr;
}
/**
* Import a passport from recovery phrase
* Returns the imported passport as JSON
* @param {string} univ_id
* @param {string[]} recovery_words
* @param {string} _password
* @returns {any}
*/
export function import_from_recovery(univ_id, recovery_words, _password) {
const ptr0 = passStringToWasm0(univ_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passArrayJsValueToWasm0(recovery_words, wasm.__wbindgen_malloc);
const len1 = WASM_VECTOR_LEN;
const ptr2 = passStringToWasm0(_password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len2 = WASM_VECTOR_LEN;
const ret = wasm.import_from_recovery(ptr0, len0, ptr1, len1, ptr2, len2);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
function passArray8ToWasm0(arg, malloc) {
const ptr = malloc(arg.length * 1, 1) >>> 0;
getUint8ArrayMemory0().set(arg, ptr / 1);
WASM_VECTOR_LEN = arg.length;
return ptr;
}
/**
* Load a passport from encrypted data (ArrayBuffer/Blob)
* This accepts encrypted passport data as bytes and returns the decrypted passport
* @param {Uint8Array} encrypted_data
* @param {string} password
* @returns {any}
*/
export function import_from_encrypted_data(encrypted_data, password) {
const ptr0 = passArray8ToWasm0(encrypted_data, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.import_from_encrypted_data(ptr0, len0, ptr1, len1);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/**
* Export a passport to encrypted data (ArrayBuffer/Blob)
* This returns encrypted passport data as bytes that can be downloaded or stored
* @param {any} passport_json
* @param {string} password
* @returns {Uint8Array}
*/
export function export_to_encrypted_data(passport_json, password) {
const ptr0 = passStringToWasm0(password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.export_to_encrypted_data(passport_json, ptr0, len0);
if (ret[3]) {
throw takeFromExternrefTable0(ret[2]);
}
var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
return v2;
}
/**
* Sign a message with the passport's private key
* @param {any} passport_json
* @param {string} message
* @returns {Uint8Array}
*/
export function sign_message(passport_json, message) {
const ptr0 = passStringToWasm0(message, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.sign_message(passport_json, ptr0, len0);
if (ret[3]) {
throw takeFromExternrefTable0(ret[2]);
}
var v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
return v2;
}
/**
* Generate a new recovery phrase
* @returns {any}
*/
export function generate_recovery_phrase() {
const ret = wasm.generate_recovery_phrase();
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/**
* Validate a recovery phrase
* @param {string[]} recovery_words
* @returns {boolean}
*/
export function validate_recovery_phrase(recovery_words) {
const ptr0 = passArrayJsValueToWasm0(recovery_words, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.validate_recovery_phrase(ptr0, len0);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return ret[0] !== 0;
}
/**
* Create a new user profile for a passport
* Returns the updated passport as JSON
* @param {any} passport_json
* @param {string | null | undefined} hub_did
* @param {any} identity_json
* @param {any} preferences_json
* @returns {any}
*/
export function create_user_profile(passport_json, hub_did, identity_json, preferences_json) {
var ptr0 = isLikeNone(hub_did) ? 0 : passStringToWasm0(hub_did, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
const ret = wasm.create_user_profile(passport_json, ptr0, len0, identity_json, preferences_json);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/**
* Update an existing user profile
* Returns the updated passport as JSON
* @param {any} passport_json
* @param {string} profile_id
* @param {any} identity_json
* @param {any} preferences_json
* @returns {any}
*/
export function update_user_profile(passport_json, profile_id, identity_json, preferences_json) {
const ptr0 = passStringToWasm0(profile_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.update_user_profile(passport_json, ptr0, len0, identity_json, preferences_json);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/**
* Delete a user profile
* Returns the updated passport as JSON
* @param {any} passport_json
* @param {string} profile_id
* @returns {any}
*/
export function delete_user_profile(passport_json, profile_id) {
const ptr0 = passStringToWasm0(profile_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.delete_user_profile(passport_json, ptr0, len0);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/**
* Change passport password
* Returns the updated passport as JSON
* @param {any} _passport_json
* @param {string} _old_password
* @param {string} _new_password
* @returns {any}
*/
export function change_passport_password(_passport_json, _old_password, _new_password) {
const ptr0 = passStringToWasm0(_old_password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(_new_password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.change_passport_password(_passport_json, ptr0, len0, ptr1, len1);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/**
* Get passport metadata from encrypted data
* This can extract public metadata without full decryption
* @param {Uint8Array} encrypted_data
* @returns {any}
*/
export function get_passport_metadata(encrypted_data) {
const ptr0 = passArray8ToWasm0(encrypted_data, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.get_passport_metadata(ptr0, len0);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/**
* Validate passport file integrity from encrypted data
* @param {Uint8Array} encrypted_data
* @returns {boolean}
*/
export function validate_passport_file(encrypted_data) {
const ptr0 = passArray8ToWasm0(encrypted_data, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.validate_passport_file(ptr0, len0);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return ret[0] !== 0;
}
const EXPECTED_RESPONSE_TYPES = new Set(['basic', 'cors', 'default']);
async function __wbg_load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
const validResponse = module.ok && EXPECTED_RESPONSE_TYPES.has(module.type);
if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
function __wbg_get_imports() {
const imports = {};
imports.wbg = {};
imports.wbg.__wbg_Error_e83987f665cf5504 = function(arg0, arg1) {
const ret = Error(getStringFromWasm0(arg0, arg1));
return ret;
};
imports.wbg.__wbg_Number_bb48ca12f395cd08 = function(arg0) {
const ret = Number(arg0);
return ret;
};
imports.wbg.__wbg_String_8f0eb39a4a4c2f66 = function(arg0, arg1) {
const ret = String(arg1);
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
};
imports.wbg.__wbg___wbindgen_bigint_get_as_i64_f3ebc5a755000afd = function(arg0, arg1) {
const v = arg1;
const ret = typeof(v) === 'bigint' ? v : undefined;
getDataViewMemory0().setBigInt64(arg0 + 8 * 1, isLikeNone(ret) ? BigInt(0) : ret, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
};
imports.wbg.__wbg___wbindgen_boolean_get_6d5a1ee65bab5f68 = function(arg0) {
const v = arg0;
const ret = typeof(v) === 'boolean' ? v : undefined;
return isLikeNone(ret) ? 0xFFFFFF : ret ? 1 : 0;
};
imports.wbg.__wbg___wbindgen_debug_string_df47ffb5e35e6763 = function(arg0, arg1) {
const ret = debugString(arg1);
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
};
imports.wbg.__wbg___wbindgen_in_bb933bd9e1b3bc0f = function(arg0, arg1) {
const ret = arg0 in arg1;
return ret;
};
imports.wbg.__wbg___wbindgen_is_bigint_cb320707dcd35f0b = function(arg0) {
const ret = typeof(arg0) === 'bigint';
return ret;
};
imports.wbg.__wbg___wbindgen_is_function_ee8a6c5833c90377 = function(arg0) {
const ret = typeof(arg0) === 'function';
return ret;
};
imports.wbg.__wbg___wbindgen_is_object_c818261d21f283a4 = function(arg0) {
const val = arg0;
const ret = typeof(val) === 'object' && val !== null;
return ret;
};
imports.wbg.__wbg___wbindgen_is_string_fbb76cb2940daafd = function(arg0) {
const ret = typeof(arg0) === 'string';
return ret;
};
imports.wbg.__wbg___wbindgen_is_undefined_2d472862bd29a478 = function(arg0) {
const ret = arg0 === undefined;
return ret;
};
imports.wbg.__wbg___wbindgen_jsval_eq_6b13ab83478b1c50 = function(arg0, arg1) {
const ret = arg0 === arg1;
return ret;
};
imports.wbg.__wbg___wbindgen_jsval_loose_eq_b664b38a2f582147 = function(arg0, arg1) {
const ret = arg0 == arg1;
return ret;
};
imports.wbg.__wbg___wbindgen_number_get_a20bf9b85341449d = function(arg0, arg1) {
const obj = arg1;
const ret = typeof(obj) === 'number' ? obj : undefined;
getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
};
imports.wbg.__wbg___wbindgen_string_get_e4f06c90489ad01b = function(arg0, arg1) {
const obj = arg1;
const ret = typeof(obj) === 'string' ? obj : undefined;
var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len1 = WASM_VECTOR_LEN;
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
};
imports.wbg.__wbg___wbindgen_throw_b855445ff6a94295 = function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbg_call_525440f72fbfc0ea = function() { return handleError(function (arg0, arg1, arg2) {
const ret = arg0.call(arg1, arg2);
return ret;
}, arguments) };
imports.wbg.__wbg_call_e762c39fa8ea36bf = function() { return handleError(function (arg0, arg1) {
const ret = arg0.call(arg1);
return ret;
}, arguments) };
imports.wbg.__wbg_crypto_574e78ad8b13b65f = function(arg0) {
const ret = arg0.crypto;
return ret;
};
imports.wbg.__wbg_done_2042aa2670fb1db1 = function(arg0) {
const ret = arg0.done;
return ret;
};
imports.wbg.__wbg_getRandomValues_38a1ff1ea09f6cc7 = function() { return handleError(function (arg0, arg1) {
globalThis.crypto.getRandomValues(getArrayU8FromWasm0(arg0, arg1));
}, arguments) };
imports.wbg.__wbg_getRandomValues_b8f5dbd5f3995a9e = function() { return handleError(function (arg0, arg1) {
arg0.getRandomValues(arg1);
}, arguments) };
imports.wbg.__wbg_get_7bed016f185add81 = function(arg0, arg1) {
const ret = arg0[arg1 >>> 0];
return ret;
};
imports.wbg.__wbg_get_efcb449f58ec27c2 = function() { return handleError(function (arg0, arg1) {
const ret = Reflect.get(arg0, arg1);
return ret;
}, arguments) };
imports.wbg.__wbg_get_with_ref_key_1dc361bd10053bfe = function(arg0, arg1) {
const ret = arg0[arg1];
return ret;
};
imports.wbg.__wbg_instanceof_ArrayBuffer_70beb1189ca63b38 = function(arg0) {
let result;
try {
result = arg0 instanceof ArrayBuffer;
} catch (_) {
result = false;
}
const ret = result;
return ret;
};
imports.wbg.__wbg_instanceof_Uint8Array_20c8e73002f7af98 = function(arg0) {
let result;
try {
result = arg0 instanceof Uint8Array;
} catch (_) {
result = false;
}
const ret = result;
return ret;
};
imports.wbg.__wbg_isArray_96e0af9891d0945d = function(arg0) {
const ret = Array.isArray(arg0);
return ret;
};
imports.wbg.__wbg_isSafeInteger_d216eda7911dde36 = function(arg0) {
const ret = Number.isSafeInteger(arg0);
return ret;
};
imports.wbg.__wbg_iterator_e5822695327a3c39 = function() {
const ret = Symbol.iterator;
return ret;
};
imports.wbg.__wbg_length_69bca3cb64fc8748 = function(arg0) {
const ret = arg0.length;
return ret;
};
imports.wbg.__wbg_length_cdd215e10d9dd507 = function(arg0) {
const ret = arg0.length;
return ret;
};
imports.wbg.__wbg_msCrypto_a61aeb35a24c1329 = function(arg0) {
const ret = arg0.msCrypto;
return ret;
};
imports.wbg.__wbg_new_1acc0b6eea89d040 = function() {
const ret = new Object();
return ret;
};
imports.wbg.__wbg_new_5a79be3ab53b8aa5 = function(arg0) {
const ret = new Uint8Array(arg0);
return ret;
};
imports.wbg.__wbg_new_68651c719dcda04e = function() {
const ret = new Map();
return ret;
};
imports.wbg.__wbg_new_e17d9f43105b08be = function() {
const ret = new Array();
return ret;
};
imports.wbg.__wbg_new_no_args_ee98eee5275000a4 = function(arg0, arg1) {
const ret = new Function(getStringFromWasm0(arg0, arg1));
return ret;
};
imports.wbg.__wbg_new_with_length_01aa0dc35aa13543 = function(arg0) {
const ret = new Uint8Array(arg0 >>> 0);
return ret;
};
imports.wbg.__wbg_next_020810e0ae8ebcb0 = function() { return handleError(function (arg0) {
const ret = arg0.next();
return ret;
}, arguments) };
imports.wbg.__wbg_next_2c826fe5dfec6b6a = function(arg0) {
const ret = arg0.next;
return ret;
};
imports.wbg.__wbg_node_905d3e251edff8a2 = function(arg0) {
const ret = arg0.node;
return ret;
};
imports.wbg.__wbg_now_793306c526e2e3b6 = function() {
const ret = Date.now();
return ret;
};
imports.wbg.__wbg_now_98430d19d580dbab = function() { return handleError(function () {
const ret = Date.now();
return ret;
}, arguments) };
imports.wbg.__wbg_process_dc0fbacc7c1c06f7 = function(arg0) {
const ret = arg0.process;
return ret;
};
imports.wbg.__wbg_prototypesetcall_2a6620b6922694b2 = function(arg0, arg1, arg2) {
Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
};
imports.wbg.__wbg_randomFillSync_ac0988aba3254290 = function() { return handleError(function (arg0, arg1) {
arg0.randomFillSync(arg1);
}, arguments) };
imports.wbg.__wbg_require_60cc747a6bc5215a = function() { return handleError(function () {
const ret = module.require;
return ret;
}, arguments) };
imports.wbg.__wbg_set_3f1d0b984ed272ed = function(arg0, arg1, arg2) {
arg0[arg1] = arg2;
};
imports.wbg.__wbg_set_907fb406c34a251d = function(arg0, arg1, arg2) {
const ret = arg0.set(arg1, arg2);
return ret;
};
imports.wbg.__wbg_set_c213c871859d6500 = function(arg0, arg1, arg2) {
arg0[arg1 >>> 0] = arg2;
};
imports.wbg.__wbg_static_accessor_GLOBAL_89e1d9ac6a1b250e = function() {
const ret = typeof global === 'undefined' ? null : global;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
};
imports.wbg.__wbg_static_accessor_GLOBAL_THIS_8b530f326a9e48ac = function() {
const ret = typeof globalThis === 'undefined' ? null : globalThis;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
};
imports.wbg.__wbg_static_accessor_SELF_6fdf4b64710cc91b = function() {
const ret = typeof self === 'undefined' ? null : self;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
};
imports.wbg.__wbg_static_accessor_WINDOW_b45bfc5a37f6cfa2 = function() {
const ret = typeof window === 'undefined' ? null : window;
return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
};
imports.wbg.__wbg_subarray_480600f3d6a9f26c = function(arg0, arg1, arg2) {
const ret = arg0.subarray(arg1 >>> 0, arg2 >>> 0);
return ret;
};
imports.wbg.__wbg_value_692627309814bb8c = function(arg0) {
const ret = arg0.value;
return ret;
};
imports.wbg.__wbg_versions_c01dfd4722a88165 = function(arg0) {
const ret = arg0.versions;
return ret;
};
imports.wbg.__wbindgen_cast_2241b6af4c4b2941 = function(arg0, arg1) {
// Cast intrinsic for `Ref(String) -> Externref`.
const ret = getStringFromWasm0(arg0, arg1);
return ret;
};
imports.wbg.__wbindgen_cast_4625c577ab2ec9ee = function(arg0) {
// Cast intrinsic for `U64 -> Externref`.
const ret = BigInt.asUintN(64, arg0);
return ret;
};
imports.wbg.__wbindgen_cast_9ae0607507abb057 = function(arg0) {
// Cast intrinsic for `I64 -> Externref`.
const ret = arg0;
return ret;
};
imports.wbg.__wbindgen_cast_cb9088102bce6b30 = function(arg0, arg1) {
// Cast intrinsic for `Ref(Slice(U8)) -> NamedExternref("Uint8Array")`.
const ret = getArrayU8FromWasm0(arg0, arg1);
return ret;
};
imports.wbg.__wbindgen_cast_d6cd19b81560fd6e = function(arg0) {
// Cast intrinsic for `F64 -> Externref`.
const ret = arg0;
return ret;
};
imports.wbg.__wbindgen_init_externref_table = function() {
const table = wasm.__wbindgen_externrefs;
const offset = table.grow(4);
table.set(0, undefined);
table.set(offset + 0, undefined);
table.set(offset + 1, null);
table.set(offset + 2, true);
table.set(offset + 3, false);
;
};
return imports;
}
function __wbg_finalize_init(instance, module) {
wasm = instance.exports;
__wbg_init.__wbindgen_wasm_module = module;
cachedDataViewMemory0 = null;
cachedUint8ArrayMemory0 = null;
wasm.__wbindgen_start();
return wasm;
}
function initSync(module) {
if (wasm !== undefined) return wasm;
if (typeof module !== 'undefined') {
if (Object.getPrototypeOf(module) === Object.prototype) {
({module} = module)
} else {
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
}
}
const imports = __wbg_get_imports();
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
}
const instance = new WebAssembly.Instance(module, imports);
return __wbg_finalize_init(instance, module);
}
async function __wbg_init(module_or_path) {
if (wasm !== undefined) return wasm;
if (typeof module_or_path !== 'undefined') {
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
({module_or_path} = module_or_path)
} else {
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
}
}
if (typeof module_or_path === 'undefined') {
module_or_path = new URL('sharenet_passport_bg.wasm', import.meta.url);
}
const imports = __wbg_get_imports();
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
module_or_path = fetch(module_or_path);
}
const { instance, module } = await __wbg_load(await module_or_path, imports);
return __wbg_finalize_init(instance, module);
}
export { initSync };
export default __wbg_init;

Binary file not shown.

24
pkg/sharenet_passport_bg.wasm.d.ts vendored Normal file
View file

@ -0,0 +1,24 @@
/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export const create_passport: (a: number, b: number, c: number, d: number) => [number, number, number];
export const import_from_recovery: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
export const import_from_encrypted_data: (a: number, b: number, c: number, d: number) => [number, number, number];
export const export_to_encrypted_data: (a: any, b: number, c: number) => [number, number, number, number];
export const sign_message: (a: any, b: number, c: number) => [number, number, number, number];
export const generate_recovery_phrase: () => [number, number, number];
export const validate_recovery_phrase: (a: number, b: number) => [number, number, number];
export const create_user_profile: (a: any, b: number, c: number, d: any, e: any) => [number, number, number];
export const update_user_profile: (a: any, b: number, c: number, d: any, e: any) => [number, number, number];
export const delete_user_profile: (a: any, b: number, c: number) => [number, number, number];
export const change_passport_password: (a: any, b: number, c: number, d: number, e: number) => [number, number, number];
export const get_passport_metadata: (a: number, b: number) => [number, number, number];
export const validate_passport_file: (a: number, b: number) => [number, number, number];
export const __wbindgen_malloc: (a: number, b: number) => number;
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
export const __wbindgen_exn_store: (a: number) => void;
export const __externref_table_alloc: () => number;
export const __wbindgen_externrefs: WebAssembly.Table;
export const __externref_table_dealloc: (a: number) => void;
export const __wbindgen_free: (a: number, b: number, c: number) => void;
export const __wbindgen_start: () => void;

View file

@ -65,6 +65,26 @@ pub enum Commands {
file: String,
},
/// Display complete decrypted Passport data
Show {
/// .spf file path
file: String,
},
/// Edit global Passport fields
Edit {
/// .spf file path
file: String,
/// Date of birth (format: MM-DD-YYYY)
#[arg(long, conflicts_with = "remove_date_of_birth")]
date_of_birth: Option<String>,
/// Remove date of birth
#[arg(long, conflicts_with = "date_of_birth")]
remove_date_of_birth: bool,
},
/// Sign a message (for testing)
Sign {
/// .spf file path
@ -95,7 +115,7 @@ pub enum ProfileCommands {
file: String,
/// Hub DID (optional, omit for default profile)
#[arg(short, long)]
#[arg(long)]
hub_did: Option<String>,
/// Handle
@ -149,8 +169,12 @@ pub enum ProfileCommands {
file: String,
/// Profile ID (required, use 'list' command to see available IDs)
#[arg(short, long)]
id: String,
#[arg(short, long, conflicts_with = "default")]
id: Option<String>,
/// Update the default user profile
#[arg(long, conflicts_with = "id")]
default: bool,
/// Hub DID (optional, can be updated)
#[arg(long)]
@ -199,6 +223,10 @@ pub enum ProfileCommands {
/// Enable auto-sync
#[arg(long)]
auto_sync: Option<bool>,
/// Show date of birth
#[arg(long)]
show_date_of_birth: Option<bool>,
},
/// Delete a user profile

View file

@ -29,6 +29,13 @@ impl CliInterface {
}
pub fn handle_create(&self, universe: &str, output: &str) -> Result<(), ApplicationError> {
// Validate universe ID format
if !universe.starts_with("u:") {
return Err(ApplicationError::UseCaseError(
"Invalid universe ID format. Must start with 'u:'".to_string()
));
}
let password = prompt_password("Enter password for new passport: ")
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?;
let confirm_password = prompt_password("Confirm password: ")
@ -64,7 +71,22 @@ impl CliInterface {
for i in 1..=24 {
let word = prompt_password(&format!("Word {}: ", i))
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to read recovery word: {}", e)))?;
recovery_words.push(word);
// Validate recovery word is not empty
if word.trim().is_empty() {
return Err(ApplicationError::UseCaseError(
format!("Recovery word {} cannot be empty", i)
));
}
recovery_words.push(word.trim().to_lowercase());
}
// Validate that all words are non-empty
if recovery_words.iter().any(|word| word.is_empty()) {
return Err(ApplicationError::UseCaseError(
"Recovery phrase contains empty words".to_string()
));
}
let password = prompt_password("Enter new password for passport file: ")
@ -165,6 +187,187 @@ impl CliInterface {
Ok(())
}
pub fn handle_show(&self, file: &str) -> Result<(), ApplicationError> {
let password = prompt_password("Enter password for passport file: ")
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?;
let import_use_case = ImportFromFileUseCase::new(
XChaCha20FileEncryptor,
FileSystemStorage,
);
let passport = import_use_case.execute(file, &password, None)?;
println!("🔓 Complete Decrypted Passport Data:");
println!(" File: {}", file);
println!(" Universe ID: {}", passport.univ_id());
println!(" DID: {}", passport.did().as_str());
println!(" Public Key: {}", hex::encode(&passport.public_key.0));
println!(" Private Key: {} (⚠️ SENSITIVE - DO NOT SHARE)", hex::encode(&passport.private_key.0));
println!(" Seed: {} (⚠️ SENSITIVE - DO NOT SHARE)", hex::encode(passport.seed.as_bytes()));
if let Some(date_of_birth) = &passport.date_of_birth {
println!(" Date of Birth: {}-{}-{}", date_of_birth.month, date_of_birth.day, date_of_birth.year);
} else {
println!(" Date of Birth: Not set");
}
if let Some(default_profile_id) = &passport.default_user_profile_id {
println!(" Default User Profile ID: {}", default_profile_id);
} else {
println!(" Default User Profile ID: Not set");
}
println!("\n👤 User Profiles ({} total):", passport.user_profiles().len());
for (i, profile) in passport.user_profiles().iter().enumerate() {
println!("\n {}. Profile ID: {}", i + 1, profile.id);
println!(" Profile Type: {}", if profile.is_default() { "Default" } else { "Hub-specific" });
if let Some(hub_did) = &profile.hub_did {
println!(" Hub DID: {}", hub_did);
}
println!(" Created: {}", profile.created_at);
println!(" Updated: {}", profile.updated_at);
println!(" Identity:");
if let Some(handle) = &profile.identity.handle {
println!(" Handle: {}", handle);
}
if let Some(name) = &profile.identity.display_name {
println!(" Display Name: {}", name);
}
if let Some(first_name) = &profile.identity.first_name {
println!(" First Name: {}", first_name);
}
if let Some(last_name) = &profile.identity.last_name {
println!(" Last Name: {}", last_name);
}
if let Some(email) = &profile.identity.email {
println!(" Email: {}", email);
}
if let Some(avatar) = &profile.identity.avatar_url {
println!(" Avatar URL: {}", avatar);
}
if let Some(bio) = &profile.identity.bio {
println!(" Bio: {}", bio);
}
println!(" Preferences:");
if let Some(theme) = &profile.preferences.theme {
println!(" Theme: {}", theme);
}
if let Some(language) = &profile.preferences.language {
println!(" Language: {}", language);
}
println!(" Notifications: {}", if profile.preferences.notifications_enabled { "Enabled" } else { "Disabled" });
println!(" Auto-sync: {}", if profile.preferences.auto_sync { "Enabled" } else { "Disabled" });
println!(" Show Date of Birth: {}", if profile.preferences.show_date_of_birth { "Yes" } else { "No" });
}
println!("\n⚠️ SECURITY WARNING:");
println!(" - Private key and seed are sensitive cryptographic material");
println!(" - Never share these values with anyone");
println!(" - Keep this information secure and confidential");
Ok(())
}
pub fn handle_edit(
&self,
file: &str,
date_of_birth: Option<String>,
remove_date_of_birth: bool,
) -> Result<(), ApplicationError> {
let password = prompt_password("Enter password for passport file: ")
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?;
let import_use_case = ImportFromFileUseCase::new(
XChaCha20FileEncryptor,
FileSystemStorage,
);
let mut passport = import_use_case.execute(file, &password, None)?;
let mut changes_made = false;
// Handle date of birth changes
if remove_date_of_birth {
passport.date_of_birth = None;
changes_made = true;
println!("🗑️ Date of birth removed");
} else if let Some(dob_str) = date_of_birth {
// Parse date of birth string (format: MM-DD-YYYY)
let parts: Vec<&str> = dob_str.split('-').collect();
if parts.len() != 3 {
return Err(ApplicationError::UseCaseError(
"Invalid date format. Use MM-DD-YYYY".to_string()
));
}
let month = parts[0].parse::<u8>()
.map_err(|_| ApplicationError::UseCaseError("Invalid month".to_string()))?;
let day = parts[1].parse::<u8>()
.map_err(|_| ApplicationError::UseCaseError("Invalid day".to_string()))?;
let year = parts[2].parse::<u16>()
.map_err(|_| ApplicationError::UseCaseError("Invalid year".to_string()))?;
// Basic validation
if month < 1 || month > 12 {
return Err(ApplicationError::UseCaseError("Month must be between 1 and 12".to_string()));
}
if day < 1 || day > 31 {
return Err(ApplicationError::UseCaseError("Day must be between 1 and 31".to_string()));
}
if year < 1900 || year > 2100 {
return Err(ApplicationError::UseCaseError("Year must be between 1900 and 2100".to_string()));
}
// Comprehensive date validation
let max_days = match month {
2 => {
// February - check for leap year
let is_leap_year = (year % 4 == 0) && (year % 100 != 0 || year % 400 == 0);
if is_leap_year { 29 } else { 28 }
}
4 | 6 | 9 | 11 => 30, // April, June, September, November
_ => 31, // January, March, May, July, August, October, December
};
if day > max_days {
return Err(ApplicationError::UseCaseError(
format!("Invalid day {} for month {}. Maximum days for this month is {}", day, month, max_days)
));
}
let new_dob = sharenet_passport::domain::entities::DateOfBirth {
month,
day,
year,
};
passport.date_of_birth = Some(new_dob);
changes_made = true;
println!("📅 Date of birth set to: {}-{}-{}", month, day, year);
}
if !changes_made {
println!(" No changes specified. Use --date-of-birth or --remove-date-of-birth");
return Ok(());
}
// Save the updated passport
let export_use_case = ExportPassportUseCase::new(
XChaCha20FileEncryptor,
FileSystemStorage,
);
export_use_case.execute(&passport, &password, file)?;
println!("✅ Passport updated successfully!");
println!("📄 Saved to: {}", file);
Ok(())
}
pub fn handle_sign(&self, file: &str, message: &str) -> Result<(), ApplicationError> {
let password = prompt_password("Enter password for passport file: ")
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?;
@ -239,6 +442,7 @@ impl CliInterface {
}
println!(" Notifications: {}", if profile.preferences.notifications_enabled { "Enabled" } else { "Disabled" });
println!(" Auto-sync: {}", if profile.preferences.auto_sync { "Enabled" } else { "Disabled" });
println!(" Show Date of Birth: {}", if profile.preferences.show_date_of_birth { "Yes" } else { "No" });
}
Ok(())
@ -285,6 +489,7 @@ impl CliInterface {
language,
notifications_enabled: notifications,
auto_sync,
show_date_of_birth: false,
};
let create_use_case = CreateUserProfileUseCase::new(
@ -314,7 +519,8 @@ impl CliInterface {
pub fn handle_profile_update(
&self,
file: &str,
id: &str,
id: Option<&str>,
default: bool,
hub_did: Option<String>,
handle: Option<String>,
display_name: Option<String>,
@ -327,6 +533,7 @@ impl CliInterface {
language: Option<String>,
notifications: Option<bool>,
auto_sync: Option<bool>,
show_date_of_birth: Option<bool>,
) -> Result<(), ApplicationError> {
let password = prompt_password("Enter password for passport file: ")
.map_err(|e| ApplicationError::UseCaseError(format!("Failed to read password: {}", e)))?;
@ -338,8 +545,23 @@ impl CliInterface {
let mut passport = import_use_case.execute(file, &password, None)?;
// Determine which profile to update and get profile ID
let profile_id = if default {
// Update the default profile
let default_profile = passport.default_user_profile()
.ok_or_else(|| ApplicationError::UseCaseError("Default user profile not found".to_string()))?;
Some(default_profile.id.clone())
} else if let Some(id) = id {
// Update specific profile by ID
Some(id.to_string())
} else {
return Err(ApplicationError::UseCaseError(
"Either --id or --default must be specified".to_string()
));
};
// Get existing profile by ID
let existing_profile = passport.user_profile_by_id(id)
let existing_profile = passport.user_profile_by_id(&profile_id.clone().unwrap())
.ok_or_else(|| ApplicationError::UseCaseError("User profile not found".to_string()))?;
let identity = UserIdentity {
@ -357,12 +579,14 @@ impl CliInterface {
language: language.or_else(|| existing_profile.preferences.language.clone()),
notifications_enabled: notifications.unwrap_or(existing_profile.preferences.notifications_enabled),
auto_sync: auto_sync.unwrap_or(existing_profile.preferences.auto_sync),
show_date_of_birth: show_date_of_birth.unwrap_or(existing_profile.preferences.show_date_of_birth),
};
// Clone values before using them in multiple places
let identity_clone = identity.clone();
let preferences_clone = preferences.clone();
let hub_did_clone = hub_did.clone();
let hub_did_for_use_case = hub_did.clone();
// Create updated profile with new hub_did if provided
let now = std::time::SystemTime::now()
@ -387,7 +611,8 @@ impl CliInterface {
update_use_case.execute(
&mut passport,
Some(id),
profile_id.as_deref(),
hub_did_for_use_case,
identity_clone,
preferences_clone,
&password,

View file

@ -1,2 +1,5 @@
pub mod commands;
pub mod interface;
#[cfg(test)]
pub mod tests;

File diff suppressed because it is too large Load diff

View file

@ -28,6 +28,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Commands::Info { file } => {
interface.handle_info(&file)?;
}
Commands::Show { file } => {
interface.handle_show(&file)?;
}
Commands::Edit { file, date_of_birth, remove_date_of_birth } => {
interface.handle_edit(&file, date_of_birth, remove_date_of_birth)?;
}
Commands::Sign { file, message } => {
interface.handle_sign(&file, &message)?;
}
@ -70,6 +76,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
crate::cli::commands::ProfileCommands::Update {
file,
id,
default,
hub_did,
handle,
display_name,
@ -82,10 +89,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
language,
notifications,
auto_sync,
show_date_of_birth,
} => {
interface.handle_profile_update(
&file,
&id,
id.as_deref(),
default,
hub_did,
handle,
display_name,
@ -98,6 +107,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
language,
notifications,
auto_sync,
show_date_of_birth,
)?;
}
crate::cli::commands::ProfileCommands::Delete { file, id } => {