From 20b12f3c60f85ff5759abab2143edb5da3d90b83 Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 2 Jul 2026 22:39:45 +0200 Subject: [PATCH] (feat): migrate to rust --- .cargo/config.toml | 2 + Cargo.lock | 2654 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 22 + README.md | 244 +++- bun.lock | 219 --- dist/crypto.d.ts | 18 - dist/crypto.js | 331 ----- dist/index.d.ts | 87 -- dist/index.html | 459 ------- dist/index.js | 219 --- dist/schema.d.ts | 33 - dist/schema.js | 35 - dist/user.d.ts | 13 - dist/user.js | 18 - package.json | 34 - src/crypto.ts | 439 ------ src/index.ts | 338 ----- src/lib.rs | 507 +++++++ src/schema.ts | 37 - src/user.ts | 34 - test/crypto.test.ts | 62 - test/index-helpers.test.ts | 39 - test/schema.test.ts | 39 - test/test-client.test.ts | 136 -- test/user.test.ts | 82 -- tsconfig.build.json | 18 - tsconfig.json | 32 - type-maps.yaml | 11 + 28 files changed, 3376 insertions(+), 2786 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 Cargo.lock create mode 100644 Cargo.toml delete mode 100644 bun.lock delete mode 100644 dist/crypto.d.ts delete mode 100644 dist/crypto.js delete mode 100644 dist/index.d.ts delete mode 100644 dist/index.html delete mode 100644 dist/index.js delete mode 100644 dist/schema.d.ts delete mode 100644 dist/schema.js delete mode 100644 dist/user.d.ts delete mode 100644 dist/user.js delete mode 100644 package.json delete mode 100644 src/crypto.ts delete mode 100644 src/index.ts create mode 100644 src/lib.rs delete mode 100644 src/schema.ts delete mode 100644 src/user.ts delete mode 100644 test/crypto.test.ts delete mode 100644 test/index-helpers.test.ts delete mode 100644 test/schema.test.ts delete mode 100644 test/test-client.test.ts delete mode 100644 test/user.test.ts delete mode 100644 tsconfig.build.json delete mode 100644 tsconfig.json create mode 100644 type-maps.yaml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..d363b83 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +MTP_TYPE_MAPS = { value = "type-maps.yaml", relative = true } diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9d0eb0b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2654 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[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 = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", + "zeroize", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "getrandom 0.4.3", + "hybrid-array", + "rand_core 0.10.1", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "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 = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +dependencies = [ + "const-oid 0.10.2", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8 0.10.2", + "signature 2.2.0", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "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", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hkdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + +[[package]] +name = "httlib-huffman" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "ctutils", + "typenum", +] + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "ml-dsa" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d" +dependencies = [ + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", + "hybrid-array", + "module-lattice", + "pkcs8 0.11.0", + "shake", + "signature 3.0.0", +] + +[[package]] +name = "mlkem-rs" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b0965b8b081668ff0398dc5e9dc3f2ebb9e833393f4ab5b9f725ddce11acef8" +dependencies = [ + "rand_core 0.6.4", + "serde", + "sha3", + "subtle", + "zeroize", +] + +[[package]] +name = "mlkem-tls" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b205d031298adf904d88efd6a57862d8650a4ab754aade19a9b5e87040bf4e" +dependencies = [ + "mlkem-rs", + "rand_core 0.6.4", + "subtle", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "module-lattice" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" +dependencies = [ + "ctutils", + "hybrid-array", + "num-traits", +] + +[[package]] +name = "mtp" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp.git#5bfcccc056a2a491a72e4315aa95effb2243ad3c" +dependencies = [ + "mtp-client", + "mtp-codec", + "mtp-common", + "mtp-crypto", + "mtp-host", + "mtp-transport", + "mtp-type-map", +] + +[[package]] +name = "mtp-client" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp.git#5bfcccc056a2a491a72e4315aa95effb2243ad3c" +dependencies = [ + "mtp-codec", + "mtp-common", + "mtp-crypto", + "mtp-transport", + "rand 0.8.6", + "tokio", +] + +[[package]] +name = "mtp-codec" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp.git#5bfcccc056a2a491a72e4315aa95effb2243ad3c" +dependencies = [ + "base64", + "byteorder", + "mtp-common", + "mtp-crypto", + "mtp-type-map", + "rand 0.8.6", +] + +[[package]] +name = "mtp-common" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp.git#5bfcccc056a2a491a72e4315aa95effb2243ad3c" +dependencies = [ + "quinn", + "rustls", + "thiserror 2.0.18", + "wtransport", +] + +[[package]] +name = "mtp-crypto" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp.git#5bfcccc056a2a491a72e4315aa95effb2243ad3c" +dependencies = [ + "chacha20poly1305", + "ed25519-dalek", + "getrandom 0.4.3", + "hkdf", + "ml-dsa", + "mlkem-tls", + "rand_core 0.6.4", + "serde", + "sha2 0.11.0", + "thiserror 1.0.69", + "zeroize", +] + +[[package]] +name = "mtp-host" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp.git#5bfcccc056a2a491a72e4315aa95effb2243ad3c" +dependencies = [ + "mtp-codec", + "mtp-common", + "mtp-crypto", + "mtp-transport", + "rand 0.8.6", + "tokio", +] + +[[package]] +name = "mtp-transport" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp.git#5bfcccc056a2a491a72e4315aa95effb2243ad3c" +dependencies = [ + "log", + "mtp-codec", + "mtp-common", + "rustls", + "rustls-native-certs", + "tokio", + "wtransport", +] + +[[package]] +name = "mtp-type-map" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp.git#5bfcccc056a2a491a72e4315aa95effb2243ad3c" +dependencies = [ + "serde", + "serde_yaml", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "octets" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8311fa8ab7a57759b4ff1f851a3048d9ef0effaa0130726426b742d26d8a88e7" + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.10", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der 0.8.0", + "spki 0.8.0", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[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.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[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 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rcgen" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" +dependencies = [ + "aws-lc-rs", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustls" +version = "0.23.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[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 = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest 0.10.7", + "keccak 0.1.6", +] + +[[package]] +name = "shake" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", + "sponge-cursor", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest 0.11.3", + "rand_core 0.10.1", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.10", +] + +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der 0.8.0", +] + +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[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.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tauth-sdk" +version = "0.1.0" +dependencies = [ + "base64", + "mtp", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "url", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +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.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[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.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +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.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[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.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf85cb06032201fa7c6f829d7db5a7e5aa45bcc0655327713065f6f0576731bf" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[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.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[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 0.52.6", + "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-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wtransport" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4aacf790813ee1956751491800537f4e04af7557b7b370501ccbfbc85963e4" +dependencies = [ + "bytes", + "pem", + "quinn", + "rcgen", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "sha2 0.11.0", + "socket2", + "thiserror 2.0.18", + "time", + "tokio", + "tracing", + "url", + "wtransport-proto", + "x509-parser", +] + +[[package]] +name = "wtransport-proto" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" +dependencies = [ + "httlib-huffman", + "octets", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "aws-lc-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "yasna" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" +dependencies = [ + "bit-vec", + "time", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..792ac7b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "tauth-sdk" +version = "0.1.0" +edition = "2024" +license = "UNLICENSED" + +[lib] +name = "tauth_sdk" +path = "src/lib.rs" + +[dependencies] +base64 = "0.22" +mtp = { git = "https://git.methanium.net/methanium/mtp.git", features = ["client", "crypto"] } +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +thiserror = "2" +tokio = { version = "1", features = ["rt", "time"] } +url = "2" + +[dev-dependencies] +tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] } diff --git a/README.md b/README.md index 36fb848..7e4242d 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,190 @@ -# TypeScript TAuth SDK +# TAuth SDK -TypeScript SDK for TAuth-based login and explicit transport session management. +Rust library for integrating applications with TAuth and MTP. -This SDK starts a local HTTP callback server, verifies a challenge, and gives you explicit control over TTP connections per `userId:sessionId`. +The SDK handles app key material, TAuth login URLs, callback parsing, Omikron lookup, authenticated MTP connections, and loading/saving user-specific app data. ## Requirements -- A modern version of Bun -- A domain with TXT record support -- App x448 key pair (`privateKey`, `publicKey`) (can be generated) +- Rust 1.95+ +- A TAuth app identifier, usually your app domain +- An MTP key pair generated by this library +- A public callback URL in your application ## Install +```toml +[dependencies] +tauth-sdk = { git = "https://git.methanium.net/tensamin/tauth-sdk.git" } +serde = { version = "1", features = ["derive"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +``` + +## Generate App Keys + +Run this once, store the private key securely, and publish the public key where TAuth expects app verification data. + +```rust +use tauth_sdk::generate_key_pair; + +fn main() { + let keys = generate_key_pair(); + + println!("private: {}", keys.private); + println!("public: {}", keys.public); +} +``` + +Keys are base64-encoded MTP key material. Do not expose the private key. + +## Create A Client + +```rust +use tauth_sdk::{TAuthClient, TAuthConfig}; + +let client = TAuthClient::from_config( + TAuthConfig::new( + "my.cool.app", + "https://my.cool.app/tauth/callback", + std::env::var("TAUTH_PRIVATE_KEY")?, + ) + .frontend_url("https://tauth.tensamin.net/login"), +)?; +``` + +For the default TAuth frontend, this shorter form is equivalent: + +```rust +let client = TAuthClient::new( + "my.cool.app", + "https://my.cool.app/tauth/callback", + std::env::var("TAUTH_PRIVATE_KEY")?, +)?; +``` + +## Start Login + +Redirect the user to `client.auth_url(None)` from your web handler. + +```rust +let login_url = client.auth_url(None); +``` + +The URL includes: + +- `identifier`: your app identifier +- `redirect`: your callback URL +- `public_key`: your app MTP public key bundle +- `challenge`: optional caller-supplied challenge + +## Handle Callback + +In your callback route, parse the full callback URL and persist the returned `user_id` and `session_id` for your user. + +```rust +let callback = client.parse_callback(full_callback_url)?; + +save_session(callback.user_id, callback.session_id).await?; +``` + +`AuthCallback` contains: + +```rust +pub struct AuthCallback { + pub user_id: u64, + pub session_id: u64, + pub challenge: Option, + pub original_challenge: Option, + pub host_public_key: Option, +} +``` + +## Connect To TAuth Data Over MTP + +If the callback or your application already has the Omikron host public key, pass it explicitly: + +```rust +let session = client + .connect_session(callback.user_id, callback.session_id, host_public_key) + .await?; +``` + +If the Omikron API returns a `public_key`, the SDK can look it up automatically: + +```rust +let session = client.session_from_callback(&callback).await?; +``` + +The connection uses MTP authenticated login with your app keyring and the Omikron host public key. + +## Load And Save App Data + +Use serde types for application data: + +```rust +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +struct AppData { + name: String, + value: String, +} + +let data: AppData = session.load().await?; + +session + .save(&AppData { + name: "example".into(), + value: "updated".into(), + }) + .await?; +``` + +Raw JSON is also supported: + +```rust +let data = session.load_json().await?; +session.save_json(&data).await?; +``` + +## Other Helpers + +Fetch a TAuth user profile: + +```rust +let user = client.user(callback.user_id).await?; +``` + +Connect directly to a known Omikron URL: + +```rust +let connection = client + .connect_to_omikron(user_id, session_id, "https://omikron.example:4433", host_public_key) + .await?; +``` + +Use lower-level MTP connection helpers when needed: + +```rust +let value = tauth_sdk::load_json(&connection).await?; +tauth_sdk::save_json(&connection, &value).await?; +``` + +## MTP Type Map + +This crate compiles an MTP type map for TAuth app data messages: + +- `LoadAppData` +- `LoadAppDataResponse` +- `SaveAppData` +- `SaveAppDataResponse` +- `AppData` + +The map lives in `type-maps.yaml` and is wired through `.cargo/config.toml` using `MTP_TYPE_MAPS`. + +## Development + ```bash -bun add https://git.methanium.net/tensamin/tauth-sdk/archive/0.0.3.tar.gz +cargo test +cargo build --release ``` - -## Generate an app key pair - -Use the built-in helper once and store the keys securely: - -```ts -import { generateKeyPair } from "@tensamin/tauth-sdk"; - -const keys = generateKeyPair(); -console.log(keys.private); -console.log(keys.public); -``` - -## Add the TXT record - -Add a TXT record at `tauth.your.domain` with your app public key as the value (base64 format). This is used to verify that your app is authorized for your domain. - -## Basic usage - -```ts -import z from "zod"; -import { TAuthClient } from "@tensamin/tauth-sdk"; - -const client = new TAuthClient({ - identifier: "my.cool.app", - privateKey: "", - publicKey: "", - saveSession: async (userId, sessionId) => { - // You must persist the session id yourself (for example in a database). - // Treat the session id like a password! - console.log("Save session", { userId, sessionId }); - }, - redirectUrl: "https://my.cool.app/callback", // This should be a public link proxying http://localhost:7878/callback - appData: z.object({ - my: z.string(), - cool: z.string(), - data: z.string(), - }), - httpServer: { - hostname: "localhost", - port: 7878, - }, -}); -``` - -## Auth endpoints exposed by the SDK - -- `GET /auth`: Redirects user to TAuth frontend -- `GET /callback`: Handles challenge flow and invokes your `saveSession(userId, sessionId)` callback - -## Connection management - -- Use `client.createTTP(userId, sessionId, omikronUrl)` when you want a persistent/manual TTP connection. -- Use `client.loadData(userId, sessionId)` for loading user specific application data. -- Use `client.saveData(userId, sessionId)` for saving user specific application data. - -These endpoints need to be exposed behind some kind of http proxy to apply ssl diff --git a/bun.lock b/bun.lock deleted file mode 100644 index 1a06ee7..0000000 --- a/bun.lock +++ /dev/null @@ -1,219 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "name": "tauth-sdk", - "dependencies": { - "@noble/curves": "^2.2.0", - "@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.9.tar.gz", - "@webtransport-bun/webtransport": "^0.3.0", - "zod": "^4.3.6", - }, - "devDependencies": { - "@types/bun": "^1.3.12", - "@types/node": "latest", - }, - "peerDependencies": { - "typescript": "^5", - }, - }, - }, - "packages": { - "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], - - "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], - - "@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="], - - "@eslint/config-helpers": ["@eslint/config-helpers@0.5.5", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w=="], - - "@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="], - - "@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="], - - "@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="], - - "@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.1", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ=="], - - "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], - - "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], - - "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], - - "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], - - "@noble/curves": ["@noble/curves@2.2.0", "", { "dependencies": { "@noble/hashes": "2.2.0" } }, "sha512-T/BoHgFXirb0ENSPBquzX0rcjXeM6Lo892a2jlYJkqk83LqZx0l1Of7DzlKJ6jkpvMrkHSnAcgb5JegL8SeIkQ=="], - - "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], - - "@tensamin/ttp-core": ["@tensamin/ttp-core@https://git.methanium.net/tensamin/ttp/archive/0.0.9.tar.gz", { "dependencies": { "@eslint/js": "^10.0.1", "@typescript-eslint/parser": "^8.58.1", "@webtransport-bun/webtransport": "^0.3.0", "globals": "^17.5.0", "typescript": "^6.0.2", "typescript-eslint": "^8.58.1", "zod": "^4.3.6" } }, "sha512-V9EK/t/tcBe1oNqzUMxtn41wc3UIS0mkLvGeR1mGfZaKn4d9bwwWQ2VmSmRSxodPYiQUJkziJV4fZvdzGjnzFw=="], - - "@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="], - - "@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="], - - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - - "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], - - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.58.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/type-utils": "8.58.1", "@typescript-eslint/utils": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.58.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ=="], - - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.58.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/types": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw=="], - - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.58.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.58.1", "@typescript-eslint/types": "^8.58.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g=="], - - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.58.1", "", { "dependencies": { "@typescript-eslint/types": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1" } }, "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w=="], - - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.58.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw=="], - - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.58.1", "", { "dependencies": { "@typescript-eslint/types": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1", "@typescript-eslint/utils": "8.58.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w=="], - - "@typescript-eslint/types": ["@typescript-eslint/types@8.58.1", "", {}, "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw=="], - - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.58.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.58.1", "@typescript-eslint/tsconfig-utils": "8.58.1", "@typescript-eslint/types": "8.58.1", "@typescript-eslint/visitor-keys": "8.58.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg=="], - - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.58.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.1", "@typescript-eslint/types": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ=="], - - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.1", "", { "dependencies": { "@typescript-eslint/types": "8.58.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ=="], - - "@webtransport-bun/webtransport": ["@webtransport-bun/webtransport@0.3.0", "", { "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ] }, "sha512-/OX/TCgBD64n/0BjMNytObq5NK2pAM0KOoXBEw49l3sTmbVXNTPP1ZRPIdIyV43agM+mKtZBaV3Er3k3YoB6sw=="], - - "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], - - "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], - - "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], - - "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - - "brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], - - "bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], - - "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "eslint": ["eslint@10.2.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.4", "@eslint/config-helpers": "^0.5.4", "@eslint/core": "^1.2.0", "@eslint/plugin-kit": "^0.7.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA=="], - - "eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="], - - "eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], - - "espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="], - - "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - - "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - - "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], - - "flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="], - - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "globals": ["globals@17.5.0", "", {}, "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g=="], - - "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], - - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - - "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], - - "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], - - "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], - - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - - "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], - - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - - "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], - - "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], - - "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "typescript-eslint": ["typescript-eslint@8.58.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.58.1", "@typescript-eslint/parser": "8.58.1", "@typescript-eslint/typescript-estree": "8.58.1", "@typescript-eslint/utils": "8.58.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg=="], - - "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], - - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - - "@tensamin/ttp-core/typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="], - - "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - } -} diff --git a/dist/crypto.d.ts b/dist/crypto.d.ts deleted file mode 100644 index 75bfc5c..0000000 --- a/dist/crypto.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -type Base64URLString = string; -export declare function encrypt(secret: string, plaintext: string): Promise; -/** - * Decrypts base64 ciphertext with a symmetric key derived from a hex shared secret. - * @param secret Hex-encoded shared secret. - * @param ciphertext Base64 ciphertext to decrypt. - * @returns Decrypted UTF-8 plaintext. - */ -export declare function decrypt(secret: string, ciphertext: Base64URLString | string): Promise; -/** - * Computes an X448 shared secret from local and peer key material. - * @param ownPrivateKey Local private key in raw/base64/base64url or PKCS#8-wrapped form. - * @param ownPublicKey Local public key in raw/base64/base64url or SPKI-wrapped form. - * @param otherPublicKey Peer public key in raw/base64/base64url or SPKI-wrapped form. - * @returns Hex-encoded shared secret, or a failure message when key material is missing/invalid. - */ -export declare function getSharedSecret(ownPrivateKey: string, ownPublicKey: string, otherPublicKey: string): Promise; -export {}; diff --git a/dist/crypto.js b/dist/crypto.js deleted file mode 100644 index 3ebd32c..0000000 --- a/dist/crypto.js +++ /dev/null @@ -1,331 +0,0 @@ -const textEncoder = new TextEncoder(); -const crypto = globalThis.crypto; -export async function encrypt(secret, plaintext) { - const sharedSecret = new Uint8Array(secret.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))); - const hkdfKey = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, ["deriveBits"]); - const okm = await crypto.subtle.deriveBits({ - name: "HKDF", - hash: "SHA-256", - salt: new Uint8Array([]), - info: textEncoder.encode("x448-aes-gcm-no-overhead"), - }, hkdfKey, 44 * 8); - const okmBytes = new Uint8Array(okm); - const keyBytes = okmBytes.slice(0, 32); - const nonce = okmBytes.slice(32, 44); - const aesKey = await crypto.subtle.importKey("raw", keyBytes, { name: "AES-GCM" }, false, ["encrypt"]); - const encryptedBuffer = await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce }, aesKey, textEncoder.encode(plaintext)); - return btoa(String.fromCharCode(...new Uint8Array(encryptedBuffer))); -} -/** - * Decrypts base64 ciphertext with a symmetric key derived from a hex shared secret. - * @param secret Hex-encoded shared secret. - * @param ciphertext Base64 ciphertext to decrypt. - * @returns Decrypted UTF-8 plaintext. - */ -export async function decrypt(secret, ciphertext) { - const sharedSecret = new Uint8Array(secret.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))); - const ciphertextBytes = Uint8Array.from(atob(ciphertext), (c) => c.charCodeAt(0)); - const hkdfKey = await crypto.subtle.importKey("raw", sharedSecret, "HKDF", false, ["deriveBits"]); - const okm = await crypto.subtle.deriveBits({ - name: "HKDF", - hash: "SHA-256", - salt: new Uint8Array([]), - info: textEncoder.encode("x448-aes-gcm-no-overhead"), - }, hkdfKey, 44 * 8); - const okmBytes = new Uint8Array(okm); - const keyBytes = okmBytes.slice(0, 32); - const nonce = okmBytes.slice(32, 44); - const aesKey = await crypto.subtle.importKey("raw", keyBytes, { name: "AES-GCM" }, false, ["decrypt"]); - const decryptedBuffer = await crypto.subtle.decrypt({ - name: "AES-GCM", - iv: nonce, - }, aesKey, ciphertextBytes); - return new TextDecoder().decode(decryptedBuffer); -} -/** - * Computes an X448 shared secret from local and peer key material. - * @param ownPrivateKey Local private key in raw/base64/base64url or PKCS#8-wrapped form. - * @param ownPublicKey Local public key in raw/base64/base64url or SPKI-wrapped form. - * @param otherPublicKey Peer public key in raw/base64/base64url or SPKI-wrapped form. - * @returns Hex-encoded shared secret, or a failure message when key material is missing/invalid. - */ -export async function getSharedSecret(ownPrivateKey, ownPublicKey, otherPublicKey) { - const otherJwk = { kty: "OKP", crv: "X448", x: otherPublicKey }; - const ownJwk = { - kty: "OKP", - crv: "X448", - x: ownPublicKey, - d: ownPrivateKey, - }; - /** - * Converts bytes to a lowercase hex string. - * @param u8 Byte array. - * @returns Hex string. - */ - const bytesToHex = (u8) => Array.from(u8, (b) => b.toString(16).padStart(2, "0")).join(""); - /** - * Decodes standard base64 text into bytes. - * @param s Base64 string. - * @returns Decoded bytes. - */ - const b64ToBytes = (s) => { - const bin = atob(s); - const out = new Uint8Array(bin.length); - for (let i = 0; i < bin.length; i++) - out[i] = bin.charCodeAt(i); - return out; - }; - /** - * Decodes URL-safe base64 text into bytes. - * @param s Base64url string. - * @returns Decoded bytes. - */ - const b64uToBytes = (s) => { - const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((s.length + 3) % 4); - return b64ToBytes(b64); - }; - /** - * Encodes bytes as URL-safe base64 without padding. - * @param u8 Byte array. - * @returns Base64url string. - */ - const bytesToB64u = (u8) => { - const b64 = btoa(String.fromCharCode(...u8)); - return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); - }; - /** - * Decodes either base64 or base64url text into bytes. - * @param s Base64/base64url string. - * @returns Decoded bytes. - */ - const decodeBase64Auto = (s) => /[-_]/.test(s) ? b64uToBytes(s) : b64ToBytes(s); - /** - * Reads a DER TLV item from the provided offset. - * @param view DER-encoded bytes. - * @param off Start offset. - * @returns Parsed TLV metadata with tag, length, and boundaries. - */ - const readTLV = (view, off) => { - const tag = view[off]; - if (tag === undefined) - throw new Error("DER: truncated"); - off += 1; - const lenByte = view[off]; - if (lenByte === undefined) - throw new Error("DER: truncated"); - off += 1; - let len = lenByte; - if (lenByte & 0x80) { - const n = lenByte & 0x7f; - if (n === 0) - throw new Error("DER: indefinite length not supported"); - if (off + n > view.length) - throw new Error("DER: truncated length"); - len = 0; - for (let i = 0; i < n; i++) { - const b = view[off]; - if (b === undefined) - throw new Error("DER: truncated length"); - len = (len << 8) | b; - off += 1; - } - } - const start = off; - const end = off + len; - if (end > view.length) - throw new Error("DER: content truncated"); - return { tag, len, start, end }; - }; - /** - * Validates that a DER OID matches X448. - * @param view DER-encoded bytes. - * @param start Offset of the OID TLV. - * @returns True when the OID is X448. - */ - const ensureOidX448 = (view, start) => { - const oid = readTLV(view, start); - if (oid.tag !== 0x06) - return false; - const len = oid.end - oid.start; - if (len !== 3) - return false; - return (view[oid.start] === 0x2b && - view[oid.start + 1] === 0x65 && - view[oid.start + 2] === 0x6f); - }; - /** - * Extracts raw 56-byte X448 public key material from SPKI bytes. - * @param spkiBytes DER-encoded SPKI bytes. - * @returns Raw X448 public key bytes. - */ - const extractRawX448FromSPKI = (spkiBytes) => { - const view = spkiBytes; - const outer = readTLV(view, 0); - if (outer.tag !== 0x30) - throw new Error("SPKI: expected SEQUENCE"); - const alg = readTLV(view, outer.start); - if (alg.tag !== 0x30) - throw new Error("SPKI: expected AlgorithmIdentifier"); - if (!ensureOidX448(view, alg.start)) - throw new Error("SPKI: not X448"); - const bitstr = readTLV(view, alg.end); - if (bitstr.tag !== 0x03) - throw new Error("SPKI: expected BIT STRING"); - const unusedBits = view[bitstr.start]; - if (unusedBits !== 0x00) - throw new Error("SPKI: unexpected unused bits"); - const raw = view.subarray(bitstr.start + 1, bitstr.end); - if (raw.length !== 56) - throw new Error("SPKI: X448 public key must be 56 bytes"); - return raw; - }; - /** - * Extracts raw 56-byte X448 private key material from PKCS#8 bytes. - * @param pkcs8Bytes DER-encoded PKCS#8 bytes. - * @returns Raw X448 private key bytes. - */ - const extractRawX448FromPKCS8 = (pkcs8Bytes) => { - const view = pkcs8Bytes; - const outer = readTLV(view, 0); - if (outer.tag !== 0x30) - throw new Error("PKCS8: expected SEQUENCE"); - let off = outer.start; - const version = readTLV(view, off); - if (version.tag !== 0x02) - throw new Error("PKCS8: expected version INTEGER"); - off = version.end; - const alg = readTLV(view, off); - if (alg.tag !== 0x30) - throw new Error("PKCS8: expected AlgorithmIdentifier"); - if (!ensureOidX448(view, alg.start)) - throw new Error("PKCS8: not X448"); - off = alg.end; - const priv = readTLV(view, off); - if (priv.tag !== 0x04) - throw new Error("PKCS8: expected privateKey OCTET STRING"); - let raw = view.subarray(priv.start, priv.end); - // Some encoders nest another OCTET STRING inside - if (raw[0] === 0x04) { - const inner = readTLV(raw, 0); - if (inner.tag === 0x04) { - raw = raw.subarray(inner.start, inner.end); - } - } - if (raw.length !== 56) - throw new Error("PKCS8: X448 private key must be 56 bytes"); - return raw; - }; - /** - * Normalizes X448 JWK fields into raw base64url key material. - * @param jwk Candidate JWK. - * @param label Error label for diagnostics. - * @returns Normalized JWK suitable for WebCrypto import. - */ - const normalizeOkpX448Jwk = (jwk, label) => { - if (!jwk || jwk.kty !== "OKP" || jwk.crv !== "X448") { - throw new Error(`${label}: expected OKP JWK with crv "X448"`); - } - const out = { ...jwk }; - if (out.x) { - const xBytes = decodeBase64Auto(out.x); - let rawX; - try { - rawX = extractRawX448FromSPKI(xBytes); - } - catch { - if (xBytes.length !== 56) { - throw new Error(`${label}: "x" is not a valid X448 SPKI or raw 56-byte key`); - } - rawX = xBytes; - } - out.x = bytesToB64u(rawX); - } - if (out.d) { - const dBytes = decodeBase64Auto(out.d); - let rawD; - try { - rawD = extractRawX448FromPKCS8(dBytes); - } - catch { - if (dBytes.length !== 56) { - throw new Error(`${label}: "d" is not a valid X448 PKCS#8 or raw 56-byte key`); - } - rawD = dBytes; - } - out.d = bytesToB64u(rawD); - } - return out; - }; - /** - * Returns WebCrypto subtle API when available. - * @returns SubtleCrypto instance or undefined. - */ - const getSubtle = () => globalThis.crypto?.subtle; - { - /* - const hkdfAesGcmFromShared = async ( - sharedSecret: BufferSource, - infoStr: string - ): Promise => { - const subtle = getSubtle(); - if (!subtle) throw new Error("WebCrypto subtle not available"); - const info = textEncoder.encode(infoStr); - const baseKey = await subtle.importKey( - "raw", - sharedSecret, - "HKDF", - false, - ["deriveKey"] - ); - return await subtle.deriveKey( - { - name: "HKDF", - hash: "SHA-256", - salt: new Uint8Array(0), - info, - }, - baseKey, - { name: "AES-GCM", length: 256 }, - false, - ["encrypt", "decrypt"] - ); - }; - */ - } - const myJwk = normalizeOkpX448Jwk(ownJwk, "own_jwk"); - const peerJwk = normalizeOkpX448Jwk(otherJwk, "other_jwk"); - const subtle = getSubtle(); - //const infoStr = `ECDH-X448-AES-GCM-v1|my=${myJwk.x}|peer=${peerJwk.x}`; - if (subtle) { - const algorithms = [{ name: "ECDH", namedCurve: "X448" }, { name: "X448" }]; - for (const algorithm of algorithms) { - try { - const [myPriv, peerPub] = await Promise.all([ - subtle.importKey("jwk", myJwk, algorithm, false, ["deriveBits"]), - subtle.importKey("jwk", peerJwk, algorithm, false, []), - ]); - const sharedBits = await subtle.deriveBits({ name: algorithm.name, public: peerPub }, myPriv, 448); - const sharedSecret = new Uint8Array(sharedBits); - //const aeadKey = await hkdfAesGcmFromShared(sharedSecret, infoStr); - return bytesToHex(sharedSecret); - } - catch { - // Browser doesn't support this algorithm, try next or fall through to software fallback - } - } - } - const { d: dMyB64u } = myJwk; - //const { x: xMyB64u, d: dMyB64u } = myJwk; - const { x: xPeerB64u } = peerJwk; - if (!dMyB64u || !xPeerB64u) { - return "Failed to get shared secret due to missing keys"; - } - const [dRaw, xRawPeer] = [b64uToBytes(dMyB64u), b64uToBytes(xPeerB64u)]; - if (dRaw.length !== 56 || xRawPeer.length !== 56) { - return "Failed to get shared secret due to invalid key lengths"; - } - const { x448 } = await import("@noble/curves/ed448.js"); - const sharedSecret = new Uint8Array(x448.getSharedSecret(dRaw, xRawPeer)); - //const aeadKey = await hkdfAesGcmFromShared(sharedSecret, infoStr); - return bytesToHex(sharedSecret); -} diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index 6814120..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { type TransportClient, READY_STATE } from "@tensamin/ttp-core"; -import { createSchema } from "./schema"; -import type { z, ZodObject } from "zod"; -/** - * @param identifier Your app's domain. - * @param redirectUrl The URL should point to the TAuth HTTP Server at http://hostname:port/callback - * @param frontendUrl The URL of the frontend the user interacts with. Only used in development. - * @param appData A Zod schema defining the shape of the app data you want to load/save for each user. - * @param saveSession A function that saves the session ID for a given user ID. This is called after a successful authentication. - * @param httpServer Optional configuration for the TAuth HTTP Server. Defaults to { port: 7878, hostname: "localhost" }. - * @param htmlSuccessPage Optional HTML string or path to an HTML file to be served upon successful authentication. - */ -export declare class TAuthClient { - frontendUrl: string; - identifier: string; - privateKey: string; - publicKey: string; - saveSession: (userId: number, sessionId: number) => Promise | void; - redirectUrl: URL; - schema: ReturnType; - httpServer: { - port: number; - hostname: string; - }; - clientMap: Map; - response: z.ZodObject<{ - challenge: z.ZodBase64; - public_key: z.ZodBase64; - }, z.core.$strip>; - }; - challenge_response: { - request: z.ZodObject<{ - challenge: z.ZodBase64; - }, z.core.$strip>; - response: z.ZodObject<{}, z.core.$strip>; - }; - load_app_data: { - request: z.ZodObject<{}, z.core.$strip>; - response: z.ZodObject<{ - app_data: z.ZodString; - }, z.core.$strip>; - }; - save_app_data: { - request: z.ZodObject<{ - app_data: z.ZodString; - }, z.core.$strip>; - response: z.ZodObject<{}, z.core.$strip>; - }; - }>>; - private appData; - constructor({ frontendUrl, identifier, privateKey, publicKey, saveSession, redirectUrl, appData, httpServer, htmlSuccessPage, }: { - frontendUrl: string; - identifier: string; - privateKey: string; - publicKey: string; - saveSession: (userId: number, sessionId: number) => Promise | void; - redirectUrl: string; - appData: ZodObject; - httpServer?: { - port: number; - hostname: string; - }; - htmlSuccessPage?: string; - }); - generateChallenge(userId: number): Promise; - solveChallenge(userId: number, challenge: string): Promise; - createTTP(userId: number, sessionId: number, omikronUrl: string): Promise>; - loadData(userId: number, sessionId: number): Promise>; - saveData(userId: number, sessionId: number, data: any): Promise; - private createTemporaryTTPConnection; - generateLink(challenge?: string): string; -} -export declare function getFriendlyReadyState(stateIndex: number): keyof typeof READY_STATE; -export declare function generateKeyPair(): { - private: string; - public: string; -}; -export * from "./crypto"; -export * from "./schema"; -export * from "./user"; diff --git a/dist/index.html b/dist/index.html deleted file mode 100644 index 2903413..0000000 --- a/dist/index.html +++ /dev/null @@ -1,459 +0,0 @@ - - - - - - - Login successful - - - - -
-

Login successful

-

You can close this page now.

-
- - - diff --git a/dist/index.js b/dist/index.js deleted file mode 100644 index 07045eb..0000000 --- a/dist/index.js +++ /dev/null @@ -1,219 +0,0 @@ -import { createTransportClient, READY_STATE, } from "@tensamin/ttp-core"; -import { createSchema } from "./schema"; -import { x448 } from "@noble/curves/ed448.js"; -import Bun from "bun"; -import { get } from "./user"; -import { decrypt, encrypt, getSharedSecret } from "./crypto"; -import { readFile } from "node:fs/promises"; -/** - * @param identifier Your app's domain. - * @param redirectUrl The URL should point to the TAuth HTTP Server at http://hostname:port/callback - * @param frontendUrl The URL of the frontend the user interacts with. Only used in development. - * @param appData A Zod schema defining the shape of the app data you want to load/save for each user. - * @param saveSession A function that saves the session ID for a given user ID. This is called after a successful authentication. - * @param httpServer Optional configuration for the TAuth HTTP Server. Defaults to { port: 7878, hostname: "localhost" }. - * @param htmlSuccessPage Optional HTML string or path to an HTML file to be served upon successful authentication. - */ -export class TAuthClient { - frontendUrl; - identifier; - privateKey; - publicKey; - saveSession; - redirectUrl; - schema; - httpServer; - clientMap = new Map(); - appData; - constructor({ frontendUrl, identifier, privateKey, publicKey, saveSession, redirectUrl, appData, httpServer, htmlSuccessPage, }) { - this.frontendUrl = frontendUrl; - this.identifier = identifier; - this.privateKey = privateKey; - this.publicKey = publicKey; - if (typeof saveSession !== "function") { - throw new Error("[tauth] saveSession must be provided as a function"); - } - this.saveSession = saveSession; - this.redirectUrl = new URL(redirectUrl); - this.schema = createSchema(appData); - this.appData = appData; - this.httpServer = httpServer ?? { - port: 7878, - hostname: "localhost", - }; - Bun.serve({ - hostname: this.httpServer.hostname, - port: this.httpServer.port, - fetch: async (request) => { - const url = new URL(request.url); - switch (url.pathname) { - case "/callback": { - const userId = Number(url.searchParams.get("userId")); - if (!userId) { - return new Response("Missing userId", { status: 400 }); - } - const potentialChallenge = url.searchParams.get("challenge"); - if (!potentialChallenge) { - const challenge = await this.generateChallenge(userId); - const newLink = this.generateLink(challenge); - return new Response(null, { - status: 302, - headers: { - Location: newLink, - }, - }); - } - const originalChallenge = url.searchParams.get("originalChallenge"); - if (!originalChallenge) { - return new Response("Missing originalChallenge", { status: 400 }); - } - const potentialSessionId = Number(url.searchParams.get("sessionId")); - if (!potentialSessionId || isNaN(potentialSessionId)) { - return new Response("Invalid or missing sessionId", { - status: 400, - }); - } - if ((await this.solveChallenge(userId, originalChallenge)) === - potentialChallenge) { - await this.saveSession(userId, potentialSessionId); - return new Response(htmlSuccessPage || - (await readFile(new URL("./index.html", import.meta.url), "utf8")), { - headers: { - "Content-Type": "text/html", - }, - status: 200, - }); - } - else { - return new Response("Failed to solve challenge", { status: 400 }); - } - } - case "/auth": { - return new Response(null, { - status: 302, - headers: { - Location: this.generateLink(), - }, - }); - } - default: { - return new Response("Not Found", { status: 404 }); - } - } - }, - }); - console.log(`[tauth] HTTP Server running at http://${this.httpServer.hostname}:${this.httpServer.port}`); - } - async generateChallenge(userId) { - const userData = await get(userId); - if (!userData) { - throw new Error("User not found"); - } - const sharedSecret = await getSharedSecret(this.privateKey, this.publicKey, userData.public_key); - const encrypted = await encrypt(sharedSecret, crypto.randomUUID()); - const decrypted = await decrypt(sharedSecret, encrypted); - console.log(`[tauth] Generated challenge for user ${userId}: ${decrypted} (encrypted: ${encrypted})`); - return Array.from(atob(encrypted)) - .map((chat) => chat.charCodeAt(0).toString(16).padStart(2, "0")) - .join(""); - } - async solveChallenge(userId, challenge) { - const userData = await get(userId); - if (!userData) { - throw new Error("User not found"); - } - const sharedSecret = await getSharedSecret(this.privateKey, this.publicKey, userData.public_key); - return await decrypt(sharedSecret, challenge); - } - createTTP(userId, sessionId, omikronUrl) { - console.log(`[tauth] Creating TTP client for user ${userId} with session ${sessionId} at ${omikronUrl}`); - const client = createTransportClient(this.schema, { - url: omikronUrl, - onReadyStateChange: (stateIndex) => { - const state = getFriendlyReadyState(stateIndex); - console.log(state); - if (state === "OPEN") { - this.clientMap.get(`${userId}:${sessionId}`)?.send("identification", { - app_identifier: this.identifier, - app_session_id: sessionId, - app_public_key: this.publicKey, - user_id: userId, - }); - } - }, - }); - this.clientMap.set(`${userId}:${sessionId}`, client); - client.connect(); - return Promise.resolve(client); - } - async loadData(userId, sessionId) { - const connection = await this.createTemporaryTTPConnection(userId, sessionId); - const rawAppData = await connection.send("load_app_data", {}); - const appData = JSON.parse(rawAppData.data.app_data); - const safeAppData = this.appData.safeParse(appData); - await connection.close(); - if (!safeAppData.success) { - throw new Error(`Invalid app data: ${JSON.stringify(safeAppData.error.issues)}`); - } - return safeAppData.data; - } - async saveData(userId, sessionId, data) { - const connection = await this.createTemporaryTTPConnection(userId, sessionId); - const safeData = this.appData.safeParse(data); - if (!safeData.success) { - throw new Error(`Invalid app data: ${JSON.stringify(safeData.error.issues)}`); - } - await connection.send("save_app_data", { - app_data: JSON.stringify(safeData.data), - }); - await connection.close(); - } - async createTemporaryTTPConnection(userId, sessionId) { - const { ip_address } = await fetch("https://omega.tensamin.net/api/get/omikron/" + userId).then((res) => res.json()); - let temporaryClient; - temporaryClient = createTransportClient(this.schema, { - url: ip_address, - onReadyStateChange: (stateIndex) => { - const state = getFriendlyReadyState(stateIndex); - if (state === "OPEN") { - temporaryClient?.send("identification", { - app_identifier: this.identifier, - app_session_id: sessionId, - app_public_key: this.publicKey, - user_id: userId, - }); - } - }, - }); - temporaryClient.connect(); - return temporaryClient; - } - generateLink(challenge) { - return new URL(`?identifier=${this.identifier}&redirect=${this.redirectUrl}${challenge ? `&challenge=${challenge}` : ""}`, this.frontendUrl).toString(); - } -} -export function getFriendlyReadyState(stateIndex) { - return Object.keys(READY_STATE).find((key) => READY_STATE[key] === stateIndex); -} -export function generateKeyPair() { - function toBase64(bytes) { - if (typeof Buffer !== "undefined") { - return Buffer.from(bytes).toString("base64"); - } - let binary = ""; - for (const byte of bytes) { - binary += String.fromCharCode(byte); - } - return btoa(binary); - } - const priv = new Uint8Array(56); - crypto.getRandomValues(priv); - const pub = x448.getPublicKey(priv); - return { - private: toBase64(priv), - public: toBase64(pub), - }; -} -export * from "./crypto"; -export * from "./schema"; -export * from "./user"; diff --git a/dist/schema.d.ts b/dist/schema.d.ts deleted file mode 100644 index 7100948..0000000 --- a/dist/schema.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -import z, { ZodObject } from "zod"; -export declare function createSchema(appData: ZodObject): { - identification: { - request: z.ZodObject<{ - app_identifier: z.ZodString; - app_session_id: z.ZodNumber; - app_public_key: z.ZodString; - user_id: z.ZodNumber; - }, z.core.$strip>; - response: z.ZodObject<{ - challenge: z.ZodBase64; - public_key: z.ZodBase64; - }, z.core.$strip>; - }; - challenge_response: { - request: z.ZodObject<{ - challenge: z.ZodBase64; - }, z.core.$strip>; - response: z.ZodObject<{}, z.core.$strip>; - }; - load_app_data: { - request: z.ZodObject<{}, z.core.$strip>; - response: z.ZodObject<{ - app_data: z.ZodString; - }, z.core.$strip>; - }; - save_app_data: { - request: z.ZodObject<{ - app_data: z.ZodString; - }, z.core.$strip>; - response: z.ZodObject<{}, z.core.$strip>; - }; -}; diff --git a/dist/schema.js b/dist/schema.js deleted file mode 100644 index 3d2f05c..0000000 --- a/dist/schema.js +++ /dev/null @@ -1,35 +0,0 @@ -import z, { ZodObject } from "zod"; -export function createSchema(appData) { - return { - identification: { - request: z.object({ - app_identifier: z.string(), - app_session_id: z.number(), - app_public_key: z.string(), - user_id: z.number(), - }), - response: z.object({ - challenge: z.base64(), - public_key: z.base64(), - }), - }, - challenge_response: { - request: z.object({ - challenge: z.base64(), - }), - response: z.object({}), - }, - load_app_data: { - request: z.object({}), - response: z.object({ - app_data: z.string(), - }), - }, - save_app_data: { - request: z.object({ - app_data: z.string(), - }), - response: z.object({}), - }, - }; -} diff --git a/dist/user.d.ts b/dist/user.d.ts deleted file mode 100644 index 6758258..0000000 --- a/dist/user.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -export type User = { - status: string; - username: string; - public_key: string; - user_id: number; - iota_id: number; - sub_level: number; - sub_end: number; - display: string; - status_message: string; - about: string; -}; -export declare function get(userId: number): Promise; diff --git a/dist/user.js b/dist/user.js deleted file mode 100644 index f017726..0000000 --- a/dist/user.js +++ /dev/null @@ -1,18 +0,0 @@ -const userCacheMap = new Map(); -export async function get(userId) { - try { - const cachedUser = userCacheMap.get(userId); - if (cachedUser) { - return cachedUser; - } - const fetchedUser = await fetch("https://omega.tensamin.net/api/get/user/" + userId).then((res) => res.json()); - if (fetchedUser) { - userCacheMap.set(userId, fetchedUser); - } - return fetchedUser; - } - catch (error) { - console.error("Error fetching user data for userId:", userId, error); - return undefined; - } -} diff --git a/package.json b/package.json deleted file mode 100644 index e3fe420..0000000 --- a/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "@tensamin/tauth-sdk", - "version": "0.0.4", - "type": "module", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", - "files": [ - "dist" - ], - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "default": "./dist/index.js" - } - }, - "scripts": { - "build": "bun run test && tsc -p tsconfig.build.json && cp src/index.html dist/index.html", - "test": "bun test" - }, - "devDependencies": { - "@types/bun": "^1.3.12", - "@types/node": "latest" - }, - "peerDependencies": { - "typescript": "^5" - }, - "dependencies": { - "@noble/curves": "^2.2.0", - "@tensamin/ttp-core": "https://git.methanium.net/tensamin/ttp/archive/0.0.9.tar.gz", - "@webtransport-bun/webtransport": "^0.3.0", - "zod": "^4.3.6" - } -} diff --git a/src/crypto.ts b/src/crypto.ts deleted file mode 100644 index 7738a2b..0000000 --- a/src/crypto.ts +++ /dev/null @@ -1,439 +0,0 @@ -type Base64URLString = string; - -type JWK = { - kty: string; - crv: string; - x?: string; - d?: string; -}; - -const textEncoder = new TextEncoder(); -const crypto = globalThis.crypto; - -export async function encrypt( - secret: string, - plaintext: string, -): Promise { - const sharedSecret = new Uint8Array( - secret.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)), - ); - - const hkdfKey = await crypto.subtle.importKey( - "raw", - sharedSecret, - "HKDF", - false, - ["deriveBits"], - ); - - const okm = await crypto.subtle.deriveBits( - { - name: "HKDF", - hash: "SHA-256", - salt: new Uint8Array([]), - info: textEncoder.encode("x448-aes-gcm-no-overhead"), - }, - hkdfKey, - 44 * 8, - ); - - const okmBytes = new Uint8Array(okm); - const keyBytes = okmBytes.slice(0, 32); - const nonce = okmBytes.slice(32, 44); - - const aesKey = await crypto.subtle.importKey( - "raw", - keyBytes, - { name: "AES-GCM" }, - false, - ["encrypt"], - ); - - const encryptedBuffer = await crypto.subtle.encrypt( - { name: "AES-GCM", iv: nonce }, - aesKey, - textEncoder.encode(plaintext), - ); - - return btoa(String.fromCharCode(...new Uint8Array(encryptedBuffer))); -} - -/** - * Decrypts base64 ciphertext with a symmetric key derived from a hex shared secret. - * @param secret Hex-encoded shared secret. - * @param ciphertext Base64 ciphertext to decrypt. - * @returns Decrypted UTF-8 plaintext. - */ -export async function decrypt( - secret: string, - ciphertext: Base64URLString | string, -): Promise { - const sharedSecret = new Uint8Array( - secret.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)), - ); - - const ciphertextBytes = Uint8Array.from(atob(ciphertext), (c) => - c.charCodeAt(0), - ); - - const hkdfKey = await crypto.subtle.importKey( - "raw", - sharedSecret, - "HKDF", - false, - ["deriveBits"], - ); - - const okm = await crypto.subtle.deriveBits( - { - name: "HKDF", - hash: "SHA-256", - salt: new Uint8Array([]), - info: textEncoder.encode("x448-aes-gcm-no-overhead"), - }, - hkdfKey, - 44 * 8, - ); - - const okmBytes = new Uint8Array(okm); - const keyBytes = okmBytes.slice(0, 32); - const nonce = okmBytes.slice(32, 44); - - const aesKey = await crypto.subtle.importKey( - "raw", - keyBytes, - { name: "AES-GCM" }, - false, - ["decrypt"], - ); - - const decryptedBuffer = await crypto.subtle.decrypt( - { - name: "AES-GCM", - iv: nonce, - }, - aesKey, - ciphertextBytes, - ); - - return new TextDecoder().decode(decryptedBuffer); -} - -/** - * Computes an X448 shared secret from local and peer key material. - * @param ownPrivateKey Local private key in raw/base64/base64url or PKCS#8-wrapped form. - * @param ownPublicKey Local public key in raw/base64/base64url or SPKI-wrapped form. - * @param otherPublicKey Peer public key in raw/base64/base64url or SPKI-wrapped form. - * @returns Hex-encoded shared secret, or a failure message when key material is missing/invalid. - */ -export async function getSharedSecret( - ownPrivateKey: string, - ownPublicKey: string, - otherPublicKey: string, -): Promise { - const otherJwk: JWK = { kty: "OKP", crv: "X448", x: otherPublicKey }; - const ownJwk: JWK = { - kty: "OKP", - crv: "X448", - x: ownPublicKey, - d: ownPrivateKey, - }; - - /** - * Converts bytes to a lowercase hex string. - * @param u8 Byte array. - * @returns Hex string. - */ - const bytesToHex = (u8: Uint8Array): string => - Array.from(u8, (b) => b.toString(16).padStart(2, "0")).join(""); - - /** - * Decodes standard base64 text into bytes. - * @param s Base64 string. - * @returns Decoded bytes. - */ - const b64ToBytes = (s: Base64URLString): Uint8Array => { - const bin = atob(s); - const out = new Uint8Array(bin.length); - for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i); - return out; - }; - - /** - * Decodes URL-safe base64 text into bytes. - * @param s Base64url string. - * @returns Decoded bytes. - */ - const b64uToBytes = (s: Base64URLString): Uint8Array => { - const b64 = - s.replace(/-/g, "+").replace(/_/g, "/") + "===".slice((s.length + 3) % 4); - return b64ToBytes(b64); - }; - - /** - * Encodes bytes as URL-safe base64 without padding. - * @param u8 Byte array. - * @returns Base64url string. - */ - const bytesToB64u = (u8: Uint8Array): string => { - const b64 = btoa(String.fromCharCode(...u8)); - return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); - }; - - /** - * Decodes either base64 or base64url text into bytes. - * @param s Base64/base64url string. - * @returns Decoded bytes. - */ - const decodeBase64Auto = (s: string): Uint8Array => - /[-_]/.test(s) ? b64uToBytes(s) : b64ToBytes(s); - - /** - * Reads a DER TLV item from the provided offset. - * @param view DER-encoded bytes. - * @param off Start offset. - * @returns Parsed TLV metadata with tag, length, and boundaries. - */ - const readTLV = (view: Uint8Array, off: number) => { - const tag = view[off]; - if (tag === undefined) throw new Error("DER: truncated"); - off += 1; - - const lenByte = view[off]; - if (lenByte === undefined) throw new Error("DER: truncated"); - off += 1; - - let len = lenByte; - if (lenByte & 0x80) { - const n = lenByte & 0x7f; - if (n === 0) throw new Error("DER: indefinite length not supported"); - if (off + n > view.length) throw new Error("DER: truncated length"); - len = 0; - for (let i = 0; i < n; i++) { - const b = view[off]; - if (b === undefined) throw new Error("DER: truncated length"); - len = (len << 8) | b; - off += 1; - } - } - const start = off; - const end = off + len; - if (end > view.length) throw new Error("DER: content truncated"); - return { tag, len, start, end }; - }; - - /** - * Validates that a DER OID matches X448. - * @param view DER-encoded bytes. - * @param start Offset of the OID TLV. - * @returns True when the OID is X448. - */ - const ensureOidX448 = (view: Uint8Array, start: number): boolean => { - const oid = readTLV(view, start); - if (oid.tag !== 0x06) return false; - const len = oid.end - oid.start; - if (len !== 3) return false; - return ( - view[oid.start] === 0x2b && - view[oid.start + 1] === 0x65 && - view[oid.start + 2] === 0x6f - ); - }; - - /** - * Extracts raw 56-byte X448 public key material from SPKI bytes. - * @param spkiBytes DER-encoded SPKI bytes. - * @returns Raw X448 public key bytes. - */ - const extractRawX448FromSPKI = (spkiBytes: Uint8Array): Uint8Array => { - const view = spkiBytes; - const outer = readTLV(view, 0); - if (outer.tag !== 0x30) throw new Error("SPKI: expected SEQUENCE"); - const alg = readTLV(view, outer.start); - if (alg.tag !== 0x30) throw new Error("SPKI: expected AlgorithmIdentifier"); - if (!ensureOidX448(view, alg.start)) throw new Error("SPKI: not X448"); - const bitstr = readTLV(view, alg.end); - if (bitstr.tag !== 0x03) throw new Error("SPKI: expected BIT STRING"); - const unusedBits = view[bitstr.start]; - if (unusedBits !== 0x00) throw new Error("SPKI: unexpected unused bits"); - const raw = view.subarray(bitstr.start + 1, bitstr.end); - if (raw.length !== 56) - throw new Error("SPKI: X448 public key must be 56 bytes"); - return raw; - }; - - /** - * Extracts raw 56-byte X448 private key material from PKCS#8 bytes. - * @param pkcs8Bytes DER-encoded PKCS#8 bytes. - * @returns Raw X448 private key bytes. - */ - const extractRawX448FromPKCS8 = (pkcs8Bytes: Uint8Array): Uint8Array => { - const view = pkcs8Bytes; - const outer = readTLV(view, 0); - if (outer.tag !== 0x30) throw new Error("PKCS8: expected SEQUENCE"); - let off = outer.start; - - const version = readTLV(view, off); - if (version.tag !== 0x02) - throw new Error("PKCS8: expected version INTEGER"); - off = version.end; - - const alg = readTLV(view, off); - if (alg.tag !== 0x30) - throw new Error("PKCS8: expected AlgorithmIdentifier"); - if (!ensureOidX448(view, alg.start)) throw new Error("PKCS8: not X448"); - off = alg.end; - - const priv = readTLV(view, off); - if (priv.tag !== 0x04) - throw new Error("PKCS8: expected privateKey OCTET STRING"); - let raw = view.subarray(priv.start, priv.end); - - // Some encoders nest another OCTET STRING inside - if (raw[0] === 0x04) { - const inner = readTLV(raw, 0); - if (inner.tag === 0x04) { - raw = raw.subarray(inner.start, inner.end); - } - } - if (raw.length !== 56) - throw new Error("PKCS8: X448 private key must be 56 bytes"); - return raw; - }; - - /** - * Normalizes X448 JWK fields into raw base64url key material. - * @param jwk Candidate JWK. - * @param label Error label for diagnostics. - * @returns Normalized JWK suitable for WebCrypto import. - */ - const normalizeOkpX448Jwk = (jwk: JWK, label: string): JWK => { - if (!jwk || jwk.kty !== "OKP" || jwk.crv !== "X448") { - throw new Error(`${label}: expected OKP JWK with crv "X448"`); - } - const out = { ...jwk }; - - if (out.x) { - const xBytes = decodeBase64Auto(out.x); - let rawX: Uint8Array; - try { - rawX = extractRawX448FromSPKI(xBytes); - } catch { - if (xBytes.length !== 56) { - throw new Error( - `${label}: "x" is not a valid X448 SPKI or raw 56-byte key`, - ); - } - rawX = xBytes; - } - out.x = bytesToB64u(rawX); - } - - if (out.d) { - const dBytes = decodeBase64Auto(out.d); - let rawD: Uint8Array; - try { - rawD = extractRawX448FromPKCS8(dBytes); - } catch { - if (dBytes.length !== 56) { - throw new Error( - `${label}: "d" is not a valid X448 PKCS#8 or raw 56-byte key`, - ); - } - rawD = dBytes; - } - out.d = bytesToB64u(rawD); - } - - return out; - }; - - /** - * Returns WebCrypto subtle API when available. - * @returns SubtleCrypto instance or undefined. - */ - const getSubtle = () => globalThis.crypto?.subtle; - - { - /* - const hkdfAesGcmFromShared = async ( - sharedSecret: BufferSource, - infoStr: string - ): Promise => { - const subtle = getSubtle(); - if (!subtle) throw new Error("WebCrypto subtle not available"); - const info = textEncoder.encode(infoStr); - const baseKey = await subtle.importKey( - "raw", - sharedSecret, - "HKDF", - false, - ["deriveKey"] - ); - return await subtle.deriveKey( - { - name: "HKDF", - hash: "SHA-256", - salt: new Uint8Array(0), - info, - }, - baseKey, - { name: "AES-GCM", length: 256 }, - false, - ["encrypt", "decrypt"] - ); - }; - */ - } - - const myJwk: JWK = normalizeOkpX448Jwk(ownJwk, "own_jwk"); - const peerJwk: JWK = normalizeOkpX448Jwk(otherJwk, "other_jwk"); - - const subtle = getSubtle(); - //const infoStr = `ECDH-X448-AES-GCM-v1|my=${myJwk.x}|peer=${peerJwk.x}`; - - if (subtle) { - const algorithms = [{ name: "ECDH", namedCurve: "X448" }, { name: "X448" }]; - - for (const algorithm of algorithms) { - try { - const [myPriv, peerPub] = await Promise.all([ - subtle.importKey("jwk", myJwk, algorithm, false, ["deriveBits"]), - subtle.importKey("jwk", peerJwk, algorithm, false, []), - ]); - - const sharedBits = await subtle.deriveBits( - { name: algorithm.name, public: peerPub }, - myPriv, - 448, - ); - - const sharedSecret = new Uint8Array(sharedBits); - //const aeadKey = await hkdfAesGcmFromShared(sharedSecret, infoStr); - - return bytesToHex(sharedSecret); - } catch { - // Browser doesn't support this algorithm, try next or fall through to software fallback - } - } - } - - const { d: dMyB64u } = myJwk; - //const { x: xMyB64u, d: dMyB64u } = myJwk; - const { x: xPeerB64u } = peerJwk; - - if (!dMyB64u || !xPeerB64u) { - return "Failed to get shared secret due to missing keys"; - } - - const [dRaw, xRawPeer] = [b64uToBytes(dMyB64u), b64uToBytes(xPeerB64u)]; - if (dRaw.length !== 56 || xRawPeer.length !== 56) { - return "Failed to get shared secret due to invalid key lengths"; - } - - const { x448 } = await import("@noble/curves/ed448.js"); - const sharedSecret = new Uint8Array(x448.getSharedSecret(dRaw, xRawPeer)); - //const aeadKey = await hkdfAesGcmFromShared(sharedSecret, infoStr); - - return bytesToHex(sharedSecret); -} diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index a78603c..0000000 --- a/src/index.ts +++ /dev/null @@ -1,338 +0,0 @@ -import { - createTransportClient, - type TransportClient, - READY_STATE, -} from "@tensamin/ttp-core"; -import { createSchema } from "./schema"; -import type { z, ZodObject } from "zod"; -import { x448 } from "@noble/curves/ed448.js"; -import Bun from "bun"; -import { get } from "./user"; -import { decrypt, encrypt, getSharedSecret } from "./crypto"; -import { readFile } from "node:fs/promises"; - -/** - * @param identifier Your app's domain. - * @param redirectUrl The URL should point to the TAuth HTTP Server at http://hostname:port/callback - * @param frontendUrl The URL of the frontend the user interacts with. Only used in development. - * @param appData A Zod schema defining the shape of the app data you want to load/save for each user. - * @param saveSession A function that saves the session ID for a given user ID. This is called after a successful authentication. - * @param httpServer Optional configuration for the TAuth HTTP Server. Defaults to { port: 7878, hostname: "localhost" }. - * @param htmlSuccessPage Optional HTML string or path to an HTML file to be served upon successful authentication. - */ -export class TAuthClient { - frontendUrl: string; - identifier: string; - privateKey: string; - publicKey: string; - saveSession: (userId: number, sessionId: number) => Promise | void; - redirectUrl: URL; - schema: ReturnType; - httpServer: { - port: number; - hostname: string; - }; - clientMap = new Map>(); - private appData: ZodObject; - - constructor({ - frontendUrl, - identifier, - privateKey, - publicKey, - saveSession, - redirectUrl, - appData, - httpServer, - htmlSuccessPage, - }: { - frontendUrl: string; - identifier: string; - privateKey: string; - publicKey: string; - saveSession: (userId: number, sessionId: number) => Promise | void; - redirectUrl: string; - appData: ZodObject; - httpServer?: { - port: number; - hostname: string; - }; - htmlSuccessPage?: string; - }) { - this.frontendUrl = frontendUrl; - this.identifier = identifier; - this.privateKey = privateKey; - this.publicKey = publicKey; - if (typeof saveSession !== "function") { - throw new Error("[tauth] saveSession must be provided as a function"); - } - this.saveSession = saveSession; - this.redirectUrl = new URL(redirectUrl); - this.schema = createSchema(appData); - this.appData = appData; - this.httpServer = httpServer ?? { - port: 7878, - hostname: "localhost", - }; - - Bun.serve({ - hostname: this.httpServer.hostname, - port: this.httpServer.port, - fetch: async (request) => { - const url = new URL(request.url); - switch (url.pathname) { - case "/callback": { - const userId = Number(url.searchParams.get("userId")); - if (!userId) { - return new Response("Missing userId", { status: 400 }); - } - - const potentialChallenge = url.searchParams.get("challenge"); - if (!potentialChallenge) { - const challenge = await this.generateChallenge(userId); - const newLink = this.generateLink(challenge); - return new Response(null, { - status: 302, - headers: { - Location: newLink, - }, - }); - } - - const originalChallenge = url.searchParams.get("originalChallenge"); - if (!originalChallenge) { - return new Response("Missing originalChallenge", { status: 400 }); - } - - const potentialSessionId = Number( - url.searchParams.get("sessionId"), - ); - if (!potentialSessionId || isNaN(potentialSessionId)) { - return new Response("Invalid or missing sessionId", { - status: 400, - }); - } - - if ( - (await this.solveChallenge(userId, originalChallenge)) === - potentialChallenge - ) { - await this.saveSession(userId, potentialSessionId); - - return new Response( - htmlSuccessPage || - (await readFile(new URL("./index.html", import.meta.url), "utf8")), - { - headers: { - "Content-Type": "text/html", - }, - status: 200, - }, - ); - } else { - return new Response("Failed to solve challenge", { status: 400 }); - } - } - - case "/auth": { - return new Response(null, { - status: 302, - headers: { - Location: this.generateLink(), - }, - }); - } - - default: { - return new Response("Not Found", { status: 404 }); - } - } - }, - }); - - console.log( - `[tauth] HTTP Server running at http://${this.httpServer.hostname}:${this.httpServer.port}`, - ); - } - - async generateChallenge(userId: number): Promise { - const userData = await get(userId); - if (!userData) { - throw new Error("User not found"); - } - - const sharedSecret = await getSharedSecret( - this.privateKey, - this.publicKey, - userData.public_key, - ); - - const encrypted = await encrypt(sharedSecret, crypto.randomUUID()); - const decrypted = await decrypt(sharedSecret, encrypted); - console.log( - `[tauth] Generated challenge for user ${userId}: ${decrypted} (encrypted: ${encrypted})`, - ); - - return Array.from(atob(encrypted)) - .map((chat) => chat.charCodeAt(0).toString(16).padStart(2, "0")) - .join(""); - } - - async solveChallenge(userId: number, challenge: string) { - const userData = await get(userId); - if (!userData) { - throw new Error("User not found"); - } - - const sharedSecret = await getSharedSecret( - this.privateKey, - this.publicKey, - userData.public_key, - ); - - return await decrypt(sharedSecret, challenge); - } - - createTTP( - userId: number, - sessionId: number, - omikronUrl: string, - ): Promise> { - console.log( - `[tauth] Creating TTP client for user ${userId} with session ${sessionId} at ${omikronUrl}`, - ); - - const client = createTransportClient(this.schema, { - url: omikronUrl, - onReadyStateChange: (stateIndex) => { - const state = getFriendlyReadyState(stateIndex); - console.log(state); - if (state === "OPEN") { - this.clientMap.get(`${userId}:${sessionId}`)?.send("identification", { - app_identifier: this.identifier, - app_session_id: sessionId, - app_public_key: this.publicKey, - user_id: userId, - }); - } - }, - }); - - this.clientMap.set(`${userId}:${sessionId}`, client); - client.connect(); - - return Promise.resolve(client); - } - - async loadData(userId: number, sessionId: number) { - const connection = await this.createTemporaryTTPConnection( - userId, - sessionId, - ); - - const rawAppData = await connection.send("load_app_data", {}); - const appData = JSON.parse(rawAppData.data.app_data); - const safeAppData = this.appData.safeParse(appData); - - await connection.close(); - - if (!safeAppData.success) { - throw new Error( - `Invalid app data: ${JSON.stringify(safeAppData.error.issues)}`, - ); - } - - return safeAppData.data; - } - - async saveData(userId: number, sessionId: number, data: any) { - const connection = await this.createTemporaryTTPConnection( - userId, - sessionId, - ); - - const safeData = this.appData.safeParse(data); - - if (!safeData.success) { - throw new Error( - `Invalid app data: ${JSON.stringify(safeData.error.issues)}`, - ); - } - - await connection.send("save_app_data", { - app_data: JSON.stringify(safeData.data), - }); - - await connection.close(); - } - - private async createTemporaryTTPConnection( - userId: number, - sessionId: number, - ): Promise> { - const { ip_address } = await fetch( - "https://omega.tensamin.net/api/get/omikron/" + userId, - ).then((res) => res.json()); - - let temporaryClient: TransportClient | undefined; - temporaryClient = createTransportClient(this.schema, { - url: ip_address, - onReadyStateChange: (stateIndex) => { - const state = getFriendlyReadyState(stateIndex); - if (state === "OPEN") { - temporaryClient?.send("identification", { - app_identifier: this.identifier, - app_session_id: sessionId, - app_public_key: this.publicKey, - user_id: userId, - }); - } - }, - }); - - temporaryClient.connect(); - return temporaryClient; - } - - generateLink(challenge?: string): string { - return new URL( - `?identifier=${this.identifier}&redirect=${this.redirectUrl}${challenge ? `&challenge=${challenge}` : ""}`, - this.frontendUrl, - ).toString(); - } -} - -export function getFriendlyReadyState( - stateIndex: number, -): keyof typeof READY_STATE { - return Object.keys(READY_STATE).find( - (key) => READY_STATE[key as keyof typeof READY_STATE] === stateIndex, - ) as keyof typeof READY_STATE; -} - -export function generateKeyPair(): { private: string; public: string } { - function toBase64(bytes: Uint8Array): string { - if (typeof Buffer !== "undefined") { - return Buffer.from(bytes).toString("base64"); - } - - let binary = ""; - for (const byte of bytes) { - binary += String.fromCharCode(byte); - } - - return btoa(binary); - } - - const priv = new Uint8Array(56); - crypto.getRandomValues(priv); - const pub = x448.getPublicKey(priv); - return { - private: toBase64(priv), - public: toBase64(pub), - }; -} - -export * from "./crypto"; -export * from "./schema"; -export * from "./user"; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0786fa6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,507 @@ +use base64::{Engine as _, engine::general_purpose::STANDARD}; +use mtp::client::{ClientConfig, MTPClient, MTPConnection}; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::crypto::{Keyring, PublicKeyBundle}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::time::Duration; +use url::Url; + +const DEFAULT_FRONTEND_URL: &str = "https://tauth.tensamin.net/login"; +const OMEGA_API: &str = "https://omega.tensamin.net/api"; + +#[derive(Debug, thiserror::Error)] +pub enum TAuthError { + #[error("invalid url: {0}")] + Url(#[from] url::ParseError), + #[error("http request failed: {0}")] + Http(#[from] reqwest::Error), + #[error("invalid key material: {0}")] + Key(String), + #[error("mtp communication failed: {0}")] + Mtp(#[from] mtp::common::CommunicationError), + #[error("mtp codec failed: {0}")] + Codec(#[from] mtp::common::CodecError), + #[error("expected string app data in MTP response")] + InvalidAppData, + #[error("json serialization failed: {0}")] + Json(#[from] serde_json::Error), + #[error("missing host public key; pass one explicitly or expose it in the omikron response")] + MissingHostPublicKey, + #[error("missing callback parameter: {0}")] + MissingCallbackParam(&'static str), + #[error("invalid callback parameter `{name}`: {value}")] + InvalidCallbackParam { name: &'static str, value: String }, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct TAuthKeyPair { + pub private: String, + pub public: String, +} + +pub type TAuthKeys = TAuthKeyPair; + +#[derive(Clone, Debug)] +pub struct TAuthConfig { + pub identifier: String, + pub redirect_url: String, + pub private_key: String, + pub frontend_url: String, + pub auth_timeout: Duration, +} + +impl TAuthConfig { + pub fn new( + identifier: impl Into, + redirect_url: impl Into, + private_key: impl Into, + ) -> Self { + Self { + identifier: identifier.into(), + redirect_url: redirect_url.into(), + private_key: private_key.into(), + frontend_url: DEFAULT_FRONTEND_URL.to_string(), + auth_timeout: Duration::from_secs(30), + } + } + + pub fn frontend_url(mut self, frontend_url: impl Into) -> Self { + self.frontend_url = frontend_url.into(); + self + } + + pub fn auth_timeout(mut self, auth_timeout: Duration) -> Self { + self.auth_timeout = auth_timeout; + self + } +} + +#[derive(Debug)] +pub struct TAuthClient { + frontend_url: Url, + identifier: String, + redirect_url: Url, + keyring: Keyring, + public_key_bundle: PublicKeyBundle, + auth_timeout: Duration, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct User { + pub status: String, + pub username: String, + pub public_key: String, + pub user_id: u64, + pub iota_id: u64, + pub sub_level: u64, + pub sub_end: u64, + pub display: String, + pub status_message: String, + pub about: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct OmikronResponse { + ip_address: String, + #[serde(default)] + public_key: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct AuthCallback { + pub user_id: u64, + pub session_id: u64, + pub challenge: Option, + pub original_challenge: Option, + pub host_public_key: Option, +} + +pub struct TAuthSession { + user_id: u64, + session_id: u64, + connection: MTPConnection, +} + +impl TAuthSession { + pub fn new(user_id: u64, session_id: u64, connection: MTPConnection) -> Self { + Self { + user_id, + session_id, + connection, + } + } + + pub fn user_id(&self) -> u64 { + self.user_id + } + + pub fn session_id(&self) -> u64 { + self.session_id + } + + pub fn connection(&self) -> &MTPConnection { + &self.connection + } + + pub async fn load_json(&self) -> Result { + load_json(&self.connection).await + } + + pub async fn load(&self) -> Result { + Ok(serde_json::from_value(self.load_json().await?)?) + } + + pub async fn save_json(&self, data: &Value) -> Result<(), TAuthError> { + save_json(&self.connection, data).await + } + + pub async fn save(&self, data: &T) -> Result<(), TAuthError> { + self.save_json(&serde_json::to_value(data)?).await + } +} + +impl TAuthClient { + pub fn new( + identifier: impl Into, + redirect_url: impl AsRef, + private_key: impl AsRef, + ) -> Result { + Self::from_config(TAuthConfig::new(identifier, redirect_url.as_ref(), private_key.as_ref())) + } + + pub fn with_frontend_url( + identifier: impl Into, + redirect_url: impl AsRef, + private_key: impl AsRef, + frontend_url: impl AsRef, + ) -> Result { + Self::from_config( + TAuthConfig::new(identifier, redirect_url.as_ref(), private_key.as_ref()) + .frontend_url(frontend_url.as_ref()), + ) + } + + pub fn from_config(config: TAuthConfig) -> Result { + let keyring = decode_keyring(&config.private_key)?; + let public_key_bundle = keyring.public_key_bundle(); + + Ok(Self { + frontend_url: Url::parse(&config.frontend_url)?, + identifier: config.identifier, + redirect_url: Url::parse(&config.redirect_url)?, + keyring, + public_key_bundle, + auth_timeout: config.auth_timeout, + }) + } + + pub fn identifier(&self) -> &str { + &self.identifier + } + + pub fn public_key(&self) -> String { + encode_bytes(&self.public_key_bundle.as_bytes()) + } + + pub fn redirect_url(&self) -> &Url { + &self.redirect_url + } + + pub fn frontend_url(&self) -> &Url { + &self.frontend_url + } + + pub fn key_pair(&self) -> TAuthKeyPair { + TAuthKeyPair { + private: encode_bytes(&self.keyring.to_bytes()), + public: self.public_key(), + } + } + + pub fn generate_link(&self, challenge: Option<&str>) -> String { + self.auth_url(challenge) + } + + pub fn auth_url(&self, challenge: Option<&str>) -> String { + let mut link = self.frontend_url.clone(); + { + let mut pairs = link.query_pairs_mut(); + pairs.append_pair("identifier", &self.identifier); + pairs.append_pair("redirect", self.redirect_url.as_str()); + pairs.append_pair("public_key", &self.public_key()); + if let Some(challenge) = challenge { + pairs.append_pair("challenge", challenge); + } + } + link.to_string() + } + + pub fn parse_callback(&self, callback_url: impl AsRef) -> Result { + parse_callback(callback_url) + } + + pub async fn fetch_user(user_id: u64) -> Result { + let url = format!("{OMEGA_API}/get/user/{user_id}"); + Ok(reqwest::get(url).await?.error_for_status()?.json().await?) + } + + pub async fn user(&self, user_id: u64) -> Result { + Self::fetch_user(user_id).await + } + + pub async fn session_from_callback( + &self, + callback: &AuthCallback, + ) -> Result { + let connection = if let Some(host_public_key) = &callback.host_public_key { + self.connect(callback.user_id, callback.session_id, host_public_key) + .await? + } else { + self.connect_with_omikron_key(callback.user_id, callback.session_id) + .await? + }; + + Ok(TAuthSession::new( + callback.user_id, + callback.session_id, + connection, + )) + } + + pub async fn connect_with_omikron_key( + &self, + user_id: u64, + session_id: u64, + ) -> Result { + let omikron = fetch_omikron(user_id).await?; + let host_public_key = omikron.public_key.ok_or(TAuthError::MissingHostPublicKey)?; + self.connect_to_omikron(user_id, session_id, omikron.ip_address, &host_public_key) + .await + } + + pub async fn connect( + &self, + user_id: u64, + session_id: u64, + host_public_key: impl AsRef, + ) -> Result { + let omikron = fetch_omikron(user_id).await?; + self.connect_to_omikron(user_id, session_id, omikron.ip_address, host_public_key) + .await + } + + pub async fn connect_to_omikron( + &self, + user_id: u64, + session_id: u64, + omikron_url: impl Into, + host_public_key: impl AsRef, + ) -> Result { + let host_public_key = decode_public_key_bundle(host_public_key.as_ref())?; + let config = ClientConfig::new(omikron_url.into()) + .with_client_id(session_id) + .with_description(format!("tauth:{}:{user_id}", self.identifier)) + .with_auth_timeout(self.auth_timeout); + Ok(MTPClient::auth_connect(config, &self.keyring, &host_public_key).await?) + } + + pub async fn connect_or_register( + &self, + user_id: u64, + session_id: Option, + host_public_key: impl AsRef, + ) -> Result { + let omikron = fetch_omikron(user_id).await?; + self.connect_or_register_to_omikron(user_id, session_id, omikron.ip_address, host_public_key) + .await + } + + pub async fn connect_or_register_to_omikron( + &self, + user_id: u64, + session_id: Option, + omikron_url: impl Into, + host_public_key: impl AsRef, + ) -> Result { + let host_public_key = decode_public_key_bundle(host_public_key.as_ref())?; + let config = ClientConfig::new(omikron_url.into()) + .with_description(format!("tauth:{}:{user_id}", self.identifier)) + .with_auth_timeout(self.auth_timeout); + Ok(MTPClient::auth_connect_or_register( + config, + session_id, + &self.keyring, + &host_public_key, + ) + .await?) + } + + pub async fn connect_session( + &self, + user_id: u64, + session_id: u64, + host_public_key: impl AsRef, + ) -> Result { + let connection = self.connect(user_id, session_id, host_public_key).await?; + Ok(TAuthSession::new(user_id, session_id, connection)) + } + + pub async fn load_data(connection: &MTPConnection) -> Result { + load_json(connection).await + } + + pub async fn save_data(connection: &MTPConnection, data: &Value) -> Result<(), TAuthError> { + save_json(connection, data).await + } +} + +pub fn generate_key_pair() -> TAuthKeyPair { + let keyring = Keyring::generate(); + TAuthKeyPair { + private: encode_bytes(&keyring.to_bytes()), + public: encode_bytes(&keyring.public_key_bundle().as_bytes()), + } +} + +pub fn generate_keypair() -> TAuthKeyPair { + generate_key_pair() +} + +pub fn parse_callback(callback_url: impl AsRef) -> Result { + let url = Url::parse(callback_url.as_ref())?; + let query = url.query_pairs(); + + let get = |name: &'static str| -> Option { + query + .clone() + .find(|(key, _)| key == name) + .map(|(_, value)| value.into_owned()) + }; + + let user_id = parse_required_u64("userId", get("userId"))?; + let session_id = parse_required_u64("sessionId", get("sessionId"))?; + + Ok(AuthCallback { + user_id, + session_id, + challenge: get("challenge"), + original_challenge: get("originalChallenge"), + host_public_key: get("hostPublicKey").or_else(|| get("host_public_key")), + }) +} + +pub async fn load_json(connection: &MTPConnection) -> Result { + let request = CommunicationValue::new(CommunicationType::LoadAppData); + let response = connection + .request(&request, Some(CommunicationType::LoadAppDataResponse)) + .await?; + let app_data = match response.get_data(DataType::AppData) { + DataValue::Str(value) => value, + _ => return Err(TAuthError::InvalidAppData), + }; + Ok(serde_json::from_str(app_data)?) +} + +pub async fn save_json(connection: &MTPConnection, data: &Value) -> Result<(), TAuthError> { + let app_data = serde_json::to_string(data)?; + let request = CommunicationValue::new(CommunicationType::SaveAppData) + .add_typed_default(DataType::AppData, DataValue::Str(app_data)); + connection + .request(&request, Some(CommunicationType::SaveAppDataResponse)) + .await?; + Ok(()) +} + +fn parse_required_u64(name: &'static str, value: Option) -> Result { + let value = value.ok_or(TAuthError::MissingCallbackParam(name))?; + value + .parse() + .map_err(|_| TAuthError::InvalidCallbackParam { name, value }) +} + +fn encode_bytes(bytes: &[u8]) -> String { + STANDARD.encode(bytes) +} + +fn decode_bytes(value: &str) -> Result, TAuthError> { + STANDARD + .decode(value.trim()) + .map_err(|error| TAuthError::Key(error.to_string())) +} + +fn decode_keyring(value: &str) -> Result { + Keyring::from_bytes(&decode_bytes(value)?).map_err(|error| TAuthError::Key(error.to_string())) +} + +fn decode_public_key_bundle(value: &str) -> Result { + PublicKeyBundle::from_bytes(&decode_bytes(value)?) + .map_err(|error| TAuthError::Key(error.to_string())) +} + +async fn fetch_omikron(user_id: u64) -> Result { + let url = format!("{OMEGA_API}/get/omikron/{user_id}"); + Ok(reqwest::get(url).await?.error_for_status()?.json().await?) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn generated_keys_are_mtp_key_material() { + let keys = generate_key_pair(); + let keyring = decode_keyring(&keys.private).unwrap(); + let public = decode_public_key_bundle(&keys.public).unwrap(); + + assert_eq!(public.as_bytes(), keyring.public_key_bundle().as_bytes()); + } + + #[test] + fn client_generates_auth_link_with_public_key() { + let keys = generate_key_pair(); + let client = TAuthClient::with_frontend_url( + "test.app", + "https://app.example/callback", + keys.private, + "https://tauth.example/login", + ) + .unwrap(); + + let link = Url::parse(&client.generate_link(Some("abc123"))).unwrap(); + assert_eq!(link.query_pairs().find(|(key, _)| key == "identifier").unwrap().1, "test.app"); + assert_eq!(link.query_pairs().find(|(key, _)| key == "challenge").unwrap().1, "abc123"); + assert!(link.query_pairs().any(|(key, value)| key == "public_key" && !value.is_empty())); + } + + #[test] + fn parses_callback_url() { + let callback = parse_callback( + "https://app.example/callback?userId=42&sessionId=7&challenge=a&originalChallenge=b&hostPublicKey=c", + ) + .unwrap(); + + assert_eq!(callback.user_id, 42); + assert_eq!(callback.session_id, 7); + assert_eq!(callback.challenge.as_deref(), Some("a")); + assert_eq!(callback.original_challenge.as_deref(), Some("b")); + assert_eq!(callback.host_public_key.as_deref(), Some("c")); + } + + #[test] + fn parses_snake_case_host_key_callback_url() { + let callback = parse_callback( + "https://app.example/callback?userId=42&sessionId=7&host_public_key=c", + ) + .unwrap(); + + assert_eq!(callback.host_public_key.as_deref(), Some("c")); + } + + #[test] + fn callback_requires_user_id_and_session_id() { + let err = parse_callback("https://app.example/callback?userId=42").unwrap_err(); + assert!(matches!(err, TAuthError::MissingCallbackParam("sessionId"))); + } +} diff --git a/src/schema.ts b/src/schema.ts deleted file mode 100644 index 01339db..0000000 --- a/src/schema.ts +++ /dev/null @@ -1,37 +0,0 @@ -import z, { ZodObject } from "zod"; - -export function createSchema(appData: ZodObject) { - return { - identification: { - request: z.object({ - app_identifier: z.string(), - app_session_id: z.number(), - app_public_key: z.string(), - user_id: z.number(), - }), - response: z.object({ - challenge: z.base64(), - public_key: z.base64(), - }), - }, - challenge_response: { - request: z.object({ - challenge: z.base64(), - }), - response: z.object({}), - }, - - load_app_data: { - request: z.object({}), - response: z.object({ - app_data: z.string(), - }), - }, - save_app_data: { - request: z.object({ - app_data: z.string(), - }), - response: z.object({}), - }, - }; -} diff --git a/src/user.ts b/src/user.ts deleted file mode 100644 index c4943aa..0000000 --- a/src/user.ts +++ /dev/null @@ -1,34 +0,0 @@ -export type User = { - status: string; - username: string; - public_key: string; - user_id: number; - iota_id: number; - sub_level: number; - sub_end: number; - display: string; - status_message: string; - about: string; -}; - -const userCacheMap = new Map(); - -export async function get(userId: number): Promise { - try { - const cachedUser = userCacheMap.get(userId); - if (cachedUser) { - return cachedUser; - } - - const fetchedUser = await fetch( - "https://omega.tensamin.net/api/get/user/" + userId, - ).then((res) => res.json()); - if (fetchedUser) { - userCacheMap.set(userId, fetchedUser); - } - return fetchedUser; - } catch (error) { - console.error("Error fetching user data for userId:", userId, error); - return undefined; - } -} diff --git a/test/crypto.test.ts b/test/crypto.test.ts deleted file mode 100644 index 9b7fa02..0000000 --- a/test/crypto.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import { x448 } from "@noble/curves/ed448.js"; -import { decrypt, encrypt, getSharedSecret } from "../src/crypto"; - -function toBase64(bytes: Uint8Array): string { - if (typeof Buffer !== "undefined") { - return Buffer.from(bytes).toString("base64"); - } - - let binary = ""; - for (const byte of bytes) { - binary += String.fromCharCode(byte); - } - return btoa(binary); -} - -describe("crypto", () => { - test("encrypt/decrypt roundtrip", async () => { - const privA = new Uint8Array(56); - crypto.getRandomValues(privA); - const pubA = x448.getPublicKey(privA); - - const privB = new Uint8Array(56); - crypto.getRandomValues(privB); - const pubB = x448.getPublicKey(privB); - - const sharedA = await getSharedSecret( - toBase64(privA), - toBase64(pubA), - toBase64(pubB), - ); - - const sharedB = await getSharedSecret( - toBase64(privB), - toBase64(pubB), - toBase64(pubA), - ); - - expect(sharedA).toBe(sharedB); - - const message = "hello tauth sdk"; - const cipher = await encrypt(sharedA, message); - const plain = await decrypt(sharedB, cipher); - - expect(plain).toBe(message); - }); - - test("decrypt fails when using wrong shared secret", async () => { - const secretA = "01".repeat(56); - const secretB = "02".repeat(56); - - const cipher = await encrypt(secretA, "sensitive"); - - await expect(decrypt(secretB, cipher)).rejects.toThrow(); - }); - - test("getSharedSecret rejects malformed key material", async () => { - await expect(getSharedSecret("AA==", "AA==", "AA==")).rejects.toThrow( - "not a valid X448", - ); - }); -}); diff --git a/test/index-helpers.test.ts b/test/index-helpers.test.ts deleted file mode 100644 index 18c93af..0000000 --- a/test/index-helpers.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import { READY_STATE } from "@tensamin/ttp-core"; -import { TAuthClient, generateKeyPair, getFriendlyReadyState } from "../src/index"; - -describe("index helpers", () => { - test("generateKeyPair returns base64 keys with expected length", () => { - const pair = generateKeyPair(); - const priv = Buffer.from(pair.private, "base64"); - const pub = Buffer.from(pair.public, "base64"); - - expect(priv.length).toBe(56); - expect(pub.length).toBe(56); - }); - - test("getFriendlyReadyState maps numeric state to key", () => { - for (const [name, value] of Object.entries(READY_STATE)) { - expect(getFriendlyReadyState(value)).toBe(name); - } - }); - - test("generateLink builds auth URL with and without challenge", () => { - const client = Object.create(TAuthClient.prototype) as TAuthClient; - client.frontendUrl = "https://tauth.example.com/login"; - client.identifier = "my-app"; - client.redirectUrl = new URL("https://app.example.com/callback"); - - const withoutChallenge = client.generateLink(); - const withChallenge = client.generateLink("abcdef"); - - const parsedWithout = new URL(withoutChallenge); - const parsedWith = new URL(withChallenge); - - expect(parsedWithout.searchParams.get("identifier")).toBe("my-app"); - expect(parsedWithout.searchParams.get("redirect")).toBe( - "https://app.example.com/callback", - ); - expect(parsedWith.searchParams.get("challenge")).toBe("abcdef"); - }); -}); diff --git a/test/schema.test.ts b/test/schema.test.ts deleted file mode 100644 index a574b41..0000000 --- a/test/schema.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import z from "zod"; -import { createSchema } from "../src/schema"; - -describe("schema", () => { - const appData = z.object({ - my: z.string(), - cool: z.string(), - }); - - const schema = createSchema(appData); - - test("accepts valid identification request", () => { - const parsed = schema.identification.request.parse({ - app_identifier: "app-1", - app_session_id: 42, - app_public_key: "base64key", - user_id: 123, - }); - - expect(parsed.user_id).toBe(123); - }); - - test("rejects invalid challenge response request", () => { - const result = schema.challenge_response.request.safeParse({ - challenge: "not-base64", - }); - - expect(result.success).toBe(false); - }); - - test("accepts valid save_app_data request", () => { - const result = schema.save_app_data.request.safeParse({ - app_data: JSON.stringify({ my: "a", cool: "b" }), - }); - - expect(result.success).toBe(true); - }); -}); diff --git a/test/test-client.test.ts b/test/test-client.test.ts deleted file mode 100644 index 064964b..0000000 --- a/test/test-client.test.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import { TAuthClient, generateKeyPair } from "../src/index"; -import z from "zod"; - -const appDataSchema = z.object({ - name: z.string(), - value: z.string(), -}); - -function createClient(overrides?: Partial[0]>) { - const keys = generateKeyPair(); - return new TAuthClient({ - frontendUrl: "https://tauth.example.com/login", - identifier: "test.app", - privateKey: keys.private, - publicKey: keys.public, - saveSession: () => {}, - redirectUrl: "https://test.app/callback", - appData: appDataSchema, - httpServer: { port: 0, hostname: "localhost" }, - ...overrides, - }); -} - -describe("TAuthClient", () => { - test("constructor throws if saveSession is not a function", () => { - const keys = generateKeyPair(); - expect(() => { - new TAuthClient({ - frontendUrl: "https://tauth.example.com/login", - identifier: "test.app", - privateKey: keys.private, - publicKey: keys.public, - saveSession: "not-a-function" as any, - redirectUrl: "https://test.app/callback", - appData: appDataSchema, - httpServer: { port: 0, hostname: "localhost" }, - }); - }).toThrow("saveSession must be provided as a function"); - }); - - test("constructor sets properties correctly", () => { - const keys = generateKeyPair(); - const saveSession = () => {}; - const client = createClient({ - frontendUrl: "https://tauth.example.com/login", - identifier: "my-app", - privateKey: keys.private, - publicKey: keys.public, - saveSession, - redirectUrl: "https://my-app.com/callback", - }); - - expect(client.frontendUrl).toBe("https://tauth.example.com/login"); - expect(client.identifier).toBe("my-app"); - expect(client.privateKey).toBe(keys.private); - expect(client.publicKey).toBe(keys.public); - expect(client.saveSession).toBe(saveSession); - expect(client.redirectUrl.toString()).toBe("https://my-app.com/callback"); - }); - - test("constructor uses default httpServer when not provided", () => { - const keys = generateKeyPair(); - const client = new TAuthClient({ - frontendUrl: "https://tauth.example.com/login", - identifier: "test.app", - privateKey: keys.private, - publicKey: keys.public, - saveSession: () => {}, - redirectUrl: "https://test.app/callback", - appData: appDataSchema, - httpServer: { port: 0, hostname: "localhost" }, - }); - - expect(client.httpServer).toBeDefined(); - expect(client.httpServer.port).toBe(0); - }); - - test("generateLink produces correct URL without challenge", () => { - const client = Object.create(TAuthClient.prototype) as TAuthClient; - client.frontendUrl = "https://tauth.example.com/login"; - client.identifier = "test-app"; - client.redirectUrl = new URL("https://test.app/callback"); - - const link = client.generateLink(); - const parsed = new URL(link); - - expect(parsed.origin).toBe("https://tauth.example.com"); - expect(parsed.pathname).toBe("/login"); - expect(parsed.searchParams.get("identifier")).toBe("test-app"); - expect(parsed.searchParams.get("redirect")).toBe("https://test.app/callback"); - expect(parsed.searchParams.has("challenge")).toBe(false); - }); - - test("generateLink includes challenge when provided", () => { - const client = Object.create(TAuthClient.prototype) as TAuthClient; - client.frontendUrl = "https://tauth.example.com/login"; - client.identifier = "test-app"; - client.redirectUrl = new URL("https://test.app/callback"); - - const link = client.generateLink("abc123"); - const parsed = new URL(link); - - expect(parsed.searchParams.get("challenge")).toBe("abc123"); - expect(parsed.searchParams.get("identifier")).toBe("test-app"); - }); - - test("schema is created from appData", () => { - const client = createClient(); - - expect(client.schema).toBeDefined(); - expect(client.schema.identification).toBeDefined(); - expect(client.schema.challenge_response).toBeDefined(); - expect(client.schema.load_app_data).toBeDefined(); - expect(client.schema.save_app_data).toBeDefined(); - }); - - test("clientMap is initialized empty", () => { - const client = createClient(); - expect(client.clientMap.size).toBe(0); - }); - - test("generateKeyPair returns valid base64 key pair", () => { - const keys = generateKeyPair(); - - expect(keys.private).toBeDefined(); - expect(keys.public).toBeDefined(); - expect(typeof keys.private).toBe("string"); - expect(typeof keys.public).toBe("string"); - - const privBuf = Buffer.from(keys.private, "base64"); - const pubBuf = Buffer.from(keys.public, "base64"); - expect(privBuf.length).toBe(56); - expect(pubBuf.length).toBe(56); - }); -}); diff --git a/test/user.test.ts b/test/user.test.ts deleted file mode 100644 index db2c1e3..0000000 --- a/test/user.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"; -import { get } from "../src/user"; - -describe("user.get", () => { - const originalFetch = globalThis.fetch; - - beforeEach(() => { - // Reset mocks each test and isolate IDs to avoid cache collisions. - mock.restore(); - }); - - afterEach(() => { - globalThis.fetch = originalFetch; - }); - - test("fetches and returns user data", async () => { - const fetchMock = mock(async () => { - return { - json: async () => ({ - status: "ok", - username: "alice", - public_key: "pub", - user_id: 9001, - iota_id: 1, - sub_level: 0, - sub_end: 0, - display: "Alice", - status_message: "hi", - about: "about", - }), - } as Response; - }); - - globalThis.fetch = fetchMock as typeof fetch; - - const result = await get(9001); - - expect(result?.username).toBe("alice"); - expect(fetchMock).toHaveBeenCalledTimes(1); - }); - - test("returns cached value for repeated user id", async () => { - const fetchMock = mock(async () => { - return { - json: async () => ({ - status: "ok", - username: "bob", - public_key: "pub2", - user_id: 9002, - iota_id: 1, - sub_level: 0, - sub_end: 0, - display: "Bob", - status_message: "hi", - about: "about", - }), - } as Response; - }); - - globalThis.fetch = fetchMock as typeof fetch; - - const first = await get(9002); - const second = await get(9002); - - expect(first?.username).toBe("bob"); - expect(second?.username).toBe("bob"); - expect(fetchMock).toHaveBeenCalledTimes(1); - }); - - test("returns undefined when fetch fails", async () => { - const fetchMock = mock(async () => { - throw new Error("network down"); - }); - - globalThis.fetch = fetchMock as typeof fetch; - - const result = await get(9003); - - expect(result).toBeUndefined(); - expect(fetchMock).toHaveBeenCalledTimes(1); - }); -}); diff --git a/tsconfig.build.json b/tsconfig.build.json deleted file mode 100644 index cd25925..0000000 --- a/tsconfig.build.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "noEmit": false, - "declaration": true, - "declarationMap": false, - "emitDeclarationOnly": false, - "outDir": "./dist", - "rootDir": "./src", - "noUnusedLocals": false, - "noUnusedParameters": false, - "allowImportingTsExtensions": false, - "moduleResolution": "bundler", - "verbatimModuleSyntax": true, - "sourceMap": false - }, - "include": ["src"] -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 0cb920c..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "compilerOptions": { - // Environment setup & latest features - "lib": ["ES2022", "DOM"], - "target": "ESNext", - "module": "esnext", - "moduleDetection": "force", - "jsx": "react-jsx", - "allowJs": true, - - // Bundler mode - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "noEmit": true, - - // Best practices - "strict": true, - "skipLibCheck": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, - - // Some stricter flags (disabled by default) - "noUnusedLocals": false, - "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false, - - // Node.js types - "types": ["node"] - } -} diff --git a/type-maps.yaml b/type-maps.yaml new file mode 100644 index 0000000..c234b65 --- /dev/null +++ b/type-maps.yaml @@ -0,0 +1,11 @@ +protocol_version: "1.0" + +type_maps: + "1.0": + CommunicationTypes: + LoadAppData: 32 + LoadAppDataResponse: 33 + SaveAppData: 34 + SaveAppDataResponse: 35 + DataTypes: + AppData: 32