diff --git a/Cargo.lock b/Cargo.lock
index 65b7b0b..a712105 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -135,6 +135,20 @@ dependencies = [
"critical-section",
]
+[[package]]
+name = "authenticator"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "base64 0.22.1",
+ "clap",
+ "pubky",
+ "pubky-common",
+ "rpassword",
+ "tokio",
+ "url",
+]
+
[[package]]
name = "autocfg"
version = "1.3.0"
@@ -336,9 +350,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
-version = "1.6.1"
+version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
+checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
[[package]]
name = "cc"
@@ -365,9 +379,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.11"
+version = "4.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3"
+checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
dependencies = [
"clap_builder",
"clap_derive",
@@ -375,9 +389,9 @@ dependencies = [
[[package]]
name = "clap_builder"
-version = "4.5.11"
+version = "4.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa"
+checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
dependencies = [
"anstream",
"anstyle",
@@ -387,9 +401,9 @@ dependencies = [
[[package]]
name = "clap_derive"
-version = "4.5.11"
+version = "4.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e"
+checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -635,6 +649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
dependencies = [
"pkcs8",
+ "serde",
"signature",
]
@@ -998,6 +1013,24 @@ dependencies = [
"want",
]
+[[package]]
+name = "hyper-rustls"
+version = "0.27.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155"
+dependencies = [
+ "futures-util",
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+ "webpki-roots",
+]
+
[[package]]
name = "hyper-util"
version = "0.1.6"
@@ -1077,9 +1110,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
-version = "0.3.69"
+version = "0.3.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
dependencies = [
"wasm-bindgen",
]
@@ -1203,13 +1236,14 @@ dependencies = [
[[package]]
name = "mio"
-version = "0.8.11"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
+checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
dependencies = [
+ "hermit-abi",
"libc",
"wasi",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
@@ -1237,16 +1271,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
-[[package]]
-name = "num_cpus"
-version = "1.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
-dependencies = [
- "hermit-abi",
- "libc",
-]
-
[[package]]
name = "object"
version = "0.36.1"
@@ -1400,10 +1424,10 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkarr"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4548c673cbf8c91b69f7a17d3a042710aa73cffe5e82351db5378f26c3be64d8"
+version = "2.2.0"
+source = "git+https://github.com/Pubky/pkarr?branch=v3#17975121c809d97dcad907fbb2ffc782e994d270"
dependencies = [
+ "base32",
"bytes",
"document-features",
"dyn-clone",
@@ -1415,13 +1439,13 @@ dependencies = [
"mainline",
"rand",
"self_cell",
+ "serde",
"simple-dns",
"thiserror",
"tracing",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
- "z32",
]
[[package]]
@@ -1488,7 +1512,7 @@ checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
name = "pubky"
version = "0.1.0"
dependencies = [
- "argon2",
+ "base64 0.22.1",
"bytes",
"js-sys",
"pkarr",
@@ -1506,6 +1530,7 @@ dependencies = [
name = "pubky-common"
version = "0.1.0"
dependencies = [
+ "argon2",
"base32",
"blake3",
"crypto_secretbox",
@@ -1556,6 +1581,54 @@ dependencies = [
"psl-types",
]
+[[package]]
+name = "quinn"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156"
+dependencies = [
+ "bytes",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2",
+ "thiserror",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd"
+dependencies = [
+ "bytes",
+ "rand",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "slab",
+ "thiserror",
+ "tinyvec",
+ "tracing",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285"
+dependencies = [
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
[[package]]
name = "quote"
version = "1.0.36"
@@ -1675,6 +1748,7 @@ dependencies = [
"http-body",
"http-body-util",
"hyper",
+ "hyper-rustls",
"hyper-util",
"ipnet",
"js-sys",
@@ -1683,25 +1757,73 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
+ "quinn",
+ "rustls",
+ "rustls-pemfile",
+ "rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper 1.0.1",
"tokio",
+ "tokio-rustls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
+ "webpki-roots",
"winreg",
]
+[[package]]
+name = "ring"
+version = "0.17.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rpassword"
+version = "7.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f"
+dependencies = [
+ "libc",
+ "rtoolbox",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rtoolbox"
+version = "0.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+[[package]]
+name = "rustc-hash"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
+
[[package]]
name = "rustc_version"
version = "0.4.0"
@@ -1711,6 +1833,47 @@ dependencies = [
"semver",
]
+[[package]]
+name = "rustls"
+version = "0.23.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
+dependencies = [
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425"
+dependencies = [
+ "base64 0.22.1",
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
[[package]]
name = "rustversion"
version = "1.0.17"
@@ -1752,9 +1915,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
-version = "1.0.204"
+version = "1.0.209"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
+checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
dependencies = [
"serde_derive",
]
@@ -1780,9 +1943,9 @@ dependencies = [
[[package]]
name = "serde_derive"
-version = "1.0.204"
+version = "1.0.209"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
+checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
dependencies = [
"proc-macro2",
"quote",
@@ -2073,34 +2236,44 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.38.0"
+version = "1.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
+checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
- "num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
- "windows-sys 0.48.0",
+ "windows-sys 0.52.0",
]
[[package]]
name = "tokio-macros"
-version = "2.3.0"
+version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
+checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+[[package]]
+name = "tokio-rustls"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
+dependencies = [
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+]
+
[[package]]
name = "tokio-util"
version = "0.7.11"
@@ -2315,6 +2488,12 @@ dependencies = [
"subtle",
]
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
[[package]]
name = "url"
version = "2.5.2"
@@ -2361,19 +2540,20 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
-version = "0.2.92"
+version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
dependencies = [
"cfg-if",
+ "once_cell",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.92"
+version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
dependencies = [
"bumpalo",
"log",
@@ -2398,9 +2578,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.92"
+version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -2408,9 +2588,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.92"
+version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
dependencies = [
"proc-macro2",
"quote",
@@ -2421,20 +2601,29 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.92"
+version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
[[package]]
name = "web-sys"
-version = "0.3.69"
+version = "0.3.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
+checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
dependencies = [
"js-sys",
"wasm-bindgen",
]
+[[package]]
+name = "webpki-roots"
+version = "0.26.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
+dependencies = [
+ "rustls-pki-types",
+]
+
[[package]]
name = "winapi"
version = "0.3.9"
@@ -2615,12 +2804,6 @@ dependencies = [
"windows-sys 0.48.0",
]
-[[package]]
-name = "z32"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edb37266251c28b03d08162174a91c3a092e3bd4f476f8205ee1c507b78b7bdc"
-
[[package]]
name = "zeroize"
version = "1.8.1"
diff --git a/Cargo.toml b/Cargo.toml
index 9e2e527..8514809 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,9 +1,18 @@
[workspace]
-members = [ "pubky","pubky-*"]
+members = [
+ "pubky",
+ "pubky-*",
+
+ "examples/authz/authenticator"
+]
# See: https://github.com/rust-lang/rust/issues/90148#issuecomment-949194352
resolver = "2"
+[workspace.dependencies]
+pkarr = { git = "https://github.com/Pubky/pkarr", branch = "v3", package = "pkarr", features = ["async"] }
+serde = { version = "^1.0.209", features = ["derive"] }
+
[profile.release]
lto = true
opt-level = 'z'
diff --git a/examples/authz/3rd-party-app/.gitignore b/examples/authz/3rd-party-app/.gitignore
new file mode 100644
index 0000000..a547bf3
--- /dev/null
+++ b/examples/authz/3rd-party-app/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/examples/authz/3rd-party-app/index.html b/examples/authz/3rd-party-app/index.html
new file mode 100644
index 0000000..1249852
--- /dev/null
+++ b/examples/authz/3rd-party-app/index.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+ Pubky Auth Demo
+
+
+
+
+
+
+
+
+
+ Third Party app!
+ this is a demo for using Pubky Auth in an unhosted (no backend) app.
+
+
+
diff --git a/examples/authz/3rd-party-app/package-lock.json b/examples/authz/3rd-party-app/package-lock.json
new file mode 100644
index 0000000..98ba42f
--- /dev/null
+++ b/examples/authz/3rd-party-app/package-lock.json
@@ -0,0 +1,1146 @@
+{
+ "name": "pubky-auth-3rd-party",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "pubky-auth-3rd-party",
+ "version": "0.0.0",
+ "dependencies": {
+ "@synonymdev/pubky": "file:../../../pubky/pkg",
+ "lit": "^3.2.0",
+ "qrcode": "^1.5.4"
+ },
+ "devDependencies": {
+ "vite": "^5.4.2"
+ }
+ },
+ "../../../pubky/pkg": {
+ "name": "@synonymdev/pubky",
+ "version": "0.1.14",
+ "license": "MIT",
+ "devDependencies": {
+ "browser-resolve": "^2.0.0",
+ "esmify": "^2.1.1",
+ "tape": "^5.8.1",
+ "tape-run": "^11.0.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@lit-labs/ssr-dom-shim": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz",
+ "integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ=="
+ },
+ "node_modules/@lit/reactive-element": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
+ "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
+ "dependencies": {
+ "@lit-labs/ssr-dom-shim": "^1.2.0"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz",
+ "integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz",
+ "integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz",
+ "integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz",
+ "integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz",
+ "integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz",
+ "integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz",
+ "integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz",
+ "integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz",
+ "integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz",
+ "integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz",
+ "integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz",
+ "integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz",
+ "integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz",
+ "integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz",
+ "integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz",
+ "integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@synonymdev/pubky": {
+ "resolved": "../../../pubky/pkg",
+ "link": true
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+ "dev": true
+ },
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/dijkstrajs": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lit": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/lit/-/lit-3.2.0.tgz",
+ "integrity": "sha512-s6tI33Lf6VpDu7u4YqsSX78D28bYQulM+VAzsGch4fx2H0eLZnJsUBsPWmGYSGoKDNbjtRv02rio1o+UdPVwvw==",
+ "dependencies": {
+ "@lit/reactive-element": "^2.0.4",
+ "lit-element": "^4.1.0",
+ "lit-html": "^3.2.0"
+ }
+ },
+ "node_modules/lit-element": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.0.tgz",
+ "integrity": "sha512-gSejRUQJuMQjV2Z59KAS/D4iElUhwKpIyJvZ9w+DIagIQjfJnhR20h2Q5ddpzXGS+fF0tMZ/xEYGMnKmaI/iww==",
+ "dependencies": {
+ "@lit-labs/ssr-dom-shim": "^1.2.0",
+ "@lit/reactive-element": "^2.0.4",
+ "lit-html": "^3.2.0"
+ }
+ },
+ "node_modules/lit-html": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.0.tgz",
+ "integrity": "sha512-pwT/HwoxqI9FggTrYVarkBKFN9MlTUpLrDHubTmW4SrkL3kkqW5gxwbxMMUnbbRHBC0WTZnYHcjDSCM559VyfA==",
+ "dependencies": {
+ "@types/trusted-types": "^2.0.2"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
+ "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
+ "dev": true
+ },
+ "node_modules/pngjs": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.42",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.42.tgz",
+ "integrity": "sha512-hywKUQB9Ra4dR1mGhldy5Aj1X3MWDSIA1cEi+Uy0CjheLvP6Ual5RlwMCh8i/X121yEDLDIKBsrCQ8ba3FDMfQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.1",
+ "source-map-js": "^1.2.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/qrcode": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
+ "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
+ "dependencies": {
+ "dijkstrajs": "^1.0.1",
+ "pngjs": "^5.0.0",
+ "yargs": "^15.3.1"
+ },
+ "bin": {
+ "qrcode": "bin/qrcode"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
+ },
+ "node_modules/rollup": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz",
+ "integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "1.0.5"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.21.2",
+ "@rollup/rollup-android-arm64": "4.21.2",
+ "@rollup/rollup-darwin-arm64": "4.21.2",
+ "@rollup/rollup-darwin-x64": "4.21.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.21.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.21.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.21.2",
+ "@rollup/rollup-linux-arm64-musl": "4.21.2",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.21.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.21.2",
+ "@rollup/rollup-linux-x64-gnu": "4.21.2",
+ "@rollup/rollup-linux-x64-musl": "4.21.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.21.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.21.2",
+ "@rollup/rollup-win32-x64-msvc": "4.21.2",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz",
+ "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.41",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which-module": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
+ },
+ "node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
+ },
+ "node_modules/yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "dependencies": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ }
+ }
+}
diff --git a/examples/authz/3rd-party-app/package.json b/examples/authz/3rd-party-app/package.json
new file mode 100644
index 0000000..8ccad30
--- /dev/null
+++ b/examples/authz/3rd-party-app/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "pubky-auth-3rd-party",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "start": "npm run dev",
+ "dev": "vite --host --open",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@synonymdev/pubky": "file:../../../pubky/pkg",
+ "lit": "^3.2.0",
+ "qrcode": "^1.5.4"
+ },
+ "devDependencies": {
+ "vite": "^5.4.2"
+ }
+}
diff --git a/examples/authz/3rd-party-app/public/pubky.svg b/examples/authz/3rd-party-app/public/pubky.svg
new file mode 100644
index 0000000..6802915
--- /dev/null
+++ b/examples/authz/3rd-party-app/public/pubky.svg
@@ -0,0 +1 @@
+
diff --git a/examples/authz/3rd-party-app/src/index.css b/examples/authz/3rd-party-app/src/index.css
new file mode 100644
index 0000000..809fde4
--- /dev/null
+++ b/examples/authz/3rd-party-app/src/index.css
@@ -0,0 +1,48 @@
+:root {
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
+ Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ color: white;
+
+ background: radial-gradient(
+ circle,
+ transparent 20%,
+ #151718 20%,
+ #151718 80%,
+ transparent 80%,
+ transparent
+ ),
+ radial-gradient(
+ circle,
+ transparent 20%,
+ #151718 20%,
+ #151718 80%,
+ transparent 80%,
+ transparent
+ )
+ 25px 25px,
+ linear-gradient(#202020 1px, transparent 2px) 0 -1px,
+ linear-gradient(90deg, #202020 1px, #151718 2px) -1px 0;
+ background-size: 50px 50px, 50px 50px, 25px 25px, 25px 25px;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 20rem;
+ min-height: 100vh;
+ font-family: var(--font-family);
+}
+
+h1 {
+ font-weight: bold;
+ font-size: 3.2rem;
+ line-height: 1.1;
+}
+
+main {
+ max-width: 80rem;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
diff --git a/examples/authz/3rd-party-app/src/pubky-auth-widget.js b/examples/authz/3rd-party-app/src/pubky-auth-widget.js
new file mode 100644
index 0000000..2070f82
--- /dev/null
+++ b/examples/authz/3rd-party-app/src/pubky-auth-widget.js
@@ -0,0 +1,336 @@
+import { LitElement, css, html } from 'lit'
+import { createRef, ref } from 'lit/directives/ref.js';
+import QRCode from 'qrcode'
+
+const DEFAULT_HTTP_RELAY = "https://demo.httprelay.io/link"
+
+/**
+ */
+export class PubkyAuthWidget extends LitElement {
+ static get properties() {
+ return {
+ /**
+ * Relay endpoint for the widget to receive Pubky AuthTokens
+ *
+ * Internally, a random channel ID will be generated and a
+ * GET request made for `${realy url}/${channelID}`
+ *
+ * If no relay is passed, the widget will use a default relay:
+ * https://demo.httprelay.io/link
+ */
+ relay: { type: String },
+ /**
+ * Capabilities requested or this application encoded as a string.
+ */
+ caps: { type: String },
+ /**
+ * Widget's state (open or closed)
+ */
+ open: { type: Boolean },
+ /**
+ * Show "copied to clipboard" note
+ */
+ showCopied: { type: Boolean },
+ }
+ }
+
+ canvasRef = createRef();
+
+ constructor() {
+ if (!window.pubky) {
+ throw new Error("window.pubky is unavailable, make sure to import `@synonymdev/pubky` before this web component.")
+ }
+
+ super()
+
+ this.open = false;
+
+ // TODO: allow using mainnet
+ /** @type {import("@synonymdev/pubky").PubkyClient} */
+ this.pubkyClient = window.pubky.PubkyClient.testnet();
+ }
+
+ connectedCallback() {
+ super.connectedCallback()
+
+ let [url, promise] = this.pubkyClient.authRequest(this.relay || DEFAULT_HTTP_RELAY, this.caps);
+
+ promise.then(session => {
+ console.log({ id: session.pubky().z32(), capabilities: session.capabilities() })
+ alert(`Successfully signed in to ${session.pubky().z32()} with capabilities: ${session.capabilities().join(",")}`)
+ }).catch(e => {
+ console.error(e)
+ })
+
+ // let keypair = pubky.Keypair.random();
+ // const Homeserver = pubky.PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo')
+ // this.pubkyClient.signup(keypair, Homeserver).then(() => {
+ // this.pubkyClient.sendAuthToken(keypair, url)
+ // })
+
+ this.authUrl = url
+ }
+
+ render() {
+ return html`
+
+ `
+ }
+
+ _setQr(canvas) {
+ QRCode.toCanvas(canvas, this.authUrl, {
+ margin: 2,
+ scale: 8,
+
+ color: {
+ light: '#fff',
+ dark: '#000',
+ },
+ });
+ }
+
+ _switchOpen() {
+ this.open = !this.open
+ }
+
+ async _copyToClipboard() {
+ try {
+ await navigator.clipboard.writeText(this.authUrl);
+ this.showCopied = true;
+ setTimeout(() => { this.showCopied = false }, 1000)
+ } catch (error) {
+ console.error('Failed to copy text: ', error);
+ }
+ }
+
+
+
+ render() {
+ return html`
+
+ `
+ }
+
+ static get styles() {
+ return css`
+ * {
+ box-sizing: border-box;
+ }
+
+ :host {
+ --full-width: 22rem;
+ --full-height: 31rem;
+ --header-height: 3rem;
+ --closed-width: 3rem;
+ }
+
+ a {
+ text-decoration: none;
+ }
+
+ button {
+ padding: 0;
+ background: none;
+ border: none;
+ color: inherit;
+ cursor: pointer;
+ }
+
+ p {
+ margin: 0;
+ }
+
+ /** End reset */
+
+ #widget {
+ color: white;
+
+ position: fixed;
+ top: 1rem;
+ right: 1rem;
+
+ background-color:red;
+
+ z-index: 99999;
+ overflow: hidden;
+ background: rgba(43, 43, 43, .7372549019607844);
+ border: 1px solid #3c3c3c;
+ box-shadow: 0 10px 34px -10px rgba(236, 243, 222, .05);
+ border-radius: 8px;
+ -webkit-backdrop-filter: blur(8px);
+ backdrop-filter: blur(8px);
+
+ width: var(--closed-width);
+ height: var(--header-height);
+
+ will-change: height,width;
+ transition-property: height, width;
+ transition-duration: 80ms;
+ transition-timing-function: ease-in;
+ }
+
+ #widget.open{
+ width: var(--full-width);
+ height: var(--full-height);
+ }
+
+ .header {
+ height: var(--header-height);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ #widget
+ .header .text {
+ display: none;
+ font-weight: bold;
+ }
+ #widget.open
+ .header .text {
+ display: block
+ }
+
+ #widget.open
+ .header {
+ width: var(--full-width);
+ justify-content: center;
+ }
+
+ #pubky-icon {
+ height: 100%;
+ width: 100%;
+ }
+
+ #widget.open
+ #pubky-icon {
+ width: var(--header-height);
+ height: 74%;
+ }
+
+ #widget-content{
+ width: var(--full-width);
+ padding: 0 1rem
+ }
+
+ #widget p {
+ font-size: .87rem;
+ line-height: 1rem;
+ text-align: center;
+ color: #fff;
+ opacity: .5;
+
+ /* Fix flash wrap in open animation */
+ text-wrap: nowrap;
+ }
+
+ #qr {
+ width: 18em !important;
+ height: 18em !important;
+ }
+
+ .card {
+ position: relative;
+ background: #3b3b3b;
+ border-radius: 5px;
+ padding: 1rem;
+ margin-top: 1rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ .card.url {
+ padding: .625rem;
+ justify-content: space-between;
+ max-width:100%;
+ }
+
+ .url p {
+ display: flex;
+ align-items: center;
+
+ line-height: 1!important;
+ width: 93%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-wrap: nowrap;
+ }
+
+ .line {
+ height: 1px;
+ background-color: #3b3b3b;
+ flex: 1 1;
+ margin-bottom: 1rem;
+ }
+
+ .copied {
+ will-change: opacity;
+ transition-property: opacity;
+ transition-duration: 80ms;
+ transition-timing-function: ease-in;
+
+ opacity: 0;
+
+ position: absolute;
+ right: 0;
+ top: -1.6rem;
+ font-size: 0.9em;
+ background: rgb(43 43 43 / 98%);
+ padding: .5rem;
+ border-radius: .3rem;
+ color: #ddd;
+ }
+
+ .copied.show {
+ opacity:1
+ }
+ `
+ }
+}
+
+window.customElements.define('pubky-auth-widget', PubkyAuthWidget)
diff --git a/examples/authz/README.md b/examples/authz/README.md
new file mode 100644
index 0000000..905bda6
--- /dev/null
+++ b/examples/authz/README.md
@@ -0,0 +1,29 @@
+# Pubky Auth Example
+
+This example shows 3rd party authorization in Pubky.
+
+It consists of 2 parts:
+
+1. [3rd party app](./3rd-party-app): A web component showing the how to implement a Pubky Auth widget.
+2. [Authenticator CLI](./authenticator): A CLI showing the authenticator (key chain) asking user for consent and generating the AuthToken.
+
+## Usage
+
+First you need to be running a local testnet Homeserver, in the root of this repo run
+
+```bash
+cargo run --bin pubky_homeserver -- --testnet
+```
+
+Run the frontend of the 3rd party app
+
+```bash
+cd ./3rd-party-app
+npm start
+```
+
+Copy the Pubky Auth URL from the frontend.
+
+Finally run the CLI to paste the Pubky Auth in.
+
+You should see the frontend reacting by showing the success of authorization and session details.
diff --git a/examples/authz/authenticator/Cargo.lock b/examples/authz/authenticator/Cargo.lock
new file mode 100644
index 0000000..f2fe8b2
--- /dev/null
+++ b/examples/authz/authenticator/Cargo.lock
@@ -0,0 +1,1906 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
+
+[[package]]
+name = "argon2"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
+dependencies = [
+ "base64ct",
+ "blake2",
+ "cpufeatures",
+ "password-hash",
+]
+
+[[package]]
+name = "arrayref"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "atomic-polyfill"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
+dependencies = [
+ "critical-section",
+]
+
+[[package]]
+name = "authenticator"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "keyring",
+ "pubky",
+ "pubky-common",
+ "rpassword",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "backtrace"
+version = "0.3.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base32"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
+[[package]]
+name = "bitflags"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+
+[[package]]
+name = "blake2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "blake3"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "cc",
+ "cfg-if",
+ "constant_time_eq",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
+
+[[package]]
+name = "cc"
+version = "1.1.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+ "zeroize",
+]
+
+[[package]]
+name = "cobs"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15"
+
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+
+[[package]]
+name = "cookie"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
+dependencies = [
+ "percent-encoding",
+ "time",
+ "version_check",
+]
+
+[[package]]
+name = "cookie_store"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4934e6b7e8419148b6ef56950d277af8561060b56afd59e2aadf98b59fce6baa"
+dependencies = [
+ "cookie",
+ "idna 0.5.0",
+ "log",
+ "publicsuffix",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "time",
+ "url",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc"
+version = "3.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
+
+[[package]]
+name = "critical-section"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f64009896348fc5af4222e9cf7d7d82a95a256c634ebcf61c53e4ea461422242"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "rand_core",
+ "typenum",
+]
+
+[[package]]
+name = "crypto_secretbox"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1"
+dependencies = [
+ "aead",
+ "cipher",
+ "generic-array",
+ "poly1305",
+ "salsa20",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "curve25519-dalek-derive",
+ "digest",
+ "fiat-crypto",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "der"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
+dependencies = [
+ "const-oid",
+ "zeroize",
+]
+
+[[package]]
+name = "deranged"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "document-features"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
+
+[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "pkcs8",
+ "serde",
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "rand_core",
+ "serde",
+ "sha2",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "embedded-io"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
+
+[[package]]
+name = "embedded-io"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
+[[package]]
+name = "flume"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "nanorand",
+ "spin",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+ "zeroize",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "gimli"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
+
+[[package]]
+name = "hash32"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "heapless"
+version = "0.7.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
+dependencies = [
+ "atomic-polyfill",
+ "hash32",
+ "rustc_version",
+ "serde",
+ "spin",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+
+[[package]]
+name = "http"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
+dependencies = [
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9"
+
+[[package]]
+name = "hyper"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "idna"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "inout"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "js-sys"
+version = "0.3.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "keyring"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73b9af47ded4df3067484d7d45758ca2b36bd083bf6d024c2952bbd8af1cdaa4"
+dependencies = [
+ "byteorder",
+ "linux-keyutils",
+ "security-framework",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.158"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
+
+[[package]]
+name = "linux-keyutils"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "761e49ec5fd8a5a463f9b84e877c373d888935b71c6be78f3767fe2ae6bed18e"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
+[[package]]
+name = "litrs"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
+
+[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
+name = "lru"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904"
+
+[[package]]
+name = "mainline"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b751ffb57303217bcae8f490eee6044a5b40eadf6ca05ff476cad37e7b7970d"
+dependencies = [
+ "bytes",
+ "crc",
+ "ed25519-dalek",
+ "flume",
+ "lru",
+ "rand",
+ "serde",
+ "serde_bencode",
+ "serde_bytes",
+ "sha1_smol",
+ "thiserror",
+ "tracing",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "wasi",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "nanorand"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "object"
+version = "0.36.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
+[[package]]
+name = "password-hash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
+dependencies = [
+ "base64ct",
+ "rand_core",
+ "subtle",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pin-project"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkarr"
+version = "2.2.0"
+source = "git+https://github.com/Pubky/pkarr?branch=v3#17975121c809d97dcad907fbb2ffc782e994d270"
+dependencies = [
+ "base32",
+ "bytes",
+ "document-features",
+ "dyn-clone",
+ "ed25519-dalek",
+ "flume",
+ "futures",
+ "js-sys",
+ "lru",
+ "mainline",
+ "rand",
+ "self_cell",
+ "serde",
+ "simple-dns",
+ "thiserror",
+ "tracing",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "poly1305"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
+dependencies = [
+ "cpufeatures",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "postcard"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f7f0a8d620d71c457dd1d47df76bb18960378da56af4527aaa10f515eee732e"
+dependencies = [
+ "cobs",
+ "embedded-io 0.4.0",
+ "embedded-io 0.6.1",
+ "heapless",
+ "serde",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "psl-types"
+version = "2.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
+
+[[package]]
+name = "pubky"
+version = "0.1.0"
+dependencies = [
+ "argon2",
+ "bytes",
+ "js-sys",
+ "pkarr",
+ "pubky-common",
+ "reqwest",
+ "thiserror",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+]
+
+[[package]]
+name = "pubky-common"
+version = "0.1.0"
+dependencies = [
+ "base32",
+ "blake3",
+ "crypto_secretbox",
+ "ed25519-dalek",
+ "js-sys",
+ "once_cell",
+ "pkarr",
+ "postcard",
+ "rand",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "publicsuffix"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457"
+dependencies = [
+ "idna 0.3.0",
+ "psl-types",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.12.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63"
+dependencies = [
+ "base64",
+ "bytes",
+ "cookie",
+ "cookie_store",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "ipnet",
+ "js-sys",
+ "log",
+ "mime",
+ "once_cell",
+ "percent-encoding",
+ "pin-project-lite",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows-registry",
+]
+
+[[package]]
+name = "rpassword"
+version = "7.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f"
+dependencies = [
+ "libc",
+ "rtoolbox",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rtoolbox"
+version = "0.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e"
+dependencies = [
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "salsa20"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "self_cell"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a"
+
+[[package]]
+name = "semver"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
+
+[[package]]
+name = "serde"
+version = "1.0.209"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_bencode"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a70dfc7b7438b99896e7f8992363ab8e2c4ba26aa5ec675d32d1c3c2c33d413e"
+dependencies = [
+ "serde",
+ "serde_bytes",
+]
+
+[[package]]
+name = "serde_bytes"
+version = "0.11.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.209"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1_smol"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "simple-dns"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01607fe2e61894468c6dc0b26103abb073fb08b79a3d9e4b6d76a1a341549958"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "socket2"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "2.0.76"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.3.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
+
+[[package]]
+name = "time-macros"
+version = "0.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.40.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
+dependencies = [
+ "backtrace",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "socket2",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project",
+ "pin-project-lite",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "url"
+version = "2.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
+dependencies = [
+ "form_urlencoded",
+ "idna 0.5.0",
+ "percent-encoding",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
+
+[[package]]
+name = "web-sys"
+version = "0.3.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "windows-registry"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
+dependencies = [
+ "windows-result",
+ "windows-strings",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
+dependencies = [
+ "windows-result",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
diff --git a/examples/authz/authenticator/Cargo.toml b/examples/authz/authenticator/Cargo.toml
new file mode 100644
index 0000000..932701b
--- /dev/null
+++ b/examples/authz/authenticator/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "authenticator"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0.86"
+base64 = "0.22.1"
+clap = { version = "4.5.16", features = ["derive"] }
+pubky = { version = "0.1.0", path = "../../../pubky" }
+pubky-common = { version = "0.1.0", path = "../../../pubky-common" }
+rpassword = "7.3.1"
+tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
+url = "2.5.2"
diff --git a/examples/authz/authenticator/src/main.rs b/examples/authz/authenticator/src/main.rs
new file mode 100644
index 0000000..410b8f5
--- /dev/null
+++ b/examples/authz/authenticator/src/main.rs
@@ -0,0 +1,80 @@
+use anyhow::Result;
+use clap::Parser;
+use pubky::PubkyClient;
+use std::path::PathBuf;
+use url::Url;
+
+use pubky_common::{capabilities::Capability, crypto::PublicKey};
+
+/// local testnet HOMESERVER
+const HOMESERVER: &str = "8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo";
+
+#[derive(Parser, Debug)]
+#[command(version, about, long_about = None)]
+struct Cli {
+ /// Path to a recovery_file of the Pubky you want to sign in with
+ recovery_file: PathBuf,
+
+ /// Pubky Auth url
+ url: Url,
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ let cli = Cli::parse();
+
+ let recovery_file = std::fs::read(&cli.recovery_file)?;
+ println!("\nSuccessfully opened recovery file");
+
+ let url = cli.url;
+
+ let caps = url
+ .query_pairs()
+ .filter_map(|(key, value)| {
+ if key == "caps" {
+ return Some(
+ value
+ .split(',')
+ .filter_map(|cap| Capability::try_from(cap).ok())
+ .collect::>(),
+ );
+ };
+ None
+ })
+ .next()
+ .unwrap_or_default();
+
+ if !caps.is_empty() {
+ println!("\nRequired Capabilities:");
+ }
+
+ for cap in &caps {
+ println!(" {} : {:?}", cap.scope, cap.actions);
+ }
+
+ // === Consent form ===
+
+ println!("\nEnter your recovery_file's passphrase to confirm:");
+ let passphrase = rpassword::read_password()?;
+
+ let keypair = pubky_common::recovery_file::decrypt_recovery_file(&recovery_file, &passphrase)?;
+
+ println!("Successfully decrypted recovery file...");
+ println!("PublicKey: {}", keypair.public_key());
+
+ let client = PubkyClient::testnet();
+
+ // For the purposes of this demo, we need to make sure
+ // the user has an account on the local homeserver.
+ if client.signin(&keypair).await.is_err() {
+ client
+ .signup(&keypair, &PublicKey::try_from(HOMESERVER).unwrap())
+ .await?;
+ };
+
+ println!("Sending AuthToken to the 3rd party app...");
+
+ client.send_auth_token(&keypair, url).await?;
+
+ Ok(())
+}
diff --git a/pubky-common/Cargo.toml b/pubky-common/Cargo.toml
index 0a9df3b..9676fba 100644
--- a/pubky-common/Cargo.toml
+++ b/pubky-common/Cargo.toml
@@ -10,12 +10,24 @@ base32 = "0.5.0"
blake3 = "1.5.1"
ed25519-dalek = "2.1.1"
once_cell = "1.19.0"
-pkarr = "2.1.0"
+pkarr = { workspace = true }
rand = "0.8.5"
thiserror = "1.0.60"
postcard = { version = "1.0.8", features = ["alloc"] }
-serde = { version = "1.0.204", features = ["derive"] }
crypto_secretbox = { version = "0.1.1", features = ["std"] }
+argon2 = { version = "0.5.3", features = ["std"] }
+
+serde = { workspace = true, optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = "0.3.69"
+
+[dev-dependencies]
+postcard = "1.0.8"
+
+[features]
+
+serde = ["dep:serde", "ed25519-dalek/serde", "pkarr/serde"]
+full = ['serde']
+
+default = ['full']
diff --git a/pubky-common/src/auth.rs b/pubky-common/src/auth.rs
index 5d5ebba..866fe5e 100644
--- a/pubky-common/src/auth.rs
+++ b/pubky-common/src/auth.rs
@@ -2,104 +2,153 @@
use std::sync::{Arc, Mutex};
-use ed25519_dalek::ed25519::SignatureBytes;
+use serde::{Deserialize, Serialize};
use crate::{
- crypto::{random_hash, Keypair, PublicKey, Signature},
+ capabilities::{Capabilities, Capability},
+ crypto::{Keypair, PublicKey, Signature},
+ namespaces::PUBKY_AUTH,
timestamp::Timestamp,
};
// 30 seconds
const TIME_INTERVAL: u64 = 30 * 1_000_000;
-#[derive(Debug, PartialEq)]
-pub struct AuthnSignature(Box<[u8]>);
+const CURRENT_VERSION: u8 = 0;
+// 45 seconds in the past or the future
+const TIMESTAMP_WINDOW: i64 = 45 * 1_000_000;
-impl AuthnSignature {
- pub fn new(signer: &Keypair, audience: &PublicKey, token: Option<&[u8]>) -> Self {
- let mut bytes = Vec::with_capacity(96);
-
- let time: u64 = Timestamp::now().into();
- let time_step = time / TIME_INTERVAL;
-
- let token_hash = token.map_or(random_hash(), crate::crypto::hash);
-
- let signature = signer
- .sign(&signable(
- &time_step.to_be_bytes(),
- &signer.public_key(),
- audience,
- token_hash.as_bytes(),
- ))
- .to_bytes();
-
- bytes.extend_from_slice(&signature);
- bytes.extend_from_slice(token_hash.as_bytes());
-
- Self(bytes.into())
- }
-
- /// Sign a randomly generated nonce
- pub fn generate(keypair: &Keypair, audience: &PublicKey) -> Self {
- AuthnSignature::new(keypair, audience, None)
- }
-
- pub fn as_bytes(&self) -> &[u8] {
- &self.0
- }
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct AuthToken {
+ /// Signature over the token.
+ signature: Signature,
+ /// A namespace to ensure this signature can't be used for any
+ /// other purposes that share the same message structurea by accident.
+ namespace: [u8; 10],
+ /// Version of the [AuthToken], in case we need to upgrade it to support unforseen usecases.
+ ///
+ /// Version 0:
+ /// - Signer is implicitly the same as the root keypair for
+ /// the [AuthToken::pubky], without any delegation.
+ /// - Capabilities are only meant for resoucres on the homeserver.
+ version: u8,
+ /// Timestamp
+ timestamp: Timestamp,
+ /// The [PublicKey] of the owner of the resources being accessed by this token.
+ pubky: PublicKey,
+ // Variable length capabilities
+ capabilities: Capabilities,
}
-#[derive(Debug, Clone)]
-pub struct AuthnVerifier {
- audience: PublicKey,
- inner: Arc>>,
- // TODO: Support permisisons
- // token_hashes: HashSet<[u8; 32]>,
-}
+impl AuthToken {
+ pub fn sign(keypair: &Keypair, capabilities: impl Into) -> Self {
+ let timestamp = Timestamp::now();
-impl AuthnVerifier {
- pub fn new(audience: PublicKey) -> Self {
- Self {
- audience,
- inner: Arc::new(Mutex::new(Vec::new())),
+ let mut token = Self {
+ signature: Signature::from_bytes(&[0; 64]),
+ namespace: *PUBKY_AUTH,
+ version: 0,
+ timestamp,
+ pubky: keypair.public_key(),
+ capabilities: capabilities.into(),
+ };
+
+ let serialized = token.serialize();
+
+ token.signature = keypair.sign(&serialized[65..]);
+
+ token
+ }
+
+ pub fn capabilities(&self) -> &[Capability] {
+ &self.capabilities.0
+ }
+
+ pub fn verify(bytes: &[u8]) -> Result {
+ if bytes[75] > CURRENT_VERSION {
+ return Err(Error::UnknownVersion);
+ }
+
+ let token = AuthToken::deserialize(bytes)?;
+
+ match token.version {
+ 0 => {
+ let now = Timestamp::now();
+
+ // Chcek timestamp;
+ let diff = token.timestamp.difference(&now);
+ if diff > TIMESTAMP_WINDOW {
+ return Err(Error::TooFarInTheFuture);
+ }
+ if diff < -TIMESTAMP_WINDOW {
+ return Err(Error::Expired);
+ }
+
+ token
+ .pubky
+ .verify(AuthToken::signable(token.version, bytes), &token.signature)
+ .map_err(|_| Error::InvalidSignature)?;
+
+ Ok(token)
+ }
+ _ => unreachable!(),
}
}
- pub fn verify(&self, bytes: &[u8], signer: &PublicKey) -> Result<(), AuthnSignatureError> {
+ pub fn serialize(&self) -> Vec {
+ postcard::to_allocvec(self).unwrap()
+ }
+
+ pub fn deserialize(bytes: &[u8]) -> Result {
+ Ok(postcard::from_bytes(bytes)?)
+ }
+
+ pub fn pubky(&self) -> &PublicKey {
+ &self.pubky
+ }
+
+ /// A unique ID for this [AuthToken], which is a concatenation of
+ /// [AuthToken::pubky] and [AuthToken::timestamp].
+ ///
+ /// Assuming that [AuthToken::timestamp] is unique for every [AuthToken::pubky].
+ fn id(version: u8, bytes: &[u8]) -> Box<[u8]> {
+ match version {
+ 0 => bytes[75..115].into(),
+ _ => unreachable!(),
+ }
+ }
+
+ fn signable(version: u8, bytes: &[u8]) -> &[u8] {
+ match version {
+ 0 => bytes[65..].into(),
+ _ => unreachable!(),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Default)]
+/// Keeps track of used AuthToken until they expire.
+pub struct AuthVerifier {
+ seen: Arc>>>,
+}
+
+impl AuthVerifier {
+ pub fn verify(&self, bytes: &[u8]) -> Result {
self.gc();
- if bytes.len() != 96 {
- return Err(AuthnSignatureError::InvalidLength(bytes.len()));
+ let token = AuthToken::verify(bytes)?;
+
+ let mut seen = self.seen.lock().unwrap();
+
+ let id = AuthToken::id(token.version, bytes);
+
+ match seen.binary_search_by(|element| element.cmp(&id)) {
+ Ok(_) => Err(Error::AlreadyUsed),
+ Err(index) => {
+ seen.insert(index, id);
+ Ok(token)
+ }
}
-
- let signature_bytes: SignatureBytes = bytes[0..64]
- .try_into()
- .expect("validate token length on instantiating");
- let signature = Signature::from(signature_bytes);
-
- let token_hash: [u8; 32] = bytes[64..].try_into().expect("should not be reachable");
-
- let now = Timestamp::now().into_inner();
- let past = now - TIME_INTERVAL;
- let future = now + TIME_INTERVAL;
-
- let result = verify_at(now, self, &signature, signer, &token_hash);
-
- match result {
- Ok(_) => return Ok(()),
- Err(AuthnSignatureError::AlreadyUsed) => return Err(AuthnSignatureError::AlreadyUsed),
- _ => {}
- }
-
- let result = verify_at(past, self, &signature, signer, &token_hash);
-
- match result {
- Ok(_) => return Ok(()),
- Err(AuthnSignatureError::AlreadyUsed) => return Err(AuthnSignatureError::AlreadyUsed),
- _ => {}
- }
-
- verify_at(future, self, &signature, signer, &token_hash)
}
// === Private Methods ===
@@ -108,7 +157,7 @@ impl AuthnVerifier {
fn gc(&self) {
let threshold = ((Timestamp::now().into_inner() / TIME_INTERVAL) - 2).to_be_bytes();
- let mut inner = self.inner.lock().unwrap();
+ let mut inner = self.seen.lock().unwrap();
match inner.binary_search_by(|element| element[0..8].cmp(&threshold)) {
Ok(index) | Err(index) => {
@@ -118,103 +167,113 @@ impl AuthnVerifier {
}
}
-fn verify_at(
- time: u64,
- verifier: &AuthnVerifier,
- signature: &Signature,
- signer: &PublicKey,
- token_hash: &[u8; 32],
-) -> Result<(), AuthnSignatureError> {
- let time_step = time / TIME_INTERVAL;
- let time_step_bytes = time_step.to_be_bytes();
-
- let result = signer.verify(
- &signable(&time_step_bytes, signer, &verifier.audience, token_hash),
- signature,
- );
-
- if result.is_ok() {
- let mut inner = verifier.inner.lock().unwrap();
-
- let mut candidate = [0_u8; 40];
- candidate[..8].copy_from_slice(&time_step_bytes);
- candidate[8..].copy_from_slice(token_hash);
-
- match inner.binary_search_by(|element| element.cmp(&candidate)) {
- Ok(index) | Err(index) => {
- inner.insert(index, candidate);
- }
- };
-
- return Ok(());
- }
-
- Err(AuthnSignatureError::InvalidSignature)
-}
-
-fn signable(
- time_step_bytes: &[u8; 8],
- signer: &PublicKey,
- audience: &PublicKey,
- token_hash: &[u8; 32],
-) -> [u8; 115] {
- let mut arr = [0; 115];
-
- arr[..11].copy_from_slice(crate::namespaces::PUBKY_AUTHN);
- arr[11..19].copy_from_slice(time_step_bytes);
- arr[19..51].copy_from_slice(signer.as_bytes());
- arr[51..83].copy_from_slice(audience.as_bytes());
- arr[83..].copy_from_slice(token_hash);
-
- arr
-}
-
-#[derive(thiserror::Error, Debug)]
-pub enum AuthnSignatureError {
- #[error("AuthnSignature should be 96 bytes long, got {0} bytes instead")]
- InvalidLength(usize),
-
- #[error("Invalid signature")]
+#[derive(thiserror::Error, Debug, PartialEq, Eq)]
+pub enum Error {
+ #[error("Unknown version")]
+ UnknownVersion,
+ #[error("AuthToken has a timestamp that is more than 45 seconds in the future")]
+ TooFarInTheFuture,
+ #[error("AuthToken has a timestamp that is more than 45 seconds in the past")]
+ Expired,
+ #[error("Invalid Signature")]
InvalidSignature,
-
- #[error("Authn signature already used")]
+ #[error(transparent)]
+ Postcard(#[from] postcard::Error),
+ #[error("AuthToken already used")]
AlreadyUsed,
}
#[cfg(test)]
mod tests {
- use crate::crypto::Keypair;
+ use crate::{
+ auth::TIMESTAMP_WINDOW, capabilities::Capability, crypto::Keypair, timestamp::Timestamp,
+ };
- use super::{AuthnSignature, AuthnVerifier};
+ use super::*;
+
+ #[test]
+ fn v0_id_signable() {
+ let signer = Keypair::random();
+ let capabilities = vec![Capability::root()];
+
+ let token = AuthToken::sign(&signer, capabilities.clone());
+
+ let serialized = &token.serialize();
+
+ let mut id = vec![];
+ id.extend_from_slice(&token.timestamp.to_bytes());
+ id.extend_from_slice(signer.public_key().as_bytes());
+
+ assert_eq!(AuthToken::id(token.version, serialized), id.into());
+
+ assert_eq!(
+ AuthToken::signable(token.version, serialized),
+ &serialized[65..]
+ )
+ }
#[test]
fn sign_verify() {
- let keypair = Keypair::random();
- let signer = keypair.public_key();
- let audience = Keypair::random().public_key();
+ let signer = Keypair::random();
+ let capabilities = vec![Capability::root()];
- let verifier = AuthnVerifier::new(audience.clone());
+ let verifier = AuthVerifier::default();
- let authn_signature = AuthnSignature::generate(&keypair, &audience);
+ let token = AuthToken::sign(&signer, capabilities.clone());
- verifier
- .verify(authn_signature.as_bytes(), &signer)
- .unwrap();
+ let serialized = &token.serialize();
- {
- // Invalid signable
- let mut invalid = authn_signature.as_bytes().to_vec();
- invalid[64..].copy_from_slice(&[0; 32]);
+ verifier.verify(serialized).unwrap();
- assert!(verifier.verify(&invalid, &signer).is_err())
- }
+ assert_eq!(token.capabilities, capabilities.into());
+ }
- {
- // Invalid signer
- let mut invalid = authn_signature.as_bytes().to_vec();
- invalid[0..32].copy_from_slice(&[0; 32]);
+ #[test]
+ fn expired() {
+ let signer = Keypair::random();
+ let capabilities = Capabilities(vec![Capability::root()]);
- assert!(verifier.verify(&invalid, &signer).is_err())
- }
+ let verifier = AuthVerifier::default();
+
+ let timestamp = (&Timestamp::now()) - (TIMESTAMP_WINDOW as u64);
+
+ let mut signable = vec![];
+ signable.extend_from_slice(signer.public_key().as_bytes());
+ signable.extend_from_slice(&postcard::to_allocvec(&capabilities).unwrap());
+
+ let signature = signer.sign(&signable);
+
+ let token = AuthToken {
+ signature,
+ namespace: *PUBKY_AUTH,
+ version: 0,
+ timestamp,
+ pubky: signer.public_key(),
+ capabilities,
+ };
+
+ let serialized = token.serialize();
+
+ let result = verifier.verify(&serialized);
+
+ assert_eq!(result, Err(Error::Expired));
+ }
+
+ #[test]
+ fn already_used() {
+ let signer = Keypair::random();
+ let capabilities = vec![Capability::root()];
+
+ let verifier = AuthVerifier::default();
+
+ let token = AuthToken::sign(&signer, capabilities.clone());
+
+ let serialized = &token.serialize();
+
+ verifier.verify(serialized).unwrap();
+
+ assert_eq!(token.capabilities, capabilities.into());
+
+ assert_eq!(verifier.verify(serialized), Err(Error::AlreadyUsed));
}
}
diff --git a/pubky-common/src/capabilities.rs b/pubky-common/src/capabilities.rs
new file mode 100644
index 0000000..7929860
--- /dev/null
+++ b/pubky-common/src/capabilities.rs
@@ -0,0 +1,237 @@
+use std::fmt::Display;
+
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Capability {
+ pub scope: String,
+ pub actions: Vec,
+}
+
+impl Capability {
+ /// Create a root [Capability] at the `/` path with all the available [PubkyAbility]
+ pub fn root() -> Self {
+ Capability {
+ scope: "/".to_string(),
+ actions: vec![Action::Read, Action::Write],
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Action {
+ /// Can read the scope at the specified path (GET requests).
+ Read,
+ /// Can write to the scope at the specified path (PUT/POST/DELETE requests).
+ Write,
+ /// Unknown ability
+ Unknown(char),
+}
+
+impl From<&Action> for char {
+ fn from(value: &Action) -> Self {
+ match value {
+ Action::Read => 'r',
+ Action::Write => 'w',
+ Action::Unknown(char) => char.to_owned(),
+ }
+ }
+}
+
+impl TryFrom for Action {
+ type Error = Error;
+
+ fn try_from(value: char) -> Result {
+ match value {
+ 'r' => Ok(Self::Read),
+ 'w' => Ok(Self::Write),
+ _ => Err(Error::InvalidAction),
+ }
+ }
+}
+
+impl Display for Capability {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}:{}",
+ self.scope,
+ self.actions.iter().map(char::from).collect::()
+ )
+ }
+}
+
+impl TryFrom for Capability {
+ type Error = Error;
+
+ fn try_from(value: String) -> Result {
+ value.as_str().try_into()
+ }
+}
+
+impl TryFrom<&str> for Capability {
+ type Error = Error;
+
+ fn try_from(value: &str) -> Result {
+ if value.matches(':').count() != 1 {
+ return Err(Error::InvalidFormat);
+ }
+
+ if !value.starts_with('/') {
+ return Err(Error::InvalidScope);
+ }
+
+ let actions_str = value.rsplit(':').next().unwrap_or("");
+
+ let mut actions = Vec::new();
+
+ for char in actions_str.chars() {
+ let ability = Action::try_from(char)?;
+
+ match actions.binary_search_by(|element| char::from(element).cmp(&char)) {
+ Ok(_) => {}
+ Err(index) => {
+ actions.insert(index, ability);
+ }
+ }
+ }
+
+ let scope = value[0..value.len() - actions_str.len() - 1].to_string();
+
+ Ok(Capability { scope, actions })
+ }
+}
+
+impl Serialize for Capability {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ let string = self.to_string();
+
+ string.serialize(serializer)
+ }
+}
+
+impl<'de> Deserialize<'de> for Capability {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let string: String = Deserialize::deserialize(deserializer)?;
+
+ string.try_into().map_err(serde::de::Error::custom)
+ }
+}
+
+#[derive(thiserror::Error, Debug, PartialEq, Eq)]
+pub enum Error {
+ #[error("Capability: Invalid scope: does not start with `/`")]
+ InvalidScope,
+ #[error("Capability: Invalid format should be :")]
+ InvalidFormat,
+ #[error("Capability: Invalid Action")]
+ InvalidAction,
+ #[error("Capabilities: Invalid capabilities format")]
+ InvalidCapabilities,
+}
+
+#[derive(Clone, Default, Debug, PartialEq, Eq)]
+/// A wrapper around `Vec` to enable serialization without
+/// a varint. Useful when [Capabilities] are at the end of a struct.
+pub struct Capabilities(pub Vec);
+
+impl Capabilities {
+ pub fn contains(&self, capability: &Capability) -> bool {
+ self.0.contains(capability)
+ }
+}
+
+impl From> for Capabilities {
+ fn from(value: Vec) -> Self {
+ Self(value)
+ }
+}
+
+impl From for Vec {
+ fn from(value: Capabilities) -> Self {
+ value.0
+ }
+}
+
+impl TryFrom<&str> for Capabilities {
+ type Error = Error;
+
+ fn try_from(value: &str) -> Result {
+ let mut caps = vec![];
+
+ for s in value.split(',') {
+ if let Ok(cap) = Capability::try_from(s) {
+ caps.push(cap);
+ };
+ }
+
+ Ok(Capabilities(caps))
+ }
+}
+
+impl Display for Capabilities {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let string = self
+ .0
+ .iter()
+ .map(|c| c.to_string())
+ .collect::>()
+ .join(",");
+
+ write!(f, "{}", string)
+ }
+}
+
+impl Serialize for Capabilities {
+ fn serialize(&self, serializer: S) -> Result
+ where
+ S: serde::Serializer,
+ {
+ self.to_string().serialize(serializer)
+ }
+}
+
+impl<'de> Deserialize<'de> for Capabilities {
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: serde::Deserializer<'de>,
+ {
+ let string: String = Deserialize::deserialize(deserializer)?;
+
+ let mut caps = vec![];
+
+ for s in string.split(',') {
+ if let Ok(cap) = Capability::try_from(s) {
+ caps.push(cap);
+ };
+ }
+
+ Ok(Capabilities(caps))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn pubky_caps() {
+ let cap = Capability {
+ scope: "/pub/pubky.app/".to_string(),
+ actions: vec![Action::Read, Action::Write],
+ };
+
+ // Read and write withing directory `/pub/pubky.app/`.
+ let expected_string = "/pub/pubky.app/:rw";
+
+ assert_eq!(cap.to_string(), expected_string);
+
+ assert_eq!(Capability::try_from(expected_string), Ok(cap))
+ }
+}
diff --git a/pubky-common/src/lib.rs b/pubky-common/src/lib.rs
index cedc227..cfb56f2 100644
--- a/pubky-common/src/lib.rs
+++ b/pubky-common/src/lib.rs
@@ -1,5 +1,7 @@
pub mod auth;
+pub mod capabilities;
pub mod crypto;
pub mod namespaces;
+pub mod recovery_file;
pub mod session;
pub mod timestamp;
diff --git a/pubky-common/src/namespaces.rs b/pubky-common/src/namespaces.rs
index 6c951dd..6aa37cd 100644
--- a/pubky-common/src/namespaces.rs
+++ b/pubky-common/src/namespaces.rs
@@ -1 +1 @@
-pub const PUBKY_AUTHN: &[u8; 11] = b"PUBKY:AUTHN";
+pub const PUBKY_AUTH: &[u8; 10] = b"PUBKY:AUTH";
diff --git a/pubky/src/shared/recovery_file.rs b/pubky-common/src/recovery_file.rs
similarity index 67%
rename from pubky/src/shared/recovery_file.rs
rename to pubky-common/src/recovery_file.rs
index 4bcbc27..0a2f9b4 100644
--- a/pubky/src/shared/recovery_file.rs
+++ b/pubky-common/src/recovery_file.rs
@@ -1,13 +1,12 @@
use argon2::Argon2;
use pkarr::Keypair;
-use pubky_common::crypto::{decrypt, encrypt};
-use crate::error::{Error, Result};
+use crate::crypto::{decrypt, encrypt};
static SPEC_NAME: &str = "recovery";
static SPEC_LINE: &str = "pubky.org/recovery";
-pub fn decrypt_recovery_file(recovery_file: &[u8], passphrase: &str) -> Result {
+pub fn decrypt_recovery_file(recovery_file: &[u8], passphrase: &str) -> Result {
let encryption_key = recovery_file_encryption_key_from_passphrase(passphrase)?;
let newline_index = recovery_file
@@ -39,7 +38,7 @@ pub fn decrypt_recovery_file(recovery_file: &[u8], passphrase: &str) -> Result Result> {
+pub fn create_recovery_file(keypair: &Keypair, passphrase: &str) -> Result, Error> {
let encryption_key = recovery_file_encryption_key_from_passphrase(passphrase)?;
let secret_key = keypair.secret_key();
@@ -54,7 +53,7 @@ pub fn create_recovery_file(keypair: &Keypair, passphrase: &str) -> Result Result<[u8; 32]> {
+fn recovery_file_encryption_key_from_passphrase(passphrase: &str) -> Result<[u8; 32], Error> {
let argon2id = Argon2::default();
let mut out = [0; 32];
@@ -64,19 +63,39 @@ fn recovery_file_encryption_key_from_passphrase(passphrase: &str) -> Result<[u8;
Ok(out)
}
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+ // === Recovery file ==
+ #[error("Recovery file should start with a spec line, followed by a new line character")]
+ RecoveryFileMissingSpecLine,
+
+ #[error("Recovery file should start with a spec line, followed by a new line character")]
+ RecoveryFileVersionNotSupported,
+
+ #[error("Recovery file should contain an encrypted secret key after the new line character")]
+ RecoverFileMissingEncryptedSecretKey,
+
+ #[error("Recovery file encrypted secret key should be 32 bytes, got {0}")]
+ RecoverFileInvalidSecretKeyLength(usize),
+
+ #[error(transparent)]
+ Argon(#[from] argon2::Error),
+
+ #[error(transparent)]
+ Crypto(#[from] crate::crypto::Error),
+}
+
#[cfg(test)]
mod tests {
use super::*;
- use crate::PubkyClient;
-
#[test]
fn encrypt_decrypt_recovery_file() {
let passphrase = "very secure password";
let keypair = Keypair::random();
- let recovery_file = PubkyClient::create_recovery_file(&keypair, passphrase).unwrap();
- let recovered = PubkyClient::decrypt_recovery_file(&recovery_file, passphrase).unwrap();
+ let recovery_file = create_recovery_file(&keypair, passphrase).unwrap();
+ let recovered = decrypt_recovery_file(&recovery_file, passphrase).unwrap();
assert_eq!(recovered.public_key(), keypair.public_key());
}
diff --git a/pubky-common/src/session.rs b/pubky-common/src/session.rs
index 5a35e14..5ce64d0 100644
--- a/pubky-common/src/session.rs
+++ b/pubky-common/src/session.rs
@@ -1,31 +1,48 @@
+use pkarr::PublicKey;
use postcard::{from_bytes, to_allocvec};
use serde::{Deserialize, Serialize};
extern crate alloc;
use alloc::vec::Vec;
-use crate::timestamp::Timestamp;
+use crate::{auth::AuthToken, capabilities::Capability, timestamp::Timestamp};
// TODO: add IP address?
// TODO: use https://crates.io/crates/user-agent-parser to parse the session
// and get more informations from the user-agent.
-#[derive(Clone, Default, Serialize, Deserialize, Debug, Eq, PartialEq)]
+#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct Session {
- pub version: usize,
- pub created_at: u64,
+ version: usize,
+ pubky: PublicKey,
+ created_at: u64,
/// User specified name, defaults to the user-agent.
- pub name: String,
- pub user_agent: String,
+ name: String,
+ user_agent: String,
+ capabilities: Vec,
}
impl Session {
- pub fn new() -> Self {
+ pub fn new(token: &AuthToken, user_agent: Option) -> Self {
Self {
+ version: 0,
+ pubky: token.pubky().to_owned(),
created_at: Timestamp::now().into_inner(),
- ..Default::default()
+ capabilities: token.capabilities().to_vec(),
+ user_agent: user_agent.as_deref().unwrap_or("").to_string(),
+ name: user_agent.as_deref().unwrap_or("").to_string(),
}
}
+ // === Getters ===
+
+ pub fn pubky(&self) -> &PublicKey {
+ &self.pubky
+ }
+
+ pub fn capabilities(&self) -> &Vec {
+ &self.capabilities
+ }
+
// === Setters ===
pub fn set_user_agent(&mut self, user_agent: String) -> &mut Self {
@@ -38,6 +55,12 @@ impl Session {
self
}
+ pub fn set_capabilities(&mut self, capabilities: Vec) -> &mut Self {
+ self.capabilities = capabilities;
+
+ self
+ }
+
// === Public Methods ===
pub fn serialize(&self) -> Vec {
@@ -51,6 +74,8 @@ impl Session {
Ok(from_bytes(bytes)?)
}
+
+ // TODO: add `can_read()`, `can_write()` and `is_root()` methods
}
pub type Result = core::result::Result;
@@ -65,18 +90,34 @@ pub enum Error {
#[cfg(test)]
mod tests {
+ use crate::crypto::Keypair;
+
use super::*;
#[test]
fn serialize() {
+ let keypair = Keypair::from_secret_key(&[0; 32]);
+ let pubky = keypair.public_key();
+
let session = Session {
user_agent: "foo".to_string(),
- ..Default::default()
+ capabilities: vec![Capability::root()],
+ created_at: 0,
+ pubky,
+ version: 0,
+ name: "".to_string(),
};
let serialized = session.serialize();
- assert_eq!(serialized, [0, 0, 0, 3, 102, 111, 111,]);
+ assert_eq!(
+ serialized,
+ [
+ 0, 59, 106, 39, 188, 206, 182, 164, 45, 98, 163, 168, 208, 42, 111, 13, 115, 101,
+ 50, 21, 119, 29, 226, 67, 166, 58, 192, 72, 161, 139, 89, 218, 41, 0, 0, 3, 102,
+ 111, 111, 1, 4, 47, 58, 114, 119
+ ]
+ );
let deseiralized = Session::deserialize(&serialized).unwrap();
diff --git a/pubky-common/src/timestamp.rs b/pubky-common/src/timestamp.rs
index 174c7e3..848f894 100644
--- a/pubky-common/src/timestamp.rs
+++ b/pubky-common/src/timestamp.rs
@@ -75,8 +75,8 @@ impl Timestamp {
self.0.to_be_bytes()
}
- pub fn difference(&self, rhs: &Timestamp) -> u64 {
- self.0.abs_diff(rhs.0)
+ pub fn difference(&self, rhs: &Timestamp) -> i64 {
+ (self.0 as i64) - (rhs.0 as i64)
}
pub fn into_inner(&self) -> u64 {
@@ -264,4 +264,17 @@ mod tests {
assert_eq!(decoded, timestamp)
}
+
+ #[test]
+ fn serde() {
+ let timestamp = Timestamp::now();
+
+ let serialized = postcard::to_allocvec(×tamp).unwrap();
+
+ assert_eq!(serialized, timestamp.to_bytes());
+
+ let deserialized: Timestamp = postcard::from_bytes(&serialized).unwrap();
+
+ assert_eq!(deserialized, timestamp);
+ }
}
diff --git a/pubky-homeserver/Cargo.toml b/pubky-homeserver/Cargo.toml
index 68323b9..c8abfd5 100644
--- a/pubky-homeserver/Cargo.toml
+++ b/pubky-homeserver/Cargo.toml
@@ -8,17 +8,17 @@ anyhow = "1.0.82"
axum = { version = "0.7.5", features = ["macros"] }
axum-extra = { version = "0.9.3", features = ["typed-header", "async-read-body"] }
base32 = "0.5.1"
-bytes = "1.6.1"
+bytes = "^1.7.1"
clap = { version = "4.5.11", features = ["derive"] }
dirs-next = "2.0.0"
flume = "0.11.0"
futures-util = "0.3.30"
heed = "0.20.3"
hex = "0.4.3"
-pkarr = { version = "2.1.0", features = ["async"] }
+pkarr = { workspace = true }
postcard = { version = "1.0.8", features = ["alloc"] }
pubky-common = { version = "0.1.0", path = "../pubky-common" }
-serde = { version = "1.0.204", features = ["derive"] }
+serde = { workspace = true }
tokio = { version = "1.37.0", features = ["full"] }
toml = "0.8.19"
tower-cookies = "0.10.0"
diff --git a/pubky-homeserver/src/database/tables.rs b/pubky-homeserver/src/database/tables.rs
index 81a87da..e879bd0 100644
--- a/pubky-homeserver/src/database/tables.rs
+++ b/pubky-homeserver/src/database/tables.rs
@@ -9,12 +9,18 @@ use heed::{Env, RwTxn};
use blobs::{BlobsTable, BLOBS_TABLE};
use entries::{EntriesTable, ENTRIES_TABLE};
-use self::events::{EventsTable, EVENTS_TABLE};
+use self::{
+ events::{EventsTable, EVENTS_TABLE},
+ sessions::{SessionsTable, SESSIONS_TABLE},
+ users::{UsersTable, USERS_TABLE},
+};
pub const TABLES_COUNT: u32 = 5;
#[derive(Debug, Clone)]
pub struct Tables {
+ pub users: UsersTable,
+ pub sessions: SessionsTable,
pub blobs: BlobsTable,
pub entries: EntriesTable,
pub events: EventsTable,
@@ -23,6 +29,12 @@ pub struct Tables {
impl Tables {
pub fn new(env: &Env, wtxn: &mut RwTxn) -> anyhow::Result {
Ok(Self {
+ users: env
+ .open_database(wtxn, Some(USERS_TABLE))?
+ .expect("Users table already created"),
+ sessions: env
+ .open_database(wtxn, Some(SESSIONS_TABLE))?
+ .expect("Sessions table already created"),
blobs: env
.open_database(wtxn, Some(BLOBS_TABLE))?
.expect("Blobs table already created"),
diff --git a/pubky-homeserver/src/error.rs b/pubky-homeserver/src/error.rs
index 5fad2f0..b6e5a14 100644
--- a/pubky-homeserver/src/error.rs
+++ b/pubky-homeserver/src/error.rs
@@ -5,7 +5,6 @@ use axum::{
http::StatusCode,
response::IntoResponse,
};
-use pubky_common::auth::AuthnSignatureError;
pub type Result = core::result::Result;
@@ -71,8 +70,8 @@ impl From for Error {
// === Pubky specific errors ===
-impl From for Error {
- fn from(error: AuthnSignatureError) -> Self {
+impl From for Error {
+ fn from(error: pubky_common::auth::Error) -> Self {
Self::new(StatusCode::BAD_REQUEST, Some(error))
}
}
diff --git a/pubky-homeserver/src/routes.rs b/pubky-homeserver/src/routes.rs
index 163baec..7422f20 100644
--- a/pubky-homeserver/src/routes.rs
+++ b/pubky-homeserver/src/routes.rs
@@ -19,9 +19,9 @@ mod root;
fn base(state: AppState) -> Router {
Router::new()
.route("/", get(root::handler))
- .route("/:pubky", put(auth::signup))
+ .route("/signup", post(auth::signup))
+ .route("/session", post(auth::signin))
.route("/:pubky/session", get(auth::session))
- .route("/:pubky/session", post(auth::signin))
.route("/:pubky/session", delete(auth::signout))
.route("/:pubky/*path", put(public::put))
.route("/:pubky/*path", get(public::get))
diff --git a/pubky-homeserver/src/routes/auth.rs b/pubky-homeserver/src/routes/auth.rs
index 72246f9..dbcffe4 100644
--- a/pubky-homeserver/src/routes/auth.rs
+++ b/pubky-homeserver/src/routes/auth.rs
@@ -13,7 +13,7 @@ use pubky_common::{crypto::random_bytes, session::Session, timestamp::Timestamp}
use crate::{
database::tables::{
sessions::{SessionsTable, SESSIONS_TABLE},
- users::{User, UsersTable, USERS_TABLE},
+ users::User,
},
error::{Error, Result},
extractors::Pubky,
@@ -25,13 +25,12 @@ pub async fn signup(
State(state): State,
user_agent: Option>,
cookies: Cookies,
- pubky: Pubky,
uri: Uri,
body: Bytes,
) -> Result {
// TODO: Verify invitation link.
// TODO: add errors in case of already axisting user.
- signin(State(state), user_agent, cookies, pubky, uri, body).await
+ signin(State(state), user_agent, cookies, uri, body).await
}
pub async fn session(
@@ -90,21 +89,16 @@ pub async fn signin(
State(state): State,
user_agent: Option>,
cookies: Cookies,
- pubky: Pubky,
uri: Uri,
body: Bytes,
) -> Result {
- let public_key = pubky.public_key();
+ let token = state.verifier.verify(&body)?;
- state.verifier.verify(&body, public_key)?;
+ let public_key = token.pubky();
let mut wtxn = state.db.env.write_txn()?;
- let users: UsersTable = state
- .db
- .env
- .open_database(&wtxn, Some(USERS_TABLE))?
- .expect("Users table already created");
+ let users = state.db.tables.users;
if let Some(existing) = users.get(&wtxn, public_key)? {
users.put(&mut wtxn, public_key, &existing)?;
} else {
@@ -119,21 +113,16 @@ pub async fn signin(
let session_secret = base32::encode(base32::Alphabet::Crockford, &random_bytes::<16>());
- let sessions: SessionsTable = state
+ let session = Session::new(&token, user_agent.map(|ua| ua.to_string())).serialize();
+
+ state
.db
- .env
- .open_database(&wtxn, Some(SESSIONS_TABLE))?
- .expect("Sessions table already created");
-
- let mut session = Session::new();
-
- if let Some(user_agent) = user_agent {
- session.set_user_agent(user_agent.to_string());
- }
-
- sessions.put(&mut wtxn, &session_secret, &session.serialize())?;
+ .tables
+ .sessions
+ .put(&mut wtxn, &session_secret, &session)?;
let mut cookie = Cookie::new(public_key.to_string(), session_secret);
+
cookie.set_path("/");
if *uri.scheme().unwrap_or(&Scheme::HTTP) == Scheme::HTTPS {
cookie.set_secure(true);
@@ -145,5 +134,5 @@ pub async fn signin(
wtxn.commit()?;
- Ok(())
+ Ok(session)
}
diff --git a/pubky-homeserver/src/routes/public.rs b/pubky-homeserver/src/routes/public.rs
index cdfb0a9..4cf2eed 100644
--- a/pubky-homeserver/src/routes/public.rs
+++ b/pubky-homeserver/src/routes/public.rs
@@ -26,8 +26,8 @@ pub async fn put(
let public_key = pubky.public_key().clone();
let path = path.as_str();
- authorize(&mut state, cookies, &public_key, path)?;
verify(path)?;
+ authorize(&mut state, cookies, &public_key, path)?;
let mut stream = body.into_data_stream();
@@ -134,20 +134,32 @@ pub async fn delete(
Ok(())
}
+/// Authorize write (PUT or DELETE) for Public paths.
fn authorize(
state: &mut AppState,
cookies: Cookies,
public_key: &PublicKey,
- _: &str,
+ path: &str,
) -> Result<()> {
// TODO: can we move this logic to the extractor or a layer
// to perform this validation?
- let _ = state
+ let session = state
.db
.get_session(cookies, public_key)?
.ok_or(Error::with_status(StatusCode::UNAUTHORIZED))?;
- Ok(())
+ if session.pubky() == public_key
+ && session.capabilities().iter().any(|cap| {
+ path.starts_with(&cap.scope[1..])
+ && cap
+ .actions
+ .contains(&pubky_common::capabilities::Action::Write)
+ })
+ {
+ return Ok(());
+ }
+
+ Err(Error::with_status(StatusCode::FORBIDDEN))
}
fn verify(path: &str) -> Result<()> {
diff --git a/pubky-homeserver/src/server.rs b/pubky-homeserver/src/server.rs
index cdc352c..c94a803 100644
--- a/pubky-homeserver/src/server.rs
+++ b/pubky-homeserver/src/server.rs
@@ -1,7 +1,7 @@
use std::{future::IntoFuture, net::SocketAddr};
use anyhow::{Error, Result};
-use pubky_common::auth::AuthnVerifier;
+use pubky_common::auth::AuthVerifier;
use tokio::{net::TcpListener, signal, task::JoinSet};
use tracing::{debug, info, warn};
@@ -21,7 +21,7 @@ pub struct Homeserver {
#[derive(Clone, Debug)]
pub(crate) struct AppState {
- pub verifier: AuthnVerifier,
+ pub verifier: AuthVerifier,
pub db: DB,
pub pkarr_client: PkarrClientAsync,
}
@@ -31,7 +31,6 @@ impl Homeserver {
debug!(?config);
let keypair = config.keypair();
- let public_key = keypair.public_key();
let db = DB::open(&config.storage()?)?;
@@ -46,7 +45,7 @@ impl Homeserver {
.as_async();
let state = AppState {
- verifier: AuthnVerifier::new(public_key.clone()),
+ verifier: AuthVerifier::default(),
db,
pkarr_client: pkarr_client.clone(),
};
@@ -75,7 +74,7 @@ impl Homeserver {
publish_server_packet(pkarr_client, &keypair, config.domain(), port).await?;
- info!("Homeserver listening on pubky://{public_key}");
+ info!("Homeserver listening on pubky://{}", keypair.public_key());
Ok(Self {
tasks,
diff --git a/pubky/Cargo.toml b/pubky/Cargo.toml
index 4c82cfb..6871377 100644
--- a/pubky/Cargo.toml
+++ b/pubky/Cargo.toml
@@ -14,17 +14,17 @@ crate-type = ["cdylib", "rlib"]
thiserror = "1.0.62"
wasm-bindgen = "0.2.92"
url = "2.5.2"
-bytes = "1.6.1"
+bytes = "^1.7.1"
+base64 = "0.22.1"
pubky-common = { version = "0.1.0", path = "../pubky-common" }
-argon2 = { version = "0.5.3", features = ["std"] }
+pkarr = { workspace = true, features = ["async"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
-pkarr = { version="2.1.0", features = ["async"] }
-reqwest = { version = "0.12.5", features = ["cookies"], default-features = false }
+reqwest = { version = "0.12.5", features = ["cookies", "rustls-tls"], default-features = false }
+tokio = { version = "1.37.0", features = ["full"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
-pkarr = { version = "2.1.0", default-features = false }
reqwest = { version = "0.12.5", default-features = false }
js-sys = "0.3.69"
diff --git a/pubky/pkg/README.md b/pubky/pkg/README.md
index 3fcbcd1..2228266 100644
--- a/pubky/pkg/README.md
+++ b/pubky/pkg/README.md
@@ -67,22 +67,6 @@ await client.delete(url);
let client = new PubkyClient()
```
-#### createRecoveryFile
-```js
-let recoveryFile = PubkyClient.createRecoveryFile(keypair, passphrase)
-```
-- keypair: An instance of [Keypair](#keypair).
-- passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/).
-- Returns: A recovery file with a spec line and an encrypted secret key.
-
-#### createRecoveryFile
-```js
-let keypair = PubkyClient.decryptRecoveryfile(recoveryFile, passphrase)
-```
-- recoveryFile: An instance of Uint8Array containing the recovery file blob.
-- passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/).
-- Returns: An instance of [Keypair](#keypair).
-
#### signup
```js
await client.signup(keypair, homeserver)
@@ -90,12 +74,58 @@ await client.signup(keypair, homeserver)
- keypair: An instance of [Keypair](#keypair).
- homeserver: An instance of [PublicKey](#publickey) representing the homeserver.
-#### session
+Returns:
+- session: An instance of [Session](#session).
+
+#### signin
+```js
+let session = await client.signin(keypair)
+```
+- keypair: An instance of [Keypair](#keypair).
+
+Returns:
+- An instance of [Session](#session).
+
+#### signout
+```js
+await client.signout(publicKey)
+```
+- publicKey: An instance of [PublicKey](#publicKey).
+
+#### authRequest
+```js
+let [pubkyauthUrl, sessionPromise] = client.authRequest(relay, capabilities);
+
+showQr(pubkyauthUrl);
+
+let session = await sessionPromise;
+```
+
+Sign in to a user's Homeserver, without access to their [Keypair](#keypair), nor even [PublicKey](#publickey),
+instead request permissions (showing the user pubkyauthUrl), and await a Session after the user consenting to that request.
+
+- relay: A URL to an [HTTP relay](https://httprelay.io/features/link/) endpoint.
+- capabilities: A list of capabilities required for the app for example `/pub/pubky.app/:rw,/pub/example.com/:r`.
+
+Returns:
+- pubkyauthUrl: A url to show to the user to scan or paste into an Authenticator app holding the user [Keypair](#keypair)
+- sessionPromise: A promise that resolves into a [Session](#session) on success.
+
+#### sendAuthToken
+```js
+await client.sendAuthToken(keypair, pubkyauthUrl);
+```
+Consenting to authentication or authorization according to the required capabilities in the `pubkyauthUrl` , and sign and send an auth token to the requester.
+
+- keypair: An instance of [KeyPair](#keypair)
+- pubkyauthUrl: A string `pubkyauth://` url
+
+#### session {#session-method}
```js
let session = await client.session(publicKey)
```
- publicKey: An instance of [PublicKey](#publickey).
-- Returns: A session object if signed in, or undefined if not.
+- Returns: A [Session](#session) object if signed in, or undefined if not.
#### put
```js
@@ -144,7 +174,7 @@ let keypair = Keypair.fromSecretKey(secretKey)
- Returns: A new Keypair.
-#### publicKey
+#### publicKey {#publickey-method}
```js
let publicKey = keypair.publicKey()
```
@@ -172,6 +202,38 @@ let pubky = publicKey.z32();
```
Returns: The z-base-32 encoded string representation of the PublicKey.
+### Session
+
+#### pubky
+```js
+let pubky = session.pubky();
+```
+Returns an instance of [PublicKey](#publickey)
+
+#### capabilities
+```js
+let capabilities = session.capabilities();
+```
+Returns an array of capabilities, for example `["/pub/pubky.app/:rw"]`
+
+### Helper functions
+
+#### createRecoveryFile
+```js
+let recoveryFile = createRecoveryFile(keypair, passphrase)
+```
+- keypair: An instance of [Keypair](#keypair).
+- passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/).
+- Returns: A recovery file with a spec line and an encrypted secret key.
+
+#### createRecoveryFile
+```js
+let keypair = decryptRecoveryfile(recoveryFile, passphrase)
+```
+- recoveryFile: An instance of Uint8Array containing the recovery file blob.
+- passphrase: A utf-8 string [passphrase](https://www.useapassphrase.com/).
+- Returns: An instance of [Keypair](#keypair).
+
## Test and Development
For test and development, you can run a local homeserver in a test network.
diff --git a/pubky/pkg/test/auth.js b/pubky/pkg/test/auth.js
index d193dfe..2207946 100644
--- a/pubky/pkg/test/auth.js
+++ b/pubky/pkg/test/auth.js
@@ -2,14 +2,15 @@ import test from 'tape'
import { PubkyClient, Keypair, PublicKey } from '../index.cjs'
+const Homeserver = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo')
+
test('auth', async (t) => {
const client = PubkyClient.testnet();
const keypair = Keypair.random()
const publicKey = keypair.publicKey()
- const homeserver = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo')
- await client.signup(keypair, homeserver)
+ await client.signup(keypair, Homeserver)
const session = await client.session(publicKey)
t.ok(session, "signup")
@@ -28,3 +29,35 @@ test('auth', async (t) => {
t.ok(session, "signin")
}
})
+
+test("3rd party signin", async (t) => {
+ let keypair = Keypair.random();
+ let pubky = keypair.publicKey().z32();
+
+ // Third party app side
+ let capabilities = "/pub/pubky.app/:rw,/pub/foo.bar/file:r";
+ let client = PubkyClient.testnet();
+ let [pubkyauth_url, pubkyauthResponse] = client
+ .authRequest("https://demo.httprelay.io/link", capabilities);
+
+ if (globalThis.document) {
+ // Skip `sendAuthToken` in browser
+ // TODO: figure out why does it fail in browser unit tests
+ // but not in real browser (check pubky-auth-widget.js commented part)
+ return
+ }
+
+ // Authenticator side
+ {
+ let client = PubkyClient.testnet();
+
+ await client.signup(keypair, Homeserver);
+
+ await client.sendAuthToken(keypair, pubkyauth_url)
+ }
+
+ let session = await pubkyauthResponse;
+
+ t.is(session.pubky().z32(), pubky)
+ t.deepEqual(session.capabilities(), capabilities.split(','))
+})
diff --git a/pubky/pkg/test/public.js b/pubky/pkg/test/public.js
index 6355ee4..ec30bb2 100644
--- a/pubky/pkg/test/public.js
+++ b/pubky/pkg/test/public.js
@@ -2,13 +2,14 @@ import test from 'tape'
import { PubkyClient, Keypair, PublicKey } from '../index.cjs'
+const Homeserver = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo');
+
test('public: put/get', async (t) => {
const client = PubkyClient.testnet();
const keypair = Keypair.random();
- const homeserver = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo');
- await client.signup(keypair, homeserver);
+ await client.signup(keypair, Homeserver);
const publicKey = keypair.publicKey();
@@ -46,8 +47,7 @@ test("not found", async (t) => {
const keypair = Keypair.random();
- const homeserver = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo');
- await client.signup(keypair, homeserver);
+ await client.signup(keypair, Homeserver);
const publicKey = keypair.publicKey();
@@ -64,8 +64,7 @@ test("unauthorized", async (t) => {
const keypair = Keypair.random()
const publicKey = keypair.publicKey()
- const homeserver = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo')
- await client.signup(keypair, homeserver)
+ await client.signup(keypair, Homeserver)
const session = await client.session(publicKey)
t.ok(session, "signup")
@@ -92,8 +91,7 @@ test("forbidden", async (t) => {
const keypair = Keypair.random()
const publicKey = keypair.publicKey()
- const homeserver = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo')
- await client.signup(keypair, homeserver)
+ await client.signup(keypair, Homeserver)
const session = await client.session(publicKey)
t.ok(session, "signup")
@@ -119,8 +117,7 @@ test("list", async (t) => {
const publicKey = keypair.publicKey()
const pubky = publicKey.z32()
- const homeserver = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo')
- await client.signup(keypair, homeserver)
+ await client.signup(keypair, Homeserver)
@@ -251,10 +248,7 @@ test('list shallow', async (t) => {
const publicKey = keypair.publicKey()
const pubky = publicKey.z32()
- const homeserver = PublicKey.from('8pinxxgqs41n4aididenw5apqp1urfmzdztr8jt4abrkdn435ewo')
- await client.signup(keypair, homeserver)
-
-
+ await client.signup(keypair, Homeserver)
let urls = [
`pubky://${pubky}/pub/a.com/a.txt`,
diff --git a/pubky/pkg/test/recovery.js b/pubky/pkg/test/recovery.js
index cf05160..0c033e5 100644
--- a/pubky/pkg/test/recovery.js
+++ b/pubky/pkg/test/recovery.js
@@ -1,11 +1,11 @@
import test from 'tape'
-import { PubkyClient, Keypair } from '../index.cjs'
+import { Keypair, createRecoveryFile, decryptRecoveryFile } from '../index.cjs'
test('recovery', async (t) => {
const keypair = Keypair.random();
- const recoveryFile = PubkyClient.createRecoveryFile(keypair, 'very secure password');
+ const recoveryFile = createRecoveryFile(keypair, 'very secure password');
t.is(recoveryFile.length, 91)
t.deepEqual(
@@ -13,7 +13,7 @@ test('recovery', async (t) => {
[112, 117, 98, 107, 121, 46, 111, 114, 103, 47, 114, 101, 99, 111, 118, 101, 114, 121, 10]
)
- const recovered = PubkyClient.decryptRecoveryFile(recoveryFile, 'very secure password')
+ const recovered = decryptRecoveryFile(recoveryFile, 'very secure password')
t.is(recovered.publicKey().z32(), keypair.publicKey().z32())
})
diff --git a/pubky/src/bin/patch.mjs b/pubky/src/bin/patch.mjs
index 0e9ebe9..a8ed503 100644
--- a/pubky/src/bin/patch.mjs
+++ b/pubky/src/bin/patch.mjs
@@ -20,17 +20,15 @@ const patched = content
.replace("require(`util`)", "globalThis")
// attach to `imports` instead of module.exports
.replace("= module.exports", "= imports")
-
- // add suffix Class
- .replace(/\nclass (.*?) \{/g, "\nclass $1Class {")
- .replace(/\nmodule\.exports\.(.*?) = (.*?);/g, "\nexport const $1 = imports.$1 = $1Class")
-
- // quick and dirty fix for a bug caused by the previous replace
- .replace(/__wasmClass/g, "wasm")
-
- .replace(/\nmodule\.exports\.(.*?)\s+/g, "\nexport const $1 = imports.$1 ")
+ // Export classes
+ .replace(/\nclass (.*?) \{/g, "\n export class $1 {")
+ // Export functions
+ .replace(/\nmodule.exports.(.*?) = function/g, "\nimports.$1 = $1;\nexport function $1")
+ // Add exports to 'imports'
+ .replace(/\nmodule\.exports\.(.*?)\s+/g, "\nimports.$1")
+ // Export default
.replace(/$/, 'export default imports')
- // inline bytes Uint8Array
+ // inline wasm bytes
.replace(
/\nconst path.*\nconst bytes.*\n/,
`
@@ -56,7 +54,7 @@ const bytes = __toBinary(${JSON.stringify(await readFile(path.join(__dirname, `.
`,
);
-await writeFile(path.join(__dirname, `../../pkg/browser.js`), patched);
+await writeFile(path.join(__dirname, `../../pkg/browser.js`), patched + "\nglobalThis['pubky'] = imports");
// Move outside of nodejs
diff --git a/pubky/src/error.rs b/pubky/src/error.rs
index 40eca3b..c8d80e1 100644
--- a/pubky/src/error.rs
+++ b/pubky/src/error.rs
@@ -15,19 +15,6 @@ pub enum Error {
#[error("Could not resolve endpoint for {0}")]
ResolveEndpoint(String),
- // === Recovery file ==
- #[error("Recovery file should start with a spec line, followed by a new line character")]
- RecoveryFileMissingSpecLine,
-
- #[error("Recovery file should start with a spec line, followed by a new line character")]
- RecoveryFileVersionNotSupported,
-
- #[error("Recovery file should contain an encrypted secret key after the new line character")]
- RecoverFileMissingEncryptedSecretKey,
-
- #[error("Recovery file encrypted secret key should be 32 bytes, got {0}")]
- RecoverFileInvalidSecretKeyLength(usize),
-
#[error("Could not convert the passed type into a Url")]
InvalidUrl,
@@ -51,7 +38,10 @@ pub enum Error {
Crypto(#[from] pubky_common::crypto::Error),
#[error(transparent)]
- Argon(#[from] argon2::Error),
+ RecoveryFile(#[from] pubky_common::recovery_file::Error),
+
+ #[error(transparent)]
+ AuthToken(#[from] pubky_common::auth::Error),
}
#[cfg(target_arch = "wasm32")]
diff --git a/pubky/src/native.rs b/pubky/src/native.rs
index c1835ae..ba0f086 100644
--- a/pubky/src/native.rs
+++ b/pubky/src/native.rs
@@ -1,19 +1,23 @@
use std::net::ToSocketAddrs;
use std::time::Duration;
-use ::pkarr::{mainline::dht::Testnet, PkarrClient, PublicKey, SignedPacket};
use bytes::Bytes;
-use pkarr::Keypair;
-use pubky_common::session::Session;
+use pubky_common::{
+ capabilities::Capabilities,
+ recovery_file::{create_recovery_file, decrypt_recovery_file},
+ session::Session,
+};
use reqwest::{RequestBuilder, Response};
+use tokio::sync::oneshot;
use url::Url;
+use pkarr::Keypair;
+
+use ::pkarr::{mainline::dht::Testnet, PkarrClient, PublicKey, SignedPacket};
+
use crate::{
- error::Result,
- shared::{
- list_builder::ListBuilder,
- recovery_file::{create_recovery_file, decrypt_recovery_file},
- },
+ error::{Error, Result},
+ shared::list_builder::ListBuilder,
PubkyClient,
};
@@ -84,6 +88,15 @@ impl PubkyClient {
PubkyClientBuilder::default()
}
+ /// Create a client connected to the local network
+ /// with the bootstrapping node: `localhost:6881`
+ pub fn testnet() -> Self {
+ Self::test(&Testnet {
+ bootstrap: vec!["localhost:6881".to_string()],
+ nodes: vec![],
+ })
+ }
+
/// Creates a [PubkyClient] with:
/// - DHT bootstrap nodes set to the `testnet` bootstrap nodes.
/// - DHT request timout set to 500 milliseconds. (unless in CI, then it is left as default 2000)
@@ -105,7 +118,7 @@ impl PubkyClient {
///
/// The homeserver is a Pkarr domain name, where the TLD is a Pkarr public key
/// for example "pubky.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy"
- pub async fn signup(&self, keypair: &Keypair, homeserver: &PublicKey) -> Result<()> {
+ pub async fn signup(&self, keypair: &Keypair, homeserver: &PublicKey) -> Result {
self.inner_signup(keypair, homeserver).await
}
@@ -123,7 +136,7 @@ impl PubkyClient {
}
/// Signin to a homeserver.
- pub async fn signin(&self, keypair: &Keypair) -> Result<()> {
+ pub async fn signin(&self, keypair: &Keypair) -> Result {
self.inner_signin(keypair).await
}
@@ -156,12 +169,58 @@ impl PubkyClient {
/// Create a recovery file of the `keypair`, containing the secret key encrypted
/// using the `passphrase`.
pub fn create_recovery_file(keypair: &Keypair, passphrase: &str) -> Result> {
- create_recovery_file(keypair, passphrase)
+ Ok(create_recovery_file(keypair, passphrase)?)
}
/// Recover a keypair from a recovery file by decrypting the secret key using `passphrase`.
pub fn decrypt_recovery_file(recovery_file: &[u8], passphrase: &str) -> Result {
- decrypt_recovery_file(recovery_file, passphrase)
+ Ok(decrypt_recovery_file(recovery_file, passphrase)?)
+ }
+
+ /// Return `pubkyauth://` url and wait for the incoming [AuthToken]
+ /// verifying that AuthToken, and if capabilities were requested, signing in to
+ /// the Pubky's homeserver and returning the [Session] information.
+ pub fn auth_request(
+ &self,
+ relay: impl TryInto,
+ capabilities: &Capabilities,
+ ) -> Result<(Url, tokio::sync::oneshot::Receiver