chore: update dependencies and enhance OLM password handling
- Updated various dependencies in package.json and bun.lock to their latest versions for improved stability and security. - Introduced a new ecosystem.config.cjs file for better environment management. - Enhanced OLM password handling with encryption and decryption functionalities. (Testing) - Improved UI components for password dialogs to provide better user feedback and error handling. - Added new database schema for managing nests and roles in the application.
This commit is contained in:
parent
bf095582c8
commit
55e78db2cb
12 changed files with 487 additions and 138 deletions
184
bun.lock
184
bun.lock
|
|
@ -5,60 +5,60 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "sipher",
|
"name": "sipher",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@convex-dev/better-auth": "^0.10.9",
|
"@convex-dev/better-auth": "latest",
|
||||||
"@marsidev/react-turnstile": "^1.4.1",
|
"@marsidev/react-turnstile": "latest",
|
||||||
"@matrix-org/olm": "^3.2.15",
|
"@matrix-org/olm": "latest",
|
||||||
"@nanostores/react": "^1.0.0",
|
"@nanostores/react": "latest",
|
||||||
"@phosphor-icons/react": "^2.1.10",
|
"@phosphor-icons/react": "latest",
|
||||||
"@radix-ui/react-avatar": "^1.1.11",
|
"@radix-ui/react-avatar": "latest",
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
"@radix-ui/react-checkbox": "latest",
|
||||||
"@radix-ui/react-context-menu": "^2.2.16",
|
"@radix-ui/react-context-menu": "latest",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "latest",
|
||||||
"@radix-ui/react-hover-card": "^1.1.15",
|
"@radix-ui/react-hover-card": "latest",
|
||||||
"@radix-ui/react-label": "^2.1.8",
|
"@radix-ui/react-label": "latest",
|
||||||
"@radix-ui/react-menubar": "^1.1.16",
|
"@radix-ui/react-menubar": "latest",
|
||||||
"@radix-ui/react-popover": "^1.1.15",
|
"@radix-ui/react-popover": "latest",
|
||||||
"@radix-ui/react-progress": "^1.1.8",
|
"@radix-ui/react-progress": "latest",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
"@radix-ui/react-scroll-area": "latest",
|
||||||
"@radix-ui/react-separator": "^1.1.8",
|
"@radix-ui/react-separator": "latest",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "latest",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "latest",
|
||||||
"@types/bun": "^1.3.5",
|
"@types/bun": "latest",
|
||||||
"@types/libsodium-wrappers": "^0.7.14",
|
"@types/libsodium-wrappers": "latest",
|
||||||
"better-auth": "1.4.10",
|
"better-auth": "latest",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "latest",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "latest",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "latest",
|
||||||
"convex": "^1.31.2",
|
"convex": "latest",
|
||||||
"cross-env": "^10.1.0",
|
"cross-env": "latest",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "latest",
|
||||||
"dexie": "^4.2.1",
|
"dexie": "latest",
|
||||||
"dexie-react-hooks": "^4.2.0",
|
"dexie-react-hooks": "latest",
|
||||||
"framer-motion": "^12.23.27",
|
"framer-motion": "latest",
|
||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "latest",
|
||||||
"moment": "^2.30.1",
|
"moment": "latest",
|
||||||
"nanostores": "^1.1.0",
|
"nanostores": "latest",
|
||||||
"next": "16.1.1",
|
"next": "latest",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "latest",
|
||||||
"react": "19.2.3",
|
"react": "latest",
|
||||||
"react-day-picker": "^9.13.0",
|
"react-day-picker": "latest",
|
||||||
"react-dom": "19.2.3",
|
"react-dom": "latest",
|
||||||
"socket.io": "^4.8.3",
|
"socket.io": "latest",
|
||||||
"socket.io-client": "^4.8.3",
|
"socket.io-client": "latest",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "latest",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "latest",
|
||||||
"zod": "^4.3.5",
|
"zod": "latest",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.18",
|
"@tailwindcss/postcss": "latest",
|
||||||
"@types/node": "^25.0.3",
|
"@types/node": "latest",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "latest",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "latest",
|
||||||
"babel-plugin-react-compiler": "1.0.0",
|
"babel-plugin-react-compiler": "latest",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "latest",
|
||||||
"tsx": "^4.21.0",
|
"tsx": "latest",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "latest",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "latest",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -71,17 +71,17 @@
|
||||||
|
|
||||||
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
|
||||||
|
|
||||||
"@better-auth/core": ["@better-auth/core@1.4.10", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.1.12" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-call": "1.1.7", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-AThrfb6CpG80wqkanfrbN2/fGOYzhGladHFf3JhaWt/3/Vtf4h084T6PJLrDE7M/vCCGYvDI1DkvP3P1OB2HAg=="],
|
"@better-auth/core": ["@better-auth/core@1.4.12", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "zod": "^4.1.12" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-call": "1.1.7", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-VfqZwMAEl9rnGx092BIZ2Q5z8rt7jjN2OAbvPqehufSKZGmh8JsdtZRBMl/CHQir9bwi2Ev0UF4+7TQp+DXEMg=="],
|
||||||
|
|
||||||
"@better-auth/passkey": ["@better-auth/passkey@1.4.9", "", { "dependencies": { "@simplewebauthn/browser": "^13.1.2", "@simplewebauthn/server": "^13.1.2", "zod": "^4.1.12" }, "peerDependencies": { "@better-auth/core": "1.4.9", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-auth": "1.4.9", "better-call": "1.1.7", "nanostores": "^1.0.1" } }, "sha512-fPsV0LYbmPytxrTaltM2RXbJnmSttX9UWr4wkZtJYgCBGeFqN8+8ZzBTZXOymWDJTVQ0kVZrD7c7/HyxXEG1zA=="],
|
"@better-auth/passkey": ["@better-auth/passkey@1.4.9", "", { "dependencies": { "@simplewebauthn/browser": "^13.1.2", "@simplewebauthn/server": "^13.1.2", "zod": "^4.1.12" }, "peerDependencies": { "@better-auth/core": "1.4.9", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "better-auth": "1.4.9", "better-call": "1.1.7", "nanostores": "^1.0.1" } }, "sha512-fPsV0LYbmPytxrTaltM2RXbJnmSttX9UWr4wkZtJYgCBGeFqN8+8ZzBTZXOymWDJTVQ0kVZrD7c7/HyxXEG1zA=="],
|
||||||
|
|
||||||
"@better-auth/telemetry": ["@better-auth/telemetry@1.4.10", "", { "dependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21" }, "peerDependencies": { "@better-auth/core": "1.4.10" } }, "sha512-Dq4XJX6EKsUu0h3jpRagX739p/VMOTcnJYWRrLtDYkqtZFg+sFiFsSWVcfapZoWpRSUGYX9iKwl6nDHn6Ju2oQ=="],
|
"@better-auth/telemetry": ["@better-auth/telemetry@1.4.12", "", { "dependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21" }, "peerDependencies": { "@better-auth/core": "1.4.12" } }, "sha512-4q504Og42PzkUbZjXDt+FyeYaS0WZmAlEOC3nbBCZDObTVCRUnGgJW52B2maJ7BCVvAQgBGLEeQmQzU5+63J0A=="],
|
||||||
|
|
||||||
"@better-auth/utils": ["@better-auth/utils@0.3.0", "", {}, "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw=="],
|
"@better-auth/utils": ["@better-auth/utils@0.3.0", "", {}, "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw=="],
|
||||||
|
|
||||||
"@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="],
|
"@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="],
|
||||||
|
|
||||||
"@convex-dev/better-auth": ["@convex-dev/better-auth@0.10.9", "", { "dependencies": { "@better-auth/passkey": "1.4.9", "@better-fetch/fetch": "^1.1.18", "common-tags": "^1.8.2", "convex-helpers": "^0.1.95", "jose": "^6.1.0", "remeda": "^2.32.0", "semver": "^7.7.3", "type-fest": "^4.39.1", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "1.4.9", "convex": "^1.25.0", "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-GxLQv4oKh9vLAn/LadN8NqY/naAcyP3qmhSli3P5nObtDH1aq/EI/PZ/WpbdJCDrelL0TXg1JvGaWJueuGLXIA=="],
|
"@convex-dev/better-auth": ["@convex-dev/better-auth@0.10.10", "", { "dependencies": { "@better-auth/passkey": "1.4.9", "@better-fetch/fetch": "^1.1.18", "common-tags": "^1.8.2", "convex-helpers": "^0.1.95", "jose": "^6.1.0", "remeda": "^2.32.0", "semver": "^7.7.3", "type-fest": "^4.39.1", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "1.4.9", "convex": "^1.25.0", "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-BpwQ2kph43O7hmtGQAJ+ie3KrjONp83659QDjKDdH+X8yIdGevgehaqS5GHB0iJo7zQTtvs687GnAeLZ4Xx3/w=="],
|
||||||
|
|
||||||
"@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
|
"@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="],
|
||||||
|
|
||||||
|
|
@ -389,15 +389,15 @@
|
||||||
|
|
||||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "postcss": "^8.4.41", "tailwindcss": "4.1.18" } }, "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g=="],
|
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.18", "@tailwindcss/oxide": "4.1.18", "postcss": "^8.4.41", "tailwindcss": "4.1.18" } }, "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
|
"@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
|
||||||
|
|
||||||
"@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="],
|
"@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="],
|
||||||
|
|
||||||
"@types/libsodium-wrappers": ["@types/libsodium-wrappers@0.7.14", "", {}, "sha512-5Kv68fXuXK0iDuUir1WPGw2R9fOZUlYlSAa0ztMcL0s0BfIDTqg9GXz8K30VJpPP3sxWhbolnQma2x+/TfkzDQ=="],
|
"@types/libsodium-wrappers": ["@types/libsodium-wrappers@0.7.14", "", {}, "sha512-5Kv68fXuXK0iDuUir1WPGw2R9fOZUlYlSAa0ztMcL0s0BfIDTqg9GXz8K30VJpPP3sxWhbolnQma2x+/TfkzDQ=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
|
"@types/node": ["@types/node@25.0.8", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg=="],
|
||||||
|
|
||||||
"@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="],
|
"@types/react": ["@types/react@19.2.8", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg=="],
|
||||||
|
|
||||||
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||||
|
|
||||||
|
|
@ -413,11 +413,11 @@
|
||||||
|
|
||||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ=="],
|
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ=="],
|
||||||
|
|
||||||
"better-auth": ["better-auth@1.4.10", "", { "dependencies": { "@better-auth/core": "1.4.10", "@better-auth/telemetry": "1.4.10", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "better-call": "1.1.7", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.12" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": ">=0.41.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-0kqwEBJLe8eyFzbUspRG/htOriCf9uMLlnpe34dlIJGdmDfPuQISd4shShvUrvIVhPxsY1dSTXdXPLpqISYOYg=="],
|
"better-auth": ["better-auth@1.4.12", "", { "dependencies": { "@better-auth/core": "1.4.12", "@better-auth/telemetry": "1.4.12", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.21", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "better-call": "1.1.7", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.12" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": ">=0.41.0", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-FsFMnWgk+AGrxsIGbpWLCibgYcbm6uNhPHln3ohXFDXSRa0gk39Beuh54Q+x6ml2qYodF0snxf/tPtDpBI/JiA=="],
|
||||||
|
|
||||||
"better-call": ["better-call@1.1.7", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-6gaJe1bBIEgVebQu/7q9saahVzvBsGaByEnE8aDVncZEDiJO7sdNB28ot9I6iXSbR25egGmmZ6aIURXyQHRraQ=="],
|
"better-call": ["better-call@1.1.7", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.7.10", "set-cookie-parser": "^2.7.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-6gaJe1bBIEgVebQu/7q9saahVzvBsGaByEnE8aDVncZEDiJO7sdNB28ot9I6iXSbR25egGmmZ6aIURXyQHRraQ=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
"bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
|
||||||
|
|
||||||
"caniuse-lite": ["caniuse-lite@1.0.30001757", "", {}, "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ=="],
|
"caniuse-lite": ["caniuse-lite@1.0.30001757", "", {}, "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ=="],
|
||||||
|
|
||||||
|
|
@ -431,7 +431,7 @@
|
||||||
|
|
||||||
"common-tags": ["common-tags@1.8.2", "", {}, "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA=="],
|
"common-tags": ["common-tags@1.8.2", "", {}, "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA=="],
|
||||||
|
|
||||||
"convex": ["convex@1.31.2", "", { "dependencies": { "esbuild": "0.25.4", "prettier": "^3.0.0" }, "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "optionalPeers": ["@auth0/auth0-react", "@clerk/clerk-react", "react"], "bin": { "convex": "bin/main.js" } }, "sha512-RFuJOwlL2bM5X63egvBI5ZZZH6wESREpAbHsLjODxzDeJuewTLKrEnbvHV/NWp1uJYpgEFJziuGHmZ0tnAmmJg=="],
|
"convex": ["convex@1.31.4", "", { "dependencies": { "esbuild": "0.27.0", "prettier": "^3.0.0" }, "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "optionalPeers": ["@auth0/auth0-react", "@clerk/clerk-react", "react"], "bin": { "convex": "bin/main.js" } }, "sha512-iDm283Gb/CFRb30cvhH6Z9qlYof6dhtin415FarKUKB3K7gumO0rn8snY0CTvUrThV3UnCtttbuL/1oY7LscyA=="],
|
||||||
|
|
||||||
"convex-helpers": ["convex-helpers@0.1.106", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "convex": "^1.25.4", "hono": "^4.0.5", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "typescript": "^5.5", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@standard-schema/spec", "hono", "react", "typescript", "zod"], "bin": { "convex-helpers": "bin.cjs" } }, "sha512-hWRe3yDaAVHMe4CUYw1YoQLiPZ1KIx6Kbf0w6UcRDx1BXpJgMCl3GVIMiSeYiA0PkbwjnIwGWIvoUVKloG5Tyw=="],
|
"convex-helpers": ["convex-helpers@0.1.106", "", { "peerDependencies": { "@standard-schema/spec": "^1.0.0", "convex": "^1.25.4", "hono": "^4.0.5", "react": "^17.0.2 || ^18.0.0 || ^19.0.0", "typescript": "^5.5", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@standard-schema/spec", "hono", "react", "typescript", "zod"], "bin": { "convex-helpers": "bin.cjs" } }, "sha512-hWRe3yDaAVHMe4CUYw1YoQLiPZ1KIx6Kbf0w6UcRDx1BXpJgMCl3GVIMiSeYiA0PkbwjnIwGWIvoUVKloG5Tyw=="],
|
||||||
|
|
||||||
|
|
@ -471,7 +471,7 @@
|
||||||
|
|
||||||
"esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
|
"esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="],
|
||||||
|
|
||||||
"framer-motion": ["framer-motion@12.23.27", "", { "dependencies": { "motion-dom": "^12.23.23", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-EAcX8FS8jzZ4tSKpj+1GhwbVY+r1gfamPFwXZAsioPqu/ffRwU2otkKg6GEDCR41FVJv3RoBN7Aqep6drL9Itg=="],
|
"framer-motion": ["framer-motion@12.26.2", "", { "dependencies": { "motion-dom": "^12.26.2", "motion-utils": "^12.24.10", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-lflOQEdjquUi9sCg5Y1LrsZDlsjrHw7m0T9Yedvnk7Bnhqfkc89/Uha10J3CFhkL+TCZVCRw9eUGyM/lyYhXQA=="],
|
||||||
|
|
||||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
|
|
@ -523,9 +523,9 @@
|
||||||
|
|
||||||
"moment": ["moment@2.30.1", "", {}, "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="],
|
"moment": ["moment@2.30.1", "", {}, "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="],
|
||||||
|
|
||||||
"motion-dom": ["motion-dom@12.23.23", "", { "dependencies": { "motion-utils": "^12.23.6" } }, "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA=="],
|
"motion-dom": ["motion-dom@12.26.2", "", { "dependencies": { "motion-utils": "^12.24.10" } }, "sha512-KLMT1BroY8oKNeliA3JMNJ+nbCIsTKg6hJpDb4jtRAJ7nCKnnpg/LTq/NGqG90Limitz3kdAnAVXecdFVGlWTw=="],
|
||||||
|
|
||||||
"motion-utils": ["motion-utils@12.23.6", "", {}, "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ=="],
|
"motion-utils": ["motion-utils@12.24.10", "", {}, "sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww=="],
|
||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
|
|
@ -639,8 +639,6 @@
|
||||||
|
|
||||||
"@better-auth/passkey/zod": ["zod@4.2.1", "", {}, "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw=="],
|
"@better-auth/passkey/zod": ["zod@4.2.1", "", {}, "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw=="],
|
||||||
|
|
||||||
"@convex-dev/better-auth/zod": ["zod@4.2.1", "", {}, "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw=="],
|
|
||||||
|
|
||||||
"@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
"@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
|
||||||
|
|
||||||
"@radix-ui/react-checkbox/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
|
"@radix-ui/react-checkbox/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
|
||||||
|
|
@ -723,7 +721,7 @@
|
||||||
|
|
||||||
"@types/cors/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
|
"@types/cors/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
|
||||||
|
|
||||||
"convex/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
|
"convex/esbuild": ["esbuild@0.27.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.0", "@esbuild/android-arm": "0.27.0", "@esbuild/android-arm64": "0.27.0", "@esbuild/android-x64": "0.27.0", "@esbuild/darwin-arm64": "0.27.0", "@esbuild/darwin-x64": "0.27.0", "@esbuild/freebsd-arm64": "0.27.0", "@esbuild/freebsd-x64": "0.27.0", "@esbuild/linux-arm": "0.27.0", "@esbuild/linux-arm64": "0.27.0", "@esbuild/linux-ia32": "0.27.0", "@esbuild/linux-loong64": "0.27.0", "@esbuild/linux-mips64el": "0.27.0", "@esbuild/linux-ppc64": "0.27.0", "@esbuild/linux-riscv64": "0.27.0", "@esbuild/linux-s390x": "0.27.0", "@esbuild/linux-x64": "0.27.0", "@esbuild/netbsd-arm64": "0.27.0", "@esbuild/netbsd-x64": "0.27.0", "@esbuild/openbsd-arm64": "0.27.0", "@esbuild/openbsd-x64": "0.27.0", "@esbuild/openharmony-arm64": "0.27.0", "@esbuild/sunos-x64": "0.27.0", "@esbuild/win32-arm64": "0.27.0", "@esbuild/win32-ia32": "0.27.0", "@esbuild/win32-x64": "0.27.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA=="],
|
||||||
|
|
||||||
"engine.io/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
|
"engine.io/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
|
||||||
|
|
||||||
|
|
@ -763,54 +761,56 @@
|
||||||
|
|
||||||
"@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
"@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
|
"convex/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="],
|
"convex/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.0", "", { "os": "android", "cpu": "arm" }, "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="],
|
"convex/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.0", "", { "os": "android", "cpu": "arm64" }, "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="],
|
"convex/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.0", "", { "os": "android", "cpu": "x64" }, "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="],
|
"convex/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="],
|
"convex/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="],
|
"convex/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="],
|
"convex/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="],
|
"convex/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="],
|
"convex/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="],
|
"convex/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="],
|
"convex/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="],
|
"convex/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="],
|
"convex/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="],
|
"convex/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.0", "", { "os": "linux", "cpu": "none" }, "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="],
|
"convex/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="],
|
"convex/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.0", "", { "os": "linux", "cpu": "x64" }, "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.4", "", { "os": "none", "cpu": "arm64" }, "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ=="],
|
"convex/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.0", "", { "os": "none", "cpu": "arm64" }, "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="],
|
"convex/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.0", "", { "os": "none", "cpu": "x64" }, "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A=="],
|
"convex/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="],
|
"convex/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="],
|
"convex/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.0", "", { "os": "none", "cpu": "arm64" }, "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="],
|
"convex/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="],
|
"convex/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg=="],
|
||||||
|
|
||||||
"convex/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="],
|
"convex/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ=="],
|
||||||
|
|
||||||
|
"convex/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.0", "", { "os": "win32", "cpu": "x64" }, "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
68
convex/betterAuth/schemas/nests.ts
Normal file
68
convex/betterAuth/schemas/nests.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { defineTable } from "convex/server";
|
||||||
|
import { v } from "convex/values";
|
||||||
|
|
||||||
|
export const nests = {
|
||||||
|
nests: defineTable({
|
||||||
|
type: v.union(v.literal("global"), v.literal("regional"), v.literal("private")),
|
||||||
|
name: v.string(),
|
||||||
|
description: v.optional(v.string()),
|
||||||
|
images: v.object({
|
||||||
|
banner: v.id("storage"),
|
||||||
|
icon: v.id("storage"),
|
||||||
|
}),
|
||||||
|
colors: v.optional(
|
||||||
|
v.object({
|
||||||
|
primary: v.string(),
|
||||||
|
accent: v.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
createdAt: v.number(),
|
||||||
|
updatedAt: v.number(),
|
||||||
|
managerId: v.id("user"),
|
||||||
|
members: v.array(v.id("user")),
|
||||||
|
channels: v.array(v.id("channel")),
|
||||||
|
roles: v.array(v.id("role")),
|
||||||
|
region: v.optional(v.string()),
|
||||||
|
emojis: v.array(v.object({
|
||||||
|
id: v.id("storage"),
|
||||||
|
name: v.string(),
|
||||||
|
createdAt: v.number(),
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
.index("managerId", ["managerId"])
|
||||||
|
.index("type", ["type"])
|
||||||
|
.index("type_region", ["type", "region"])
|
||||||
|
.index("createdAt", ["createdAt"]),
|
||||||
|
roles: defineTable({
|
||||||
|
nestId: v.id("nests"),
|
||||||
|
name: v.string(),
|
||||||
|
color: v.optional(v.string()),
|
||||||
|
hoist: v.optional(v.boolean()),
|
||||||
|
mentionable: v.optional(v.boolean()),
|
||||||
|
icon: v.optional(v.id("storage")),
|
||||||
|
position: v.optional(v.number()),
|
||||||
|
permissions: v.array(v.int64()), // Permissions as bitfield
|
||||||
|
flags: v.array(v.int64()), // Flags as bitfield
|
||||||
|
createdAt: v.number(),
|
||||||
|
updatedAt: v.number(),
|
||||||
|
})
|
||||||
|
.index("nestId", ["nestId"])
|
||||||
|
.index("nestId_position", ["nestId", "position"]),
|
||||||
|
channels: defineTable({
|
||||||
|
type: v.union(v.literal("text"), v.literal("category"), v.literal("announcement")),
|
||||||
|
name: v.string(),
|
||||||
|
nestId: v.id("nests"),
|
||||||
|
position: v.number(),
|
||||||
|
permissions: v.array(v.int64()), // Permissions as bitfield
|
||||||
|
overwrites: v.array(v.object({
|
||||||
|
id: v.union(v.id("user"), v.id("role")),
|
||||||
|
allow: v.union(v.array(v.int64()), v.null()), // Permissions as bitfield
|
||||||
|
deny: v.union(v.array(v.int64()), v.null()), // Permissions as bitfield
|
||||||
|
})),
|
||||||
|
createdAt: v.number(),
|
||||||
|
updatedAt: v.number(),
|
||||||
|
})
|
||||||
|
.index("nestId", ["nestId"])
|
||||||
|
.index("nestId_position", ["nestId", "position"])
|
||||||
|
.index("nestId_type", ["nestId", "type"])
|
||||||
|
}
|
||||||
44
ecosystem.config.cjs
Normal file
44
ecosystem.config.cjs
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Load .env.local if it exists
|
||||||
|
function loadEnvFile(filename) {
|
||||||
|
const envPath = path.resolve(__dirname, filename);
|
||||||
|
if (!fs.existsSync(envPath)) return {};
|
||||||
|
|
||||||
|
const content = fs.readFileSync(envPath, 'utf-8');
|
||||||
|
const env = {};
|
||||||
|
for (const line of content.split('\n')) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
||||||
|
const [key, ...rest] = trimmed.split('=');
|
||||||
|
if (key) env[key.trim()] = rest.join('=').trim().replace(/^["']|["']$/g, '');
|
||||||
|
}
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
const envLocal = loadEnvFile('.env.local');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: 'sipher',
|
||||||
|
script: 'src/server.ts',
|
||||||
|
interpreter: 'node_modules/.bin/tsx',
|
||||||
|
instances: 1,
|
||||||
|
exec_mode: 'fork',
|
||||||
|
watch: false,
|
||||||
|
max_memory_restart: '4G',
|
||||||
|
env: {
|
||||||
|
...envLocal,
|
||||||
|
NODE_ENV: 'development',
|
||||||
|
PORT: 3000,
|
||||||
|
},
|
||||||
|
env_production: {
|
||||||
|
...envLocal,
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
PORT: 8081,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
16
package.json
16
package.json
|
|
@ -5,11 +5,11 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "bun src/lib/scripts/copy-olm.ts",
|
"postinstall": "bun src/lib/scripts/copy-olm.ts",
|
||||||
"dev": "cross-env NODE_ENV=development PORT=3000 tsx src/server.ts",
|
"dev": "cross-env NODE_ENV=development PORT=3000 tsx src/server.ts",
|
||||||
"build": "bun src/lib/scripts/copy-olm.ts && next build",
|
"build": "bun src/lib/scripts/copy-olm.ts && convex deploy --cmd \"bun run build\"",
|
||||||
"start": "cross-env NODE_ENV=production PORT=8081 tsx src/server.ts"
|
"start": "cross-env NODE_ENV=production PORT=8081 tsx src/server.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@convex-dev/better-auth": "^0.10.9",
|
"@convex-dev/better-auth": "^0.10.10",
|
||||||
"@marsidev/react-turnstile": "^1.4.1",
|
"@marsidev/react-turnstile": "^1.4.1",
|
||||||
"@matrix-org/olm": "^3.2.15",
|
"@matrix-org/olm": "^3.2.15",
|
||||||
"@nanostores/react": "^1.0.0",
|
"@nanostores/react": "^1.0.0",
|
||||||
|
|
@ -27,18 +27,18 @@
|
||||||
"@radix-ui/react-separator": "^1.1.8",
|
"@radix-ui/react-separator": "^1.1.8",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@types/bun": "^1.3.5",
|
"@types/bun": "^1.3.6",
|
||||||
"@types/libsodium-wrappers": "^0.7.14",
|
"@types/libsodium-wrappers": "^0.7.14",
|
||||||
"better-auth": "1.4.10",
|
"better-auth": "1.4.12",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
"convex": "^1.31.2",
|
"convex": "^1.31.4",
|
||||||
"cross-env": "^10.1.0",
|
"cross-env": "^10.1.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"dexie": "^4.2.1",
|
"dexie": "^4.2.1",
|
||||||
"dexie-react-hooks": "^4.2.0",
|
"dexie-react-hooks": "^4.2.0",
|
||||||
"framer-motion": "^12.23.27",
|
"framer-motion": "^12.26.2",
|
||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.562.0",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"nanostores": "^1.1.0",
|
"nanostores": "^1.1.0",
|
||||||
|
|
@ -55,8 +55,8 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.18",
|
"@tailwindcss/postcss": "^4.1.18",
|
||||||
"@types/node": "^25.0.3",
|
"@types/node": "^25.0.8",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.8",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"babel-plugin-react-compiler": "1.0.0",
|
"babel-plugin-react-compiler": "1.0.0",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,58 @@
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
|
|
||||||
|
// Track OLM initialization state
|
||||||
|
let olmInitPromise: Promise<any> | null = null;
|
||||||
|
|
||||||
// Load OLM via script tag to bypass bundler entirely
|
// Load OLM via script tag to bypass bundler entirely
|
||||||
export async function loadOlm() {
|
export async function loadOlm() {
|
||||||
if (typeof window === "undefined") throw new Error("OLM requires browser");
|
if (typeof window === "undefined") throw new Error("OLM requires browser");
|
||||||
if ((window as any).Olm) return (window as any).Olm;
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
// If already initialized, return cached Olm
|
||||||
|
if ((window as any).__olmInitialized && (window as any).Olm) {
|
||||||
|
console.debug("[makeKeysOnSignUp]: OLM already initialized");
|
||||||
|
return (window as any).Olm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If initialization is in progress, wait for it
|
||||||
|
if (olmInitPromise) {
|
||||||
|
console.debug("[makeKeysOnSignUp]: OLM initialization in progress, waiting for it");
|
||||||
|
return olmInitPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start initialization
|
||||||
|
olmInitPromise = new Promise((resolve, reject) => {
|
||||||
|
// Check if script already loaded but not initialized
|
||||||
|
if ((window as any).Olm) {
|
||||||
|
const Olm = (window as any).Olm;
|
||||||
|
Olm.init({ locateFile: () => "/olm.wasm" })
|
||||||
|
.then(() => {
|
||||||
|
(window as any).__olmInitialized = true;
|
||||||
|
resolve(Olm);
|
||||||
|
})
|
||||||
|
.catch(reject);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
script.src = "/olm.js";
|
script.src = "/olm.js";
|
||||||
script.onload = async () => {
|
script.onload = async () => {
|
||||||
|
try {
|
||||||
const Olm = (window as any).Olm;
|
const Olm = (window as any).Olm;
|
||||||
await Olm.init({ locateFile: () => "/olm.wasm" });
|
await Olm.init({ locateFile: () => "/olm.wasm" });
|
||||||
|
(window as any).__olmInitialized = true;
|
||||||
resolve(Olm);
|
resolve(Olm);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
script.onerror = (err) => {
|
||||||
|
console.error("[makeKeysOnSignUp]: Failed to load OLM: ", err);
|
||||||
|
reject(new Error(`Failed to load OLM: ${err}`));
|
||||||
};
|
};
|
||||||
script.onerror = () => reject(new Error("Failed to load OLM"));
|
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return olmInitPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendKeysToServerFn = (args: {
|
type SendKeysToServerFn = (args: {
|
||||||
|
|
@ -64,8 +101,8 @@ export default async function makeKeysOnSignUp(
|
||||||
|
|
||||||
const pickledAccount = account.pickle(localPassword);
|
const pickledAccount = account.pickle(localPassword);
|
||||||
|
|
||||||
// Store password in sessionStorage for unpickling later
|
// Note: Password storage is handled by the OlmContext with encryption
|
||||||
sessionStorage.setItem(`olm_password_${odId}`, localPassword);
|
// Do NOT store plain text password here
|
||||||
|
|
||||||
// Cache the account in window
|
// Cache the account in window
|
||||||
if (!(window as any).olmAccountCache) {
|
if (!(window as any).olmAccountCache) {
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,7 @@ export default function AppContainer() {
|
||||||
api.auth.retrieveServerOlmAccount,
|
api.auth.retrieveServerOlmAccount,
|
||||||
data?.user?.id ? { userId: data.user.id } : "skip"
|
data?.user?.id ? { userId: data.user.id } : "skip"
|
||||||
);
|
);
|
||||||
|
|
||||||
const sendKeysToServer = useMutation(api.auth.sendKeysToServer);
|
const sendKeysToServer = useMutation(api.auth.sendKeysToServer);
|
||||||
const consumeOTK = useMutation(api.auth.consumeOTK);
|
const consumeOTK = useMutation(api.auth.consumeOTK);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useOlmContext } from "@/contexts/olm-context";
|
import { useOlmContext } from "@/contexts/olm-context";
|
||||||
import { Info, KeyRound, ShieldCheck } from "lucide-react";
|
import { AlertCircle, Info, KeyRound, ShieldCheck } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "../ui/dialog";
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "../ui/dialog";
|
||||||
|
|
@ -8,21 +8,30 @@ import { Input } from "../ui/input";
|
||||||
export default function OlmPasswordDialog({ userId }: { userId: string }) {
|
export default function OlmPasswordDialog({ userId }: { userId: string }) {
|
||||||
const [needsPassword, setNeedsPassword] = useState(false);
|
const [needsPassword, setNeedsPassword] = useState(false);
|
||||||
const [password, setPasswordInput] = useState("");
|
const [password, setPasswordInput] = useState("");
|
||||||
const { setPassword } = useOlmContext();
|
|
||||||
|
const { setPassword, passwordError, clearPasswordError } = useOlmContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Get the password from the session storage
|
// The context handles loading & decrypting the password from sessionStorage.
|
||||||
const password = sessionStorage.getItem(`olm_password_${userId}`);
|
// We only need to show the dialog if the context doesn't have a password.
|
||||||
console.log("🔒 Password from session storage:", password);
|
// This will be handled by the passwordError effect below.
|
||||||
if (!password) {
|
// For initial load, we check if there's encrypted data - if not, show dialog.
|
||||||
|
const hasStoredPassword = sessionStorage.getItem(`olm_password_${userId}`);
|
||||||
|
if (!hasStoredPassword) {
|
||||||
setNeedsPassword(true);
|
setNeedsPassword(true);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
// If there IS stored data, the context will decrypt it and load it.
|
||||||
setPassword(password);
|
// If decryption fails or password is wrong, passwordError will be set.
|
||||||
setNeedsPassword(false);
|
|
||||||
}, [userId]);
|
}, [userId]);
|
||||||
|
|
||||||
|
// Show dialog when there's a password error (wrong password was entered)
|
||||||
|
useEffect(() => {
|
||||||
|
if (passwordError) {
|
||||||
|
setNeedsPassword(true);
|
||||||
|
setPasswordInput(""); // Clear the input for retry
|
||||||
|
}
|
||||||
|
}, [passwordError]);
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (password.trim()) {
|
if (password.trim()) {
|
||||||
|
|
@ -55,25 +64,38 @@ export default function OlmPasswordDialog({ userId }: { userId: string }) {
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Enter your encryption password"
|
placeholder="Enter your encryption password"
|
||||||
className="h-11 text-center"
|
className={`h-11 text-center ${passwordError ? "border-destructive focus-visible:ring-destructive" : ""}`}
|
||||||
autoFocus
|
autoFocus
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPasswordInput(e.target.value)}
|
onChange={(e) => {
|
||||||
|
setPasswordInput(e.target.value);
|
||||||
|
if (passwordError) clearPasswordError();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="space-y-2">
|
{passwordError && (
|
||||||
<div className="flex items-center gap-2 rounded-md bg-emerald-500/10 dark:bg-emerald-400/10 px-3 py-2.5 text-emerald-700 dark:text-emerald-300 border border-emerald-200/20 dark:border-emerald-500/20">
|
<div className="flex items-center gap-2 rounded-md bg-destructive/10 px-3 py-2.5 text-destructive border border-destructive/20">
|
||||||
<ShieldCheck className="h-4 w-4 shrink-0" />
|
<AlertCircle className="h-4 w-4 shrink-0" />
|
||||||
<p className="text-xs leading-relaxed">
|
<p className="text-xs leading-relaxed">
|
||||||
Your password is stored locally and never sent to our servers.
|
{passwordError}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 rounded-md bg-blue-500/10 dark:bg-blue-400/10 px-3 py-2.5 text-blue-700 dark:text-blue-300 border border-blue-200/20 dark:border-blue-500/20">
|
)}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center gap-2 rounded-md bg-chart-3/10 px-3 py-2.5 text-chart-3 border border-chart-3/20">
|
||||||
<Info className="h-4 w-4 shrink-0" />
|
<Info className="h-4 w-4 shrink-0" />
|
||||||
<p className="text-xs leading-relaxed">
|
<p className="text-xs leading-relaxed">
|
||||||
You'll be asked to re-enter this password each time you start a new browser session.
|
You'll be asked to re-enter this password each time you start a new browser session.
|
||||||
|
<br />
|
||||||
|
When continuing, the window will be reloaded, please do not close the window or refresh the page by yourself.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-2 rounded-md bg-chart-2/10 px-3 py-2.5 text-chart-2 border border-chart-2/20">
|
||||||
|
<ShieldCheck className="h-4 w-4 shrink-0" />
|
||||||
|
<p className="text-xs leading-relaxed">
|
||||||
|
Your password is encrypted before being stored in your browser's session storage using a secure key that cannot be exported.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export default function OlmSetupDialog({
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!localPassword.trim()) return;
|
if (!localPassword.trim()) return;
|
||||||
await onCreateAccount(localPassword);
|
await onCreateAccount(localPassword);
|
||||||
setLocalPassword(""); // Clear password after attempt
|
setLocalPassword("");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { loadOlm } from "@/app/auth/scripts/makeKeys";
|
import { loadOlm } from "@/app/auth/scripts/makeKeys";
|
||||||
|
import { decryptPassword, encryptPassword, getOrCreatePasswordEncryptionKey } from "@/lib/crypto";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { checkOlmStatus, getOlmAccount, handleOlmAccountCreation, SendKeysToServerFn } from "@/lib/olm";
|
import { checkOlmStatus, getOlmAccount, handleOlmAccountCreation, SendKeysToServerFn } from "@/lib/olm";
|
||||||
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
|
@ -25,10 +26,12 @@ interface OlmContextValue {
|
||||||
|
|
||||||
// Password & setup
|
// Password & setup
|
||||||
password: string | null;
|
password: string | null;
|
||||||
|
passwordError: string | null;
|
||||||
showOlmModal: boolean;
|
showOlmModal: boolean;
|
||||||
setShowOlmModal: (show: boolean) => void;
|
setShowOlmModal: (show: boolean) => void;
|
||||||
handleCreateAccount: (password: string) => Promise<void>;
|
handleCreateAccount: (password: string) => Promise<void>;
|
||||||
setPassword: (password: string) => void;
|
setPassword: (password: string) => void;
|
||||||
|
clearPasswordError: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const OlmContext = createContext<OlmContextValue | null>(null);
|
const OlmContext = createContext<OlmContextValue | null>(null);
|
||||||
|
|
@ -55,14 +58,34 @@ export function OlmProvider({
|
||||||
const [olmAccount, setOlmAccount] = useState<Olm.Account | null>(null);
|
const [olmAccount, setOlmAccount] = useState<Olm.Account | null>(null);
|
||||||
const [olmStatus, setOlmStatus] = useState<SiPher.OlmStatus>("checking");
|
const [olmStatus, setOlmStatus] = useState<SiPher.OlmStatus>("checking");
|
||||||
const [password, setPasswordState] = useState<string | null>(null);
|
const [password, setPasswordState] = useState<string | null>(null);
|
||||||
|
const [passwordError, setPasswordError] = useState<string | null>(null);
|
||||||
const [showOlmModal, setShowOlmModal] = useState(false);
|
const [showOlmModal, setShowOlmModal] = useState(false);
|
||||||
|
|
||||||
// Cache sessions in memory: recipientId -> Session
|
// Cache sessions in memory: recipientId -> Session
|
||||||
const sessionsRef = useRef<Map<string, Olm.Session>>(new Map());
|
const sessionsRef = useRef<Map<string, Olm.Session>>(new Map());
|
||||||
// Track pending session creation to prevent race conditions
|
// Track pending session creation to prevent race conditions
|
||||||
const pendingSessionsRef = useRef<Map<string, Promise<Olm.Session | null>>>(new Map());
|
const pendingSessionsRef = useRef<Map<string, Promise<Olm.Session | null>>>(new Map());
|
||||||
|
// Encryption key for secure password storage (persisted in IndexedDB)
|
||||||
|
const encryptionKeyRef = useRef<CryptoKey | null>(null);
|
||||||
|
const [encryptionKeyReady, setEncryptionKeyReady] = useState(false);
|
||||||
|
// Track if password was set manually (to prevent load-from-storage race condition)
|
||||||
|
const passwordSetManuallyRef = useRef(false);
|
||||||
|
// Track if we're currently loading the OLM account (prevent duplicate loads)
|
||||||
|
const isLoadingAccountRef = useRef(false);
|
||||||
const [, forceUpdate] = useState({});
|
const [, forceUpdate] = useState({});
|
||||||
|
|
||||||
|
// Initialize encryption key on mount
|
||||||
|
useEffect(() => {
|
||||||
|
getOrCreatePasswordEncryptionKey()
|
||||||
|
.then((key) => {
|
||||||
|
encryptionKeyRef.current = key;
|
||||||
|
setEncryptionKeyReady(true);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("[OlmContext]: Failed to initialize encryption key:", err);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Helper: Cache session in memory
|
// Helper: Cache session in memory
|
||||||
const cacheSession = useCallback((recipientId: string, session: Olm.Session) => {
|
const cacheSession = useCallback((recipientId: string, session: Olm.Session) => {
|
||||||
sessionsRef.current.set(recipientId, session);
|
sessionsRef.current.set(recipientId, session);
|
||||||
|
|
@ -137,18 +160,34 @@ export function OlmProvider({
|
||||||
// Helper: Clear password from state and storage
|
// Helper: Clear password from state and storage
|
||||||
const clearPassword = useCallback(() => {
|
const clearPassword = useCallback(() => {
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
passwordSetManuallyRef.current = false;
|
||||||
setPasswordState(null);
|
setPasswordState(null);
|
||||||
sessionStorage.removeItem(getPasswordStorageKey(userId));
|
sessionStorage.removeItem(getPasswordStorageKey(userId));
|
||||||
}, [userId, getPasswordStorageKey]);
|
}, [userId, getPasswordStorageKey]);
|
||||||
|
|
||||||
// Load password from sessionStorage on mount
|
// Load and decrypt password from sessionStorage on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userId) return;
|
if (!userId || !encryptionKeyReady || !encryptionKeyRef.current) return;
|
||||||
|
// Skip if password was set manually (prevents race condition loop)
|
||||||
|
if (passwordSetManuallyRef.current) return;
|
||||||
|
|
||||||
|
const loadStoredPassword = async () => {
|
||||||
const stored = sessionStorage.getItem(getPasswordStorageKey(userId));
|
const stored = sessionStorage.getItem(getPasswordStorageKey(userId));
|
||||||
if (stored) {
|
if (!stored) return;
|
||||||
setPasswordState(stored);
|
|
||||||
|
const decrypted = await decryptPassword(stored, encryptionKeyRef.current!);
|
||||||
|
if (decrypted) {
|
||||||
|
setPasswordState(decrypted);
|
||||||
|
console.debug("[OlmContext]: Password loaded and decrypted from storage");
|
||||||
|
} else {
|
||||||
|
// Decryption failed - clear stale data
|
||||||
|
sessionStorage.removeItem(getPasswordStorageKey(userId));
|
||||||
|
console.debug("[OlmContext]: Cleared stale encrypted password");
|
||||||
}
|
}
|
||||||
}, [userId, getPasswordStorageKey]);
|
};
|
||||||
|
|
||||||
|
loadStoredPassword();
|
||||||
|
}, [userId, getPasswordStorageKey, encryptionKeyReady]);
|
||||||
|
|
||||||
// Check OLM status when user data and server status are available
|
// Check OLM status when user data and server status are available
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -169,34 +208,70 @@ export function OlmProvider({
|
||||||
// Load and unpickle the OLM account when password is available
|
// Load and unpickle the OLM account when password is available
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userId || !password) return;
|
if (!userId || !password) return;
|
||||||
|
// Prevent duplicate loads
|
||||||
|
if (isLoadingAccountRef.current) {
|
||||||
|
console.debug("[OlmContext]: Already loading account, skipping...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const loadAccount = async () => {
|
const loadAccount = async () => {
|
||||||
|
isLoadingAccountRef.current = true;
|
||||||
try {
|
try {
|
||||||
console.debug("[OlmContext]: Loading OLM account...");
|
console.debug("[OlmContext]: Loading OLM account...");
|
||||||
const account = await getOlmAccount(userId, password);
|
const account = await getOlmAccount(userId, password);
|
||||||
if (!account) {
|
if (!account) {
|
||||||
console.warn("[OlmContext]: No OLM account found");
|
console.warn("[OlmContext]: No OLM account found");
|
||||||
|
isLoadingAccountRef.current = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setOlmAccount(account);
|
setOlmAccount(account);
|
||||||
|
setPasswordError(null);
|
||||||
console.debug("[OlmContext]: OLM account loaded successfully");
|
console.debug("[OlmContext]: OLM account loaded successfully");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[OlmContext]: Failed to load OLM account:", err);
|
console.error("[OlmContext]: Failed to load OLM account:", err);
|
||||||
// Password might be wrong - clear it
|
// Password is wrong - clear it and set error
|
||||||
|
setPasswordError("Incorrect encryption password. Please try again.");
|
||||||
clearPassword();
|
clearPassword();
|
||||||
|
} finally {
|
||||||
|
isLoadingAccountRef.current = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadAccount();
|
loadAccount();
|
||||||
}, [userId, password, clearPassword]);
|
}, [userId, password, clearPassword]);
|
||||||
|
|
||||||
// Set password and store in sessionStorage
|
// Clear password error
|
||||||
|
const clearPasswordError = useCallback(() => {
|
||||||
|
setPasswordError(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Set password and store encrypted in sessionStorage
|
||||||
const setPassword = useCallback((newPassword: string) => {
|
const setPassword = useCallback((newPassword: string) => {
|
||||||
if (!userId) return;
|
if (!userId) return;
|
||||||
|
|
||||||
sessionStorage.setItem(getPasswordStorageKey(userId), newPassword);
|
// Mark as manually set to prevent load-from-storage race condition
|
||||||
|
passwordSetManuallyRef.current = true;
|
||||||
|
setPasswordError(null);
|
||||||
setPasswordState(newPassword);
|
setPasswordState(newPassword);
|
||||||
|
|
||||||
|
// Encrypt and store asynchronously
|
||||||
|
if (encryptionKeyRef.current) {
|
||||||
|
encryptPassword(newPassword, encryptionKeyRef.current)
|
||||||
|
.then((encrypted) => {
|
||||||
|
// Only store if the password hasn't been cleared since we started
|
||||||
|
// This prevents the race condition where clearPassword runs before this .then()
|
||||||
|
if (passwordSetManuallyRef.current) {
|
||||||
|
sessionStorage.setItem(getPasswordStorageKey(userId), encrypted);
|
||||||
|
console.debug("[OlmContext]: Password encrypted and stored");
|
||||||
|
} else {
|
||||||
|
console.debug("[OlmContext]: Skipped storing password (was cleared)");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("[OlmContext]: Failed to encrypt password:", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
}, [userId, getPasswordStorageKey]);
|
}, [userId, getPasswordStorageKey]);
|
||||||
|
|
||||||
// Handle OLM account creation
|
// Handle OLM account creation
|
||||||
|
|
@ -325,6 +400,8 @@ export function OlmProvider({
|
||||||
senderId: string,
|
senderId: string,
|
||||||
preKeyMessage: string
|
preKeyMessage: string
|
||||||
): Promise<Olm.Session | null> => {
|
): Promise<Olm.Session | null> => {
|
||||||
|
console.debug("[OlmContext]: Args passed to createInboundSession", { senderId, preKeyMessage });
|
||||||
|
|
||||||
if (!validateSessionRequirements()) {
|
if (!validateSessionRequirements()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -338,8 +415,8 @@ export function OlmProvider({
|
||||||
try {
|
try {
|
||||||
console.debug(`[OlmContext]: Creating inbound session from sender ${senderId}`);
|
console.debug(`[OlmContext]: Creating inbound session from sender ${senderId}`);
|
||||||
|
|
||||||
const Olm = await loadOlm();
|
const Olm: typeof import("@matrix-org/olm") = await loadOlm();
|
||||||
const newSession = new Olm.Session();
|
const newSession: Olm.Session = new Olm.Session();
|
||||||
|
|
||||||
// Create inbound session from the pre-key message
|
// Create inbound session from the pre-key message
|
||||||
newSession.create_inbound(olmAccount!, preKeyMessage);
|
newSession.create_inbound(olmAccount!, preKeyMessage);
|
||||||
|
|
@ -374,10 +451,12 @@ export function OlmProvider({
|
||||||
createInboundSession,
|
createInboundSession,
|
||||||
sessions: sessionsRef.current,
|
sessions: sessionsRef.current,
|
||||||
password,
|
password,
|
||||||
|
passwordError,
|
||||||
showOlmModal,
|
showOlmModal,
|
||||||
setShowOlmModal,
|
setShowOlmModal,
|
||||||
handleCreateAccount,
|
handleCreateAccount,
|
||||||
setPassword,
|
setPassword,
|
||||||
|
clearPasswordError,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,7 @@ export function SocketProvider({ children, user, refetchUser }: SocketProviderPr
|
||||||
const processIncomingDM = useCallback(
|
const processIncomingDM = useCallback(
|
||||||
async (data: { content: { type: 0 | 1; body: unknown }, participants: string[] }) => {
|
async (data: { content: { type: 0 | 1; body: unknown }, participants: string[] }) => {
|
||||||
// Get the current user id
|
// Get the current user id
|
||||||
|
console.debug("[Socket]: Processing incoming DM", data);
|
||||||
const currentUserId = user.id;
|
const currentUserId = user.id;
|
||||||
if (!currentUserId) {
|
if (!currentUserId) {
|
||||||
console.error("[Socket]: No user ID available");
|
console.error("[Socket]: No user ID available");
|
||||||
|
|
|
||||||
88
src/lib/crypto/index.ts
Normal file
88
src/lib/crypto/index.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { db } from "@/lib/db";
|
||||||
|
|
||||||
|
const PASSWORD_ENCRYPTION_KEY_ID = "password_encryption_key";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get or create the password encryption key.
|
||||||
|
* The key is non-extractable, meaning its raw bytes cannot be read,
|
||||||
|
* even if an attacker accesses IndexedDB directly.
|
||||||
|
*/
|
||||||
|
export async function getOrCreatePasswordEncryptionKey(): Promise<CryptoKey> {
|
||||||
|
// Try to load existing key from DB
|
||||||
|
const existing = await db.encryptionKeys.get(PASSWORD_ENCRYPTION_KEY_ID);
|
||||||
|
if (existing) {
|
||||||
|
console.debug("[PEC - getOrCreatePasswordEncryptionKey]: Loaded existing encryption key from DB");
|
||||||
|
return existing.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate new AES-GCM key (non-extractable)
|
||||||
|
const newKey = await crypto.subtle.generateKey(
|
||||||
|
{ name: "AES-GCM", length: 256 },
|
||||||
|
false, // NOT extractable - raw key bytes cannot be exported
|
||||||
|
["encrypt", "decrypt"]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Store in DB
|
||||||
|
await db.encryptionKeys.add({
|
||||||
|
id: PASSWORD_ENCRYPTION_KEY_ID,
|
||||||
|
key: newKey,
|
||||||
|
createdAt: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
console.debug("[PEC - getOrCreatePasswordEncryptionKey]: Generated and stored new encryption key");
|
||||||
|
return newKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt a password string using AES-GCM.
|
||||||
|
* Returns a base64-encoded string containing IV + ciphertext.
|
||||||
|
*/
|
||||||
|
export async function encryptPassword(password: string, key: CryptoKey): Promise<string> {
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const data = encoder.encode(password);
|
||||||
|
|
||||||
|
// Generate random IV for each encryption
|
||||||
|
const iv = crypto.getRandomValues(new Uint8Array(12));
|
||||||
|
|
||||||
|
const encrypted = await crypto.subtle.encrypt(
|
||||||
|
{ name: "AES-GCM", iv },
|
||||||
|
key,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
|
// Combine IV + ciphertext
|
||||||
|
const combined = new Uint8Array(iv.length + encrypted.byteLength);
|
||||||
|
combined.set(iv);
|
||||||
|
combined.set(new Uint8Array(encrypted), iv.length);
|
||||||
|
|
||||||
|
// Encode as base64
|
||||||
|
return btoa(String.fromCharCode(...combined));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt a password string using AES-GCM.
|
||||||
|
* Returns null if decryption fails (e.g., wrong key or corrupted data).
|
||||||
|
*/
|
||||||
|
export async function decryptPassword(encryptedData: string, key: CryptoKey): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
// Decode from base64
|
||||||
|
const combined = new Uint8Array(
|
||||||
|
atob(encryptedData).split("").map(c => c.charCodeAt(0))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Extract IV (first 12 bytes) and ciphertext
|
||||||
|
const iv = combined.slice(0, 12);
|
||||||
|
const data = combined.slice(12);
|
||||||
|
|
||||||
|
const decrypted = await crypto.subtle.decrypt(
|
||||||
|
{ name: "AES-GCM", iv },
|
||||||
|
key,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
|
||||||
|
return new TextDecoder().decode(decrypted);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn("[PEC - decryptPassword]: Password decryption failed:", err);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,13 @@ export interface UnreadCount {
|
||||||
count: number;
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Encryption key storage (for password protection in session storage) */
|
||||||
|
export interface EncryptionKey {
|
||||||
|
id: string;
|
||||||
|
key: CryptoKey;
|
||||||
|
createdAt: number;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Database
|
// Database
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
@ -38,6 +45,7 @@ class SipherDB extends Dexie {
|
||||||
channels!: EntityTable<SiPher.Channel, "id">;
|
channels!: EntityTable<SiPher.Channel, "id">;
|
||||||
messages!: EntityTable<SiPher.Messages.ClientEncrypted.EncryptedMessage, "id">;
|
messages!: EntityTable<SiPher.Messages.ClientEncrypted.EncryptedMessage, "id">;
|
||||||
unreadCounts!: EntityTable<UnreadCount, "channelId">;
|
unreadCounts!: EntityTable<UnreadCount, "channelId">;
|
||||||
|
encryptionKeys!: EntityTable<EncryptionKey, "id">;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super("SipherDB");
|
super("SipherDB");
|
||||||
|
|
@ -48,6 +56,7 @@ class SipherDB extends Dexie {
|
||||||
channels: "id, *participants, type, lastMessageAt, createdAt",
|
channels: "id, *participants, type, lastMessageAt, createdAt",
|
||||||
messages: "id, channelId, fromUserId, timestamp, status",
|
messages: "id, channelId, fromUserId, timestamp, status",
|
||||||
unreadCounts: "channelId",
|
unreadCounts: "channelId",
|
||||||
|
encryptionKeys: "id, createdAt",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue