diff --git a/backend/package-lock.json b/backend/package-lock.json index c743979e6..9ae4b0d08 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -50,6 +50,7 @@ "nanoid": "^3.3.6", "node-cache": "^5.1.2", "nodemailer": "^6.8.0", + "ora": "^5.4.1", "passport": "^0.6.0", "passport-github": "^1.1.0", "passport-gitlab2": "^5.0.0", @@ -6574,7 +6575,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -7010,6 +7010,39 @@ "node": ">=8" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -7274,7 +7307,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7380,6 +7412,28 @@ "node": ">=6" } }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", + "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -7430,7 +7484,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -7441,8 +7494,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/color-support": { "version": "1.1.3", @@ -7703,6 +7755,25 @@ "node": ">=0.10.0" } }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -8950,7 +9021,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -9400,6 +9470,14 @@ "node": ">=0.10.0" } }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -9468,6 +9546,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -10410,6 +10499,21 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", @@ -10600,7 +10704,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, "engines": { "node": ">=6" } @@ -13835,7 +13938,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -13863,6 +13965,28 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -15441,6 +15565,18 @@ "node": ">=10" } }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -16088,7 +16224,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -16705,6 +16840,14 @@ "makeerror": "1.0.12" } }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -22216,7 +22359,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -22549,6 +22691,27 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + } + } + }, "body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -22741,7 +22904,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -22811,6 +22973,19 @@ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", + "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==" + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -22848,7 +23023,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -22856,8 +23030,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "color-support": { "version": "1.1.3", @@ -23057,6 +23230,21 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "requires": { + "clone": "^1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==" + } + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -23984,8 +24172,7 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "has-proto": { "version": "1.0.1", @@ -24308,6 +24495,11 @@ "is-extglob": "^2.1.1" } }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -24349,6 +24541,11 @@ "which-typed-array": "^1.1.11" } }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -25091,6 +25288,15 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, "long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", @@ -25236,8 +25442,7 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "minimatch": { "version": "3.1.2", @@ -27519,7 +27724,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -27538,6 +27742,22 @@ "type-check": "^0.4.0" } }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + } + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -28751,6 +28971,15 @@ "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -29249,7 +29478,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -29698,6 +29926,14 @@ "makeerror": "1.0.12" } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "requires": { + "defaults": "^1.0.3" + } + }, "webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index 9768762e8..0d681a99c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -41,13 +41,14 @@ "nanoid": "^3.3.6", "node-cache": "^5.1.2", "nodemailer": "^6.8.0", + "ora": "^5.4.1", "passport": "^0.6.0", "passport-github": "^1.1.0", "passport-gitlab2": "^5.0.0", "passport-google-oauth20": "^2.0.0", + "pg": "^8.11.3", "pino": "^8.16.1", "pino-http": "^8.5.1", - "pg": "^8.11.3", "posthog-node": "^2.6.0", "probot": "^12.3.1", "query-string": "^7.1.3", diff --git a/backend/src/bootstrap.ts b/backend/src/bootstrap.ts new file mode 100644 index 000000000..a1ea148c2 --- /dev/null +++ b/backend/src/bootstrap.ts @@ -0,0 +1,43 @@ +import ora from "ora"; +import nodemailer from "nodemailer"; +import { getSmtpHost, getSmtpPort } from "./config"; +import { logger } from "./utils/logging"; +import mongoose from "mongoose"; +import { redisClient } from "./services/RedisService"; + +type BootstrapOpt = { + transporter: nodemailer.Transporter; +}; + +export const bootstrap = async ({ transporter }: BootstrapOpt) => { + const spinner = ora().start(); + spinner.info("Checking configurations..."); + spinner.info("Testing smtp connection"); + + await transporter + .verify() + .then(async () => { + spinner.succeed("SMTP successfully connected"); + }) + .catch(async (err) => { + spinner.fail(`SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()}`); + logger.error(err); + }); + + spinner.info("Testing mongodb connection"); + if (mongoose.connection.readyState !== mongoose.ConnectionStates.connected) { + spinner.fail("Mongo DB - Failed to connect"); + } else { + spinner.succeed("Mongodb successfully connected"); + } + + spinner.info("Testing redis connection"); + const redisPing = await redisClient?.ping(); + if (!redisPing) { + spinner.fail("Redis - Failed to connect"); + } else { + spinner.succeed("Redis successfully connected"); + } + + spinner.stop(); +}; diff --git a/backend/src/config/serverConfig.ts b/backend/src/config/serverConfig.ts new file mode 100644 index 000000000..7f1867d15 --- /dev/null +++ b/backend/src/config/serverConfig.ts @@ -0,0 +1,24 @@ +import { IServerConfig, ServerConfig } from "../models/serverConfig"; + +let serverConfig: IServerConfig; + +export const serverConfigInit = async () => { + const cfg = await ServerConfig.findOne({}); + if (!cfg) { + const cfg = new ServerConfig(); + await cfg.save(); + serverConfig = cfg; + } else { + serverConfig = cfg; + } + return serverConfig; +}; + +export const getServerConfig = () => serverConfig; + +export const updateServerConfig = async (data: Partial) => { + const cfg = await ServerConfig.findByIdAndUpdate(serverConfig._id, data, { new: true }); + if (!cfg) throw new Error("Failed to update server config"); + serverConfig = cfg; + return serverConfig; +}; diff --git a/backend/src/controllers/v1/adminController.ts b/backend/src/controllers/v1/adminController.ts new file mode 100644 index 000000000..ded9549ea --- /dev/null +++ b/backend/src/controllers/v1/adminController.ts @@ -0,0 +1,87 @@ +import { Request, Response } from "express"; +import { getHttpsEnabled } from "../../config"; +import { getServerConfig, updateServerConfig as setServerConfig } from "../../config/serverConfig"; +import { initializeDefaultOrg, issueAuthTokens } from "../../helpers"; +import { validateRequest } from "../../helpers/validation"; +import { User } from "../../models"; +import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; +import * as reqValidator from "../../validation/admin"; + +export const getServerConfigInfo = (_req: Request, res: Response) => { + const config = getServerConfig(); + return res.send({ config }); +}; + +export const updateServerConfig = async (req: Request, res: Response) => { + const { + body: { allowSignUp } + } = await validateRequest(reqValidator.UpdateServerConfigV1, req); + const config = await setServerConfig({ allowSignUp }); + return res.send({ config }); +}; + +export const adminSignUp = async (req: Request, res: Response) => { + const cfg = getServerConfig(); + if (cfg.initialized) throw UnauthorizedRequestError({ message: "Admin has been created" }); + const { + body: { + email, + publicKey, + salt, + lastName, + verifier, + firstName, + protectedKey, + protectedKeyIV, + protectedKeyTag, + encryptedPrivateKey, + encryptedPrivateKeyIV, + encryptedPrivateKeyTag + } + } = await validateRequest(reqValidator.SignupV1, req); + let user = await User.findOne({ email }); + if (user) throw BadRequestError({ message: "User already exist" }); + user = new User({ + email, + firstName, + lastName, + encryptionVersion: 2, + protectedKey, + protectedKeyIV, + protectedKeyTag, + publicKey, + encryptedPrivateKey, + iv: encryptedPrivateKeyIV, + tag: encryptedPrivateKeyTag, + salt, + verifier, + superAdmin: true + }); + await user.save(); + await initializeDefaultOrg({ organizationName: "Admin Org", user }); + + await setServerConfig({ initialized: true }); + + // issue tokens + const tokens = await issueAuthTokens({ + userId: user._id, + ip: req.realIP, + userAgent: req.headers["user-agent"] ?? "" + }); + + const token = tokens.token; + + // store (refresh) token in httpOnly cookie + res.cookie("jid", tokens.refreshToken, { + httpOnly: true, + path: "/", + sameSite: "strict", + secure: await getHttpsEnabled() + }); + + return res.status(200).send({ + message: "Successfully set up admin account", + user, + token + }); +}; diff --git a/backend/src/controllers/v1/index.ts b/backend/src/controllers/v1/index.ts index b32debeca..fe171ae86 100644 --- a/backend/src/controllers/v1/index.ts +++ b/backend/src/controllers/v1/index.ts @@ -16,6 +16,8 @@ import * as workspaceController from "./workspaceController"; import * as secretScanningController from "./secretScanningController"; import * as webhookController from "./webhookController"; import * as secretImpsController from "./secretImpsController"; +import * as adminController from "./adminController"; + export { authController, botController, @@ -34,5 +36,6 @@ export { workspaceController, secretScanningController, webhookController, - secretImpsController + secretImpsController, + adminController }; diff --git a/backend/src/index.ts b/backend/src/index.ts index df3abb0e3..e27e2fe33 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -35,6 +35,7 @@ import { import { apiKeyData as v3apiKeyDataRouter } from "./ee/routes/v3"; import { serviceTokenData as v3ServiceTokenDataRouter } from "./ee/routes/v3"; import { + admin as v1AdminRouter, auth as v1AuthRouter, bot as v1BotRouter, integrationAuth as v1IntegrationAuthRouter, @@ -94,6 +95,7 @@ import { syncSecretsToThirdPartyServices } from "./queues/integrations/syncSecre import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPushEvent"; const SmeeClient = require("smee-client"); // eslint-disable-line import path from "path"; +import { serverConfigInit } from "./config/serverConfig"; let handler: null | any = null; @@ -101,6 +103,7 @@ const main = async () => { const port = await getPort(); await setup(); + const serverCfg = await serverConfigInit(); await EELicenseService.initGlobalFeatureSet(); @@ -203,6 +206,7 @@ const main = async () => { // v1 routes app.use("/api/v1/signup", v1SignupRouter); app.use("/api/v1/auth", v1AuthRouter); + app.use("/api/v1/admin", v1AdminRouter); app.use("/api/v1/bot", v1BotRouter); app.use("/api/v1/user", v1UserRouter); app.use("/api/v1/user-action", v1UserActionRouter); @@ -271,7 +275,22 @@ const main = async () => { app.use(requestErrorHandler); const server = app.listen(port, async () => { - logger.info(`Server started listening at port ${port}`); + if (!serverCfg.initialized) { + logger.info(`Welcome to Infisical + +Create your Infisical administrator account at: +http://localhost:${port}/admin/signup +`); + } else { + logger.info(`Welcome back! + +To access Infisical Administrator Panel open +http://localhost:${port}/admin + +To access Infisical server +http://localhost:${port} +`); + } }); // await createTestUserForDevelopment(); diff --git a/backend/src/middleware/index.ts b/backend/src/middleware/index.ts index ded40da81..347519b6e 100644 --- a/backend/src/middleware/index.ts +++ b/backend/src/middleware/index.ts @@ -7,17 +7,21 @@ import requireSecretAuth from "./requireSecretAuth"; import requireSecretsAuth from "./requireSecretsAuth"; import requireBlindIndicesEnabled from "./requireBlindIndicesEnabled"; import requireE2EEOff from "./requireE2EEOff"; +import { requireSuperAdminAccess } from "./requireSuperAdminAccess"; import validateRequest from "./validateRequest"; +import { disableSignUpByServerCfg } from "./serverAdmin"; export { - requireAuth, - requireMfaAuth, - requireSignupAuth, - requireWorkspaceAuth, - requireServiceTokenAuth, - requireSecretAuth, - requireSecretsAuth, - requireBlindIndicesEnabled, - requireE2EEOff, - validateRequest, + requireAuth, + requireMfaAuth, + requireSignupAuth, + requireWorkspaceAuth, + requireServiceTokenAuth, + requireSecretAuth, + requireSecretsAuth, + requireBlindIndicesEnabled, + requireE2EEOff, + validateRequest, + requireSuperAdminAccess, + disableSignUpByServerCfg }; diff --git a/backend/src/middleware/requireSuperAdminAccess.ts b/backend/src/middleware/requireSuperAdminAccess.ts new file mode 100644 index 000000000..4445433ff --- /dev/null +++ b/backend/src/middleware/requireSuperAdminAccess.ts @@ -0,0 +1,8 @@ +import { NextFunction, Request, Response } from "express"; +import { UnauthorizedRequestError } from "../utils/errors"; + +export const requireSuperAdminAccess = (req: Request, _res: Response, next: NextFunction) => { + const isSuperAdmin = req.user.superAdmin; + if (!isSuperAdmin) throw UnauthorizedRequestError({ message: "Requires superadmin access" }); + return next(); +}; diff --git a/backend/src/middleware/serverAdmin.ts b/backend/src/middleware/serverAdmin.ts new file mode 100644 index 000000000..d57dbf714 --- /dev/null +++ b/backend/src/middleware/serverAdmin.ts @@ -0,0 +1,9 @@ +import { NextFunction, Request, Response } from "express"; +import { getServerConfig } from "../config/serverConfig"; +import { BadRequestError } from "../utils/errors"; + +export const disableSignUpByServerCfg = (_req: Request, _res: Response, next: NextFunction) => { + const cfg = getServerConfig(); + if (!cfg.allowSignUp) throw BadRequestError({ message: "Signup are disabled" }); + return next(); +}; diff --git a/backend/src/models/serverConfig.ts b/backend/src/models/serverConfig.ts new file mode 100644 index 000000000..13e469bd5 --- /dev/null +++ b/backend/src/models/serverConfig.ts @@ -0,0 +1,25 @@ +import { Schema, Types, model } from "mongoose"; + +export interface IServerConfig { + _id: Types.ObjectId; + initialized: boolean; + allowSignUp: boolean; +} + +const serverConfigSchema = new Schema( + { + initialized: { + type: Boolean, + default: false + }, + allowSignUp: { + type: Boolean, + default: true + } + }, + { + timestamps: true + } +); + +export const ServerConfig = model("ServerConfig", serverConfigSchema); diff --git a/backend/src/models/user.ts b/backend/src/models/user.ts index 911776a63..a3d73b8a2 100644 --- a/backend/src/models/user.ts +++ b/backend/src/models/user.ts @@ -1,125 +1,141 @@ import { Document, Schema, Types, model } from "mongoose"; export enum AuthMethod { - EMAIL = "email", - GOOGLE = "google", - GITHUB = "github", - GITLAB = "gitlab", - OKTA_SAML = "okta-saml", - AZURE_SAML = "azure-saml", - JUMPCLOUD_SAML = "jumpcloud-saml", + EMAIL = "email", + GOOGLE = "google", + GITHUB = "github", + GITLAB = "gitlab", + OKTA_SAML = "okta-saml", + AZURE_SAML = "azure-saml", + JUMPCLOUD_SAML = "jumpcloud-saml" } export interface IUser extends Document { - _id: Types.ObjectId; - authProvider?: AuthMethod; - authMethods: AuthMethod[]; - email: string; - firstName?: string; - lastName?: string; - encryptionVersion: number; - protectedKey: string; - protectedKeyIV: string; - protectedKeyTag: string; - publicKey?: string; - encryptedPrivateKey?: string; - iv?: string; - tag?: string; - salt?: string; - verifier?: string; - isMfaEnabled: boolean; - mfaMethods: boolean; - devices: { - ip: string; - userAgent: string; - }[]; + _id: Types.ObjectId; + authProvider?: AuthMethod; + authMethods: AuthMethod[]; + email: string; + superAdmin?: boolean; + firstName?: string; + lastName?: string; + encryptionVersion: number; + protectedKey: string; + protectedKeyIV: string; + protectedKeyTag: string; + publicKey?: string; + encryptedPrivateKey?: string; + iv?: string; + tag?: string; + salt?: string; + verifier?: string; + isMfaEnabled: boolean; + mfaMethods: boolean; + devices: { + ip: string; + userAgent: string; + }[]; } const userSchema = new Schema( - { - authProvider: { // TODO field: deprecate - type: String, - enum: AuthMethod, - }, - authMethods: { - type: [{ - type: String, - enum: AuthMethod, - }], - default: [AuthMethod.EMAIL], - required: true - }, - email: { - type: String, - required: true, - unique: true, - }, - firstName: { - type: String, - }, - lastName: { - type: String, - }, - encryptionVersion: { - type: Number, - select: false, - default: 1, // to resolve backward-compatibility issues - }, - protectedKey: { // introduced as part of encryption version 2 - type: String, - select: false, - }, - protectedKeyIV: { // introduced as part of encryption version 2 - type: String, - select: false, - }, - protectedKeyTag: { // introduced as part of encryption version 2 - type: String, - select: false, - }, - publicKey: { - type: String, - select: false, - }, - encryptedPrivateKey: { - type: String, - select: false, - }, - iv: { // iv of [encryptedPrivateKey] - type: String, - select: false, - }, - tag: { // tag of [encryptedPrivateKey] - type: String, - select: false, - }, - salt: { - type: String, - select: false, - }, - verifier: { - type: String, - select: false, - }, - isMfaEnabled: { - type: Boolean, - default: false, - }, - mfaMethods: [{ - type: String, - }], - devices: { - type: [{ - ip: String, - userAgent: String, - }], - default: [], - select: false, - }, - }, - { - timestamps: true, - } + { + authProvider: { + // TODO field: deprecate + type: String, + enum: AuthMethod + }, + authMethods: { + type: [ + { + type: String, + enum: AuthMethod + } + ], + default: [AuthMethod.EMAIL], + required: true + }, + email: { + type: String, + required: true, + unique: true + }, + firstName: { + type: String + }, + lastName: { + type: String + }, + encryptionVersion: { + type: Number, + select: false, + default: 1 // to resolve backward-compatibility issues + }, + protectedKey: { + // introduced as part of encryption version 2 + type: String, + select: false + }, + protectedKeyIV: { + // introduced as part of encryption version 2 + type: String, + select: false + }, + protectedKeyTag: { + // introduced as part of encryption version 2 + type: String, + select: false + }, + publicKey: { + type: String, + select: false + }, + encryptedPrivateKey: { + type: String, + select: false + }, + superAdmin: { + type: Boolean + }, + iv: { + // iv of [encryptedPrivateKey] + type: String, + select: false + }, + tag: { + // tag of [encryptedPrivateKey] + type: String, + select: false + }, + salt: { + type: String, + select: false + }, + verifier: { + type: String, + select: false + }, + isMfaEnabled: { + type: Boolean, + default: false + }, + mfaMethods: [ + { + type: String + } + ], + devices: { + type: [ + { + ip: String, + userAgent: String + } + ], + default: [], + select: false + } + }, + { + timestamps: true + } ); -export const User = model("User", userSchema); \ No newline at end of file +export const User = model("User", userSchema); diff --git a/backend/src/routes/v1/admin.ts b/backend/src/routes/v1/admin.ts new file mode 100644 index 000000000..5e7989ce0 --- /dev/null +++ b/backend/src/routes/v1/admin.ts @@ -0,0 +1,20 @@ +import express from "express"; +import { adminController } from "../../controllers/v1"; +const router = express.Router(); +import { requireAuth, requireSuperAdminAccess } from "../../middleware"; +import { AuthMode } from "../../variables"; + +router.get("/config", adminController.getServerConfigInfo); + +router.post("/signup", adminController.adminSignUp); + +router.patch( + "/config", + requireAuth({ + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY] + }), + requireSuperAdminAccess, + adminController.updateServerConfig +); + +export default router; diff --git a/backend/src/routes/v1/index.ts b/backend/src/routes/v1/index.ts index 2d7bf27ef..c19ead569 100644 --- a/backend/src/routes/v1/index.ts +++ b/backend/src/routes/v1/index.ts @@ -18,6 +18,7 @@ import integrationAuth from "./integrationAuth"; import secretsFolder from "./secretsFolder"; import webhooks from "./webhook"; import secretImps from "./secretImps"; +import admin from "./admin"; export { signup, @@ -39,5 +40,6 @@ export { secretsFolder, webhooks, secretImps, - sso + sso, + admin }; diff --git a/backend/src/routes/v1/signup.ts b/backend/src/routes/v1/signup.ts index f3b5d3adf..e7b92f643 100644 --- a/backend/src/routes/v1/signup.ts +++ b/backend/src/routes/v1/signup.ts @@ -2,18 +2,21 @@ import express from "express"; const router = express.Router(); import { signupController } from "../../controllers/v1"; import { authLimiter } from "../../helpers/rateLimiter"; +import { disableSignUpByServerCfg } from "../../middleware"; // TODO: consider moving to users/v3/signup router.post( // TODO endpoint: consider moving to v3/users/signup/mail "/email/signup", + disableSignUpByServerCfg, authLimiter, signupController.beginEmailSignup ); router.post( "/email/verify", // TODO endpoint: consider moving to v3/users/signup/verify + disableSignUpByServerCfg, authLimiter, signupController.verifyEmailSignup ); diff --git a/backend/src/routes/v2/signup.ts b/backend/src/routes/v2/signup.ts index eb376f56c..8a404cfc2 100644 --- a/backend/src/routes/v2/signup.ts +++ b/backend/src/routes/v2/signup.ts @@ -1,49 +1,51 @@ import express from "express"; const router = express.Router(); import { body } from "express-validator"; -import { requireSignupAuth, validateRequest } from "../../middleware"; +import { disableSignUpByServerCfg, requireSignupAuth, validateRequest } from "../../middleware"; import { signupController } from "../../controllers/v2"; import { authLimiter } from "../../helpers/rateLimiter"; router.post( - "/complete-account/signup", // TODO endpoint: deprecate (moved to v3/signup/complete/account-signup) - authLimiter, - requireSignupAuth, - body("email").exists().isString().trim().notEmpty().isEmail(), - body("firstName").exists().isString().trim().notEmpty(), - body("lastName").exists().isString().trim().notEmpty(), - body("protectedKey").exists().isString().trim().notEmpty(), - body("protectedKeyIV").exists().isString().trim().notEmpty(), - body("protectedKeyTag").exists().isString().trim().notEmpty(), - body("publicKey").exists().isString().trim().notEmpty(), - body("encryptedPrivateKey").exists().isString().trim().notEmpty(), - body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(), - body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(), - body("salt").exists().isString().trim().notEmpty(), - body("verifier").exists().isString().trim().notEmpty(), - body("organizationName").exists().isString().trim().notEmpty(), - validateRequest, - signupController.completeAccountSignup + "/complete-account/signup", // TODO endpoint: deprecate (moved to v3/signup/complete/account-signup), + disableSignUpByServerCfg, + authLimiter, + requireSignupAuth, + body("email").exists().isString().trim().notEmpty().isEmail(), + body("firstName").exists().isString().trim().notEmpty(), + body("lastName").exists().isString().trim().notEmpty(), + body("protectedKey").exists().isString().trim().notEmpty(), + body("protectedKeyIV").exists().isString().trim().notEmpty(), + body("protectedKeyTag").exists().isString().trim().notEmpty(), + body("publicKey").exists().isString().trim().notEmpty(), + body("encryptedPrivateKey").exists().isString().trim().notEmpty(), + body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(), + body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(), + body("salt").exists().isString().trim().notEmpty(), + body("verifier").exists().isString().trim().notEmpty(), + body("organizationName").exists().isString().trim().notEmpty(), + validateRequest, + signupController.completeAccountSignup ); router.post( - "/complete-account/invite", // TODO: consider moving to v3/users/new/complete-account/invite - authLimiter, - requireSignupAuth, - body("email").exists().isString().trim().notEmpty().isEmail(), - body("firstName").exists().isString().trim().notEmpty(), - body("lastName").exists().isString().trim().notEmpty(), - body("protectedKey").exists().isString().trim().notEmpty(), - body("protectedKeyIV").exists().isString().trim().notEmpty(), - body("protectedKeyTag").exists().isString().trim().notEmpty(), - body("publicKey").exists().trim().notEmpty(), - body("encryptedPrivateKey").exists().isString().trim().notEmpty(), - body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(), - body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(), - body("salt").exists().isString().trim().notEmpty(), - body("verifier").exists().isString().trim().notEmpty(), - validateRequest, - signupController.completeAccountInvite + "/complete-account/invite", // TODO: consider moving to v3/users/new/complete-account/invite + disableSignUpByServerCfg, + authLimiter, + requireSignupAuth, + body("email").exists().isString().trim().notEmpty().isEmail(), + body("firstName").exists().isString().trim().notEmpty(), + body("lastName").exists().isString().trim().notEmpty(), + body("protectedKey").exists().isString().trim().notEmpty(), + body("protectedKeyIV").exists().isString().trim().notEmpty(), + body("protectedKeyTag").exists().isString().trim().notEmpty(), + body("publicKey").exists().trim().notEmpty(), + body("encryptedPrivateKey").exists().isString().trim().notEmpty(), + body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(), + body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(), + body("salt").exists().isString().trim().notEmpty(), + body("verifier").exists().isString().trim().notEmpty(), + validateRequest, + signupController.completeAccountInvite ); -export default router; \ No newline at end of file +export default router; diff --git a/backend/src/routes/v3/signup.ts b/backend/src/routes/v3/signup.ts index 241f5d04c..e2b13a0a2 100644 --- a/backend/src/routes/v3/signup.ts +++ b/backend/src/routes/v3/signup.ts @@ -2,10 +2,11 @@ import express from "express"; const router = express.Router(); import { signupController } from "../../controllers/v3"; import { authLimiter } from "../../helpers/rateLimiter"; -import { validateRequest } from "../../middleware"; +import { disableSignUpByServerCfg, validateRequest } from "../../middleware"; router.post( "/complete-account/signup", // TODO: consider moving endpoint to v3/users/new/complete-account/signup + disableSignUpByServerCfg, authLimiter, validateRequest, signupController.completeAccountSignup diff --git a/backend/src/services/smtp.ts b/backend/src/services/smtp.ts index cf688f801..027b81295 100644 --- a/backend/src/services/smtp.ts +++ b/backend/src/services/smtp.ts @@ -8,30 +8,28 @@ import { SMTP_HOST_ZOHOMAIL } from "../variables"; import SMTPConnection from "nodemailer/lib/smtp-connection"; -import * as Sentry from "@sentry/node"; import { getSmtpHost, getSmtpPassword, getSmtpPort, getSmtpSecure, - getSmtpUsername, + getSmtpUsername } from "../config"; -import { logger } from "../utils/logging"; export const initSmtp = async () => { const mailOpts: SMTPConnection.Options = { host: await getSmtpHost(), - port: await getSmtpPort(), + port: await getSmtpPort() }; if ((await getSmtpUsername()) && (await getSmtpPassword())) { mailOpts.auth = { user: await getSmtpUsername(), - pass: await getSmtpPassword(), + pass: await getSmtpPassword() }; } - if ((await getSmtpSecure()) ? (await getSmtpSecure()) : false) { + if ((await getSmtpSecure()) ? await getSmtpSecure() : false) { switch (await getSmtpHost()) { case SMTP_HOST_SENDGRID: mailOpts.requireTLS = true; @@ -39,38 +37,38 @@ export const initSmtp = async () => { case SMTP_HOST_MAILGUN: mailOpts.requireTLS = true; mailOpts.tls = { - ciphers: "TLSv1.2", - } + ciphers: "TLSv1.2" + }; break; case SMTP_HOST_SOCKETLABS: mailOpts.requireTLS = true; mailOpts.tls = { - ciphers: "TLSv1.2", - } + ciphers: "TLSv1.2" + }; break; case SMTP_HOST_ZOHOMAIL: mailOpts.requireTLS = true; mailOpts.tls = { - ciphers: "TLSv1.2", - } + ciphers: "TLSv1.2" + }; break; case SMTP_HOST_GMAIL: mailOpts.requireTLS = true; mailOpts.tls = { - ciphers: "TLSv1.2", - } + ciphers: "TLSv1.2" + }; break; case SMTP_HOST_OFFICE365: mailOpts.requireTLS = true; mailOpts.tls = { ciphers: "TLSv1.2" - } + }; break; default: if ((await getSmtpHost()).includes("amazonaws.com")) { mailOpts.tls = { - ciphers: "TLSv1.2", - } + ciphers: "TLSv1.2" + }; } else { mailOpts.secure = true; } @@ -79,20 +77,6 @@ export const initSmtp = async () => { } const transporter = nodemailer.createTransport(mailOpts); - transporter - .verify() - .then(async () => { - Sentry.setUser(null); - Sentry.captureMessage("SMTP - Successfully connected"); - logger.info("SMTP - Successfully connected"); - }) - .catch(async (err) => { - Sentry.setUser(null); - Sentry.captureException( - `SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()} \n\t${err}` - ); - logger.error(err, `SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()}`); - }); return transporter; }; diff --git a/backend/src/utils/setup/index.ts b/backend/src/utils/setup/index.ts index b9fe39992..de087e684 100644 --- a/backend/src/utils/setup/index.ts +++ b/backend/src/utils/setup/index.ts @@ -33,6 +33,7 @@ import { initializeSamlStrategy } from "../authn/passport"; import { logger } from "../logging"; +import { bootstrap } from "../../bootstrap"; /** * Prepare Infisical upon startup. This includes tasks like: @@ -55,14 +56,15 @@ export const setup = async () => { await TelemetryService.logTelemetryMessage(); // initializing SMTP configuration - setTransporter(await initSmtp()); + const transporter = await initSmtp(); + setTransporter(transporter); // initializing global feature set await EELicenseService.initGlobalFeatureSet(); // initializing auth strategies await initializeGoogleStrategy(); - await initializeGitHubStrategy() + await initializeGitHubStrategy(); await initializeGitLabStrategy(); await initializeSamlStrategy(); @@ -73,6 +75,7 @@ export const setup = async () => { // initializing the database connection await DatabaseService.initDatabase(await getMongoURL()); + await bootstrap({ transporter }); /** * NOTE: the order in this setup function is critical. @@ -92,7 +95,7 @@ export const setup = async () => { await backfillTrustedIps(); await backfillUserAuthMethods(); // await backfillPermission(); - await migrateRoleFromOwnerToAdmin() + await migrateRoleFromOwnerToAdmin(); // re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY // to base64 256-bit ROOT_ENCRYPTION_KEY diff --git a/backend/src/validation/admin.ts b/backend/src/validation/admin.ts new file mode 100644 index 000000000..aee6c88cc --- /dev/null +++ b/backend/src/validation/admin.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; + +export const UpdateServerConfigV1 = z.object({ + body: z.object({ + allowSignUp: z.boolean().optional() + }) +}); + +export const SignupV1 = z.object({ + body: z.object({ + email: z.string().email().trim(), + firstName: z.string().trim(), + lastName: z.string().trim().optional(), + protectedKey: z.string().trim(), + protectedKeyIV: z.string().trim(), + protectedKeyTag: z.string().trim(), + publicKey: z.string().trim(), + encryptedPrivateKey: z.string().trim(), + encryptedPrivateKeyIV: z.string().trim(), + encryptedPrivateKeyTag: z.string().trim(), + salt: z.string().trim(), + verifier: z.string().trim() + }) +});