diff --git a/package-lock.json b/package-lock.json index bfdb2805..1ecd1aac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@lucia-auth/adapter-drizzle": "1.1.0", "@node-rs/argon2": "1.8.3", + "@node-rs/argon2-linux-x64-gnu": "1.8.3", "@react-email/components": "0.0.25", "@react-email/tailwind": "0.1.0", "axios": "1.7.7", @@ -23,6 +24,7 @@ "http-errors": "2.0.0", "lucia": "3.2.0", "next": "14.2.13", + "node-fetch": "3.3.2", "nodemailer": "6.9.15", "oslo": "1.2.1", "react": "^18", @@ -2569,6 +2571,33 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@emnapi/core": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-0.45.0.tgz", + "integrity": "sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", + "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.1.tgz", + "integrity": "sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild-kit/core-utils": { "version": "3.3.2", "dev": true, @@ -2853,6 +2882,27 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@expo/cli/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "optional": true, + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/@expo/cli/node_modules/picomatch": { "version": "3.0.1", "license": "MIT", @@ -3207,6 +3257,27 @@ "node": ">= 10.0.0" } }, + "node_modules/@expo/image-utils/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "optional": true, + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/@expo/image-utils/node_modules/resolve-from": { "version": "5.0.0", "license": "MIT", @@ -3540,6 +3611,27 @@ "node": ">=12" } }, + "node_modules/@expo/rudder-sdk-node/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "optional": true, + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/@expo/sdk-runtime-versions": { "version": "1.0.0", "license": "MIT", @@ -3746,6 +3838,45 @@ "lucia": "3.x" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.5.tgz", + "integrity": "sha512-kwUxR7J9WLutBbulqg1dfOrMTwhMdXLdcGUhcbCcGwnPLt3gz19uHVdwH1syKVDbE022ZS2vZxOWflFLS0YTjw==", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.1.0", + "@emnapi/runtime": "^1.1.0", + "@tybys/wasm-util": "^0.9.0" + } + }, + "node_modules/@napi-rs/wasm-runtime/node_modules/@emnapi/core": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.2.0.tgz", + "integrity": "sha512-E7Vgw78I93we4ZWdYCb4DGAwRROGkMIXk7/y87UmANR+J6qsWusmC3gLt0H+O0KOt5e6O38U8oJamgbudrES/w==", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.0.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-runtime/node_modules/@emnapi/runtime": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", + "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@napi-rs/wasm-runtime/node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@next/env": { "version": "14.2.13", "license": "MIT" @@ -3831,6 +3962,36 @@ "@node-rs/argon2-win32-x64-msvc": "1.8.3" } }, + "node_modules/@node-rs/argon2-android-arm-eabi": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-1.7.0.tgz", + "integrity": "sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-android-arm64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-1.7.0.tgz", + "integrity": "sha512-s9j/G30xKUx8WU50WIhF0fIl1EdhBGq0RQ06lEhZ0Gi0ap8lhqbE2Bn5h3/G2D1k0Dx+yjeVVNmt/xOQIRG38A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@node-rs/argon2-darwin-arm64": { "version": "1.8.3", "cpu": [ @@ -3845,6 +4006,353 @@ "node": ">= 10" } }, + "node_modules/@node-rs/argon2-darwin-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-1.7.0.tgz", + "integrity": "sha512-5oi/pxqVhODW/pj1+3zElMTn/YukQeywPHHYDbcAW3KsojFjKySfhcJMd1DjKTc+CHQI+4lOxZzSUzK7mI14Hw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-freebsd-x64": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-1.7.0.tgz", + "integrity": "sha512-Ify08683hA4QVXYoIm5SUWOY5DPIT/CMB0CQT+IdxQAg/F+qp342+lUkeAtD5bvStQuCx/dFO3bnnzoe2clMhA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm-gnueabihf": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-1.7.0.tgz", + "integrity": "sha512-7DjDZ1h5AUHAtRNjD19RnQatbhL+uuxBASuuXIBu4/w6Dx8n7YPxwTP4MXfsvuRgKuMWiOb/Ub/HJ3kXVCXRkg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-1.7.0.tgz", + "integrity": "sha512-nJDoMP4Y3YcqGswE4DvP080w6O24RmnFEDnL0emdI8Nou17kNYBzP2546Nasx9GCyLzRcYQwZOUjrtUuQ+od2g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-1.7.0.tgz", + "integrity": "sha512-BKWS8iVconhE3jrb9mj6t1J9vwUqQPpzCbUKxfTGJfc+kNL58F1SXHBoe2cDYGnHrFEHTY0YochzXoAfm4Dm/A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-x64-gnu": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-1.8.3.tgz", + "integrity": "sha512-OBH+EFG7BGjFyldaao2H2gSCLmjtrrwf420B1L+lFn7JLW9UAjsIPFKAcWsYwPa/PwYzIge9Y7SGcpqlsSEX0w==", + "cpu": [ + "x64" + ], + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-x64-musl": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-1.7.0.tgz", + "integrity": "sha512-/o1efYCYIxjfuoRYyBTi2Iy+1iFfhqHCvvVsnjNSgO1xWiWrX0Rrt/xXW5Zsl7vS2Y+yu8PL8KFWRzZhaVxfKA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-wasm32-wasi": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-1.7.0.tgz", + "integrity": "sha512-Evmk9VcxqnuwQftfAfYEr6YZYSPLzmKUsbFIMep5nTt9PT4XYRFAERj7wNYp+rOcBenF3X4xoB+LhwcOMTNE5w==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/core": "^0.45.0", + "@emnapi/runtime": "^0.45.0", + "@tybys/wasm-util": "^0.8.1", + "memfs-browser": "^3.4.13000" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@node-rs/argon2-win32-arm64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-1.7.0.tgz", + "integrity": "sha512-qgsU7T004COWWpSA0tppDqDxbPLgg8FaU09krIJ7FBl71Sz8SFO40h7fDIjfbTT5w7u6mcaINMQ5bSHu75PCaA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-win32-ia32-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-1.7.0.tgz", + "integrity": "sha512-JGafwWYQ/HpZ3XSwP4adQ6W41pRvhcdXvpzIWtKvX+17+xEXAe2nmGWM6s27pVkg1iV2ZtoYLRDkOUoGqZkCcg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-win32-x64-msvc": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-x64-msvc/-/argon2-win32-x64-msvc-1.7.0.tgz", + "integrity": "sha512-9oq4ShyFakw8AG3mRls0AoCpxBFcimYx7+jvXeAf2OqKNO+mSA6eZ9z7KQeVCi0+SOEUYxMGf5UiGiDb9R6+9Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2/node_modules/@node-rs/argon2-android-arm-eabi": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-1.8.3.tgz", + "integrity": "sha512-JFZPlNM0A8Og+Tncb8UZsQrhEMlbHBXPsT3hRoKImzVmTmq28Os0ucFWow0AACp2coLHBSydXH3Dh0lZup3rWw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2/node_modules/@node-rs/argon2-android-arm64": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-1.8.3.tgz", + "integrity": "sha512-zaf8P3T92caeW2xnMA7P1QvRA4pIt/04oilYP44XlTCtMye//vwXDMeK53sl7dvYiJKnzAWDRx41k8vZvpZazg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2/node_modules/@node-rs/argon2-darwin-x64": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-1.8.3.tgz", + "integrity": "sha512-YMjmBGFZhLfYjfQ2gll9A+BZu/zAMV7lWZIbKxb7ZgEofILQwuGmExjDtY3Jplido/6leCEdpmlk2oIsME00LA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2/node_modules/@node-rs/argon2-freebsd-x64": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-1.8.3.tgz", + "integrity": "sha512-Hq3Rj5Yb2RolTG/luRPnv+XiGCbi5nAK25Pc8ou/tVapwX+iktEm/NXbxc5zsMxraYVkCvfdwBjweC5O+KqCGw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2/node_modules/@node-rs/argon2-linux-arm-gnueabihf": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-1.8.3.tgz", + "integrity": "sha512-x49l8RgzKoG0/V0IXa5rrEl1TcJEc936ctlYFvqcunSOyowZ6kiWtrp1qrbOR8gbaNILl11KTF52vF6+h8UlEQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2/node_modules/@node-rs/argon2-linux-arm64-gnu": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-1.8.3.tgz", + "integrity": "sha512-gJesam/qA63reGkb9qJ2TjFSLBtY41zQh2oei7nfnYsmVQPuHHWItJxEa1Bm21SPW53gZex4jFJbDIgj0+PxIw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2/node_modules/@node-rs/argon2-linux-arm64-musl": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-1.8.3.tgz", + "integrity": "sha512-7O6kQdSKzB4Tjx/EBa8zKIxnmLkQE8VdJgPm6Ksrpn+ueo0mx2xf76fIDnbbTCtm3UbB+y+FkTo2wLA7tOqIKg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2/node_modules/@node-rs/argon2-linux-x64-musl": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-1.8.3.tgz", + "integrity": "sha512-bDbMuyekIxZaN7NaX+gHVkOyABB8bcMEJYeRPW1vCXKHj3brJns1wiUFSxqeUXreupifNVJlQfPt1Y5B/vFXgQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2/node_modules/@node-rs/argon2-wasm32-wasi": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-1.8.3.tgz", + "integrity": "sha512-NBf2cMCDbNKMzp13Pog8ZPmI0M9U4Ak5b95EUjkp17kdKZFds12dwW67EMnj7Zy+pRqby2QLECaWebDYfNENTg==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@node-rs/argon2/node_modules/@node-rs/argon2-win32-arm64-msvc": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-1.8.3.tgz", + "integrity": "sha512-AHpPo7UbdW5WWjwreVpgFSY0o1RY4A7cUFaqDXZB2OqEuyrhMxBdZct9PX7PQKI18D85pLsODnR+gvVuTwJ6rQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2/node_modules/@node-rs/argon2-win32-ia32-msvc": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-1.8.3.tgz", + "integrity": "sha512-bqzn2rcQkEwCINefhm69ttBVVkgHJb/V03DdBKsPFtiX6H47axXKz62d1imi26zFXhOEYxhKbu3js03GobJOLw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2/node_modules/@node-rs/argon2-win32-x64-msvc": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-x64-msvc/-/argon2-win32-x64-msvc-1.8.3.tgz", + "integrity": "sha512-ILlrRThdbp5xNR5gwYM2ic1n/vG5rJ8dQZ+YMRqksl+lnTJ/6FDe5BOyIhiPtiDwlCiCtUA+1NxpDB9KlUCAIA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@node-rs/bcrypt": { "version": "1.9.0", "license": "MIT", @@ -4346,6 +4854,27 @@ "optional": true, "peer": true }, + "node_modules/@react-native/dev-middleware/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "optional": true, + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/@react-native/dev-middleware/node_modules/open": { "version": "7.4.2", "license": "MIT", @@ -4468,6 +4997,15 @@ "tslib": "^2.4.0" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.8.3.tgz", + "integrity": "sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/better-sqlite3": { "version": "7.6.11", "devOptional": true, @@ -6203,6 +6741,27 @@ "node-fetch": "^2.6.12" } }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "optional": true, + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "license": "MIT", @@ -6260,6 +6819,14 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.1", "devOptional": true, @@ -8118,6 +8685,28 @@ "version": "4.2.3", "license": "MIT" }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/fetch-retry": { "version": "4.1.1", "license": "MIT", @@ -8319,6 +8908,17 @@ "node": ">= 6" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "license": "MIT", @@ -8372,6 +8972,12 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/fs-monkey": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", + "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", + "optional": true + }, "node_modules/fs.realpath": { "version": "1.0.0", "devOptional": true, @@ -10046,6 +10652,21 @@ "node": ">= 10" } }, + "node_modules/lucia/node_modules/@node-rs/argon2-linux-x64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-1.7.0.tgz", + "integrity": "sha512-EmgqZOlf4Jurk/szW1iTsVISx25bKksVC5uttJDUloTgsAgIGReCpUUO1R24pBhu9ESJa47iv8NSf3yAfGv6jQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/lucia/node_modules/oslo": { "version": "1.2.0", "license": "MIT", @@ -10150,6 +10771,27 @@ "node": ">= 0.6" } }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "optional": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/memfs-browser": { + "version": "3.5.10302", + "resolved": "https://registry.npmjs.org/memfs-browser/-/memfs-browser-3.5.10302.tgz", + "integrity": "sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw==", + "optional": true, + "dependencies": { + "memfs": "3.5.3" + } + }, "node_modules/memory-cache": { "version": "0.2.0", "license": "BSD-2-Clause", @@ -10546,24 +11188,39 @@ "node": ">= 0.10.5" } }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { - "version": "2.7.0", - "license": "MIT", - "optional": true, - "peer": true, + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "dependencies": { - "whatwg-url": "^5.0.0" + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" }, "engines": { - "node": "4.x || >=6.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "node_modules/node-forge": { @@ -11046,6 +11703,21 @@ "node": ">= 10" } }, + "node_modules/oslo/node_modules/@node-rs/argon2-linux-x64-gnu": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-1.7.0.tgz", + "integrity": "sha512-EmgqZOlf4Jurk/szW1iTsVISx25bKksVC5uttJDUloTgsAgIGReCpUUO1R24pBhu9ESJa47iv8NSf3yAfGv6jQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/p-finally": { "version": "1.0.0", "license": "MIT", @@ -12139,6 +12811,134 @@ } } }, + "node_modules/react-email/node_modules/next/node_modules/@next/swc-darwin-x64": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz", + "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/react-email/node_modules/next/node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz", + "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/react-email/node_modules/next/node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz", + "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/react-email/node_modules/next/node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz", + "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/react-email/node_modules/next/node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", + "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/react-email/node_modules/next/node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", + "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/react-email/node_modules/next/node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", + "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/react-email/node_modules/next/node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", + "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/react-email/node_modules/onetime": { "version": "5.1.2", "dev": true, @@ -13806,7 +14606,8 @@ }, "node_modules/tr46": { "version": "0.0.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "optional": true, "peer": true }, @@ -14342,15 +15143,25 @@ "defaults": "^1.0.3" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", - "license": "BSD-2-Clause", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", "optional": true, "peer": true }, "node_modules/whatwg-url": { "version": "5.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "optional": true, "peer": true, "dependencies": { diff --git a/package.json b/package.json index 556846b8..c7908d3d 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,12 @@ "db:studio": "drizzle-kit studio", "build": "next build && tsc --project tsconfig.server.json && tsc-alias -p tsconfig.server.json", "start": "ENVIRONMENT=prod node dist/server/index.js", - "email": "email dev --dir emailTemplates --port 3002" + "email": "email dev --dir server/emails/templates --port 3002" }, "dependencies": { "@lucia-auth/adapter-drizzle": "1.1.0", "@node-rs/argon2": "1.8.3", + "@node-rs/argon2-linux-x64-gnu": "1.8.3", "@react-email/components": "0.0.25", "@react-email/tailwind": "0.1.0", "axios": "1.7.7", @@ -28,6 +29,7 @@ "http-errors": "2.0.0", "lucia": "3.2.0", "next": "14.2.13", + "node-fetch": "3.3.2", "nodemailer": "6.9.15", "oslo": "1.2.1", "react": "^18", diff --git a/server/db/.gitignore b/server/db/.gitignore new file mode 100644 index 00000000..403db979 --- /dev/null +++ b/server/db/.gitignore @@ -0,0 +1 @@ +names.json \ No newline at end of file diff --git a/server/db/schema.ts b/server/db/schema.ts index 26baf3b6..e5f78a90 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -89,6 +89,16 @@ export const sessions = sqliteTable("session", { expiresAt: integer("expiresAt").notNull(), }); +export const userOrgs = sqliteTable("userOrgs", { + userId: text("userId") + .notNull() + .references(() => users.id), + orgId: integer("orgId") + .notNull() + .references(() => orgs.orgId), + role: text("role").notNull(), // e.g., 'admin', 'member', etc. +}); + // Define the model types for type inference export type Org = InferSelectModel; export type User = InferSelectModel; diff --git a/server/index.ts b/server/index.ts index 17f94ba6..0df00f8a 100644 --- a/server/index.ts +++ b/server/index.ts @@ -73,6 +73,8 @@ declare global { namespace Express { interface Request { user?: User; + userOrgRole?: string; + userOrgs?: number[]; } } } diff --git a/server/routers/auth/getUserOrgs.ts b/server/routers/auth/getUserOrgs.ts new file mode 100644 index 00000000..f73bebca --- /dev/null +++ b/server/routers/auth/getUserOrgs.ts @@ -0,0 +1,33 @@ +import { Request, Response, NextFunction } from 'express'; +import { db } from '@server/db'; +import { userOrgs, orgs } from '@server/db/schema'; +import { eq } from 'drizzle-orm'; +import createHttpError from 'http-errors'; +import HttpCode from '@server/types/HttpCode'; + +export async function getUserOrgs(req: Request, res: Response, next: NextFunction) { + const userId = req.user?.id; // Assuming you have user information in the request + + if (!userId) { + return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); + } + + try { + const userOrganizations = await db.select({ + orgId: userOrgs.orgId, + role: userOrgs.role, + }) + .from(userOrgs) + .where(eq(userOrgs.userId, userId)); + + req.userOrgs = userOrganizations.map(org => org.orgId); + // req.userOrgRoles = userOrganizations.reduce((acc, org) => { + // acc[org.orgId] = org.role; + // return acc; + // }, {} as Record); + + next(); + } catch (error) { + next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error retrieving user organizations')); + } +} \ No newline at end of file diff --git a/server/routers/auth/index.ts b/server/routers/auth/index.ts index 4a3ace74..a44e6af6 100644 --- a/server/routers/auth/index.ts +++ b/server/routers/auth/index.ts @@ -4,3 +4,8 @@ export * from "./logout"; export * from "./verifyTotp"; export * from "./requestTotpSecret"; export * from "./disable2fa"; +export * from "./verifyOrgAccess"; +export * from "./getUserOrgs"; +export * from "./verifySiteAccess"; +export * from "./verifyResourceAccess"; +export * from "./verifyTargetAccess"; \ No newline at end of file diff --git a/server/routers/auth/verifyOrgAccess.ts b/server/routers/auth/verifyOrgAccess.ts new file mode 100644 index 00000000..f59e1ebc --- /dev/null +++ b/server/routers/auth/verifyOrgAccess.ts @@ -0,0 +1,36 @@ +import { Request, Response, NextFunction } from 'express'; +import { db } from '@server/db'; +import { userOrgs } from '@server/db/schema'; +import { and, eq } from 'drizzle-orm'; +import createHttpError from 'http-errors'; +import HttpCode from '@server/types/HttpCode'; +import { AuthenticatedRequest } from '@server/types/Auth'; + +export function verifyOrgAccess(req: Request, res: Response, next: NextFunction) { + const userId = req.user.id; // Assuming you have user information in the request + const orgId = parseInt(req.params.orgId); + + if (!userId) { + return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); + } + + if (isNaN(orgId)) { + return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID')); + } + + db.select() + .from(userOrgs) + .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))) + .then((result) => { + if (result.length === 0) { + next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization')); + } else { + // User has access, attach the user's role to the request for potential future use + req.userOrgRole = result[0].role; + next(); + } + }) + .catch((error) => { + next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access')); + }); +} \ No newline at end of file diff --git a/server/routers/auth/verifyResourceAccess.ts b/server/routers/auth/verifyResourceAccess.ts new file mode 100644 index 00000000..2efec9fd --- /dev/null +++ b/server/routers/auth/verifyResourceAccess.ts @@ -0,0 +1,54 @@ +import { Request, Response, NextFunction } from 'express'; +import { db } from '@server/db'; +import { resources, userOrgs } from '@server/db/schema'; +import { and, eq } from 'drizzle-orm'; +import createHttpError from 'http-errors'; +import HttpCode from '@server/types/HttpCode'; + +export async function verifyResourceAccess(req: Request, res: Response, next: NextFunction) { + const userId = req.user!.id; // Assuming you have user information in the request + const resourceId = req.params.resourceId; + + if (!userId) { + return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); + } + + const resource = await db.select() + .from(resources) + .where(eq(resources.resourceId, resourceId)) + .limit(1); + + if (resource.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `resource with ID ${resourceId} not found` + ) + ); + } + + if (!resource[0].orgId) { + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + `resource with ID ${resourceId} does not have an organization ID` + ) + ); + } + + db.select() + .from(userOrgs) + .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource[0].orgId))) + .then((result) => { + if (result.length === 0) { + next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization')); + } else { + // User has access, attach the user's role to the request for potential future use + req.userOrgRole = result[0].role; + next(); + } + }) + .catch((error) => { + next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access')); + }); +} \ No newline at end of file diff --git a/server/routers/auth/verifySiteAccess.ts b/server/routers/auth/verifySiteAccess.ts new file mode 100644 index 00000000..397b1491 --- /dev/null +++ b/server/routers/auth/verifySiteAccess.ts @@ -0,0 +1,58 @@ +import { Request, Response, NextFunction } from 'express'; +import { db } from '@server/db'; +import { sites, userOrgs } from '@server/db/schema'; +import { and, eq } from 'drizzle-orm'; +import createHttpError from 'http-errors'; +import HttpCode from '@server/types/HttpCode'; + +export async function verifySiteAccess(req: Request, res: Response, next: NextFunction) { + const userId = req.user!.id; // Assuming you have user information in the request + const siteId = parseInt(req.params.siteId); + + if (!userId) { + return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); + } + + if (isNaN(siteId)) { + return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID')); + } + + const site = await db.select() + .from(sites) + .where(eq(sites.siteId, siteId)) + .limit(1); + + if (site.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `Site with ID ${siteId} not found` + ) + ); + } + + if (!site[0].orgId) { + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + `Site with ID ${siteId} does not have an organization ID` + ) + ); + } + + db.select() + .from(userOrgs) + .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, site[0].orgId))) + .then((result) => { + if (result.length === 0) { + next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization')); + } else { + // User has access, attach the user's role to the request for potential future use + req.userOrgRole = result[0].role; + next(); + } + }) + .catch((error) => { + next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access')); + }); +} \ No newline at end of file diff --git a/server/routers/auth/verifyTargetAccess.ts b/server/routers/auth/verifyTargetAccess.ts new file mode 100644 index 00000000..6958db3e --- /dev/null +++ b/server/routers/auth/verifyTargetAccess.ts @@ -0,0 +1,83 @@ +import { Request, Response, NextFunction } from 'express'; +import { db } from '@server/db'; +import { resources, targets, userOrgs } from '@server/db/schema'; +import { and, eq } from 'drizzle-orm'; +import createHttpError from 'http-errors'; +import HttpCode from '@server/types/HttpCode'; + +export async function verifyTargetAccess(req: Request, res: Response, next: NextFunction) { + const userId = req.user!.id; // Assuming you have user information in the request + const targetId = parseInt(req.params.targetId); + + if (!userId) { + return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated')); + } + + if (isNaN(targetId)) { + return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID')); + } + + const target = await db.select() + .from(targets) + .where(eq(targets.targetId, targetId)) + .limit(1); + + if (target.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `target with ID ${targetId} not found` + ) + ); + } + + const resourceId = target[0].resourceId; + + if (resourceId) { + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + `target with ID ${targetId} does not have a resource ID` + ) + ); + } + + const resource = await db.select() + .from(resources) + .where(eq(resources.resourceId, resourceId!)) + .limit(1); + + if (resource.length === 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + `resource with ID ${resourceId} not found` + ) + ); + } + + if (!resource[0].orgId) { + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + `resource with ID ${resourceId} does not have an organization ID` + ) + ); + } + + db.select() + .from(userOrgs) + .where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource[0].orgId))) + .then((result) => { + if (result.length === 0) { + next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization')); + } else { + // User has access, attach the user's role to the request for potential future use + req.userOrgRole = result[0].role; + next(); + } + }) + .catch((error) => { + next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access')); + }); +} \ No newline at end of file diff --git a/server/routers/external.ts b/server/routers/external.ts index 78e033a0..1a7e7fbc 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -7,6 +7,7 @@ import * as user from "./user"; import * as auth from "./auth"; import HttpCode from "@server/types/HttpCode"; import { verifySessionMiddleware } from "@server/middlewares"; +import { verifyOrgAccess, getUserOrgs, verifySiteAccess, verifyResourceAccess, verifyTargetAccess } from "./auth"; // Root routes export const unauthenticated = Router(); @@ -19,30 +20,30 @@ unauthenticated.get("/", (_, res) => { export const authenticated = Router(); authenticated.use(verifySessionMiddleware); -authenticated.put("/org", org.createOrg); -authenticated.get("/orgs", org.listOrgs); -authenticated.get("/org/:orgId", org.getOrg); -authenticated.post("/org/:orgId", org.updateOrg); -authenticated.delete("/org/:orgId", org.deleteOrg); +authenticated.put("/org", getUserOrgs, org.createOrg); +authenticated.get("/orgs", getUserOrgs, org.listOrgs); // TODO we need to check the orgs here +authenticated.get("/org/:orgId", verifyOrgAccess, org.getOrg); +authenticated.post("/org/:orgId", verifyOrgAccess, org.updateOrg); +authenticated.delete("/org/:orgId", verifyOrgAccess, org.deleteOrg); -authenticated.put("/org/:orgId/site", site.createSite); -authenticated.get("/org/:orgId/sites", site.listSites); -authenticated.get("/site/:siteId", site.getSite); -authenticated.post("/site/:siteId", site.updateSite); -authenticated.delete("/site/:siteId", site.deleteSite); +authenticated.put("/org/:orgId/site", verifyOrgAccess, site.createSite); +authenticated.get("/org/:orgId/sites", verifyOrgAccess, site.listSites); +authenticated.get("/site/:siteId", verifySiteAccess, site.getSite); +authenticated.post("/site/:siteId", verifySiteAccess, site.updateSite); +authenticated.delete("/site/:siteId", verifySiteAccess, site.deleteSite); -authenticated.put("/org/:orgId/site/:siteId/resource", resource.createResource); +authenticated.put("/org/:orgId/site/:siteId/resource", verifyOrgAccess, resource.createResource); authenticated.get("/site/:siteId/resources", resource.listResources); -authenticated.get("/org/:orgId/resources", resource.listResources); -authenticated.get("/resource/:resourceId", resource.getResource); -authenticated.post("/resource/:resourceId", resource.updateResource); -authenticated.delete("/resource/:resourceId", resource.deleteResource); +authenticated.get("/org/:orgId/resources", verifyOrgAccess, resource.listResources); +authenticated.get("/resource/:resourceId", verifyResourceAccess, resource.getResource); +authenticated.post("/resource/:resourceId", verifyResourceAccess, resource.updateResource); +authenticated.delete("/resource/:resourceId", verifyResourceAccess, resource.deleteResource); -authenticated.put("/resource/:resourceId/target", target.createTarget); -authenticated.get("/resource/:resourceId/targets", target.listTargets); -authenticated.get("/target/:targetId", target.getTarget); -authenticated.post("/target/:targetId", target.updateTarget); -authenticated.delete("/target/:targetId", target.deleteTarget); +authenticated.put("/resource/:resourceId/target", verifyResourceAccess, target.createTarget); +authenticated.get("/resource/:resourceId/targets", verifyResourceAccess, target.listTargets); +authenticated.get("/target/:targetId", verifyTargetAccess, target.getTarget); +authenticated.post("/target/:targetId", verifyTargetAccess, target.updateTarget); +authenticated.delete("/target/:targetId", verifyTargetAccess, target.deleteTarget); authenticated.get("/users", user.listUsers); // authenticated.get("/org/:orgId/users", user.???); // TODO: Implement this diff --git a/server/routers/org/createOrg.ts b/server/routers/org/createOrg.ts index 048f2dc9..f681ce42 100644 --- a/server/routers/org/createOrg.ts +++ b/server/routers/org/createOrg.ts @@ -11,6 +11,8 @@ const createOrgSchema = z.object({ domain: z.string().min(1).max(255), }); +const MAX_ORGS = 5; + export async function createOrg(req: Request, res: Response, next: NextFunction): Promise { try { const parsedBody = createOrgSchema.safeParse(req.body); @@ -23,6 +25,16 @@ export async function createOrg(req: Request, res: Response, next: NextFunction) ); } + const userOrgIds = req.userOrgs; + if (userOrgIds && userOrgIds.length > MAX_ORGS) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + `Maximum number of organizations reached.` + ) + ); + } + const { name, domain } = parsedBody.data; const newOrg = await db.insert(orgs).values({ diff --git a/server/routers/org/listOrgs.ts b/server/routers/org/listOrgs.ts index 8ece3110..fda2bcde 100644 --- a/server/routers/org/listOrgs.ts +++ b/server/routers/org/listOrgs.ts @@ -5,7 +5,7 @@ import { orgs } from '@server/db/schema'; import response from "@server/utils/response"; import HttpCode from '@server/types/HttpCode'; import createHttpError from 'http-errors'; -import { sql } from 'drizzle-orm'; +import { sql, inArray } from 'drizzle-orm'; const listOrgsSchema = z.object({ limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)), @@ -26,15 +26,45 @@ export async function listOrgs(req: Request, res: Response, next: NextFunction): const { limit, offset } = parsedQuery.data; + // Use the userOrgs passed from the middleware + const userOrgIds = req.userOrgs; + + if (!userOrgIds || userOrgIds.length === 0) { + return res.status(HttpCode.OK).send( + response(res, { + data: { + organizations: [], + pagination: { + total: 0, + limit, + offset, + }, + }, + success: true, + error: false, + message: "No organizations found for the user", + status: HttpCode.OK, + }) + ); + } + const organizations = await db.select() .from(orgs) + .where(inArray(orgs.orgId, userOrgIds)) .limit(limit) .offset(offset); const totalCountResult = await db.select({ count: sql`cast(count(*) as integer)` }) - .from(orgs); + .from(orgs) + .where(inArray(orgs.orgId, userOrgIds)); const totalCount = totalCountResult[0].count; + // // Add the user's role for each organization + // const organizationsWithRoles = organizations.map(org => ({ + // ...org, + // userRole: req.userOrgRoles[org.orgId], + // })); + return res.status(HttpCode.OK).send( response(res, { data: { diff --git a/server/routers/site/createSite.ts b/server/routers/site/createSite.ts index 2fcbfaa6..c54d530b 100644 --- a/server/routers/site/createSite.ts +++ b/server/routers/site/createSite.ts @@ -5,6 +5,9 @@ import { sites } from '@server/db/schema'; import response from "@server/utils/response"; import HttpCode from '@server/types/HttpCode'; import createHttpError from 'http-errors'; +import fetch from 'node-fetch'; + +const API_BASE_URL = "http://localhost:3000"; const createSiteParamsSchema = z.object({ orgId: z.number().int().positive(), @@ -67,4 +70,29 @@ export async function createSite(req: Request, res: Response, next: NextFunction } catch (error) { next(error); } -} \ No newline at end of file +} + + +async function addPeer(peer: string) { + try { + const response = await fetch(`${API_BASE_URL}/peer`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(peer), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data: any = await response.json(); + console.log('Peer added successfully:', data.status); + return data; + } catch (error: any) { + console.error('Error adding peer:', error.message); + throw error; + } +} + diff --git a/server/routers/site/deleteSite.ts b/server/routers/site/deleteSite.ts index 16d26eae..29861719 100644 --- a/server/routers/site/deleteSite.ts +++ b/server/routers/site/deleteSite.ts @@ -7,6 +7,9 @@ import response from "@server/utils/response"; import HttpCode from '@server/types/HttpCode'; import createHttpError from 'http-errors'; + +const API_BASE_URL = "http://localhost:3000"; + // Define Zod schema for request parameters validation const deleteSiteSchema = z.object({ siteId: z.string().transform(Number).pipe(z.number().int().positive()) @@ -54,3 +57,23 @@ export async function deleteSite(req: Request, res: Response, next: NextFunction next(error); } } + + +async function removePeer(publicKey: string) { + try { + const response = await fetch(`${API_BASE_URL}/peer?public_key=${encodeURIComponent(publicKey)}`, { + method: 'DELETE', + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + console.log('Peer removed successfully:', data.status); + return data; + } catch (error: any) { + console.error('Error removing peer:', error.message); + throw error; + } +} diff --git a/server/types/Auth.ts b/server/types/Auth.ts index 7146d851..9a228ee0 100644 --- a/server/types/Auth.ts +++ b/server/types/Auth.ts @@ -5,4 +5,5 @@ import { Session } from "lucia"; export interface AuthenticatedRequest extends Request { user: User; session: Session; + userOrgRole?: string; }