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` +
+ +
+
+

Scan or copy Pubky auth URL

+
+ +
+ +
+
+ ` + } + + _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` +
+ +
+
+

Scan or copy Pubky auth URL

+
+ +
+ +
+
+ ` + } + + 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>)> { + let mut relay: Url = relay + .try_into() + .map_err(|_| Error::Generic("Invalid relay Url".into()))?; + + let (pubkyauth_url, client_secret) = self.create_auth_request(&mut relay, capabilities)?; + + let (tx, rx) = oneshot::channel::>(); + + let this = self.clone(); + + tokio::spawn(async move { + let to_send = this + .subscribe_to_auth_response(relay, &client_secret) + .await?; + + tx.send(to_send) + .map_err(|_| Error::Generic("Failed to send the session after signing in with token, since the receiver is dropped".into()))?; + + Ok::<(), Error>(()) + }); + + Ok((pubkyauth_url, rx)) + } + + /// Sign an [pubky_common::auth::AuthToken], encrypt it and send it to the + /// source of the pubkyauth request url. + pub async fn send_auth_token>( + &self, + keypair: &Keypair, + pubkyauth_url: T, + ) -> Result<()> { + let url: Url = pubkyauth_url.try_into().map_err(|_| Error::InvalidUrl)?; + + self.inner_send_auth_token(keypair, url).await?; + + Ok(()) } } @@ -187,6 +246,6 @@ impl PubkyClient { self.http.request(method, url) } - pub(crate) fn store_session(&self, _: Response) {} + pub(crate) fn store_session(&self, _: &Response) {} pub(crate) fn remove_session(&self, _: &PublicKey) {} } diff --git a/pubky/src/shared/auth.rs b/pubky/src/shared/auth.rs index d4a6436..88c4259 100644 --- a/pubky/src/shared/auth.rs +++ b/pubky/src/shared/auth.rs @@ -1,9 +1,21 @@ +use std::collections::HashMap; + +use base64::{alphabet::URL_SAFE, engine::general_purpose::NO_PAD, Engine}; use reqwest::{Method, StatusCode}; +use url::Url; use pkarr::{Keypair, PublicKey}; -use pubky_common::{auth::AuthnSignature, session::Session}; +use pubky_common::{ + auth::AuthToken, + capabilities::{Capabilities, Capability}, + crypto::{decrypt, encrypt, hash, random_bytes}, + session::Session, +}; -use crate::{error::Result, PubkyClient}; +use crate::{ + error::{Error, Result}, + PubkyClient, +}; use super::pkarr::Endpoint; @@ -16,33 +28,28 @@ impl PubkyClient { &self, keypair: &Keypair, homeserver: &PublicKey, - ) -> Result<()> { + ) -> Result { let homeserver = homeserver.to_string(); - let public_key = &keypair.public_key(); + let Endpoint { mut url, .. } = self.resolve_endpoint(&homeserver).await?; - let Endpoint { - public_key: audience, - mut url, - } = self.resolve_endpoint(&homeserver).await?; + url.set_path("/signup"); - url.set_path(&format!("/{}", public_key)); - - let body = AuthnSignature::generate(keypair, &audience) - .as_bytes() - .to_owned(); + let body = AuthToken::sign(keypair, vec![Capability::root()]).serialize(); let response = self - .request(Method::PUT, url.clone()) + .request(Method::POST, url.clone()) .body(body) .send() .await?; - self.store_session(response); + self.store_session(&response); self.publish_pubky_homeserver(keypair, &homeserver).await?; - Ok(()) + let bytes = response.bytes().await?; + + Ok(Session::deserialize(&bytes)?) } /// Check the current sesison for a given Pubky in its homeserver. @@ -83,26 +90,141 @@ impl PubkyClient { } /// Signin to a homeserver. - pub(crate) async fn inner_signin(&self, keypair: &Keypair) -> Result<()> { - let pubky = keypair.public_key(); + pub(crate) async fn inner_signin(&self, keypair: &Keypair) -> Result { + let token = AuthToken::sign(keypair, vec![Capability::root()]); - let Endpoint { - public_key: audience, - mut url, - } = self.resolve_pubky_homeserver(&pubky).await?; + self.signin_with_authtoken(&token).await + } - url.set_path(&format!("/{}/session", &pubky)); + pub(crate) async fn inner_send_auth_token( + &self, + keypair: &Keypair, + pubkyauth_url: Url, + ) -> Result<()> { + let query_params: HashMap = + pubkyauth_url.query_pairs().into_owned().collect(); - let body = AuthnSignature::generate(keypair, &audience) - .as_bytes() - .to_owned(); + let relay = query_params + .get("relay") + .map(|r| url::Url::parse(r).expect("Relay query param to be valid URL")) + .expect("Missing relay query param"); - let response = self.request(Method::POST, url).body(body).send().await?; + let client_secret = query_params + .get("secret") + .map(|s| { + let engine = base64::engine::GeneralPurpose::new(&URL_SAFE, NO_PAD); + let bytes = engine.decode(s).expect("invalid client_secret"); + let arr: [u8; 32] = bytes.try_into().expect("invalid client_secret"); - self.store_session(response); + arr + }) + .expect("Missing client secret"); + + let capabilities = query_params + .get("caps") + .map(|caps_string| { + caps_string + .split(',') + .filter_map(|cap| Capability::try_from(cap).ok()) + .collect::>() + }) + .unwrap_or_default(); + + let token = AuthToken::sign(keypair, capabilities); + + let encrypted_token = encrypt(&token.serialize(), &client_secret)?; + + let engine = base64::engine::GeneralPurpose::new(&URL_SAFE, NO_PAD); + + let mut callback = relay.clone(); + let mut path_segments = callback.path_segments_mut().unwrap(); + path_segments.pop_if_empty(); + let channel_id = engine.encode(hash(&client_secret).as_bytes()); + path_segments.push(&channel_id); + drop(path_segments); + + self.request(Method::POST, callback) + .body(encrypted_token) + .send() + .await?; Ok(()) } + + pub async fn inner_third_party_signin( + &self, + encrypted_token: &[u8], + client_secret: &[u8; 32], + ) -> Result { + let decrypted = decrypt(encrypted_token, client_secret)?; + let token = AuthToken::deserialize(&decrypted)?; + + self.signin_with_authtoken(&token).await?; + + Ok(token.pubky().to_owned()) + } + + pub async fn signin_with_authtoken(&self, token: &AuthToken) -> Result { + let mut url = Url::parse(&format!("https://{}/session", token.pubky()))?; + + self.resolve_url(&mut url).await?; + + let response = self + .request(Method::POST, url) + .body(token.serialize()) + .send() + .await?; + + self.store_session(&response); + + let bytes = response.bytes().await?; + + Ok(Session::deserialize(&bytes)?) + } + + pub(crate) fn create_auth_request( + &self, + relay: &mut Url, + capabilities: &Capabilities, + ) -> Result<(Url, [u8; 32])> { + let engine = base64::engine::GeneralPurpose::new(&URL_SAFE, NO_PAD); + + let client_secret: [u8; 32] = random_bytes::<32>(); + + let pubkyauth_url = Url::parse(&format!( + "pubkyauth:///?caps={capabilities}&secret={}&relay={relay}", + engine.encode(client_secret) + ))?; + + let mut segments = relay + .path_segments_mut() + .map_err(|_| Error::Generic("Invalid relay".into()))?; + // remove trailing slash if any. + segments.pop_if_empty(); + let channel_id = &engine.encode(hash(&client_secret).as_bytes()); + segments.push(channel_id); + drop(segments); + + Ok((pubkyauth_url, client_secret)) + } + + pub(crate) async fn subscribe_to_auth_response( + &self, + relay: Url, + client_secret: &[u8; 32], + ) -> Result> { + let response = self.http.request(Method::GET, relay).send().await?; + let encrypted_token = response.bytes().await?; + let token_bytes = decrypt(&encrypted_token, client_secret)?; + let token = AuthToken::verify(&token_bytes)?; + + if token.capabilities().is_empty() { + Ok(None) + } else { + let session = self.signin_with_authtoken(&token).await?; + Ok(Some(session)) + } + } } #[cfg(test)] @@ -111,8 +233,9 @@ mod tests { use crate::*; use pkarr::{mainline::Testnet, Keypair}; - use pubky_common::session::Session; + use pubky_common::capabilities::{Capabilities, Capability}; use pubky_homeserver::Homeserver; + use reqwest::StatusCode; #[tokio::test] async fn basic_authn() { @@ -131,7 +254,7 @@ mod tests { .unwrap() .unwrap(); - assert_eq!(session, Session { ..session.clone() }); + assert!(session.capabilities().contains(&Capability::root())); client.signout(&keypair.public_key()).await.unwrap(); @@ -150,7 +273,71 @@ mod tests { .unwrap() .unwrap(); - assert_eq!(session, Session { ..session.clone() }); + assert_eq!(session.pubky(), &keypair.public_key()); + assert!(session.capabilities().contains(&Capability::root())); } } + + #[tokio::test] + async fn authz() { + let testnet = Testnet::new(10); + let server = Homeserver::start_test(&testnet).await.unwrap(); + + let keypair = Keypair::random(); + let pubky = keypair.public_key(); + + // Third party app side + let capabilities: Capabilities = + "/pub/pubky.app/:rw,/pub/foo.bar/file:r".try_into().unwrap(); + let client = PubkyClient::test(&testnet); + let (pubkyauth_url, pubkyauth_response) = client + .auth_request("https://demo.httprelay.io/link", &capabilities) + .unwrap(); + + // Authenticator side + { + let client = PubkyClient::test(&testnet); + + client.signup(&keypair, &server.public_key()).await.unwrap(); + + client + .send_auth_token(&keypair, pubkyauth_url) + .await + .unwrap(); + } + + let session = pubkyauth_response.await.unwrap().unwrap(); + + assert_eq!(session.pubky(), &pubky); + assert_eq!(session.capabilities(), &capabilities.0); + + // Test access control enforcement + + client + .put(format!("pubky://{pubky}/pub/pubky.app/foo").as_str(), &[]) + .await + .unwrap(); + + assert_eq!( + client + .put(format!("pubky://{pubky}/pub/pubky.app").as_str(), &[]) + .await + .map_err(|e| match e { + crate::Error::Reqwest(e) => e.status(), + _ => None, + }), + Err(Some(StatusCode::FORBIDDEN)) + ); + + assert_eq!( + client + .put(format!("pubky://{pubky}/pub/foo.bar/file").as_str(), &[]) + .await + .map_err(|e| match e { + crate::Error::Reqwest(e) => e.status(), + _ => None, + }), + Err(Some(StatusCode::FORBIDDEN)) + ); + } } diff --git a/pubky/src/shared/mod.rs b/pubky/src/shared/mod.rs index 550cc6e..67b456f 100644 --- a/pubky/src/shared/mod.rs +++ b/pubky/src/shared/mod.rs @@ -2,4 +2,3 @@ pub mod auth; pub mod list_builder; pub mod pkarr; pub mod public; -pub mod recovery_file; diff --git a/pubky/src/shared/pkarr.rs b/pubky/src/shared/pkarr.rs index e624a2a..d01eded 100644 --- a/pubky/src/shared/pkarr.rs +++ b/pubky/src/shared/pkarr.rs @@ -132,9 +132,15 @@ impl PubkyClient { continue; }; } + } else { + break; } } + if PublicKey::try_from(origin.as_str()).is_ok() { + return Err(Error::ResolveEndpoint(original_target.into())); + } + if let Some(public_key) = endpoint_public_key { let url = Url::parse(&format!( "{}://{}", @@ -151,10 +157,23 @@ impl PubkyClient { Err(Error::ResolveEndpoint(original_target.into())) } + + pub(crate) async fn resolve_url(&self, url: &mut Url) -> Result<()> { + if let Some(Ok(pubky)) = url.host_str().map(PublicKey::try_from) { + let Endpoint { url: x, .. } = self.resolve_endpoint(&format!("_pubky.{pubky}")).await?; + + url.set_host(x.host_str())?; + url.set_port(x.port()).expect("should work!"); + url.set_scheme(x.scheme()).expect("should work!"); + }; + + Ok(()) + } } #[derive(Debug)] pub(crate) struct Endpoint { + // TODO: we don't use this at all? pub public_key: PublicKey, pub url: Url, } diff --git a/pubky/src/wasm.rs b/pubky/src/wasm.rs index 536949f..09dc045 100644 --- a/pubky/src/wasm.rs +++ b/pubky/src/wasm.rs @@ -4,19 +4,19 @@ use std::{ }; use js_sys::{Array, Uint8Array}; -use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; +use wasm_bindgen::prelude::*; -use reqwest::{IntoUrl, Method, RequestBuilder, Response}; use url::Url; -use crate::{ - shared::recovery_file::{create_recovery_file, decrypt_recovery_file}, - PubkyClient, -}; +use pubky_common::capabilities::Capabilities; + +use crate::error::Error; +use crate::PubkyClient; mod http; mod keys; mod pkarr; +mod recovery_file; mod session; use keys::{Keypair, PublicKey}; @@ -53,30 +53,6 @@ impl PubkyClient { } } - /// Create a recovery file of the `keypair`, containing the secret key encrypted - /// using the `passphrase`. - #[wasm_bindgen(js_name = "createRecoveryFile")] - pub fn create_recovery_file( - keypair: &Keypair, - passphrase: &str, - ) -> Result { - create_recovery_file(keypair.as_inner(), passphrase) - .map(|b| b.as_slice().into()) - .map_err(|e| e.into()) - } - - /// Create a recovery file of the `keypair`, containing the secret key encrypted - /// using the `passphrase`. - #[wasm_bindgen(js_name = "decryptRecoveryFile")] - pub fn decrypt_recovery_file( - recovery_file: &[u8], - passphrase: &str, - ) -> Result { - decrypt_recovery_file(recovery_file, passphrase) - .map(Keypair::from) - .map_err(|e| e.into()) - } - /// Set Pkarr relays used for publishing and resolving Pkarr packets. /// /// By default, [PubkyClient] will use `["https://relay.pkarr.org"]` @@ -97,10 +73,16 @@ impl PubkyClient { /// The homeserver is a Pkarr domain name, where the TLD is a Pkarr public key /// for example "pubky.o4dksfbqk85ogzdb5osziw6befigbuxmuxkuxq8434q89uj56uyy" #[wasm_bindgen] - pub async fn signup(&self, keypair: &Keypair, homeserver: &PublicKey) -> Result<(), JsValue> { - self.inner_signup(keypair.as_inner(), homeserver.as_inner()) - .await - .map_err(|e| e.into()) + pub async fn signup( + &self, + keypair: &Keypair, + homeserver: &PublicKey, + ) -> Result { + Ok(Session( + self.inner_signup(keypair.as_inner(), homeserver.as_inner()) + .await + .map_err(|e| JsValue::from(e))?, + )) } /// Check the current sesison for a given Pubky in its homeserver. @@ -123,14 +105,73 @@ impl PubkyClient { .map_err(|e| e.into()) } - /// Signin to a homeserver. + /// Signin to a homeserver using the root Keypair. #[wasm_bindgen] pub async fn signin(&self, keypair: &Keypair) -> Result<(), JsValue> { self.inner_signin(keypair.as_inner()) .await + .map(|_| ()) .map_err(|e| e.into()) } + /// 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. + /// + /// Returns a tuple of [pubkyAuthUrl, Promise] + #[wasm_bindgen(js_name = "authRequest")] + pub fn auth_request(&self, relay: &str, capabilities: &str) -> Result { + let mut relay: Url = relay + .try_into() + .map_err(|_| Error::Generic("Invalid relay Url".into()))?; + + let (pubkyauth_url, client_secret) = self.create_auth_request( + &mut relay, + &Capabilities::try_from(capabilities).map_err(|_| "Invalid capaiblities")?, + )?; + + let this = self.clone(); + + let future = async move { + this.subscribe_to_auth_response(relay, &client_secret) + .await + .map(|opt| { + opt.map_or_else( + || JsValue::NULL, // Convert `None` to `JsValue::NULL` + |session| JsValue::from(Session(session)), + ) + }) + .map_err(|err| JsValue::from_str(&format!("{:?}", err))) + }; + + let promise = wasm_bindgen_futures::future_to_promise(future); + + // Return the URL and the promise + let js_tuple = js_sys::Array::new(); + js_tuple.push(&JsValue::from_str(pubkyauth_url.as_ref())); + js_tuple.push(&promise); + + Ok(js_tuple) + } + + /// Sign an [pubky_common::auth::AuthToken], encrypt it and send it to the + /// source of the pubkyauth request url. + #[wasm_bindgen(js_name = "sendAuthToken")] + pub async fn send_auth_token( + &self, + keypair: &Keypair, + pubkyauth_url: &str, + ) -> Result<(), JsValue> { + let pubkyauth_url: Url = pubkyauth_url + .try_into() + .map_err(|_| Error::Generic("Invalid relay Url".into()))?; + + self.inner_send_auth_token(keypair.as_inner(), pubkyauth_url) + .await?; + + Ok(()) + } + // === Public data === #[wasm_bindgen] diff --git a/pubky/src/wasm/http.rs b/pubky/src/wasm/http.rs index d89d4ce..61fee29 100644 --- a/pubky/src/wasm/http.rs +++ b/pubky/src/wasm/http.rs @@ -3,8 +3,6 @@ use crate::PubkyClient; use reqwest::{Method, RequestBuilder, Response}; use url::Url; -use ::pkarr::PublicKey; - impl PubkyClient { pub(crate) fn request(&self, method: Method, url: Url) -> RequestBuilder { let mut request = self.http.request(method, url).fetch_credentials_include(); @@ -18,7 +16,7 @@ impl PubkyClient { // Support cookies for nodejs - pub(crate) fn store_session(&self, response: Response) { + pub(crate) fn store_session(&self, response: &Response) { if let Some(cookie) = response .headers() .get("set-cookie") diff --git a/pubky/src/wasm/keys.rs b/pubky/src/wasm/keys.rs index 12ecdd7..3b27045 100644 --- a/pubky/src/wasm/keys.rs +++ b/pubky/src/wasm/keys.rs @@ -21,7 +21,7 @@ impl Keypair { } let len = secret_key.byte_length(); - if (len != 32) { + if len != 32 { return Err(format!("Expected secret_key to be 32 bytes, got {len}"))?; } @@ -57,7 +57,7 @@ impl From for Keypair { } #[wasm_bindgen] -pub struct PublicKey(pkarr::PublicKey); +pub struct PublicKey(pub(crate) pkarr::PublicKey); #[wasm_bindgen] impl PublicKey { @@ -91,3 +91,9 @@ impl PublicKey { &self.0 } } + +impl From for PublicKey { + fn from(value: pkarr::PublicKey) -> Self { + PublicKey(value) + } +} diff --git a/pubky/src/wasm/recovery_file.rs b/pubky/src/wasm/recovery_file.rs new file mode 100644 index 0000000..7b85178 --- /dev/null +++ b/pubky/src/wasm/recovery_file.rs @@ -0,0 +1,24 @@ +use js_sys::Uint8Array; +use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; + +use crate::error::Error; + +use super::keys::Keypair; + +/// Create a recovery file of the `keypair`, containing the secret key encrypted +/// using the `passphrase`. +#[wasm_bindgen(js_name = "createRecoveryFile")] +pub fn create_recovery_file(keypair: &Keypair, passphrase: &str) -> Result { + pubky_common::recovery_file::create_recovery_file(keypair.as_inner(), passphrase) + .map(|b| b.as_slice().into()) + .map_err(|e| Error::from(e).into()) +} + +/// Create a recovery file of the `keypair`, containing the secret key encrypted +/// using the `passphrase`. +#[wasm_bindgen(js_name = "decryptRecoveryFile")] +pub fn decrypt_recovery_file(recovery_file: &[u8], passphrase: &str) -> Result { + pubky_common::recovery_file::decrypt_recovery_file(recovery_file, passphrase) + .map(Keypair::from) + .map_err(|e| Error::from(e).into()) +} diff --git a/pubky/src/wasm/session.rs b/pubky/src/wasm/session.rs index ec2e8ca..e838a80 100644 --- a/pubky/src/wasm/session.rs +++ b/pubky/src/wasm/session.rs @@ -2,5 +2,26 @@ use pubky_common::session; use wasm_bindgen::prelude::*; +use super::keys::PublicKey; + #[wasm_bindgen] pub struct Session(pub(crate) session::Session); + +#[wasm_bindgen] +impl Session { + /// Return the [PublicKey] of this session + #[wasm_bindgen] + pub fn pubky(&self) -> PublicKey { + self.0.pubky().clone().into() + } + + /// Return the capabilities that this session has. + #[wasm_bindgen] + pub fn capabilities(&self) -> Vec { + self.0 + .capabilities() + .iter() + .map(|c| c.to_string()) + .collect() + } +}