mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-09-19 19:20:13 +00:00
Compare commits
1
Commits
main
..
eccd6294d0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eccd6294d0 |
@@ -44,7 +44,6 @@ tmp/
|
||||
.tmp-bitwarden-clients/
|
||||
|
||||
nodewarden-wiki/
|
||||
nodewarden.wiki/
|
||||
wiki/
|
||||
AGENTS.md
|
||||
settings.json
|
||||
@@ -58,7 +57,6 @@ NodeWarden-compat/
|
||||
|
||||
.reasonix/
|
||||
.upstream/
|
||||
bitwarden-upstream/
|
||||
|
||||
# Compatibility analysis documents
|
||||
BITWARDEN_COMPATIBILITY_ANALYSIS.md
|
||||
|
||||
Generated
+184
-176
@@ -11,27 +11,27 @@
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^2.2.0",
|
||||
"@simplewebauthn/server": "^13.3.2",
|
||||
"@tanstack/react-query": "^5.101.2",
|
||||
"@zip.js/zip.js": "^2.8.26",
|
||||
"@tanstack/react-query": "^5.101.4",
|
||||
"@zip.js/zip.js": "^2.8.34",
|
||||
"fflate": "^0.8.3",
|
||||
"jsqr": "1.4.0",
|
||||
"lucide-preact": "^1.22.0",
|
||||
"preact": "^10.29.3",
|
||||
"lucide-preact": "^1.27.0",
|
||||
"preact": "^10.29.7",
|
||||
"qrcode-generator": "^2.0.4",
|
||||
"wouter": "^3.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20260630.1",
|
||||
"@preact/preset-vite": "^2.10.5",
|
||||
"@types/node": "^26.0.1",
|
||||
"autoprefixer": "^10.5.2",
|
||||
"opencc-js": "^1.3.2",
|
||||
"@preact/preset-vite": "^2.10.6",
|
||||
"@types/node": "^26.1.1",
|
||||
"autoprefixer": "^10.5.4",
|
||||
"opencc-js": "^1.4.1",
|
||||
"postcss": "^8.5.23",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"tsx": "^4.22.4",
|
||||
"tsx": "^4.23.1",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.1.3",
|
||||
"wrangler": "^4.105.0"
|
||||
"vite": "^8.1.5",
|
||||
"wrangler": "^4.114.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
@@ -613,9 +613,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/workerd-darwin-64": {
|
||||
"version": "1.20260625.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260625.1.tgz",
|
||||
"integrity": "sha512-naCfBv0WnnTQIQPTniqMoUlklOIFjrAcSn1X+IAOhY8aFLF/xGYtFjs1eEE8sFib3ZuChGGpU23FFORVczqr0A==",
|
||||
"version": "1.20260722.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260722.1.tgz",
|
||||
"integrity": "sha512-vZOP8vIS3NwnuaO+gz0FZ7kIGeiO3bZmxV35Ph9zOXKSREhDFlH7wQ7mkCdhW3O4jnXsew+XT7b+DNEI2CcJGQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -630,9 +630,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/workerd-darwin-arm64": {
|
||||
"version": "1.20260625.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260625.1.tgz",
|
||||
"integrity": "sha512-jmH6zjp6Wrux46+qtFwDwrj+vd7s5bdwEqeGvdnwE0a4IEeAhKs0L42HQOyID+g5lkrHq9m55+AbhtmRAm63Pw==",
|
||||
"version": "1.20260722.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260722.1.tgz",
|
||||
"integrity": "sha512-EmIQymihDq6WNdER4+LF8Qn80yqayBUpJ+tkOO7wmY8pmgfyXjIUFNXotl21AHovTeu2seR7HdVUgeN/BilCWw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -647,9 +647,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/workerd-linux-64": {
|
||||
"version": "1.20260625.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260625.1.tgz",
|
||||
"integrity": "sha512-MiQkpA/dX8d83Zp64pzHUKfd6ca4cvwxnNobSP6CnXvfESvnNI9pfa+nfwnParla36sPmnYntNkjR7NjRuDeKQ==",
|
||||
"version": "1.20260722.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260722.1.tgz",
|
||||
"integrity": "sha512-jvZ3k9fxcnEn04s80CgIYxQfpOyAiz/8qC42DP8EBa9tR27qWyg9wmm31zIobVlrgBZn/+8NfdP73avRGcQOjQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -664,9 +664,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/workerd-linux-arm64": {
|
||||
"version": "1.20260625.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260625.1.tgz",
|
||||
"integrity": "sha512-LxxW7Qv60Xvv37+w6gUSDpYZziyqMy+cZWd9IvSA5ehVgKAxmzEaYPMiSZlxk32nbIWL9u/tfjXYCOKJ4Lo+XQ==",
|
||||
"version": "1.20260722.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260722.1.tgz",
|
||||
"integrity": "sha512-BOSB55SMNdy+DA5uj2WirgiNanpHGis5PVvXH1wSfvjRKr4JGgWK+EZzxz0RFUo6QjjQQC/NimEzNZ7va7jmKg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -681,9 +681,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudflare/workerd-windows-64": {
|
||||
"version": "1.20260625.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260625.1.tgz",
|
||||
"integrity": "sha512-LH6iIX1HHaTwVKV5VokDxxUErXJzQoNZFRwVm7Vx/3fB/ApcTcRCUaMqcxI4as94jEUqg+pmX5czOndiveohow==",
|
||||
"version": "1.20260722.1",
|
||||
"resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260722.1.tgz",
|
||||
"integrity": "sha512-sYM8YgUpKnRz2xjvdJLX1Ojzoi4MlA4gk8WTTExhGydjYB2UTs5NIbv0ZmpKgMoK9io3ixgmiW56ZnTbcWOdiA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1900,9 +1900,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.138.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz",
|
||||
"integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==",
|
||||
"version": "0.139.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
|
||||
"integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -2107,9 +2107,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@preact/preset-vite": {
|
||||
"version": "2.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.10.5.tgz",
|
||||
"integrity": "sha512-p0vJpxiVO7KWWazWny3LUZ+saXyZKWv6Ju0bYMWNJRp2YveufRPgSUB1C4MTqGJfz07EehMgfN+AJNwQy+w6Iw==",
|
||||
"version": "2.10.6",
|
||||
"resolved": "https://registry.npmjs.org/@preact/preset-vite/-/preset-vite-2.10.6.tgz",
|
||||
"integrity": "sha512-ZZ5RIT2ZVXg8UxRA8VZpoT8CdpDG7RFIqOT4bELqNBp85+HKvQBbgmh3gsoW1UOQXx26TLe+ZRNKI7q281Kckw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2199,9 +2199,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz",
|
||||
"integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2216,9 +2216,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz",
|
||||
"integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2233,9 +2233,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz",
|
||||
"integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz",
|
||||
"integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2250,9 +2250,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz",
|
||||
"integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz",
|
||||
"integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2267,9 +2267,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz",
|
||||
"integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz",
|
||||
"integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2284,9 +2284,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz",
|
||||
"integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2304,9 +2304,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz",
|
||||
"integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz",
|
||||
"integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2324,9 +2324,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz",
|
||||
"integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -2344,9 +2344,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz",
|
||||
"integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -2364,9 +2364,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz",
|
||||
"integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz",
|
||||
"integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2384,9 +2384,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz",
|
||||
"integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz",
|
||||
"integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2404,9 +2404,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz",
|
||||
"integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz",
|
||||
"integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2421,9 +2421,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz",
|
||||
"integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
|
||||
"integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -2440,9 +2440,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz",
|
||||
"integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz",
|
||||
"integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2457,9 +2457,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz",
|
||||
"integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz",
|
||||
"integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2543,9 +2543,9 @@
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.101.2",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.2.tgz",
|
||||
"integrity": "sha512-hH5MLoJhF7KaIGd7q3xTXGXvslI+GYlM1Z/35aSHHWaCJWB7XvTSHYuV3eM7tw+aE0mT/xMro4M4Q9rCGHT0lw==",
|
||||
"version": "5.101.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.4.tgz",
|
||||
"integrity": "sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2553,12 +2553,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.101.2",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.2.tgz",
|
||||
"integrity": "sha512-seDkr6kzGzX1okaaTtZPtgA688CDPlXUz1C6xSg0ESqn04Vuc8tlrYms1s3de+znBqhPVxFRfpAfUf+6XvfPWg==",
|
||||
"version": "5.101.4",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.4.tgz",
|
||||
"integrity": "sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.101.2"
|
||||
"@tanstack/query-core": "5.101.4"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@@ -2601,9 +2601,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz",
|
||||
"integrity": "sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==",
|
||||
"version": "26.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz",
|
||||
"integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2611,9 +2611,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@zip.js/zip.js": {
|
||||
"version": "2.8.26",
|
||||
"resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.26.tgz",
|
||||
"integrity": "sha512-RQ4h9F6DOiHxpdocUDrOl6xBM+yOtz+LkUol47AVWcfebGBDpZ7w7Xvz9PS24JgXvLGiXXzSAfdCdVy1tPlaFA==",
|
||||
"version": "2.8.34",
|
||||
"resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.34.tgz",
|
||||
"integrity": "sha512-+6a3lyqq69rpseLbvDPiVIWsZ/HdTGAAD6afFtug6ECPDGttb2dHnPC6cJgdPofYkzL9OvXizegq+DQVfL2rnA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"bun": ">=0.7.0",
|
||||
@@ -2677,9 +2677,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.5.2",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.2.tgz",
|
||||
"integrity": "sha512-rD5t5DwOjJdmSORcTq64j8MawTC+tbQ+HHqjR4NDumamy/ambn1UJrlKL+KdwujWxMkFjPM3pPHOEA9tl4767Q==",
|
||||
"version": "10.5.4",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz",
|
||||
"integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -2697,8 +2697,8 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.28.4",
|
||||
"caniuse-lite": "^1.0.30001799",
|
||||
"browserslist": "^4.28.6",
|
||||
"caniuse-lite": "^1.0.30001806",
|
||||
"fraction.js": "^5.3.4",
|
||||
"picocolors": "^1.1.1",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
@@ -2714,9 +2714,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/autoprefixer/node_modules/browserslist": {
|
||||
"version": "4.28.4",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz",
|
||||
"integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==",
|
||||
"version": "4.28.7",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz",
|
||||
"integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -2734,10 +2734,10 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.10.38",
|
||||
"caniuse-lite": "^1.0.30001799",
|
||||
"electron-to-chromium": "^1.5.376",
|
||||
"node-releases": "^2.0.48",
|
||||
"baseline-browser-mapping": "^2.10.44",
|
||||
"caniuse-lite": "^1.0.30001806",
|
||||
"electron-to-chromium": "^1.5.393",
|
||||
"node-releases": "^2.0.51",
|
||||
"update-browserslist-db": "^1.2.3"
|
||||
},
|
||||
"bin": {
|
||||
@@ -2758,9 +2758,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.10.40",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz",
|
||||
"integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==",
|
||||
"version": "2.11.4",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.4.tgz",
|
||||
"integrity": "sha512-s4+sLr9mZ/CyqeRritFeYV/Zx73OAtmaHn6kkBS1XRoJn1hrg3xIDUcpicAEX68tkcIN0iBCgti31C8zxtkhsQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -2855,9 +2855,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001799",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz",
|
||||
"integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==",
|
||||
"version": "1.0.30001806",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz",
|
||||
"integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3089,9 +3089,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.381",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.381.tgz",
|
||||
"integrity": "sha512-n9Wa6yB+vDsGuA8AKbl/0z7HbvWqt5jxIdvr1IUicd0ryPrk7/xzwqLv8D9AbbvZ6avVNtXYLTfmgFHkwkyelg==",
|
||||
"version": "1.5.396",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.396.tgz",
|
||||
"integrity": "sha512-yHiw2Y3C3H9U6TMbOfoWK/BPreiOPXRfTWPBwQBoZG6/8TB6eOPnsy5oaRYuatR7Fw2SJ4kKforgufeo7fq0EQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -3802,9 +3802,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-preact": {
|
||||
"version": "1.22.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-preact/-/lucide-preact-1.22.0.tgz",
|
||||
"integrity": "sha512-zFaBtoaWQgvapVEI96M3b5iUOlAEvpUfDuW3Gs8K1RHDyoOTrnXm+Cz5tupm8StKbRKn3W/YKIPolllf5voVDw==",
|
||||
"version": "1.27.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-preact/-/lucide-preact-1.27.0.tgz",
|
||||
"integrity": "sha512-pXP6GW3s6hhGJxXchebwMqjeCffYwlvp2ID060h+Gu/itGd+JxOE+SUUQTcawZkFNZWJaD9LLE4UF/CuDyO0oA==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"preact": "^10.27.2"
|
||||
@@ -3858,16 +3858,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/miniflare": {
|
||||
"version": "4.20260625.0",
|
||||
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260625.0.tgz",
|
||||
"integrity": "sha512-3kKXwRUObJsnBYPBgR0NiNZYKF/yv8GFyha1cx2EeAEraxNODgRVcyeRo+F1ok1tg5Mg7iUpOWSkknQTHuFhwA==",
|
||||
"version": "4.20260722.0",
|
||||
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260722.0.tgz",
|
||||
"integrity": "sha512-LW6ABMhCx/yIEFBLC/DO4yAhdm2T/G7jp7pr5T2kj895+CCIaHZqpMXdW9O6YE48LcYcCJChwWc8aEs1vpbTXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "0.8.1",
|
||||
"sharp": "0.34.5",
|
||||
"sharp": "0.35.2",
|
||||
"undici": "7.28.0",
|
||||
"workerd": "1.20260625.1",
|
||||
"workerd": "1.20260722.1",
|
||||
"ws": "8.21.0",
|
||||
"youch": "4.1.0-beta.10"
|
||||
},
|
||||
@@ -3904,9 +3904,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.18",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
|
||||
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
|
||||
"version": "3.3.16",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
|
||||
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3934,9 +3934,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.50",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz",
|
||||
"integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==",
|
||||
"version": "2.0.51",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz",
|
||||
"integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -4001,9 +4001,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/opencc-js": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/opencc-js/-/opencc-js-1.3.2.tgz",
|
||||
"integrity": "sha512-lO4Kq8J4TcPTa8qHcx5qazQCn+NM68kNwLJOQ58DG9MV8v25XJxByMB74zDGmr3LjJMGqDdrvQfoCi3FO0SC2A==",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/opencc-js/-/opencc-js-1.4.1.tgz",
|
||||
"integrity": "sha512-2lPLcrg7cnh1ATSduOxhnk/jq6KlR6w+5Ihl4wF7Z5e5Rva4vxTyugV5rZ7EiUCOZl+EDOLetIix+BhgYDCC+A==",
|
||||
"dev": true,
|
||||
"license": "MIT AND Apache-2.0"
|
||||
},
|
||||
@@ -4036,9 +4036,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
|
||||
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -4225,13 +4225,21 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/preact": {
|
||||
"version": "10.29.3",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.29.3.tgz",
|
||||
"integrity": "sha512-D9NL1GAnJZhc3RndVs4gDdxEeU9TcHgywMrhhOsnpdlvFjdbx0gAsLUnH6JEhlJH5giL7Tx5biWPUSEXE/HPzw==",
|
||||
"version": "10.29.7",
|
||||
"resolved": "https://registry.npmjs.org/preact/-/preact-10.29.7.tgz",
|
||||
"integrity": "sha512-DCHYrK/B10yUD3ZjLfhZ3WIE/9Vf9VFUODcRE2dRomTYDpJk6z6L9wecSfhfE6M9ZTHUdyQkoC46arIDhEV84Q==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/preact"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"preact-render-to-string": ">=5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"preact-render-to-string": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pvtsutils": {
|
||||
@@ -4374,13 +4382,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz",
|
||||
"integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==",
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
|
||||
"integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.138.0",
|
||||
"@oxc-project/types": "=0.139.0",
|
||||
"@rolldown/pluginutils": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -4390,21 +4398,21 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.1.4",
|
||||
"@rolldown/binding-darwin-arm64": "1.1.4",
|
||||
"@rolldown/binding-darwin-x64": "1.1.4",
|
||||
"@rolldown/binding-freebsd-x64": "1.1.4",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.1.4",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.1.4",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.1.4",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.1.4",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.1.4",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.1.4",
|
||||
"@rolldown/binding-linux-x64-musl": "1.1.4",
|
||||
"@rolldown/binding-openharmony-arm64": "1.1.4",
|
||||
"@rolldown/binding-wasm32-wasi": "1.1.4",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.1.4",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.1.4"
|
||||
"@rolldown/binding-android-arm64": "1.1.5",
|
||||
"@rolldown/binding-darwin-arm64": "1.1.5",
|
||||
"@rolldown/binding-darwin-x64": "1.1.5",
|
||||
"@rolldown/binding-freebsd-x64": "1.1.5",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.1.5",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.1.5",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.1.5",
|
||||
"@rolldown/binding-linux-x64-musl": "1.1.5",
|
||||
"@rolldown/binding-openharmony-arm64": "1.1.5",
|
||||
"@rolldown/binding-wasm32-wasi": "1.1.5",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.1.5",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/run-parallel": {
|
||||
@@ -4683,9 +4691,9 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz",
|
||||
"integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==",
|
||||
"version": "4.23.1",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz",
|
||||
"integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4808,16 +4816,16 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz",
|
||||
"integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==",
|
||||
"version": "8.1.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz",
|
||||
"integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"postcss": "^8.5.16",
|
||||
"rolldown": "~1.1.3",
|
||||
"picomatch": "^4.0.5",
|
||||
"postcss": "^8.5.17",
|
||||
"rolldown": "~1.1.5",
|
||||
"tinyglobby": "^0.2.17"
|
||||
},
|
||||
"bin": {
|
||||
@@ -4904,9 +4912,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/workerd": {
|
||||
"version": "1.20260625.1",
|
||||
"resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260625.1.tgz",
|
||||
"integrity": "sha512-GApQvFX52SDM6L4u0+RRnUDB1wJOnEwoXjinkmOPtIyofWBxrlZckdegJSYc1leg++lLZ3+DQ4zMVmBqYVtzfA==",
|
||||
"version": "1.20260722.1",
|
||||
"resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260722.1.tgz",
|
||||
"integrity": "sha512-NycKuc1x2onvsRfGGpM093vRlLFU2zHDAM0+APpccfg4+gZxDGCH27RmdDvkeBuoZyYqgLo3oAfF6re4mvC3vQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
@@ -4917,11 +4925,11 @@
|
||||
"node": ">=16"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@cloudflare/workerd-darwin-64": "1.20260625.1",
|
||||
"@cloudflare/workerd-darwin-arm64": "1.20260625.1",
|
||||
"@cloudflare/workerd-linux-64": "1.20260625.1",
|
||||
"@cloudflare/workerd-linux-arm64": "1.20260625.1",
|
||||
"@cloudflare/workerd-windows-64": "1.20260625.1"
|
||||
"@cloudflare/workerd-darwin-64": "1.20260722.1",
|
||||
"@cloudflare/workerd-darwin-arm64": "1.20260722.1",
|
||||
"@cloudflare/workerd-linux-64": "1.20260722.1",
|
||||
"@cloudflare/workerd-linux-arm64": "1.20260722.1",
|
||||
"@cloudflare/workerd-windows-64": "1.20260722.1"
|
||||
}
|
||||
},
|
||||
"node_modules/wouter": {
|
||||
@@ -4939,9 +4947,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wrangler": {
|
||||
"version": "4.105.0",
|
||||
"resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.105.0.tgz",
|
||||
"integrity": "sha512-7dXFH6OLj1Fv0y6ZeRPUxFTkp+duWD7/xxVi/1c0vfOeEYwIFKWB7cdqnY05DvY1Ta3BnqAwRkXfLs8PDj538g==",
|
||||
"version": "4.114.0",
|
||||
"resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.114.0.tgz",
|
||||
"integrity": "sha512-M65P25t5UHA1TIJfgZXDcj+YzVobgKdRguM2QPz0xnxLFuOcuE3ErgllDht0iaho7MS4o0g/Bb4YK2+GT+bibg==",
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -4949,10 +4957,10 @@
|
||||
"@cloudflare/unenv-preset": "2.16.1",
|
||||
"blake3-wasm": "2.1.5",
|
||||
"esbuild": "0.28.1",
|
||||
"miniflare": "4.20260625.0",
|
||||
"miniflare": "4.20260722.0",
|
||||
"path-to-regexp": "6.3.0",
|
||||
"unenv": "2.0.0-rc.24",
|
||||
"workerd": "1.20260625.1"
|
||||
"workerd": "1.20260722.1"
|
||||
},
|
||||
"bin": {
|
||||
"cf-wrangler": "bin/cf-wrangler.js",
|
||||
@@ -4966,7 +4974,7 @@
|
||||
"fsevents": "2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20260625.1"
|
||||
"@cloudflare/workers-types": "^5.20260722.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@cloudflare/workers-types": {
|
||||
|
||||
+12
-14
@@ -17,7 +17,6 @@
|
||||
"i18n:validate": "node scripts/i18n-validate.cjs",
|
||||
"test:config-compatibility": "tsx --test scripts/config-compatibility.test.ts",
|
||||
"test:web-crypto": "tsx --test scripts/web-crypto-availability.test.ts",
|
||||
"test:notifications-security": "tsx --test scripts/notifications-security.test.ts",
|
||||
"test:webauthn-mobile": "node --test scripts/webauthn-mobile-connector.test.mjs",
|
||||
"test:webauthn-connector": "node --test scripts/webauthn-connector.test.mjs && tsx --test scripts/webauthn-connector-headers.test.ts",
|
||||
"test:webauthn-connectors": "node --test scripts/webauthn-mobile-connector.test.mjs scripts/webauthn-connector.test.mjs && tsx --test scripts/webauthn-connector-headers.test.ts",
|
||||
@@ -49,8 +48,7 @@
|
||||
}
|
||||
},
|
||||
"overrides": {
|
||||
"nanoid": "3.3.18",
|
||||
"undici": "8.9.0",
|
||||
"undici": ">=7.28.0",
|
||||
"@babel/core": ">=7.29.6",
|
||||
"esbuild": ">=0.28.1",
|
||||
"ws": "8.21.0",
|
||||
@@ -58,26 +56,26 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20260630.1",
|
||||
"@preact/preset-vite": "^2.10.5",
|
||||
"@types/node": "^26.0.1",
|
||||
"autoprefixer": "^10.5.2",
|
||||
"opencc-js": "^1.3.2",
|
||||
"@preact/preset-vite": "^2.10.6",
|
||||
"@types/node": "^26.1.1",
|
||||
"autoprefixer": "^10.5.4",
|
||||
"opencc-js": "^1.4.1",
|
||||
"postcss": "^8.5.23",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"tsx": "^4.22.4",
|
||||
"tsx": "^4.23.1",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.1.3",
|
||||
"wrangler": "^4.105.0"
|
||||
"vite": "^8.1.5",
|
||||
"wrangler": "^4.114.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@noble/hashes": "^2.2.0",
|
||||
"@simplewebauthn/server": "^13.3.2",
|
||||
"@tanstack/react-query": "^5.101.2",
|
||||
"@zip.js/zip.js": "^2.8.26",
|
||||
"@tanstack/react-query": "^5.101.4",
|
||||
"@zip.js/zip.js": "^2.8.34",
|
||||
"fflate": "^0.8.3",
|
||||
"jsqr": "1.4.0",
|
||||
"lucide-preact": "^1.22.0",
|
||||
"preact": "^10.29.3",
|
||||
"lucide-preact": "^1.27.0",
|
||||
"preact": "^10.29.7",
|
||||
"qrcode-generator": "^2.0.4",
|
||||
"wouter": "^3.10.0"
|
||||
}
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import { handleNotificationsHub, handleNotificationsNegotiate } from '../src/handlers/notifications';
|
||||
import type { Env } from '../src/types';
|
||||
import { createJWT } from '../src/utils/jwt';
|
||||
|
||||
const secret = 'notification-security-test-secret-32-bytes';
|
||||
const userId = 'd75020e1-2de4-46e8-b8f1-475d127b51f2';
|
||||
const securityStamp = 'security-stamp';
|
||||
|
||||
function createTestEnv() {
|
||||
const connectionTokens = new Map<string, { userId: string; deviceIdentifier: string | null; expiresAt: number }>();
|
||||
const forwardedHubUrls: string[] = [];
|
||||
const durableObjectNames: string[] = [];
|
||||
const userRow = {
|
||||
id: userId,
|
||||
email: 'user@example.test',
|
||||
name: 'Test User',
|
||||
master_password_hint: null,
|
||||
master_password_hash: 'hash',
|
||||
key: 'key',
|
||||
private_key: null,
|
||||
public_key: null,
|
||||
kdf_type: 0,
|
||||
kdf_iterations: 600000,
|
||||
kdf_memory: null,
|
||||
kdf_parallelism: null,
|
||||
security_stamp: securityStamp,
|
||||
role: 'user',
|
||||
status: 'active',
|
||||
verify_devices: 0,
|
||||
totp_secret: null,
|
||||
totp_recovery_code: null,
|
||||
yubikey_key1: null,
|
||||
yubikey_key2: null,
|
||||
yubikey_key3: null,
|
||||
yubikey_key4: null,
|
||||
yubikey_key5: null,
|
||||
yubikey_nfc: 0,
|
||||
api_key: null,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const db = {
|
||||
prepare() {
|
||||
return {
|
||||
bind() {
|
||||
return this;
|
||||
},
|
||||
async first() {
|
||||
return userRow;
|
||||
},
|
||||
};
|
||||
},
|
||||
} as unknown as D1Database;
|
||||
|
||||
const stub = {
|
||||
async fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
||||
const request = new Request(input, init);
|
||||
const url = new URL(request.url);
|
||||
if (url.pathname === '/internal/ws-token') {
|
||||
const body = await request.json() as {
|
||||
token: string;
|
||||
userId: string;
|
||||
deviceIdentifier: string | null;
|
||||
expiresAt: number;
|
||||
};
|
||||
connectionTokens.set(body.token, body);
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
if (url.pathname === '/internal/ws-token/consume') {
|
||||
const { token } = await request.json() as { token: string };
|
||||
const connection = connectionTokens.get(token);
|
||||
connectionTokens.delete(token);
|
||||
if (!connection || connection.expiresAt <= Date.now()) return new Response(null, { status: 401 });
|
||||
return Response.json(connection);
|
||||
}
|
||||
forwardedHubUrls.push(request.url);
|
||||
return new Response(null, { status: 204 });
|
||||
},
|
||||
};
|
||||
|
||||
const env = {
|
||||
DB: db,
|
||||
JWT_SECRET: secret,
|
||||
NOTIFICATIONS_HUB: {
|
||||
idFromName(name: string) {
|
||||
durableObjectNames.push(name);
|
||||
return name;
|
||||
},
|
||||
get() {
|
||||
return stub;
|
||||
},
|
||||
},
|
||||
} as unknown as Env;
|
||||
|
||||
return { env, connectionTokens, durableObjectNames, forwardedHubUrls };
|
||||
}
|
||||
|
||||
async function validAccessToken(): Promise<string> {
|
||||
return createJWT({
|
||||
sub: userId,
|
||||
email: 'user@example.test',
|
||||
name: 'Test User',
|
||||
sstamp: securityStamp,
|
||||
}, secret);
|
||||
}
|
||||
|
||||
test('query access_token cannot authenticate a websocket', async () => {
|
||||
const { env, forwardedHubUrls } = createTestEnv();
|
||||
const token = await validAccessToken();
|
||||
const response = await handleNotificationsHub(new Request(
|
||||
`https://vault.example.test/notifications/hub?access_token=${encodeURIComponent(token)}`,
|
||||
{ headers: { Upgrade: 'websocket' } }
|
||||
), env);
|
||||
|
||||
assert.equal(response.status, 401);
|
||||
assert.deepEqual(forwardedHubUrls, []);
|
||||
});
|
||||
|
||||
test('Authorization bearer token still authenticates notifications', async () => {
|
||||
const { env, forwardedHubUrls } = createTestEnv();
|
||||
const token = await validAccessToken();
|
||||
const response = await handleNotificationsHub(new Request(
|
||||
'https://vault.example.test/notifications/hub',
|
||||
{ headers: { Authorization: `Bearer ${token}`, Upgrade: 'websocket' } }
|
||||
), env);
|
||||
|
||||
assert.equal(response.status, 204);
|
||||
assert.equal(new URL(forwardedHubUrls[0]).searchParams.get('nw_uid'), userId);
|
||||
});
|
||||
|
||||
test('negotiate issues a short-lived one-time websocket connection token', async () => {
|
||||
const { env, connectionTokens, forwardedHubUrls } = createTestEnv();
|
||||
const accessToken = await validAccessToken();
|
||||
const negotiate = await handleNotificationsNegotiate(new Request(
|
||||
'https://vault.example.test/notifications/hub/negotiate',
|
||||
{ method: 'POST', headers: { Authorization: `Bearer ${accessToken}` } }
|
||||
), env);
|
||||
const body = await negotiate.json() as { connectionToken: string };
|
||||
const stored = connectionTokens.get(body.connectionToken);
|
||||
|
||||
assert.equal(negotiate.status, 200);
|
||||
assert.ok(stored);
|
||||
assert.ok(stored.expiresAt > Date.now());
|
||||
assert.ok(stored.expiresAt <= Date.now() + 60_000);
|
||||
|
||||
const request = () => new Request(
|
||||
`https://vault.example.test/notifications/hub?id=${encodeURIComponent(body.connectionToken)}`,
|
||||
{ headers: { Upgrade: 'websocket' } }
|
||||
);
|
||||
assert.equal((await handleNotificationsHub(request(), env)).status, 204);
|
||||
assert.equal((await handleNotificationsHub(request(), env)).status, 401);
|
||||
assert.equal(forwardedHubUrls.length, 1);
|
||||
});
|
||||
|
||||
test('a non-upgrade request does not consume a websocket connection token', async () => {
|
||||
const { env, connectionTokens } = createTestEnv();
|
||||
const accessToken = await validAccessToken();
|
||||
const negotiate = await handleNotificationsNegotiate(new Request(
|
||||
'https://vault.example.test/notifications/hub/negotiate',
|
||||
{ method: 'POST', headers: { Authorization: `Bearer ${accessToken}` } }
|
||||
), env);
|
||||
const { connectionToken } = await negotiate.json() as { connectionToken: string };
|
||||
const url = `https://vault.example.test/notifications/hub?id=${encodeURIComponent(connectionToken)}`;
|
||||
|
||||
assert.equal((await handleNotificationsHub(new Request(url), env)).status, 426);
|
||||
assert.ok(connectionTokens.has(connectionToken));
|
||||
assert.equal((await handleNotificationsHub(new Request(url, { headers: { Upgrade: 'websocket' } }), env)).status, 204);
|
||||
});
|
||||
|
||||
test('a forged ticket cannot select or activate a Durable Object', async () => {
|
||||
const { env, durableObjectNames } = createTestEnv();
|
||||
const response = await handleNotificationsHub(new Request(
|
||||
'https://vault.example.test/notifications/hub?id=attacker-controlled.invalid-signature',
|
||||
{ headers: { Upgrade: 'websocket' } }
|
||||
), env);
|
||||
|
||||
assert.equal(response.status, 401);
|
||||
assert.deepEqual(durableObjectNames, []);
|
||||
});
|
||||
@@ -19,8 +19,6 @@ const SIGNALR_UPDATE_TYPE_SYNC_SEND_DELETE = 14;
|
||||
const SIGNALR_UPDATE_TYPE_AUTH_REQUEST = 15;
|
||||
const SIGNALR_UPDATE_TYPE_AUTH_REQUEST_RESPONSE = 16;
|
||||
const SIGNALR_UPDATE_TYPE_BACKUP_RESTORE_PROGRESS = 102;
|
||||
const WEBSOCKET_CONNECTION_TOKEN_PREFIX = 'ws-token:';
|
||||
const WEBSOCKET_CONNECTION_TOKEN_TTL_MS = 60 * 1000;
|
||||
|
||||
type HubProtocol = 'json' | 'messagepack';
|
||||
type HubKind = 'user' | 'anonymous-auth-request';
|
||||
@@ -34,12 +32,6 @@ interface WsAttachment {
|
||||
deviceIdentifier: string | null;
|
||||
}
|
||||
|
||||
interface WebSocketConnectionToken {
|
||||
userId: string;
|
||||
deviceIdentifier: string | null;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
function concatBytes(chunks: Uint8Array[]): Uint8Array {
|
||||
const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
||||
const out = new Uint8Array(total);
|
||||
@@ -215,52 +207,6 @@ export class NotificationsHub extends DurableObject<Env> {
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (url.pathname === '/internal/ws-token' && request.method === 'POST') {
|
||||
const body = (await request.json().catch(() => null)) as {
|
||||
token?: string;
|
||||
userId?: string;
|
||||
deviceIdentifier?: string | null;
|
||||
expiresAt?: number;
|
||||
} | null;
|
||||
const token = String(body?.token || '').trim();
|
||||
const userId = String(body?.userId || '').trim();
|
||||
const expiresAt = Number(body?.expiresAt || 0);
|
||||
if (!token || !userId || expiresAt <= Date.now() || expiresAt > Date.now() + WEBSOCKET_CONNECTION_TOKEN_TTL_MS) {
|
||||
return new Response('Invalid websocket connection token', { status: 400 });
|
||||
}
|
||||
await this.ctx.storage.put(`${WEBSOCKET_CONNECTION_TOKEN_PREFIX}${token}`, {
|
||||
userId,
|
||||
deviceIdentifier: String(body?.deviceIdentifier || '').trim() || null,
|
||||
expiresAt,
|
||||
} satisfies WebSocketConnectionToken);
|
||||
const currentAlarm = await this.ctx.storage.getAlarm();
|
||||
if (currentAlarm === null || expiresAt < currentAlarm) {
|
||||
await this.ctx.storage.setAlarm(expiresAt);
|
||||
}
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
|
||||
if (url.pathname === '/internal/ws-token/consume' && request.method === 'POST') {
|
||||
const body = (await request.json().catch(() => null)) as { token?: string } | null;
|
||||
const token = String(body?.token || '').trim();
|
||||
if (!token) return new Response('Invalid websocket connection token', { status: 400 });
|
||||
|
||||
// Delete inside a transaction so a connection ticket cannot win two concurrent upgrades.
|
||||
const connection = await this.ctx.storage.transaction(async (txn) => {
|
||||
const key = `${WEBSOCKET_CONNECTION_TOKEN_PREFIX}${token}`;
|
||||
const stored = await txn.get<WebSocketConnectionToken>(key);
|
||||
if (stored) await txn.delete(key);
|
||||
return stored || null;
|
||||
});
|
||||
if (!connection || connection.expiresAt <= Date.now()) {
|
||||
return new Response('Unauthorized', { status: 401 });
|
||||
}
|
||||
return new Response(JSON.stringify(connection), {
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
if (url.pathname === '/internal/notify' && request.method === 'POST') {
|
||||
const body = (await request.json().catch(() => null)) as {
|
||||
revisionDate?: string;
|
||||
@@ -355,27 +301,6 @@ export class NotificationsHub extends DurableObject<Env> {
|
||||
});
|
||||
}
|
||||
|
||||
async alarm(): Promise<void> {
|
||||
const now = Date.now();
|
||||
const tokens = await this.ctx.storage.list<WebSocketConnectionToken>({
|
||||
prefix: WEBSOCKET_CONNECTION_TOKEN_PREFIX,
|
||||
});
|
||||
const expiredKeys: string[] = [];
|
||||
let nextExpiration: number | null = null;
|
||||
|
||||
for (const [key, token] of tokens) {
|
||||
if (token.expiresAt <= now) {
|
||||
expiredKeys.push(key);
|
||||
} else if (nextExpiration === null || token.expiresAt < nextExpiration) {
|
||||
nextExpiration = token.expiresAt;
|
||||
}
|
||||
}
|
||||
|
||||
// Negotiated tickets that never reach an upgrade must not remain in DO storage indefinitely.
|
||||
if (expiredKeys.length > 0) await this.ctx.storage.delete(expiredKeys);
|
||||
if (nextExpiration !== null) await this.ctx.storage.setAlarm(nextExpiration);
|
||||
}
|
||||
|
||||
async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer | ArrayBufferView): Promise<void> {
|
||||
const attachment = ws.deserializeAttachment() as WsAttachment | null;
|
||||
if (!attachment) return;
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
interface AliasedValue<T> {
|
||||
present: boolean;
|
||||
value: T | null | undefined;
|
||||
}
|
||||
|
||||
function readOwnAliasedValue<T>(source: unknown, aliases: readonly string[]): AliasedValue<T> {
|
||||
if (!source || typeof source !== 'object') {
|
||||
return { present: false, value: undefined };
|
||||
}
|
||||
|
||||
const record = source as Record<string, unknown>;
|
||||
for (const alias of aliases) {
|
||||
if (Object.prototype.hasOwnProperty.call(record, alias)) {
|
||||
return { present: true, value: record[alias] as T | null | undefined };
|
||||
}
|
||||
}
|
||||
|
||||
return { present: false, value: undefined };
|
||||
}
|
||||
|
||||
/**
|
||||
* Full cipher updates use replacement semantics for nullable fields.
|
||||
* Bitwarden clients may omit a property after its value is cleared, so an
|
||||
* absent property must become null instead of falling back to stored data.
|
||||
*/
|
||||
export function readNullableFullUpdateField<T>(
|
||||
source: unknown,
|
||||
aliases: readonly string[]
|
||||
): T | null {
|
||||
const incoming = readOwnAliasedValue<T>(source, aliases);
|
||||
return incoming.present ? incoming.value ?? null : null;
|
||||
}
|
||||
+10
-5
@@ -27,7 +27,6 @@ import { deleteAllAttachmentsForCipher, deleteAllAttachmentsForCiphers } from '.
|
||||
import { parsePagination, encodeContinuationToken } from '../utils/pagination';
|
||||
import { readActingDeviceIdentifier } from '../utils/device';
|
||||
import { auditRequestMetadata, writeAuditEvent } from '../services/audit-events';
|
||||
import { readNullableFullUpdateField } from './cipher-full-update';
|
||||
|
||||
// CONTRACT:
|
||||
// Cipher JSON is the highest-risk Bitwarden compatibility surface. Preserve
|
||||
@@ -1101,10 +1100,16 @@ export async function handleUpdateCipher(request: Request, env: Env, userId: str
|
||||
cipher.passwordHistory = incomingPasswordHistory.value ?? null;
|
||||
}
|
||||
|
||||
// Nullable fields use replacement semantics on this full-update endpoint.
|
||||
// Some clients omit cleared values, so merge fallback must not resurrect them.
|
||||
cipher.notes = readNullableFullUpdateField<string>(cipherData, ['notes', 'Notes']);
|
||||
cipher.fields = readNullableFullUpdateField<Cipher['fields']>(cipherData, ['fields', 'Fields']);
|
||||
// Custom fields deletion compatibility:
|
||||
// - Accept both camelCase "fields" and PascalCase "Fields".
|
||||
// - For full update (PUT/POST on this endpoint), missing fields means cleared fields.
|
||||
// This prevents stale custom fields from being resurrected by merge fallback.
|
||||
const incomingFields = getAliasedProp(cipherData, ['fields', 'Fields']);
|
||||
if (incomingFields.present) {
|
||||
cipher.fields = incomingFields.value ?? null;
|
||||
} else if (request.method === 'PUT' || request.method === 'POST') {
|
||||
cipher.fields = null;
|
||||
}
|
||||
normalizeCipherForStorage(cipher);
|
||||
const compatibilityError = validateCipherEncryptedFieldsForCompatibility(cipher);
|
||||
if (compatibilityError) return errorResponse(compatibilityError, 400);
|
||||
|
||||
@@ -4,20 +4,18 @@ import { isAuthRequestExpired } from '../services/storage-auth-request-repo';
|
||||
import type { Env, JWTPayload } from '../types';
|
||||
import { errorResponse, jsonResponse } from '../utils/response';
|
||||
import { generateUUID } from '../utils/uuid';
|
||||
import {
|
||||
createWebSocketConnectionToken,
|
||||
verifyWebSocketConnectionToken,
|
||||
} from '../utils/websocket-connection-token';
|
||||
|
||||
const WEBSOCKET_CONNECTION_TOKEN_TTL_MS = 60 * 1000;
|
||||
|
||||
function extractAccessToken(request: Request): string | null {
|
||||
const url = new URL(request.url);
|
||||
const queryToken = String(url.searchParams.get('access_token') || '').trim();
|
||||
if (queryToken) return queryToken;
|
||||
|
||||
const authHeader = String(request.headers.get('Authorization') || '').trim();
|
||||
const match = authHeader.match(/^Bearer\s+(.+)$/i);
|
||||
return match?.[1]?.trim() || null;
|
||||
}
|
||||
|
||||
async function authenticateAccessToken(request: Request, env: Env): Promise<JWTPayload | null> {
|
||||
async function authenticateNotificationsRequest(request: Request, env: Env): Promise<JWTPayload | null> {
|
||||
const accessToken = extractAccessToken(request);
|
||||
if (!accessToken) return null;
|
||||
|
||||
@@ -25,90 +23,30 @@ async function authenticateAccessToken(request: Request, env: Env): Promise<JWTP
|
||||
return auth.verifyAccessToken(`Bearer ${accessToken}`);
|
||||
}
|
||||
|
||||
async function issueWebSocketConnectionToken(payload: JWTPayload, env: Env): Promise<string> {
|
||||
const expiresAt = Date.now() + WEBSOCKET_CONNECTION_TOKEN_TTL_MS;
|
||||
const token = await createWebSocketConnectionToken(payload.sub, expiresAt, env.JWT_SECRET);
|
||||
const id = env.NOTIFICATIONS_HUB.idFromName(payload.sub);
|
||||
const stub = env.NOTIFICATIONS_HUB.get(id);
|
||||
const response = await stub.fetch('https://notifications/internal/ws-token', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
token,
|
||||
userId: payload.sub,
|
||||
deviceIdentifier: payload.did || null,
|
||||
expiresAt,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) throw new Error('Failed to issue websocket connection token');
|
||||
return token;
|
||||
}
|
||||
|
||||
async function consumeWebSocketConnectionToken(request: Request, env: Env): Promise<JWTPayload | null> {
|
||||
const token = String(new URL(request.url).searchParams.get('id') || '').trim();
|
||||
const claims = await verifyWebSocketConnectionToken(token, env.JWT_SECRET);
|
||||
if (!claims) return null;
|
||||
|
||||
// Verify the signed routing claim before selecting a Durable Object. Otherwise an
|
||||
// attacker could activate arbitrary object names with forged token prefixes.
|
||||
const id = env.NOTIFICATIONS_HUB.idFromName(claims.userId);
|
||||
const stub = env.NOTIFICATIONS_HUB.get(id);
|
||||
const response = await stub.fetch('https://notifications/internal/ws-token/consume', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ token }),
|
||||
});
|
||||
if (!response.ok) return null;
|
||||
|
||||
const connection = (await response.json().catch(() => null)) as {
|
||||
userId?: string;
|
||||
deviceIdentifier?: string | null;
|
||||
} | null;
|
||||
if (connection?.userId !== claims.userId) return null;
|
||||
|
||||
return {
|
||||
sub: claims.userId,
|
||||
did: String(connection.deviceIdentifier || '').trim() || undefined,
|
||||
} as JWTPayload;
|
||||
}
|
||||
|
||||
async function authenticateNotificationsHub(request: Request, env: Env): Promise<JWTPayload | null> {
|
||||
// Never accept an access JWT from the URL: URLs are routinely retained by logs,
|
||||
// browser history, proxies, monitoring, and error tracking systems.
|
||||
if (request.headers.has('Authorization')) {
|
||||
return authenticateAccessToken(request, env);
|
||||
}
|
||||
return consumeWebSocketConnectionToken(request, env);
|
||||
}
|
||||
|
||||
export async function handleNotificationsNegotiate(request: Request, env: Env): Promise<Response> {
|
||||
const payload = await authenticateAccessToken(request, env);
|
||||
const payload = await authenticateNotificationsRequest(request, env);
|
||||
if (!payload?.sub) return errorResponse('Unauthorized', 401);
|
||||
|
||||
const connectionToken = await issueWebSocketConnectionToken(payload, env);
|
||||
return jsonResponse(
|
||||
{
|
||||
connectionId: generateUUID(),
|
||||
connectionToken,
|
||||
negotiateVersion: 1,
|
||||
availableTransports: [
|
||||
{
|
||||
transport: 'WebSockets',
|
||||
transferFormats: ['Text', 'Binary'],
|
||||
},
|
||||
],
|
||||
},
|
||||
200,
|
||||
{ 'Cache-Control': 'no-store' }
|
||||
);
|
||||
const connectionId = generateUUID();
|
||||
return jsonResponse({
|
||||
connectionId,
|
||||
connectionToken: connectionId,
|
||||
negotiateVersion: 1,
|
||||
availableTransports: [
|
||||
{
|
||||
transport: 'WebSockets',
|
||||
transferFormats: ['Text', 'Binary'],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export async function handleNotificationsHub(request: Request, env: Env): Promise<Response> {
|
||||
const payload = await authenticateNotificationsRequest(request, env);
|
||||
if (!payload?.sub) return errorResponse('Unauthorized', 401);
|
||||
if (request.headers.get('Upgrade')?.toLowerCase() !== 'websocket') {
|
||||
return errorResponse('Expected websocket', 426);
|
||||
}
|
||||
const payload = await authenticateNotificationsHub(request, env);
|
||||
if (!payload?.sub) return errorResponse('Unauthorized', 401);
|
||||
|
||||
const userId = payload.sub;
|
||||
const id = env.NOTIFICATIONS_HUB.idFromName(userId);
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
import { generateUUID } from './uuid';
|
||||
|
||||
const WEBSOCKET_NOTIFICATION_SCOPE = 'notifications.websocket';
|
||||
|
||||
export interface WebSocketConnectionTokenClaims {
|
||||
userId: string;
|
||||
expiresAt: number;
|
||||
nonce: string;
|
||||
scope: typeof WEBSOCKET_NOTIFICATION_SCOPE;
|
||||
}
|
||||
|
||||
function base64UrlEncode(data: Uint8Array): string {
|
||||
return btoa(String.fromCharCode(...data))
|
||||
.replace(/\+/g, '-')
|
||||
.replace(/\//g, '_')
|
||||
.replace(/=+$/, '');
|
||||
}
|
||||
|
||||
function base64UrlDecode(value: string): Uint8Array {
|
||||
let normalized = value.replace(/-/g, '+').replace(/_/g, '/');
|
||||
while (normalized.length % 4) normalized += '=';
|
||||
const binary = atob(normalized);
|
||||
return Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
||||
}
|
||||
|
||||
async function getSigningKey(secret: string): Promise<CryptoKey> {
|
||||
return crypto.subtle.importKey(
|
||||
'raw',
|
||||
new TextEncoder().encode(secret),
|
||||
{ name: 'HMAC', hash: 'SHA-256' },
|
||||
false,
|
||||
['sign', 'verify']
|
||||
);
|
||||
}
|
||||
|
||||
export async function createWebSocketConnectionToken(
|
||||
userId: string,
|
||||
expiresAt: number,
|
||||
secret: string
|
||||
): Promise<string> {
|
||||
const claims: WebSocketConnectionTokenClaims = {
|
||||
userId,
|
||||
expiresAt,
|
||||
nonce: generateUUID(),
|
||||
scope: WEBSOCKET_NOTIFICATION_SCOPE,
|
||||
};
|
||||
const payload = base64UrlEncode(new TextEncoder().encode(JSON.stringify(claims)));
|
||||
const signature = await crypto.subtle.sign(
|
||||
'HMAC',
|
||||
await getSigningKey(secret),
|
||||
new TextEncoder().encode(payload)
|
||||
);
|
||||
return `${payload}.${base64UrlEncode(new Uint8Array(signature))}`;
|
||||
}
|
||||
|
||||
export async function verifyWebSocketConnectionToken(
|
||||
token: string,
|
||||
secret: string
|
||||
): Promise<WebSocketConnectionTokenClaims | null> {
|
||||
try {
|
||||
if (!token || token.length > 1024) return null;
|
||||
const parts = token.split('.');
|
||||
if (parts.length !== 2) return null;
|
||||
const [payload, encodedSignature] = parts;
|
||||
const valid = await crypto.subtle.verify(
|
||||
'HMAC',
|
||||
await getSigningKey(secret),
|
||||
base64UrlDecode(encodedSignature),
|
||||
new TextEncoder().encode(payload)
|
||||
);
|
||||
if (!valid) return null;
|
||||
|
||||
const claims = JSON.parse(new TextDecoder().decode(base64UrlDecode(payload))) as Partial<WebSocketConnectionTokenClaims>;
|
||||
if (
|
||||
claims.scope !== WEBSOCKET_NOTIFICATION_SCOPE
|
||||
|| !String(claims.userId || '').trim()
|
||||
|| !Number.isFinite(claims.expiresAt)
|
||||
|| Number(claims.expiresAt) <= Date.now()
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return claims as WebSocketConnectionTokenClaims;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+4
-12
@@ -1674,25 +1674,17 @@ export default function App() {
|
||||
reconnectAttempts += 1;
|
||||
reconnectTimer = window.setTimeout(() => {
|
||||
reconnectTimer = null;
|
||||
void connect();
|
||||
connect();
|
||||
}, delay);
|
||||
};
|
||||
|
||||
const connect = async () => {
|
||||
const connect = () => {
|
||||
if (disposed) return;
|
||||
const accessToken = session.accessToken;
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
const negotiateResponse = await fetch('/notifications/hub/negotiate?negotiateVersion=1', {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
if (!negotiateResponse.ok) throw new Error('Notification negotiation failed');
|
||||
const negotiation = (await negotiateResponse.json()) as { connectionToken?: string };
|
||||
if (!negotiation.connectionToken || disposed) throw new Error('Notification connection token missing');
|
||||
|
||||
const hubUrl = new URL('/notifications/hub', window.location.origin);
|
||||
hubUrl.searchParams.set('id', negotiation.connectionToken);
|
||||
hubUrl.searchParams.set('access_token', accessToken);
|
||||
hubUrl.protocol = hubUrl.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
socket = new WebSocket(hubUrl.toString());
|
||||
} catch {
|
||||
@@ -1817,7 +1809,7 @@ export default function App() {
|
||||
});
|
||||
};
|
||||
|
||||
void connect();
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
disposed = true;
|
||||
|
||||
@@ -382,9 +382,7 @@ export default function VaultPage(props: VaultPageProps) {
|
||||
}
|
||||
const groupIndexByKey = new Map<string, number>();
|
||||
Array.from(groupKeys).sort().forEach((groupKey, index) => {
|
||||
// Keep indices unique (no modulo): they are used both for group colors and
|
||||
// as the group identity when selecting duplicate items to delete.
|
||||
groupIndexByKey.set(groupKey, index);
|
||||
groupIndexByKey.set(groupKey, index % 64);
|
||||
});
|
||||
const byId = new Map<string, number>();
|
||||
for (const [cipherId, groupKey] of groupKeyById.entries()) {
|
||||
|
||||
@@ -197,7 +197,7 @@ export async function downloadAdminBackupAttachmentBlob(
|
||||
authedFetch: AuthedFetch,
|
||||
blobName: string,
|
||||
masterPasswordHash: string
|
||||
): Promise<Uint8Array<ArrayBuffer>> {
|
||||
): Promise<Uint8Array> {
|
||||
const resp = await authedFetch('/api/admin/backup/blob', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
||||
@@ -186,7 +186,7 @@ export function invalidateRemoteBrowserCacheForDestination(
|
||||
cache: Record<string, RemoteBackupBrowserResponse>,
|
||||
pathByDestination: Record<string, string>,
|
||||
pageByKey: Record<string, number>
|
||||
): Omit<PersistedRemoteBrowserState, 'refreshedAt'> {
|
||||
): PersistedRemoteBrowserState {
|
||||
return {
|
||||
cache: Object.fromEntries(Object.entries(cache).filter(([key]) => !key.startsWith(`${destinationId}:`))),
|
||||
pathByDestination: Object.fromEntries(Object.entries(pathByDestination).filter(([key]) => key !== destinationId)),
|
||||
|
||||
@@ -23,7 +23,7 @@ function createState(fingerprint: string): InternalPasswordSecurityState {
|
||||
return { fingerprint, report: null, scannedAt: null, scanning: false, progress: { checked: 0, total: 0 }, scanError: false, controller: null };
|
||||
}
|
||||
|
||||
function ensurePasswordSecurityState(fingerprint: string): InternalPasswordSecurityState {
|
||||
export function getPasswordSecurityState(fingerprint: string): PasswordSecurityState {
|
||||
if (state?.fingerprint !== fingerprint) {
|
||||
state?.controller?.abort();
|
||||
state = createState(fingerprint);
|
||||
@@ -31,10 +31,6 @@ function ensurePasswordSecurityState(fingerprint: string): InternalPasswordSecur
|
||||
return state;
|
||||
}
|
||||
|
||||
export function getPasswordSecurityState(fingerprint: string): PasswordSecurityState {
|
||||
return ensurePasswordSecurityState(fingerprint);
|
||||
}
|
||||
|
||||
export function readPasswordSecurityState(fingerprint: string): PasswordSecurityState | null {
|
||||
return state?.fingerprint === fingerprint ? state : null;
|
||||
}
|
||||
@@ -45,7 +41,7 @@ export function subscribePasswordSecurityState(listener: () => void): () => void
|
||||
}
|
||||
|
||||
export function startPasswordSecurityScan(fingerprint: string, ciphers: Cipher[]): void {
|
||||
const current = ensurePasswordSecurityState(fingerprint);
|
||||
const current = getPasswordSecurityState(fingerprint);
|
||||
current.controller?.abort();
|
||||
const controller = new AbortController();
|
||||
const total = ciphers.filter((cipher) => Number(cipher.type) === 1 && !cipher.deletedDate && !(cipher as { deletedAt?: string | null }).deletedAt && !!cipher.login?.decPassword).length;
|
||||
|
||||
@@ -369,7 +369,7 @@
|
||||
.not-found-code {
|
||||
@apply rounded-full px-3 py-1 text-sm font-extrabold;
|
||||
background: #eef4ff;
|
||||
color: var(--primary-hover);
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.not-found-copy {
|
||||
@@ -574,7 +574,7 @@
|
||||
|
||||
.jwt-inline-link {
|
||||
@apply font-bold no-underline;
|
||||
color: var(--primary-hover);
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.jwt-inline-link:hover {
|
||||
@@ -613,7 +613,7 @@
|
||||
|
||||
.standalone-footer a {
|
||||
@apply font-bold no-underline;
|
||||
color: var(--primary-hover);
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.standalone-footer a:hover {
|
||||
@@ -622,5 +622,5 @@
|
||||
|
||||
.standalone-version {
|
||||
@apply font-bold;
|
||||
color: var(--primary-hover);
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] .card-brand-icon {
|
||||
color: var(--primary-strong);
|
||||
color: #bfdbfe;
|
||||
background: linear-gradient(180deg, #1f2937 0%, #111827 100%);
|
||||
border-color: color-mix(in srgb, var(--primary) 30%, var(--line));
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
@apply inline-flex h-[22px] w-[22px] shrink-0 cursor-pointer items-center justify-center rounded-full p-0 text-[13px] font-extrabold leading-none;
|
||||
border: 1px solid #bfd1f3;
|
||||
background: #eef4ff;
|
||||
color: var(--primary-hover);
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.backup-help-trigger:hover,
|
||||
@@ -194,7 +194,7 @@
|
||||
}
|
||||
|
||||
.backup-recommendation-step a {
|
||||
color: var(--primary-hover);
|
||||
color: #1d4ed8;
|
||||
font-weight: 700;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
@@ -316,14 +316,14 @@
|
||||
}
|
||||
|
||||
.backup-interval-preset:hover:not(:disabled) {
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
border-color: #2563eb;
|
||||
color: #2563eb;
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
.backup-interval-preset.active {
|
||||
border-color: var(--primary);
|
||||
background: var(--primary);
|
||||
border-color: #2563eb;
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@@ -1786,7 +1786,7 @@
|
||||
}
|
||||
|
||||
.restore-progress-item.active {
|
||||
color: var(--primary-hover);
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.restore-progress-item.done {
|
||||
@@ -1799,7 +1799,7 @@
|
||||
}
|
||||
|
||||
.restore-progress-item.active .restore-progress-dot {
|
||||
background: var(--primary-hover);
|
||||
background: #1d4ed8;
|
||||
}
|
||||
|
||||
.restore-progress-item.done .restore-progress-dot {
|
||||
@@ -1871,7 +1871,7 @@
|
||||
.authorized-device-checkbox {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
accent-color: var(--primary);
|
||||
accent-color: #2563eb;
|
||||
}
|
||||
|
||||
.authorized-devices-table td:first-child {
|
||||
@@ -1879,9 +1879,7 @@
|
||||
}
|
||||
|
||||
.authorized-devices-actions {
|
||||
/* Wrap instead of clipping: four non-shrinking buttons overflow the
|
||||
fixed 26% actions column at common widths, hiding Delete entirely. */
|
||||
flex-wrap: wrap;
|
||||
flex-wrap: nowrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
}
|
||||
|
||||
.folder-add-btn:hover {
|
||||
color: var(--primary-hover);
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
@@ -161,7 +161,7 @@
|
||||
}
|
||||
|
||||
.folder-sort-btn:hover {
|
||||
color: var(--primary-hover);
|
||||
color: #1d4ed8;
|
||||
background: #dbeafe;
|
||||
transform: scale(1.06);
|
||||
}
|
||||
@@ -230,7 +230,7 @@ select.input.duplicate-mode-toolbar-select {
|
||||
}
|
||||
|
||||
.list-head {
|
||||
@apply mb-1.5 flex flex-wrap items-center gap-2;
|
||||
@apply mb-1.5 flex items-center gap-2;
|
||||
min-height: 34px;
|
||||
}
|
||||
|
||||
@@ -860,9 +860,7 @@ select.input.duplicate-mode-toolbar-select {
|
||||
|
||||
.kv-row {
|
||||
@apply grid items-center gap-2.5 py-2.5;
|
||||
/* Floor the value column so the auto-sized actions column wraps its
|
||||
buttons instead of collapsing the value to 0 width. */
|
||||
grid-template-columns: minmax(0px, 80px) minmax(min(35%, 140px), 1fr) auto;
|
||||
grid-template-columns: minmax(0px, 80px) minmax(0, 1fr) auto;
|
||||
border-bottom: 1px solid rgba(154, 172, 205, 0.22);
|
||||
}
|
||||
|
||||
@@ -964,7 +962,7 @@ select.input.duplicate-mode-toolbar-select {
|
||||
}
|
||||
|
||||
.totp-ring-progress {
|
||||
stroke: var(--primary);
|
||||
stroke: #2563eb;
|
||||
stroke-linecap: round;
|
||||
transition: stroke-dashoffset 260ms linear, stroke 200ms ease;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user