feature/7-add-wasm-api-in-sharenet-passport-for-creating-and-editing-passports #8
27 changed files with 5829 additions and 158 deletions
172
.forgejo/workflows/ci.yml
Normal file
172
.forgejo/workflows/ci.yml
Normal 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
151
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,2 +1,5 @@
|
|||
pub mod use_cases;
|
||||
pub mod error;
|
||||
pub mod error;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod use_cases_test;
|
||||
|
|
@ -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())))?;
|
||||
|
||||
|
|
|
|||
533
libs/sharenet-passport/src/application/use_cases_test.rs
Normal file
533
libs/sharenet-passport/src/application/use_cases_test.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
346
libs/sharenet-passport/src/wasm.rs
Normal file
346
libs/sharenet-passport/src/wasm.rs
Normal 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
113
pkg/sharenet_passport.d.ts
vendored
Normal 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
832
pkg/sharenet_passport.js
Normal 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;
|
||||
BIN
pkg/sharenet_passport_bg.wasm
Normal file
BIN
pkg/sharenet_passport_bg.wasm
Normal file
Binary file not shown.
24
pkg/sharenet_passport_bg.wasm.d.ts
vendored
Normal file
24
pkg/sharenet_passport_bg.wasm.d.ts
vendored
Normal 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;
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,2 +1,5 @@
|
|||
pub mod commands;
|
||||
pub mod interface;
|
||||
pub mod interface;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
3310
sharenet-passport-cli/src/cli/tests.rs
Normal file
3310
sharenet-passport-cli/src/cli/tests.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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 } => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue