diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index 81f4722c6..df27633c6 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -136,422 +136,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -3974,136 +3558,6 @@ "tslib": "^2.4.0" } }, - "@esbuild/aix-ppc64": { - "version": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "optional": true - }, - "@esbuild/android-arm": { - "version": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "optional": true - }, - "@esbuild/android-arm64": { - "version": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "optional": true - }, - "@esbuild/android-x64": { - "version": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "optional": true - }, - "@esbuild/darwin-arm64": { - "version": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "optional": true - }, - "@esbuild/darwin-x64": { - "version": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "optional": true - }, - "@esbuild/freebsd-arm64": { - "version": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "optional": true - }, - "@esbuild/freebsd-x64": { - "version": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "optional": true - }, - "@esbuild/linux-arm": { - "version": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "optional": true - }, - "@esbuild/linux-arm64": { - "version": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "optional": true - }, - "@esbuild/linux-ia32": { - "version": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "optional": true - }, - "@esbuild/linux-loong64": { - "version": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "optional": true - }, - "@esbuild/linux-mips64el": { - "version": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "optional": true - }, - "@esbuild/linux-ppc64": { - "version": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "optional": true - }, - "@esbuild/linux-riscv64": { - "version": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "optional": true - }, - "@esbuild/linux-s390x": { - "version": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "optional": true - }, - "@esbuild/linux-x64": { - "version": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "optional": true - }, - "@esbuild/netbsd-arm64": { - "version": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "optional": true - }, - "@esbuild/netbsd-x64": { - "version": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "optional": true - }, - "@esbuild/openbsd-arm64": { - "version": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "optional": true - }, - "@esbuild/openbsd-x64": { - "version": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "optional": true - }, - "@esbuild/openharmony-arm64": { - "version": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "optional": true - }, - "@esbuild/sunos-x64": { - "version": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "optional": true - }, - "@esbuild/win32-arm64": { - "version": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "optional": true - }, - "@esbuild/win32-ia32": { - "version": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "optional": true - }, - "@esbuild/win32-x64": { - "version": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "optional": true - }, "@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", diff --git a/dashboard/src/Index.vue b/dashboard/src/Index.vue index 4deabdc63..5bb88212e 100644 --- a/dashboard/src/Index.vue +++ b/dashboard/src/Index.vue @@ -459,8 +459,8 @@ onMounted(async () => { ready.value = true; - // when done, redirect the user to setup 2fa if it is mandatory and neither totp (twoFactorAuthenticationEnabled) nor passkey is setup - if (config.value.mandatory2FA && !profile.value.twoFactorAuthenticationEnabled) { + // when done, redirect the user to setup 2fa if it is mandatory and neither totp (totpEnabled) nor passkey is setup + if (config.value.mandatory2FA && !profile.value.totpEnabled) { const [error, result] = await profileModel.getPasskey(); if (error) return window.cloudron.onError(error); diff --git a/dashboard/src/models/UsersModel.js b/dashboard/src/models/UsersModel.js index 3068c70b7..4afc76340 100644 --- a/dashboard/src/models/UsersModel.js +++ b/dashboard/src/models/UsersModel.js @@ -199,7 +199,7 @@ function create() { if (result.status !== 200) return [result]; return [null, result.body.inviteLink]; }, - async disableTwoFactorAuthentication(id) { + async disableTotp(id) { let result; try { result = await fetcher.post(`${API_ORIGIN}/api/v1/users/${id}/totp_disable`, {}, { access_token: accessToken }); diff --git a/dashboard/src/views/ProfileView.vue b/dashboard/src/views/ProfileView.vue index 05b905200..0d8eed623 100644 --- a/dashboard/src/views/ProfileView.vue +++ b/dashboard/src/views/ProfileView.vue @@ -102,7 +102,7 @@ async function onRevokeAllWebAndCliTokens() { const userPasskey = ref(null); const enableTwoFADialog = useTemplateRef('enableTwoFADialog'); -const has2FA = computed(() => profile.value.twoFactorAuthenticationEnabled || !!userPasskey.value); +const has2FA = computed(() => profile.value.totpEnabled || !!userPasskey.value); async function loadPasskey() { const [error, result] = await profileModel.getPasskey(); @@ -164,7 +164,7 @@ onMounted(async () => { await loadPasskey(); // check if we should show the 2fa setup - if (config.value.mandatory2FA && !profile.value.twoFactorAuthenticationEnabled && !userPasskey.value) { + if (config.value.mandatory2FA && !profile.value.totpEnabled && !userPasskey.value) { onOpenTwoFASetupDialog(); } }); @@ -232,7 +232,7 @@ onMounted(async () => {
{{ $t('profile.twoFactorAuth.disabled') }}
-
{{ $t('profile.twoFactorAuth.totpEnabled') }}
+
{{ $t('profile.twoFactorAuth.totpEnabled') }}
{{ $t('profile.twoFactorAuth.passkeyEnabled') }}
diff --git a/dashboard/src/views/UsersView.vue b/dashboard/src/views/UsersView.vue index 3ca79ebe8..f771b6514 100644 --- a/dashboard/src/views/UsersView.vue +++ b/dashboard/src/views/UsersView.vue @@ -71,7 +71,7 @@ function createUserActionMenu(user) { }, { icon: 'fa-solid fa-qrcode', label: t('users.passwordResetDialog.reset2FAAction'), - visible: user.twoFactorAuthenticationEnabled && !(user.source && external2FA), + visible: user.totpEnabled && !(user.source && external2FA), disabled: !canEdit(user), action: on2FAReset.bind(null, user), }, { @@ -212,10 +212,10 @@ async function on2FAReset(user) { if (!yes) return; - const [error] = await usersModel.disableTwoFactorAuthentication(user.id); + const [error] = await usersModel.disableTotp(user.id); if (error) return console.error(error); - user.twoFactorAuthenticationEnabled = false; + user.totpEnabled = false; } function onEditOrAddUser(user = null) { diff --git a/migrations/20260316000000-users-rename-twofactor-to-totp.js b/migrations/20260316000000-users-rename-twofactor-to-totp.js new file mode 100644 index 000000000..6940ed0bd --- /dev/null +++ b/migrations/20260316000000-users-rename-twofactor-to-totp.js @@ -0,0 +1,15 @@ +'use strict'; + +exports.up = function(db, callback) { + db.runSql('ALTER TABLE users CHANGE COLUMN twoFactorAuthenticationEnabled totpEnabled BOOLEAN DEFAULT false, CHANGE COLUMN twoFactorAuthenticationSecret totpSecret VARCHAR(128) DEFAULT ""', function (error) { + if (error) console.error(error); + callback(error); + }); +}; + +exports.down = function(db, callback) { + db.runSql('ALTER TABLE users CHANGE COLUMN totpEnabled twoFactorAuthenticationEnabled BOOLEAN DEFAULT false, CHANGE COLUMN totpSecret twoFactorAuthenticationSecret VARCHAR(128) DEFAULT ""', function (error) { + if (error) console.error(error); + callback(error); + }); +}; diff --git a/migrations/schema.sql b/migrations/schema.sql index e180cdf42..0d06c8085 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -24,8 +24,8 @@ CREATE TABLE IF NOT EXISTS users( ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, displayName VARCHAR(512) DEFAULT "", fallbackEmail VARCHAR(512) DEFAULT "", - twoFactorAuthenticationSecret VARCHAR(128) DEFAULT "", - twoFactorAuthenticationEnabled BOOLEAN DEFAULT false, + totpSecret VARCHAR(128) DEFAULT "", + totpEnabled BOOLEAN DEFAULT false, source VARCHAR(128) DEFAULT "", role VARCHAR(32), inviteToken VARCHAR(128) DEFAULT "", // one time token until user sets up account diff --git a/src/directoryserver.js b/src/directoryserver.js index dd48ea18b..a43318334 100644 --- a/src/directoryserver.js +++ b/src/directoryserver.js @@ -233,7 +233,7 @@ async function userSearch(req, res, next) { } }; - if (user.twoFactorAuthenticationEnabled) obj.attributes.twoFactorAuthenticationEnabled = true; + if (user.totpEnabled) obj.attributes.totpEnabled = true; // ensure all filter values are also lowercase const lowerCaseFilter = safe(function () { return ldap.parseFilter(req.filter.toString().toLowerCase()); }, null); diff --git a/src/externalldap.js b/src/externalldap.js index 4ebeb157b..e12281c49 100644 --- a/src/externalldap.js +++ b/src/externalldap.js @@ -33,7 +33,7 @@ function translateUser(ldapConfig, ldapUser) { const user = { username: typeof ldapUser[ldapConfig.usernameField] === 'string' ? ldapUser[ldapConfig.usernameField].toLowerCase() : '', email: ldapUser.mail || ldapUser.mailPrimaryAddress, - twoFactorAuthenticationEnabled: !!ldapUser.twoFactorAuthenticationEnabled, + totpEnabled: !!ldapUser.totpEnabled, displayName: ldapUser.displayName || ldapUser.cn // user.giveName + ' ' + user.sn }; diff --git a/src/oidcserver.js b/src/oidcserver.js index 0c8f64955..264b09aa3 100644 --- a/src/oidcserver.js +++ b/src/oidcserver.js @@ -412,7 +412,7 @@ async function interactionLogin(req, res, next) { // If password verified but 2FA is required and not provided, return challenge if (!verifyError && user && !user.ghost && !totpToken && !passkeyResponse) { const userPasskeys = await passkeys.listByUserId(user.id); - const has2FA = user.twoFactorAuthenticationEnabled || userPasskeys.length > 0; + const has2FA = user.totpEnabled || userPasskeys.length > 0; if (has2FA) { // Generate passkey options if user has passkeys @@ -424,7 +424,7 @@ async function interactionLogin(req, res, next) { return res.status(200).send({ twoFactorRequired: true, - totpRequired: user.twoFactorAuthenticationEnabled, + totpRequired: user.totpEnabled, passkeyOptions }); } diff --git a/src/passkeys.js b/src/passkeys.js index a3ceeb418..bd14421a2 100644 --- a/src/passkeys.js +++ b/src/passkeys.js @@ -132,7 +132,7 @@ async function getRegistrationOptions(user) { const existingPasskeys = await listByUserId(user.id); if (existingPasskeys.length > 0) throw new BoxError(BoxError.ALREADY_EXISTS, 'User already has a passkey registered'); - if (user.twoFactorAuthenticationEnabled) throw new BoxError(BoxError.ALREADY_EXISTS, 'Cannot register passkey when TOTP is enabled'); + if (user.totpEnabled) throw new BoxError(BoxError.ALREADY_EXISTS, 'Cannot register passkey when TOTP is enabled'); const { rpId, rpName } = await getRpInfo(); @@ -164,7 +164,7 @@ async function verifyRegistration(user, response, name) { // check should ideally be in a transaction const existingPasskeys = await listByUserId(user.id); if (existingPasskeys.length > 0) throw new BoxError(BoxError.ALREADY_EXISTS, 'User already has a passkey registered'); - if (user.twoFactorAuthenticationEnabled) throw new BoxError(BoxError.ALREADY_EXISTS, 'Cannot register passkey when TOTP is enabled'); + if (user.totpEnabled) throw new BoxError(BoxError.ALREADY_EXISTS, 'Cannot register passkey when TOTP is enabled'); const expectedChallenge = getAndDeleteChallenge(user.id); if (!expectedChallenge) throw new BoxError(BoxError.BAD_FIELD, 'Challenge expired or not found'); diff --git a/src/routes/auth.js b/src/routes/auth.js index f499e8f5b..8216889ea 100644 --- a/src/routes/auth.js +++ b/src/routes/auth.js @@ -65,10 +65,10 @@ async function passwordReset(req, res, next) { if (getError) return next(new HttpError(401, 'Invalid resetToken')); if (!userObject) return next(new HttpError(401, 'Invalid resetToken')); - if (userObject.twoFactorAuthenticationEnabled) { + if (userObject.totpEnabled) { if (typeof req.body.totpToken !== 'string') return next(new HttpError(401, 'A totpToken must be provided')); - const verified = speakeasy.totp.verify({ secret: userObject.twoFactorAuthenticationSecret, encoding: 'base32', token: req.body.totpToken, window: 2 }); + const verified = speakeasy.totp.verify({ secret: userObject.totpSecret, encoding: 'base32', token: req.body.totpToken, window: 2 }); if (!verified) return next(new HttpError(401, 'Invalid totpToken')); } diff --git a/src/routes/profile.js b/src/routes/profile.js index e765c688d..f50199988 100644 --- a/src/routes/profile.js +++ b/src/routes/profile.js @@ -32,7 +32,7 @@ async function get(req, res, next) { email: req.user.email, fallbackEmail: req.user.fallbackEmail, displayName: req.user.displayName, - twoFactorAuthenticationEnabled: req.user.twoFactorAuthenticationEnabled, + totpEnabled: req.user.totpEnabled, role: req.user.role, source: req.user.source, hasBackgroundImage: req.user.hasBackgroundImage, @@ -178,31 +178,31 @@ async function setPassword(req, res, next) { next(new HttpSuccess(204)); } -async function setTwoFactorAuthenticationSecret(req, res, next) { +async function setTotpSecret(req, res, next) { assert.strictEqual(typeof req.user, 'object'); - const [error, result] = await safe(users.setTwoFactorAuthenticationSecret(req.user, AuditSource.fromRequest(req))); + const [error, result] = await safe(users.setTotpSecret(req.user, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, { secret: result.secret, qrcode: result.qrcode })); } -async function enableTwoFactorAuthentication(req, res, next) { +async function enableTotp(req, res, next) { assert.strictEqual(typeof req.body, 'object'); assert.strictEqual(typeof req.user, 'object'); if (!req.body.totpToken || typeof req.body.totpToken !== 'string') return next(new HttpError(400, 'totpToken must be a nonempty string')); - const [error] = await safe(users.enableTwoFactorAuthentication(req.user, req.body.totpToken, AuditSource.fromRequest(req))); + const [error] = await safe(users.enableTotp(req.user, req.body.totpToken, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204, {})); } -async function disableTwoFactorAuthentication(req, res, next) { +async function disableTotp(req, res, next) { assert.strictEqual(typeof req.user, 'object'); - const [error] = await safe(users.disableTwoFactorAuthentication(req.user, AuditSource.fromRequest(req))); + const [error] = await safe(users.disableTotp(req.user, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(204, {})); @@ -296,9 +296,9 @@ export default { setBackgroundImage, unsetBackgroundImage, setPassword, - setTwoFactorAuthenticationSecret, - enableTwoFactorAuthentication, - disableTwoFactorAuthentication, + setTotpSecret, + enableTotp, + disableTotp, setNotificationConfig, destroyUserSession, getPasskey, diff --git a/src/routes/users.js b/src/routes/users.js index 36943f80f..afce72740 100644 --- a/src/routes/users.js +++ b/src/routes/users.js @@ -185,12 +185,12 @@ async function verifyPassword(req, res, next) { next(); } -async function disableTwoFactorAuthentication(req, res, next) { +async function disableTotp(req, res, next) { assert.strictEqual(typeof req.resources.user, 'object'); if (users.compareRoles(req.user.role, req.resources.user.role) < 0) return next(new HttpError(403, `role '${req.resources.user.role}' is required but user has only '${req.user.role}'`)); - const [error] = await safe(users.disableTwoFactorAuthentication(req.resources.user, AuditSource.fromRequest(req))); + const [error] = await safe(users.disableTotp(req.resources.user, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {})); @@ -308,7 +308,7 @@ export default { getInviteLink, sendInviteEmail, - disableTwoFactorAuthentication, + disableTotp, load }; diff --git a/src/server.js b/src/server.js index 5eb96606c..c831b2bee 100644 --- a/src/server.js +++ b/src/server.js @@ -195,9 +195,9 @@ async function initializeExpressSync() { router.post('/api/v1/profile/background_image', token, authorizeUser, multipart, routes.profile.setBackgroundImage); // backgroundImage is not exposed in LDAP. so it's personal and not locked router.del ('/api/v1/profile/background_image', token, authorizeUser, routes.profile.unsetBackgroundImage); // backgroundImage is not exposed in LDAP. so it's personal and not locked router.post('/api/v1/profile/password', json, token, authorizeUser, routes.users.verifyPassword, routes.profile.setPassword); - router.post('/api/v1/profile/totp_secret', json, token, authorizeUser, routes.profile.setTwoFactorAuthenticationSecret); - router.post('/api/v1/profile/totp_enable', json, token, authorizeUser, routes.profile.enableTwoFactorAuthentication); - router.post('/api/v1/profile/totp_disable', json, token, authorizeUser, routes.users.verifyPassword, routes.profile.disableTwoFactorAuthentication); + router.post('/api/v1/profile/totp_secret', json, token, authorizeUser, routes.profile.setTotpSecret); + router.post('/api/v1/profile/totp_enable', json, token, authorizeUser, routes.profile.enableTotp); + router.post('/api/v1/profile/totp_disable', json, token, authorizeUser, routes.users.verifyPassword, routes.profile.disableTotp); router.post('/api/v1/profile/notification_config', json, token, authorizeUser, routes.profile.setNotificationConfig); // non-admins cannot get notifications anyway router.del ('/api/v1/profile/sessions', token, authorizeUser, routes.profile.destroyUserSession); @@ -237,7 +237,7 @@ async function initializeExpressSync() { router.post('/api/v1/users/:userId/send_password_reset_email', json, token, authorizeUserManager, routes.users.load, routes.users.sendPasswordResetEmail); router.get ('/api/v1/users/:userId/invite_link', token, authorizeUserManager, routes.users.load, routes.users.getInviteLink); router.post('/api/v1/users/:userId/send_invite_email', json, token, authorizeUserManager, routes.users.load, routes.users.sendInviteEmail); - router.post('/api/v1/users/:userId/totp_disable', json, token, authorizeUserManager, routes.users.load, routes.users.disableTwoFactorAuthentication); + router.post('/api/v1/users/:userId/totp_disable', json, token, authorizeUserManager, routes.users.load, routes.users.disableTotp); // Group management router.get ('/api/v1/groups', token, authorizeUserManager, routes.groups.list); diff --git a/src/test/directoryserver-test.js b/src/test/directoryserver-test.js index 46b0700d7..662149aaf 100644 --- a/src/test/directoryserver-test.js +++ b/src/test/directoryserver-test.js @@ -135,9 +135,9 @@ describe('Directory Server (LDAP)', function () { // the 2fa is requested by client by passing `+totpToken=xxx` . totpToken // is ignored if the user has no 2fa setup. If present, it is validated. it('enable 2fa for admin', async function () { - twofa = await users.setTwoFactorAuthenticationSecret(admin, auditSource); + twofa = await users.setTotpSecret(admin, auditSource); const totpToken = speakeasy.totp({ secret: twofa.secret, encoding: 'base32' }); - await users.enableTwoFactorAuthentication(await users.get(admin.id), totpToken, auditSource); + await users.enableTotp(await users.get(admin.id), totpToken, auditSource); }); it('totp check not requested - fails with bad password', async function () { diff --git a/src/test/passkeys-test.js b/src/test/passkeys-test.js index 974b4832a..863084563 100644 --- a/src/test/passkeys-test.js +++ b/src/test/passkeys-test.js @@ -180,19 +180,19 @@ describe('Passkeys', function () { const adminUser = await users.get(admin.id); // enable TOTP first - const twofa = await users.setTwoFactorAuthenticationSecret(adminUser, auditSource); - adminUser.twoFactorAuthenticationSecret = twofa.secret; + const twofa = await users.setTotpSecret(adminUser, auditSource); + adminUser.totpSecret = twofa.secret; const totpToken = speakeasy.totp({ secret: twofa.secret, encoding: 'base32' }); - await users.enableTwoFactorAuthentication(adminUser, totpToken, auditSource); - adminUser.twoFactorAuthenticationEnabled = true; + await users.enableTotp(adminUser, totpToken, auditSource); + adminUser.totpEnabled = true; const [error] = await safe(passkeys.getRegistrationOptions(adminUser)); assert.notEqual(error, null); assert.equal(error.reason, BoxError.ALREADY_EXISTS); // disable TOTP for further tests - await users.disableTwoFactorAuthentication(adminUser, auditSource); - adminUser.twoFactorAuthenticationEnabled = false; + await users.disableTotp(adminUser, auditSource); + adminUser.totpEnabled = false; }); }); @@ -293,11 +293,11 @@ describe('Passkeys', function () { await passkeys.verifyRegistration(adminUser, response, 'Exclusion Test'); // try to enable TOTP - const twofa = await users.setTwoFactorAuthenticationSecret(adminUser, auditSource); - adminUser.twoFactorAuthenticationSecret = twofa.secret; + const twofa = await users.setTotpSecret(adminUser, auditSource); + adminUser.totpSecret = twofa.secret; const totpToken = speakeasy.totp({ secret: twofa.secret, encoding: 'base32' }); - const [error] = await safe(users.enableTwoFactorAuthentication(adminUser, totpToken, auditSource)); + const [error] = await safe(users.enableTotp(adminUser, totpToken, auditSource)); assert.notEqual(error, null); assert.equal(error.reason, BoxError.ALREADY_EXISTS); @@ -308,19 +308,19 @@ describe('Passkeys', function () { const adminUser = await users.get(admin.id); // enable TOTP - const twofa = await users.setTwoFactorAuthenticationSecret(adminUser, auditSource); - adminUser.twoFactorAuthenticationSecret = twofa.secret; + const twofa = await users.setTotpSecret(adminUser, auditSource); + adminUser.totpSecret = twofa.secret; const totpToken = speakeasy.totp({ secret: twofa.secret, encoding: 'base32' }); - await users.enableTwoFactorAuthentication(adminUser, totpToken, auditSource); - adminUser.twoFactorAuthenticationEnabled = true; + await users.enableTotp(adminUser, totpToken, auditSource); + adminUser.totpEnabled = true; const [error] = await safe(passkeys.getRegistrationOptions(adminUser)); assert.notEqual(error, null); assert.equal(error.reason, BoxError.ALREADY_EXISTS); // cleanup - await users.disableTwoFactorAuthentication(adminUser, auditSource); - adminUser.twoFactorAuthenticationEnabled = false; + await users.disableTotp(adminUser, auditSource); + adminUser.totpEnabled = false; }); }); }); diff --git a/src/test/users-test.js b/src/test/users-test.js index cde1ed0dc..603824dde 100644 --- a/src/test/users-test.js +++ b/src/test/users-test.js @@ -384,35 +384,35 @@ describe('User', function () { let twofa; it('create secret', async function () { - twofa = await users.setTwoFactorAuthenticationSecret(admin, auditSource); + twofa = await users.setTotpSecret(admin, auditSource); assert.equal(typeof twofa.secret, 'string'); assert.equal(typeof twofa.qrcode, 'string'); }); it('can create secret again', async function () { - twofa = await users.setTwoFactorAuthenticationSecret(admin, auditSource); + twofa = await users.setTotpSecret(admin, auditSource); assert.equal(typeof twofa.secret, 'string'); assert.equal(typeof twofa.qrcode, 'string'); - admin.twoFactorAuthenticationSecret = twofa.secret; // update user object + admin.totpSecret = twofa.secret; // update user object }); it('enable 2fa', async function () { const totpToken = speakeasy.totp({ secret: twofa.secret, encoding: 'base32' }); - await users.enableTwoFactorAuthentication(admin, totpToken, auditSource); + await users.enableTotp(admin, totpToken, auditSource); const u = await users.get(admin.id); - assert.equal(u.twoFactorAuthenticationEnabled, true); - admin.twoFactorAuthenticationEnabled = true; // update user object + assert.equal(u.totpEnabled, true); + admin.totpEnabled = true; // update user object }); it('cannot re-create secret', async function () { - const [error] = await safe(users.setTwoFactorAuthenticationSecret(admin, auditSource)); + const [error] = await safe(users.setTotpSecret(admin, auditSource)); assert.equal(error.reason, BoxError.ALREADY_EXISTS); }); it('cannot re-enable 2fa', async function () { const totpToken = speakeasy.totp({ secret: twofa.secret, encoding: 'base32' }); - const [error] = await safe(users.enableTwoFactorAuthentication(admin, totpToken, auditSource)); + const [error] = await safe(users.enableTotp(admin, totpToken, auditSource)); assert.equal(error.reason, BoxError.ALREADY_EXISTS); }); diff --git a/src/user-directory.js b/src/user-directory.js index ceb81264f..022e2832a 100644 --- a/src/user-directory.js +++ b/src/user-directory.js @@ -35,7 +35,7 @@ async function setProfileConfig(profileConfig, options, auditSource) { const allUsers = await users.list(); for (const user of allUsers) { - if (user.twoFactorAuthenticationEnabled) continue; + if (user.totpEnabled) continue; if (options.persistUserIdSessions === user.id) continue; // do not logout the API caller if (!user.username) continue; // if a user has no username set yet diff --git a/src/users.js b/src/users.js index fb254a7e6..b03ea26b5 100644 --- a/src/users.js +++ b/src/users.js @@ -43,7 +43,7 @@ const ROLE_OWNER = 'owner'; // the avatar and backgroundImage fields are special and not added here to reduce response sizes const USERS_FIELDS = [ 'id', 'username', 'email', 'fallbackEmail', 'password', 'salt', 'creationTime', 'inviteToken', 'resetToken', 'displayName', 'language', - 'twoFactorAuthenticationEnabled', 'twoFactorAuthenticationSecret', 'active', 'source', 'role', 'resetTokenCreationTime', 'loginLocationsJson', 'notificationConfigJson', + 'totpEnabled', 'totpSecret', 'active', 'source', 'role', 'resetTokenCreationTime', 'loginLocationsJson', 'notificationConfigJson', '(avatar IS NOT NULL) AS hasAvatar', '(backgroundImage IS NOT NULL) AS hasBackgroundImage' ].join(','); const DEFAULT_GHOST_LIFETIME = 6 * 60 * 60 * 1000; // 6 hours @@ -59,7 +59,7 @@ const randomBytesAsync = util.promisify(crypto.randomBytes); function postProcess(result) { assert.strictEqual(typeof result, 'object'); - result.twoFactorAuthenticationEnabled = !!result.twoFactorAuthenticationEnabled; + result.totpEnabled = !!result.totpEnabled; result.active = !!result.active; result.loginLocations = safe.JSON.parse(result.loginLocationsJson) || []; @@ -136,7 +136,7 @@ function validatePassword(password) { function removePrivateFields(user) { const result = _.pick(user, [ 'id', 'username', 'email', 'fallbackEmail', 'displayName', 'groupIds', 'active', 'source', 'role', 'createdAt', - 'twoFactorAuthenticationEnabled', 'notificationConfig', 'hasAvatar', 'hasBackgroundImage' ]); + 'totpEnabled', 'notificationConfig', 'hasAvatar', 'hasBackgroundImage' ]); result.inviteAccepted = !user.inviteToken; // invite status indicator return result; @@ -395,7 +395,7 @@ async function update(user, data, auditSource) { assert.strictEqual(typeof data, 'object'); assert(auditSource && typeof auditSource === 'object'); - assert(!('twoFactorAuthenticationEnabled' in data) || (typeof data.twoFactorAuthenticationEnabled === 'boolean')); + assert(!('totpEnabled' in data) || (typeof data.totpEnabled === 'boolean')); assert(!('active' in data) || (typeof data.active === 'boolean')); assert(!('loginLocations' in data) || (Array.isArray(data.loginLocations))); @@ -434,7 +434,7 @@ async function update(user, data, auditSource) { const args = [], fields = []; for (const k in data) { - if (k === 'twoFactorAuthenticationEnabled' || k === 'active') { + if (k === 'totpEnabled' || k === 'active') { fields.push(k + ' = ?'); args.push(data[k] ? 1 : 0); } else if (k === 'loginLocations' || k === 'notificationConfig') { @@ -626,7 +626,7 @@ async function verify(user, password, identifier, options) { if (user.source === 'ldap') { await externalLdap.verifyPassword(user.username, password, options); const externalLdapConfig = await externalLdap.getConfig(); - localTotpCheck = user.twoFactorAuthenticationEnabled && !externalLdap.supports2FA(externalLdapConfig); + localTotpCheck = user.totpEnabled && !externalLdap.supports2FA(externalLdapConfig); } else { const saltBinary = Buffer.from(user.salt, 'hex'); const [cryptoError, derivedKey] = await safe(pbkdf2Async(password, saltBinary, CRYPTO_ITERATIONS, CRYPTO_KEY_LENGTH, CRYPTO_DIGEST)); @@ -638,12 +638,12 @@ async function verify(user, password, identifier, options) { throw new BoxError(BoxError.INVALID_CREDENTIALS, 'Wrong password'); } - localTotpCheck = user.twoFactorAuthenticationEnabled; + localTotpCheck = user.totpEnabled; } if (localTotpCheck && !options.skipTotpCheck) { if (!options.totpToken) throw new BoxError(BoxError.INVALID_CREDENTIALS, 'A totpToken must be provided'); - const verified = speakeasy.totp.verify({ secret: user.twoFactorAuthenticationSecret, encoding: 'base32', token: options.totpToken, window: 2 }); + const verified = speakeasy.totp.verify({ secret: user.totpSecret, encoding: 'base32', token: options.totpToken, window: 2 }); if (!verified) throw new BoxError(BoxError.INVALID_CREDENTIALS, 'Invalid totpToken'); } @@ -911,7 +911,7 @@ async function setupAccount(user, data, auditSource) { return result.accessToken; } -async function setTwoFactorAuthenticationSecret(user, auditSource) { +async function setTotpSecret(user, auditSource) { assert.strictEqual(typeof user, 'object'); assert(auditSource && typeof auditSource === 'object'); @@ -920,12 +920,12 @@ async function setTwoFactorAuthenticationSecret(user, auditSource) { if (constants.DEMO && user.username === constants.DEMO_USERNAME) throw new BoxError(BoxError.BAD_STATE, 'Not allowed in demo mode'); - if (user.twoFactorAuthenticationEnabled) throw new BoxError(BoxError.ALREADY_EXISTS, '2FA is already enabled'); + if (user.totpEnabled) throw new BoxError(BoxError.ALREADY_EXISTS, '2FA is already enabled'); const cloudronName = await branding.getCloudronName(); const secret = speakeasy.generateSecret({ name: `${cloudronName}` }); - await update(user, { twoFactorAuthenticationSecret: secret.base32, twoFactorAuthenticationEnabled: false }, auditSource); + await update(user, { totpSecret: secret.base32, totpEnabled: false }, auditSource); const [error, dataUrl] = await safe(qrcode.toDataURL(secret.otpauth_url)); if (error) throw new BoxError(BoxError.INTERNAL_ERROR, error); @@ -933,7 +933,7 @@ async function setTwoFactorAuthenticationSecret(user, auditSource) { return { secret: secret.base32, qrcode: dataUrl }; } -async function enableTwoFactorAuthentication(user, totpToken, auditSource) { +async function enableTotp(user, totpToken, auditSource) { assert.strictEqual(typeof user, 'object'); assert.strictEqual(typeof totpToken, 'string'); assert(auditSource && typeof auditSource === 'object'); @@ -945,22 +945,22 @@ async function enableTwoFactorAuthentication(user, totpToken, auditSource) { const userPasskeys = await passkeys.listByUserId(user.id); if (userPasskeys.length > 0) throw new BoxError(BoxError.ALREADY_EXISTS, 'Cannot enable TOTP when passkey is registered'); - const verified = speakeasy.totp.verify({ secret: user.twoFactorAuthenticationSecret, encoding: 'base32', token: totpToken, window: 2 }); + const verified = speakeasy.totp.verify({ secret: user.totpSecret, encoding: 'base32', token: totpToken, window: 2 }); if (!verified) throw new BoxError(BoxError.INVALID_CREDENTIALS, 'Invalid 2FA code'); - if (user.twoFactorAuthenticationEnabled) throw new BoxError(BoxError.ALREADY_EXISTS, '2FA already enabled'); + if (user.totpEnabled) throw new BoxError(BoxError.ALREADY_EXISTS, '2FA already enabled'); - await update(user, { twoFactorAuthenticationEnabled: true }, auditSource); + await update(user, { totpEnabled: true }, auditSource); } -async function disableTwoFactorAuthentication(user, auditSource) { +async function disableTotp(user, auditSource) { assert.strictEqual(typeof user, 'object'); assert(auditSource && typeof auditSource === 'object'); const externalLdapConfig = await externalLdap.getConfig(); if (user.source === 'ldap' && externalLdap.supports2FA(externalLdapConfig)) throw new BoxError(BoxError.BAD_STATE, 'Cannot disable 2FA of external auth user'); - await update(user, { twoFactorAuthenticationEnabled: false, twoFactorAuthenticationSecret: '' }, auditSource); + await update(user, { totpEnabled: false, totpSecret: '' }, auditSource); } async function getAvatar(user) { @@ -1042,9 +1042,9 @@ export default { updateProfile, update, del, - setTwoFactorAuthenticationSecret, - enableTwoFactorAuthentication, - disableTwoFactorAuthentication, + setTotpSecret, + enableTotp, + disableTotp, sendPasswordResetByIdentifier, getPasswordResetLink, sendPasswordResetEmail,