From 226cc24532952091af6a64da81b040f0c31cbef3 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:12:47 -0500 Subject: [PATCH 01/37] =?UTF-8?q?Indiquer=20le=20num=C3=A9ro=20de=20la=20q?= =?UTF-8?q?uestion=20dans=20l'affichage=20enseignant=20Fixes=20#207?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/package-lock.json | 114 +++++++++--------- client/package.json | 4 +- .../pages/Teacher/ManageRoom/ManageRoom.tsx | 67 ++++++---- .../pages/Teacher/ManageRoom/manageRoom.css | 4 +- 4 files changed, 106 insertions(+), 83 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index edc30d8..638fac1 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,7 +14,7 @@ "@fortawesome/fontawesome-svg-core": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", - "@mui/icons-material": "^6.1.0", + "@mui/icons-material": "^6.4.1", "@mui/lab": "^5.0.0-alpha.153", "@mui/material": "^6.1.0", "@types/uuid": "^9.0.7", @@ -2090,15 +2090,15 @@ } }, "node_modules/@emotion/serialize": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", - "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", "license": "MIT", "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", "@emotion/unitless": "^0.10.0", - "@emotion/utils": "^1.4.1", + "@emotion/utils": "^1.4.2", "csstype": "^3.0.2" } }, @@ -3352,9 +3352,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.6.tgz", - "integrity": "sha512-nz1SlR9TdBYYPz4qKoNasMPRiGb4PaIHFkzLzhju0YVYS5QSuFF2+n7CsiHMIDcHv3piPu/xDWI53ruhOqvZwQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.1.tgz", + "integrity": "sha512-SfDLWMV5b5oXgDf3NTa2hCTPC1d2defhDH2WgFKmAiejC4mSfXYbyi+AFCLzpizauXhgBm8OaZy9BHKnrSpahQ==", "license": "MIT", "funding": { "type": "opencollective", @@ -3362,9 +3362,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.1.6.tgz", - "integrity": "sha512-5r9urIL2lxXb/sPN3LFfFYEibsXJUb986HhhIeu1gOcte460pwdSiEhBSxkAuyT8Dj7jvu9MjqSBmSumQELo8A==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.1.tgz", + "integrity": "sha512-wsxFcUTQxt4s+7Bg4GgobqRjyaHLmZGNOs+HJpbwrwmLbT6mhIJxhpqsKzzWq9aDY8xIe7HCjhpH7XI5UD6teA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0" @@ -3377,7 +3377,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^6.1.6", + "@mui/material": "^6.4.1", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -3461,22 +3461,22 @@ } }, "node_modules/@mui/material": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.6.tgz", - "integrity": "sha512-1yvejiQ/601l5AK3uIdUlAVElyCxoqKnl7QA+2oFB/2qYPWfRwDgavW/MoywS5Y2gZEslcJKhe0s2F3IthgFgw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.1.tgz", + "integrity": "sha512-MFBfia6UiKxyoLeGkAh8M15bkeDmfnsUTMRJd/vTQue6YQ8AQ6lw9HqDthyYghzDEWIvZO/lQQzLrZE8XwNJLA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.1.6", - "@mui/system": "^6.1.6", - "@mui/types": "^7.2.19", - "@mui/utils": "^6.1.6", + "@mui/core-downloads-tracker": "^6.4.1", + "@mui/system": "^6.4.1", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.1", "@popperjs/core": "^2.11.8", - "@types/react-transition-group": "^4.4.11", + "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1", - "react-is": "^18.3.1", + "react-is": "^19.0.0", "react-transition-group": "^4.4.5" }, "engines": { @@ -3489,7 +3489,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.1.6", + "@mui/material-pigment-css": "^6.4.1", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -3510,13 +3510,13 @@ } }, "node_modules/@mui/material/node_modules/@mui/private-theming": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.6.tgz", - "integrity": "sha512-ioAiFckaD/fJSnTrUMWgjl9HYBWt7ixCh7zZw7gDZ+Tae7NuprNV6QJK95EidDT7K0GetR2rU3kAeIR61Myttw==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.1.tgz", + "integrity": "sha512-DcT7mwK89owwgcEuiE7w458te4CIjHbYWW6Kn6PiR6eLtxBsoBYphA968uqsQAOBQDpbYxvkuFLwhgk4bxoN/Q==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.1.6", + "@mui/utils": "^6.4.1", "prop-types": "^15.8.1" }, "engines": { @@ -3537,14 +3537,14 @@ } }, "node_modules/@mui/material/node_modules/@mui/styled-engine": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.6.tgz", - "integrity": "sha512-I+yS1cSuSvHnZDBO7e7VHxTWpj+R7XlSZvTC4lS/OIbUNJOMMSd3UDP6V2sfwzAdmdDNBi7NGCRv2SZ6O9hGDA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.0.tgz", + "integrity": "sha512-ek/ZrDujrger12P6o4luQIfRd2IziH7jQod2WMbLqGE03Iy0zUwYmckRTVhRQTLPNccpD8KXGcALJF+uaUQlbg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@emotion/cache": "^11.13.1", - "@emotion/serialize": "^1.3.2", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", "@emotion/sheet": "^1.4.0", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -3571,16 +3571,16 @@ } }, "node_modules/@mui/material/node_modules/@mui/system": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.6.tgz", - "integrity": "sha512-qOf1VUE9wK8syiB0BBCp82oNBAVPYdj4Trh+G1s+L+ImYiKlubWhhqlnvWt3xqMevR+D2h1CXzA1vhX2FvA+VQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.1.tgz", + "integrity": "sha512-rgQzgcsHCTtzF9MZ+sL0tOhf2ZBLazpjrujClcb4Siju5lTrK0xX4PsiropActzCemNfM+mOu+0jezAVnfRK8g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.1.6", - "@mui/styled-engine": "^6.1.6", - "@mui/types": "^7.2.19", - "@mui/utils": "^6.1.6", + "@mui/private-theming": "^6.4.1", + "@mui/styled-engine": "^6.4.0", + "@mui/types": "^7.2.21", + "@mui/utils": "^6.4.1", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -3611,17 +3611,17 @@ } }, "node_modules/@mui/material/node_modules/@mui/utils": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.6.tgz", - "integrity": "sha512-sBS6D9mJECtELASLM+18WUcXF6RH3zNxBRFeyCRg8wad6NbyNrdxLuwK+Ikvc38sTZwBzAz691HmSofLqHd9sQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.1.tgz", + "integrity": "sha512-iQUDUeYh87SvR4lVojaRaYnQix8BbRV51MxaV6MBmqthecQoxwSbS5e2wnbDJUeFxY2ppV505CiqPLtd0OWkqw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/types": "^7.2.19", - "@types/prop-types": "^15.7.13", + "@mui/types": "^7.2.21", + "@types/prop-types": "^15.7.14", "clsx": "^2.1.1", "prop-types": "^15.8.1", - "react-is": "^18.3.1" + "react-is": "^19.0.0" }, "engines": { "node": ">=14.0.0" @@ -3640,6 +3640,12 @@ } } }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", + "license": "MIT" + }, "node_modules/@mui/private-theming": { "version": "5.16.14", "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.14.tgz", @@ -3740,9 +3746,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.19", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz", - "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==", + "version": "7.2.21", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.21.tgz", + "integrity": "sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==", "license": "MIT", "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -4650,9 +4656,9 @@ "license": "MIT" }, "node_modules/@types/prop-types": { - "version": "15.7.13", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "version": "15.7.14", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", "license": "MIT" }, "node_modules/@types/react": { @@ -4686,11 +4692,11 @@ } }, "node_modules/@types/react-transition-group": { - "version": "4.4.11", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", - "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", "license": "MIT", - "dependencies": { + "peerDependencies": { "@types/react": "*" } }, diff --git a/client/package.json b/client/package.json index 052063d..7457f22 100644 --- a/client/package.json +++ b/client/package.json @@ -18,7 +18,7 @@ "@fortawesome/fontawesome-svg-core": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/react-fontawesome": "^0.2.0", - "@mui/icons-material": "^6.1.0", + "@mui/icons-material": "^6.4.1", "@mui/lab": "^5.0.0-alpha.153", "@mui/material": "^6.1.0", "@types/uuid": "^9.0.7", @@ -69,4 +69,4 @@ "vite": "^5.4.5", "vite-plugin-environment": "^1.1.3" } -} \ No newline at end of file +} diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index 95e8464..8b3cf65 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -8,6 +8,7 @@ import LiveResultsComponent from 'src/components/LiveResults/LiveResults'; // import { QuestionService } from '../../../services/QuestionService'; import webSocketService, { AnswerReceptionFromBackendType } from '../../../services/WebsocketService'; import { QuizType } from '../../../Types/QuizType'; +import GroupIcon from '@mui/icons-material/Group'; import './manageRoom.css'; import { ENV_VARIABLES } from 'src/constants'; @@ -32,7 +33,8 @@ const ManageRoom: React.FC = () => { const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher'); const [connectingError, setConnectingError] = useState(''); const [currentQuestion, setCurrentQuestion] = useState(undefined); - + const [quizStarted, setQuizStarted] = useState(false); + useEffect(() => { if (quizId.id) { const fetchquiz = async () => { @@ -144,7 +146,7 @@ const ManageRoom: React.FC = () => { console.log('Quiz questions not found (cannot update answers without them).'); return; } - + // Update the students state using the functional form of setStudents setStudents((prevStudents) => { // print the list of current student names @@ -152,7 +154,7 @@ const ManageRoom: React.FC = () => { prevStudents.forEach((student) => { console.log(student.name); }); - + let foundStudent = false; const updatedStudents = prevStudents.map((student) => { console.log(`Comparing ${student.id} to ${idUser}`); @@ -172,7 +174,7 @@ const ManageRoom: React.FC = () => { updatedAnswers = [...student.answers, newAnswer]; } return { ...student, answers: updatedAnswers }; - } + } return student; }); if (!foundStudent) { @@ -315,13 +317,18 @@ const ManageRoom: React.FC = () => { if (!socket || !roomName || !quiz?.content || quiz?.content.length === 0) { // TODO: This error happens when token expires! Need to handle it properly console.log(`Error launching quiz. socket: ${socket}, roomName: ${roomName}, quiz: ${quiz}`); + setQuizStarted(true); + return; } switch (quizMode) { case 'student': + setQuizStarted(true); return launchStudentMode(); case 'teacher': + setQuizStarted(true); return launchTeacherMode(); + } }; @@ -440,9 +447,19 @@ const ManageRoom: React.FC = () => { askConfirm message={`Êtes-vous sûr de vouloir quitter?`} /> -
-
Salle: {roomName}
-
Utilisateurs: {students.length}/60
+ + + +
+
+
Salle: {roomName}
+
+ {quizStarted && ( +
+ + {students.length}/60 +
+ )}
@@ -454,8 +471,8 @@ const ManageRoom: React.FC = () => { {quizQuestions ? (
-
{quiz?.title}
+ Question {Number(currentQuestion?.question.id)}/{quizQuestions?.length} {quizMode === 'teacher' && ( @@ -492,23 +509,23 @@ const ManageRoom: React.FC = () => {
{quizMode === 'teacher' && ( -
-
- -
-
- -
-
)} +
+
+ +
+
+ +
+
)}
diff --git a/client/src/pages/Teacher/ManageRoom/manageRoom.css b/client/src/pages/Teacher/ManageRoom/manageRoom.css index ffb83fa..ad870a9 100644 --- a/client/src/pages/Teacher/ManageRoom/manageRoom.css +++ b/client/src/pages/Teacher/ManageRoom/manageRoom.css @@ -18,8 +18,8 @@ display: flex; flex-direction: column; - justify-content: center; - align-items: center; + justify-content: flex-end; + align-items: flex-end; } From b49057a8ffd30926a35e8dbeaf847d8cda02b91f Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:23:15 -0500 Subject: [PATCH 02/37] Only show the current question for teacher mode --- client/src/pages/Teacher/ManageRoom/ManageRoom.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index 8b3cf65..65c56cb 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -472,7 +472,11 @@ const ManageRoom: React.FC = () => {
{quiz?.title}
- Question {Number(currentQuestion?.question.id)}/{quizQuestions?.length} + {!isNaN(Number(currentQuestion?.question.id)) && ( + + Question {Number(currentQuestion?.question.id)}/{quizQuestions?.length} + + )} {quizMode === 'teacher' && ( From 6b4364c7c7482dca426be8ba0ac9ce7b0c8264f7 Mon Sep 17 00:00:00 2001 From: Eddi3_As Date: Wed, 29 Jan 2025 21:45:41 -0500 Subject: [PATCH 03/37] PFEH2025 - merge entre main et dev-it2-PFEA2024 pour activation SSO --- .gitignore | 3 + client/package-lock.json | 19 +- client/package.json | 1 + client/src/App.tsx | 96 +++-- .../services/WebsocketService.test.tsx | 14 +- client/src/components/Header/Header.tsx | 14 +- client/src/constants.tsx | 6 +- client/src/pages/AuthManager/AuthDrawer.tsx | 61 +++ client/src/pages/AuthManager/authDrawer.css | 48 +++ .../AuthManager/callback/AuthCallback.tsx | 27 ++ .../providers/OAuth-Oidc/ButtonAuth.tsx | 27 ++ .../providers/SimpleLogin/Login.tsx} | 169 ++++---- .../providers/SimpleLogin/Register.tsx | 114 +++++ .../providers/SimpleLogin/ResetPassword.tsx | 68 +++ .../AuthManager/providers/css/buttonAuth.css | 23 + .../AuthManager/providers/css/simpleLogin.css | 17 + client/src/pages/Home/home.css | 19 + .../src/pages/Student/JoinRoom/JoinRoom.tsx | 8 +- .../src/pages/Teacher/Dashboard/Dashboard.tsx | 2 +- .../pages/Teacher/ManageRoom/ManageRoom.tsx | 2 +- client/src/pages/Teacher/Share/Share.tsx | 2 +- client/src/services/ApiService.tsx | 169 ++++++-- client/src/services/AuthService.tsx | 28 ++ docker-compose-auth.yaml | 96 +++++ docker-compose.yaml | 41 +- oauth-tester/config.json | 96 +++++ server/.env.example | 8 +- server/.gitignore | 1 + server/__tests__/auth.test.js | 244 +++++++++++ server/app.js | 25 +- server/auth/auth-manager.js | 66 +++ .../auth/modules/passport-providers/oauth.js | 101 +++++ .../auth/modules/passport-providers/oidc.js | 103 +++++ server/auth/modules/passportjs.js | 63 +++ server/auth/modules/simpleauth.js | 125 ++++++ server/auth_config.json.example | 26 ++ server/config/auth.js | 192 +++++++++ server/controllers/auth.js | 36 ++ server/middleware/jwtToken.js | 8 +- server/models/authProvider.js | 44 ++ server/models/authUserAssociation.js | 59 +++ server/models/userAuthAssociation.js | 13 + server/models/users.js | 274 +++++++----- server/package-lock.json | 397 +++++++++++++++++- server/package.json | 8 +- .../passport-openidconnect+0.1.2.patch | 12 + server/routers/auth.js | 10 + server/routers/users.js | 3 +- server/utils.js | 35 ++ 49 files changed, 2695 insertions(+), 328 deletions(-) create mode 100644 client/src/pages/AuthManager/AuthDrawer.tsx create mode 100644 client/src/pages/AuthManager/authDrawer.css create mode 100644 client/src/pages/AuthManager/callback/AuthCallback.tsx create mode 100644 client/src/pages/AuthManager/providers/OAuth-Oidc/ButtonAuth.tsx rename client/src/pages/{Teacher/Register/Register.tsx => AuthManager/providers/SimpleLogin/Login.tsx} (63%) create mode 100644 client/src/pages/AuthManager/providers/SimpleLogin/Register.tsx create mode 100644 client/src/pages/AuthManager/providers/SimpleLogin/ResetPassword.tsx create mode 100644 client/src/pages/AuthManager/providers/css/buttonAuth.css create mode 100644 client/src/pages/AuthManager/providers/css/simpleLogin.css create mode 100644 client/src/services/AuthService.tsx create mode 100644 docker-compose-auth.yaml create mode 100644 oauth-tester/config.json create mode 100644 server/.gitignore create mode 100644 server/__tests__/auth.test.js create mode 100644 server/auth/auth-manager.js create mode 100644 server/auth/modules/passport-providers/oauth.js create mode 100644 server/auth/modules/passport-providers/oidc.js create mode 100644 server/auth/modules/passportjs.js create mode 100644 server/auth/modules/simpleauth.js create mode 100644 server/auth_config.json.example create mode 100644 server/config/auth.js create mode 100644 server/controllers/auth.js create mode 100644 server/models/authProvider.js create mode 100644 server/models/authUserAssociation.js create mode 100644 server/models/userAuthAssociation.js create mode 100644 server/patches/passport-openidconnect+0.1.2.patch create mode 100644 server/routers/auth.js create mode 100644 server/utils.js diff --git a/.gitignore b/.gitignore index 6e8de7b..d4eb19a 100644 --- a/.gitignore +++ b/.gitignore @@ -122,6 +122,9 @@ dist # Stores VSCode versions used for testing VSCode extensions .vscode-test +.env +launch.json + # yarn v2 .yarn/cache .yarn/unplugged diff --git a/client/package-lock.json b/client/package-lock.json index 8105575..4ee571d 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -23,6 +23,7 @@ "esbuild": "^0.23.1", "gift-pegjs": "^2.0.0-beta.1", "jest-environment-jsdom": "^29.7.0", + "jwt-decode": "^4.0.0", "katex": "^0.16.11", "marked": "^14.1.2", "nanoid": "^5.0.2", @@ -9476,20 +9477,12 @@ "node": ">= 10.0.0" } }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", "engines": { - "node": ">=4.0" + "node": ">=18" } }, "node_modules/katex": { diff --git a/client/package.json b/client/package.json index 690ed35..939257a 100644 --- a/client/package.json +++ b/client/package.json @@ -27,6 +27,7 @@ "esbuild": "^0.23.1", "gift-pegjs": "^2.0.0-beta.1", "jest-environment-jsdom": "^29.7.0", + "jwt-decode": "^4.0.0", "katex": "^0.16.11", "marked": "^14.1.2", "nanoid": "^5.0.2", diff --git a/client/src/App.tsx b/client/src/App.tsx index 8f8ecf8..9b16e2f 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,6 +1,6 @@ import React from 'react'; -// App.tsx -import { Routes, Route } from 'react-router-dom'; +import { useEffect, useState } from 'react'; +import { Routes, Route, Navigate, useLocation } from 'react-router-dom'; // Page main import Home from './pages/Home/Home'; @@ -8,37 +8,55 @@ import Home from './pages/Home/Home'; // Pages espace enseignant import Dashboard from './pages/Teacher/Dashboard/Dashboard'; import Share from './pages/Teacher/Share/Share'; -import Login from './pages/Teacher/Login/Login'; -import Register from './pages/Teacher/Register/Register'; -import ResetPassword from './pages/Teacher/ResetPassword/ResetPassword'; +import Register from './pages/AuthManager/providers/SimpleLogin/Register'; +import ResetPassword from './pages/AuthManager/providers/SimpleLogin/ResetPassword'; import ManageRoom from './pages/Teacher/ManageRoom/ManageRoom'; import QuizForm from './pages/Teacher/EditorQuiz/EditorQuiz'; // Pages espace étudiant import JoinRoom from './pages/Student/JoinRoom/JoinRoom'; +// Pages authentification selection +import AuthDrawer from './pages/AuthManager/AuthDrawer'; + // Header/Footer import import Header from './components/Header/Header'; import Footer from './components/Footer/Footer'; import ApiService from './services/ApiService'; +import OAuthCallback from './pages/AuthManager/callback/AuthCallback'; -const handleLogout = () => { - ApiService.logout(); -} +const App: React.FC = () => { + const [isAuthenticated, setIsAuthenticated] = useState(ApiService.isLoggedIn()); + const [isTeacherAuthenticated, setIsTeacherAuthenticated] = useState(ApiService.isLoggedInTeacher()); + const [isRoomRequireAuthentication, setRoomsRequireAuth] = useState(null); + const location = useLocation(); -const isLoggedIn = () => { - return ApiService.isLoggedIn(); -} + // Check login status every time the route changes + useEffect(() => { + const checkLoginStatus = () => { + setIsAuthenticated(ApiService.isLoggedIn()); + setIsTeacherAuthenticated(ApiService.isLoggedInTeacher()); + }; + + const fetchAuthenticatedRooms = async () => { + const data = await ApiService.getRoomsRequireAuth(); + setRoomsRequireAuth(data); + }; + + checkLoginStatus(); + fetchAuthenticatedRooms(); + }, [location]); + + const handleLogout = () => { + ApiService.logout(); + setIsAuthenticated(false); + setIsTeacherAuthenticated(false); + }; -function App() { return (
- -
- +
@@ -46,22 +64,46 @@ function App() { } /> {/* Pages espace enseignant */} - } /> - } /> - } /> - } /> - } /> - } /> - } /> + : } + /> + : } + /> + : } + /> + : } + /> {/* Pages espace étudiant */} - } /> + : } + /> + + {/* Pages authentification */} + } /> + + {/* Pages enregistrement */} + } /> + + {/* Pages rest password */} + } /> + + {/* Pages authentification sélection */} + } />
-
+
); -} +}; export default App; diff --git a/client/src/__tests__/services/WebsocketService.test.tsx b/client/src/__tests__/services/WebsocketService.test.tsx index 5a98e3e..7c3b4e0 100644 --- a/client/src/__tests__/services/WebsocketService.test.tsx +++ b/client/src/__tests__/services/WebsocketService.test.tsx @@ -23,13 +23,13 @@ describe('WebSocketService', () => { }); test('connect should initialize socket connection', () => { - WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL); + WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); expect(io).toHaveBeenCalled(); expect(WebsocketService['socket']).toBe(mockSocket); }); test('disconnect should terminate socket connection', () => { - mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL); + mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); expect(WebsocketService['socket']).toBeTruthy(); WebsocketService.disconnect(); expect(mockSocket.disconnect).toHaveBeenCalled(); @@ -37,7 +37,7 @@ describe('WebSocketService', () => { }); test('createRoom should emit create-room event', () => { - WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL); + WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); WebsocketService.createRoom(); expect(mockSocket.emit).toHaveBeenCalledWith('create-room'); }); @@ -46,7 +46,7 @@ describe('WebSocketService', () => { const roomName = 'testRoom'; const question = { id: 1, text: 'Sample Question' }; - mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL); + mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); WebsocketService.nextQuestion(roomName, question); expect(mockSocket.emit).toHaveBeenCalledWith('next-question', { roomName, question }); }); @@ -55,7 +55,7 @@ describe('WebSocketService', () => { const roomName = 'testRoom'; const questions = [{ id: 1, text: 'Sample Question' }]; - mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL); + mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); WebsocketService.launchStudentModeQuiz(roomName, questions); expect(mockSocket.emit).toHaveBeenCalledWith('launch-student-mode', { roomName, @@ -66,7 +66,7 @@ describe('WebSocketService', () => { test('endQuiz should emit end-quiz event with correct parameters', () => { const roomName = 'testRoom'; - mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL); + mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); WebsocketService.endQuiz(roomName); expect(mockSocket.emit).toHaveBeenCalledWith('end-quiz', { roomName }); }); @@ -75,7 +75,7 @@ describe('WebSocketService', () => { const enteredRoomName = 'testRoom'; const username = 'testUser'; - mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL); + mockSocket = WebsocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); WebsocketService.joinRoom(enteredRoomName, username); expect(mockSocket.emit).toHaveBeenCalledWith('join-room', { enteredRoomName, username }); }); diff --git a/client/src/components/Header/Header.tsx b/client/src/components/Header/Header.tsx index a59f806..016d23e 100644 --- a/client/src/components/Header/Header.tsx +++ b/client/src/components/Header/Header.tsx @@ -1,10 +1,10 @@ -import { useNavigate } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import * as React from 'react'; import './header.css'; import { Button } from '@mui/material'; interface HeaderProps { - isLoggedIn: () => boolean; + isLoggedIn: boolean; handleLogout: () => void; } @@ -20,7 +20,7 @@ const Header: React.FC = ({ isLoggedIn, handleLogout }) => { onClick={() => navigate('/')} /> - {isLoggedIn() && ( + {isLoggedIn && ( )} + + {!isLoggedIn && ( +
+ + + +
+ )}
); }; diff --git a/client/src/constants.tsx b/client/src/constants.tsx index 1fc104b..dccc503 100644 --- a/client/src/constants.tsx +++ b/client/src/constants.tsx @@ -1,11 +1,11 @@ // constants.tsx const ENV_VARIABLES = { MODE: 'production', - VITE_BACKEND_URL: import.meta.env.VITE_BACKEND_URL || "", - VITE_BACKEND_SOCKET_URL: import.meta.env.VITE_BACKEND_SOCKET_URL || "", + VITE_BACKEND_URL: process.env.VITE_BACKEND_URL || "", + BACKEND_URL: process.env.SITE_URL != undefined ? `${process.env.SITE_URL}${process.env.USE_PORTS ? `:${process.env.BACKEND_PORT}`:''}` : process.env.VITE_BACKEND_URL || '', + FRONTEND_URL: process.env.SITE_URL != undefined ? `${process.env.SITE_URL}${process.env.USE_PORTS ? `:${process.env.PORT}`:''}` : '' }; console.log(`ENV_VARIABLES.VITE_BACKEND_URL=${ENV_VARIABLES.VITE_BACKEND_URL}`); -console.log(`ENV_VARIABLES.VITE_BACKEND_SOCKET_URL=${ENV_VARIABLES.VITE_BACKEND_SOCKET_URL}`); export { ENV_VARIABLES }; diff --git a/client/src/pages/AuthManager/AuthDrawer.tsx b/client/src/pages/AuthManager/AuthDrawer.tsx new file mode 100644 index 0000000..093b7aa --- /dev/null +++ b/client/src/pages/AuthManager/AuthDrawer.tsx @@ -0,0 +1,61 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import './authDrawer.css'; +import SimpleLogin from './providers/SimpleLogin/Login'; +import authService from '../../services/AuthService'; +import { ENV_VARIABLES } from '../../constants'; +import ButtonAuth from './providers/OAuth-Oidc/ButtonAuth'; + +const AuthSelection: React.FC = () => { + const [authData, setAuthData] = useState(null); // Stocke les données d'auth + const navigate = useNavigate(); + + ENV_VARIABLES.VITE_BACKEND_URL; + // Récupérer les données d'authentification depuis l'API + useEffect(() => { + const fetchData = async () => { + const data = await authService.fetchAuthData(); + setAuthData(data); + }; + + fetchData(); + }, []); + + return ( +
+

Connexion

+ + {/* Formulaire de connexion Simple Login */} + {authData && authData['simpleauth'] && ( +
+ +
+ )} + + {/* Conteneur OAuth/OIDC */} + {authData && Object.keys(authData).some(key => authData[key].type === 'oidc' || authData[key].type === 'oauth') && ( +
+ {Object.keys(authData).map((providerKey) => { + const providerType = authData[providerKey].type; + if (providerType === 'oidc' || providerType === 'oauth') { + return ( + + ); + } + return null; + })} +
+ )} + +
+ +
+
+ ); +}; + +export default AuthSelection; diff --git a/client/src/pages/AuthManager/authDrawer.css b/client/src/pages/AuthManager/authDrawer.css new file mode 100644 index 0000000..1543fc2 --- /dev/null +++ b/client/src/pages/AuthManager/authDrawer.css @@ -0,0 +1,48 @@ +.auth-selection-page { + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + } + h1 { + margin-bottom: 20px; + } + .form-container{ + border: 1px solid #ccc; + border-radius: 8px; + padding: 15px; + margin: 10px 0; + width: 400px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + text-align: center; + } + form { + display: flex; + flex-direction: column; + } + input { + margin: 5px 0; + padding: 10px; + border: 1px solid #ccc; + border-radius: 4px; + } + button { + padding: 10px; + border: none; + border-radius: 4px; + background-color: #5271ff; + color: white; + cursor: pointer; + } + button:hover { + background-color: #5271ff; + } + .home-button-container{ + background: none; + color: black; + } + .home-button-container:hover{ + background: none; + color: black; + text-decoration: underline; + } \ No newline at end of file diff --git a/client/src/pages/AuthManager/callback/AuthCallback.tsx b/client/src/pages/AuthManager/callback/AuthCallback.tsx new file mode 100644 index 0000000..6206294 --- /dev/null +++ b/client/src/pages/AuthManager/callback/AuthCallback.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useEffect } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; +import apiService from '../../../services/ApiService'; + +const OAuthCallback: React.FC = () => { + const navigate = useNavigate(); + const location = useLocation(); + + useEffect(() => { + const searchParams = new URLSearchParams(location.search); + const user = searchParams.get('user'); + const username = searchParams.get('username'); + + if (user) { + apiService.saveToken(user); + apiService.saveUsername(username || ""); + navigate('/'); + } else { + navigate('/login'); + } + }, []); + + return
Loading...
; +}; + +export default OAuthCallback; diff --git a/client/src/pages/AuthManager/providers/OAuth-Oidc/ButtonAuth.tsx b/client/src/pages/AuthManager/providers/OAuth-Oidc/ButtonAuth.tsx new file mode 100644 index 0000000..c8f4efc --- /dev/null +++ b/client/src/pages/AuthManager/providers/OAuth-Oidc/ButtonAuth.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { ENV_VARIABLES } from '../../../../constants'; +import '../css/buttonAuth.css'; + +interface ButtonAuthContainerProps { + providerName: string; + providerType: 'oauth' | 'oidc'; +} + +const handleAuthLogin = (provider: string) => { + window.location.href = `${ENV_VARIABLES.BACKEND_URL}/api/auth/${provider}`; +}; + +const ButtonAuth: React.FC = ({ providerName, providerType }) => { + return ( + <> +
+

Se connecter avec {providerType.toUpperCase()}

+ +
+ + ); +}; + +export default ButtonAuth; \ No newline at end of file diff --git a/client/src/pages/Teacher/Register/Register.tsx b/client/src/pages/AuthManager/providers/SimpleLogin/Login.tsx similarity index 63% rename from client/src/pages/Teacher/Register/Register.tsx rename to client/src/pages/AuthManager/providers/SimpleLogin/Login.tsx index e09b316..6356d7a 100644 --- a/client/src/pages/Teacher/Register/Register.tsx +++ b/client/src/pages/AuthManager/providers/SimpleLogin/Login.tsx @@ -1,81 +1,88 @@ - -import { useNavigate } from 'react-router-dom'; - -// JoinRoom.tsx -import React, { useEffect, useState } from 'react'; - -import { TextField } from '@mui/material'; -import LoadingButton from '@mui/lab/LoadingButton'; - -import LoginContainer from 'src/components/LoginContainer/LoginContainer' -import ApiService from '../../../services/ApiService'; - -const Register: React.FC = () => { - const navigate = useNavigate(); - - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - - const [connectionError, setConnectionError] = useState(''); - const [isConnecting] = useState(false); - - useEffect(() => { - return () => { - - }; - }, []); - - const register = async () => { - const result = await ApiService.register(email, password); - - if (typeof result === 'string') { - setConnectionError(result); - return; - } - - navigate("/teacher/login") - }; - - - return ( - - - setEmail(e.target.value)} - placeholder="Adresse courriel" - sx={{ marginBottom: '1rem' }} - fullWidth - /> - - setPassword(e.target.value)} - placeholder="Mot de passe" - sx={{ marginBottom: '1rem' }} - fullWidth - /> - - - S'inscrire - - - - - ); -}; - -export default Register; +import { Link } from 'react-router-dom'; + +// JoinRoom.tsx +import React, { useEffect, useState } from 'react'; + +import '../css/simpleLogin.css'; +import { TextField } from '@mui/material'; +import LoadingButton from '@mui/lab/LoadingButton'; + +import LoginContainer from '../../../../components/LoginContainer/LoginContainer' +import ApiService from '../../../../services/ApiService'; + +const SimpleLogin: React.FC = () => { + + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + + const [connectionError, setConnectionError] = useState(''); + const [isConnecting] = useState(false); + + useEffect(() => { + return () => { + + }; + }, []); + + const login = async () => { + const result = await ApiService.login(email, password); + if (result !== true) { + setConnectionError(result); + return; + } + }; + + + return ( + + + setEmail(e.target.value)} + placeholder="Nom d'utilisateur" + sx={{ marginBottom: '1rem' }} + fullWidth + /> + + setPassword(e.target.value)} + placeholder="Nom de la salle" + sx={{ marginBottom: '1rem' }} + fullWidth + /> + + + Login + + +
+ + + Réinitialiser le mot de passe + + + + Créer un compte + + +
+ +
+ ); +}; + +export default SimpleLogin; diff --git a/client/src/pages/AuthManager/providers/SimpleLogin/Register.tsx b/client/src/pages/AuthManager/providers/SimpleLogin/Register.tsx new file mode 100644 index 0000000..d33527d --- /dev/null +++ b/client/src/pages/AuthManager/providers/SimpleLogin/Register.tsx @@ -0,0 +1,114 @@ +// JoinRoom.tsx +import React, { useEffect, useState } from 'react'; + +import { TextField, FormLabel, RadioGroup, FormControlLabel, Radio, Box } from '@mui/material'; +import LoadingButton from '@mui/lab/LoadingButton'; + +import LoginContainer from '../../../../components/LoginContainer/LoginContainer'; +import ApiService from '../../../../services/ApiService'; + +const Register: React.FC = () => { + + const [name, setName] = useState(''); // State for name + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [roles, setRoles] = useState(['student']); // Set 'student' as the default role + + const [connectionError, setConnectionError] = useState(''); + const [isConnecting] = useState(false); + + useEffect(() => { + return () => { }; + }, []); + + const handleRoleChange = (role: string) => { + setRoles([role]); // Update the roles array to contain the selected role + }; + + const isValidEmail = (email: string) => { + // Basic email format validation + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + }; + + const register = async () => { + if (!isValidEmail(email)) { + setConnectionError("Veuillez entrer une adresse email valide."); + return; + } + + const result = await ApiService.register(name, email, password, roles); + + if (result !== true) { + setConnectionError(result); + return; + } + }; + + return ( + + setName(e.target.value)} + placeholder="Votre nom" + sx={{ marginBottom: '1rem' }} + fullWidth + /> + + setEmail(e.target.value)} + placeholder="Adresse courriel" + sx={{ marginBottom: '1rem' }} + fullWidth + type="email" + error={!!connectionError && !isValidEmail(email)} + helperText={connectionError && !isValidEmail(email) ? "Adresse email invalide." : ""} + /> + + setPassword(e.target.value)} + placeholder="Mot de passe" + sx={{ marginBottom: '1rem' }} + fullWidth + /> + + + Choisir votre rôle + handleRoleChange(e.target.value)} + > + } label="Étudiant" /> + } label="Professeur" /> + + + + + S'inscrire + + + ); +}; + +export default Register; diff --git a/client/src/pages/AuthManager/providers/SimpleLogin/ResetPassword.tsx b/client/src/pages/AuthManager/providers/SimpleLogin/ResetPassword.tsx new file mode 100644 index 0000000..c33c9fa --- /dev/null +++ b/client/src/pages/AuthManager/providers/SimpleLogin/ResetPassword.tsx @@ -0,0 +1,68 @@ + +import { useNavigate } from 'react-router-dom'; + +// JoinRoom.tsx +import React, { useEffect, useState } from 'react'; + +import { TextField } from '@mui/material'; +import LoadingButton from '@mui/lab/LoadingButton'; + +import LoginContainer from '../../../../components/LoginContainer/LoginContainer' +import ApiService from '../../../../services/ApiService'; + +const ResetPassword: React.FC = () => { + const navigate = useNavigate(); + + const [email, setEmail] = useState(''); + + const [connectionError, setConnectionError] = useState(''); + const [isConnecting] = useState(false); + + useEffect(() => { + return () => { + + }; + }, []); + + const reset = async () => { + const result = await ApiService.resetPassword(email); + + if (!result) { + setConnectionError(result.toString()); + return; + } + + navigate("/login") + }; + + + return ( + + + setEmail(e.target.value)} + placeholder="Adresse courriel" + sx={{ marginBottom: '1rem' }} + fullWidth + /> + + + Réinitialiser le mot de passe + + + + ); +}; + +export default ResetPassword; diff --git a/client/src/pages/AuthManager/providers/css/buttonAuth.css b/client/src/pages/AuthManager/providers/css/buttonAuth.css new file mode 100644 index 0000000..98476ec --- /dev/null +++ b/client/src/pages/AuthManager/providers/css/buttonAuth.css @@ -0,0 +1,23 @@ +.provider-btn { + background-color: #ffffff; + border: 1px solid #ccc; + color: black; + margin: 4px 0 4px 0; +} + +.provider-btn:hover { + background-color: #dbdbdb; + border: 1px solid #ccc; + color: black; + margin: 4px 0 4px 0; +} + +.button-container { + border: 1px solid #ccc; + border-radius: 8px; + padding: 15px; + margin: 10px 0; + width: 400px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + text-align: center; +} \ No newline at end of file diff --git a/client/src/pages/AuthManager/providers/css/simpleLogin.css b/client/src/pages/AuthManager/providers/css/simpleLogin.css new file mode 100644 index 0000000..ddbebdb --- /dev/null +++ b/client/src/pages/AuthManager/providers/css/simpleLogin.css @@ -0,0 +1,17 @@ +.login-links { + padding-top: 10px; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +.login-links a { + padding: 4px; + color: #333; + text-decoration: none; +} + +.login-links a:hover { + text-decoration: underline; +} diff --git a/client/src/pages/Home/home.css b/client/src/pages/Home/home.css index 1fc8a8d..8a6a1a7 100644 --- a/client/src/pages/Home/home.css +++ b/client/src/pages/Home/home.css @@ -61,6 +61,25 @@ align-items: end; } +.auth-selection-btn { + position: absolute; + top: 20px; + right: 20px; +} +.auth-btn { + padding: 10px 20px; + background-color: #5271ff; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + transition: background-color 0.3s ease; +} +.auth-btn:hover { + background-color: #5976fa; +} + @media only screen and (max-width: 768px) { .btn-container { flex-direction: column; diff --git a/client/src/pages/Student/JoinRoom/JoinRoom.tsx b/client/src/pages/Student/JoinRoom/JoinRoom.tsx index f0ac8d7..65bc699 100644 --- a/client/src/pages/Student/JoinRoom/JoinRoom.tsx +++ b/client/src/pages/Student/JoinRoom/JoinRoom.tsx @@ -15,9 +15,11 @@ import LoadingButton from '@mui/lab/LoadingButton'; import LoginContainer from 'src/components/LoginContainer/LoginContainer' +import ApiService from '../../../services/ApiService' + const JoinRoom: React.FC = () => { const [roomName, setRoomName] = useState(''); - const [username, setUsername] = useState(''); + const [username, setUsername] = useState(ApiService.getUsername()); const [socket, setSocket] = useState(null); const [isWaitingForTeacher, setIsWaitingForTeacher] = useState(false); const [question, setQuestion] = useState(); @@ -34,8 +36,8 @@ const JoinRoom: React.FC = () => { }, []); const handleCreateSocket = () => { - console.log(`JoinRoom: handleCreateSocket: ${ENV_VARIABLES.VITE_BACKEND_SOCKET_URL}`); - const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL); + console.log(`JoinRoom: handleCreateSocket: ${ENV_VARIABLES.VITE_BACKEND_URL}`); + const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); socket.on('join-success', () => { setIsWaitingForTeacher(true); diff --git a/client/src/pages/Teacher/Dashboard/Dashboard.tsx b/client/src/pages/Teacher/Dashboard/Dashboard.tsx index f920d12..5e9ecba 100644 --- a/client/src/pages/Teacher/Dashboard/Dashboard.tsx +++ b/client/src/pages/Teacher/Dashboard/Dashboard.tsx @@ -78,7 +78,7 @@ const Dashboard: React.FC = () => { useEffect(() => { const fetchData = async () => { if (!ApiService.isLoggedIn()) { - navigate("/teacher/login"); + navigate("/login"); return; } else { diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index ae9bdfa..9d185dd 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -84,7 +84,7 @@ const ManageRoom: React.FC = () => { const createWebSocketRoom = () => { console.log('Creating WebSocket room...'); setConnectingError(''); - const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL); + const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL); socket.on('connect', () => { webSocketService.createRoom(); diff --git a/client/src/pages/Teacher/Share/Share.tsx b/client/src/pages/Teacher/Share/Share.tsx index 31bb72c..0dc4fe7 100644 --- a/client/src/pages/Teacher/Share/Share.tsx +++ b/client/src/pages/Teacher/Share/Share.tsx @@ -33,7 +33,7 @@ const Share: React.FC = () => { if (!ApiService.isLoggedIn()) { window.alert(`Vous n'êtes pas connecté.\nVeuillez vous connecter et revenir à ce lien`); - navigate("/teacher/login"); + navigate("/login"); return; } diff --git a/client/src/services/ApiService.tsx b/client/src/services/ApiService.tsx index ef124b4..6d6e561 100644 --- a/client/src/services/ApiService.tsx +++ b/client/src/services/ApiService.tsx @@ -1,8 +1,9 @@ import axios, { AxiosError, AxiosResponse } from 'axios'; +import { jwtDecode } from 'jwt-decode'; +import { ENV_VARIABLES } from '../constants'; import { FolderType } from 'src/Types/FolderType'; import { QuizType } from 'src/Types/QuizType'; -import { ENV_VARIABLES } from 'src/constants'; type ApiResponse = boolean | string; @@ -34,7 +35,7 @@ class ApiService { } // Helpers - private saveToken(token: string): void { + public saveToken(token: string): void { const now = new Date(); const object = { @@ -78,7 +79,71 @@ class ApiService { return true; } + public isLoggedInTeacher(): boolean { + const token = this.getToken(); + + + if (token == null) { + return false; + } + + try { + const decodedToken = jwtDecode(token) as { roles: string[] }; + + const userRoles = decodedToken.roles; + const requiredRole = 'teacher'; + + if (!userRoles || !userRoles.includes(requiredRole)) { + return false; + } + + // Update token expiry + this.saveToken(token); + + return true; + } catch (error) { + console.error("Error decoding token:", error); + return false; + } + } + + public saveUsername(username: string): void { + if (!username || username.length === 0) { + return; + } + + const object = { + username: username + } + + localStorage.setItem("username", JSON.stringify(object)); + } + + public getUsername(): string { + const objectStr = localStorage.getItem("username"); + + if (!objectStr) { + return ""; + } + + const object = JSON.parse(objectStr) + + return object.username; + } + + // Route to know if rooms need authentication to join + public async getRoomsRequireAuth(): Promise { + const url: string = this.constructRequestUrl(`/auth/getRoomsRequireAuth`); + const result: AxiosResponse = await axios.get(url); + + if (result.status == 200) { + return result.data.roomsRequireAuth; + } + return false; + } + public logout(): void { + localStorage.removeItem("username"); return localStorage.removeItem("jwt"); } @@ -88,21 +153,25 @@ class ApiService { * @returns true if successful * @returns A error string if unsuccessful, */ - public async register(email: string, password: string): Promise { + public async register(name: string, email: string, password: string, roles: string[]): Promise { try { if (!email || !password) { throw new Error(`L'email et le mot de passe sont requis.`); } - const url: string = this.constructRequestUrl(`/user/register`); + const url: string = this.constructRequestUrl(`/auth/simple-auth/register`); const headers = this.constructRequestHeaders(); - const body = { email, password }; + const body = { name, email, password, roles }; const result: AxiosResponse = await axios.post(url, body, { headers: headers }); - if (result.status !== 200) { - throw new Error(`L'enregistrement a échoué. Status: ${result.status}`); + console.log(result); + if (result.status == 200) { + window.location.href = result.request.responseURL; + } + else { + throw new Error(`La connexion a échoué. Status: ${result.status}`); } return true; @@ -124,44 +193,52 @@ class ApiService { * @returns true if successful * @returns A error string if unsuccessful, */ - public async login(email: string, password: string): Promise { - try { - - if (!email || !password) { - throw new Error(`L'email et le mot de passe sont requis.`); - } - - const url: string = this.constructRequestUrl(`/user/login`); - const headers = this.constructRequestHeaders(); - const body = { email, password }; - - const result: AxiosResponse = await axios.post(url, body, { headers: headers }); - - if (result.status !== 200) { - throw new Error(`La connexion a échoué. Status: ${result.status}`); - } - - this.saveToken(result.data.token); - - return true; - - } catch (error) { - console.log("Error details: ", error); - - console.log("axios.isAxiosError(error): ", axios.isAxiosError(error)); - - if (axios.isAxiosError(error)) { - const err = error as AxiosError; - if (err.status === 401) { - return 'Email ou mot de passe incorrect.'; - } - const data = err.response?.data as { error: string } | undefined; - return data?.error || 'Erreur serveur inconnue lors de la requête.'; - } - - return `Une erreur inattendue s'est produite.` + /** + * @returns true if successful + * @returns An error string if unsuccessful + */ +public async login(email: string, password: string): Promise { + try { + if (!email || !password) { + throw new Error("L'email et le mot de passe sont requis."); } + + const url: string = this.constructRequestUrl(`/auth/simple-auth/login`); + const headers = this.constructRequestHeaders(); + const body = { email, password }; + + const result: AxiosResponse = await axios.post(url, body, { headers: headers }); + + // If login is successful, redirect the user + if (result.status === 200) { + window.location.href = result.request.responseURL; + return true; + } else { + throw new Error(`La connexion a échoué. Statut: ${result.status}`); + } + } catch (error) { + console.log("Error details:", error); + + // Handle Axios-specific errors + if (axios.isAxiosError(error)) { + const err = error as AxiosError; + const responseData = err.response?.data as { message?: string } | undefined; + + // If there is a message field in the response, print it + if (responseData?.message) { + console.log("Backend error message:", responseData.message); + return responseData.message; + } + + // If no message is found, return a fallback message + return "Erreur serveur inconnue lors de la requête."; + } + + // Handle other non-Axios errors + return "Une erreur inattendue s'est produite."; } +} + /** * @returns true if successful @@ -174,7 +251,7 @@ class ApiService { throw new Error(`L'email est requis.`); } - const url: string = this.constructRequestUrl(`/user/reset-password`); + const url: string = this.constructRequestUrl(`/auth/simple-auth/reset-password`); const headers = this.constructRequestHeaders(); const body = { email }; @@ -210,7 +287,7 @@ class ApiService { throw new Error(`L'email, l'ancien et le nouveau mot de passe sont requis.`); } - const url: string = this.constructRequestUrl(`/user/change-password`); + const url: string = this.constructRequestUrl(`/auth/simple-auth/change-password`); const headers = this.constructRequestHeaders(); const body = { email, oldPassword, newPassword }; @@ -891,4 +968,4 @@ class ApiService { } const apiService = new ApiService(); -export default apiService; +export default apiService; \ No newline at end of file diff --git a/client/src/services/AuthService.tsx b/client/src/services/AuthService.tsx new file mode 100644 index 0000000..bca616d --- /dev/null +++ b/client/src/services/AuthService.tsx @@ -0,0 +1,28 @@ +import { ENV_VARIABLES } from '../constants'; + +class AuthService { + + private BASE_URL: string; + + constructor() { + this.BASE_URL = ENV_VARIABLES.VITE_BACKEND_URL; + } + + private constructRequestUrl(endpoint: string): string { + return `${this.BASE_URL}/api${endpoint}`; + } + + async fetchAuthData(){ + try { + const response = await fetch(this.constructRequestUrl('/auth/getActiveAuth')); + const data = await response.json(); + return data.authActive; + } catch (error) { + console.error('Erreur lors de la récupération des données d\'auth:', error); + } + }; + +} + +const authService = new AuthService(); +export default authService; \ No newline at end of file diff --git a/docker-compose-auth.yaml b/docker-compose-auth.yaml new file mode 100644 index 0000000..749c6b4 --- /dev/null +++ b/docker-compose-auth.yaml @@ -0,0 +1,96 @@ +version: '3' + +services: + + frontend: + build: + context: ./client + dockerfile: Dockerfile + container_name: frontend + ports: + - "5173:5173" + restart: always + + backend: + build: + context: ./server + dockerfile: Dockerfile + container_name: backend + ports: + - "3000:3000" + environment: + PORT: 3000 + MONGO_URI: "mongodb://mongo:27017/evaluetonsavoir" + MONGO_DATABASE: evaluetonsavoir + EMAIL_SERVICE: gmail + SENDER_EMAIL: infoevaluetonsavoir@gmail.com + EMAIL_PSW: 'vvml wmfr dkzb vjzb' + JWT_SECRET: haQdgd2jp09qb897GeBZyJetC8ECSpbFJe + SESSION_Secret: 'lookMomImQuizzing' + SITE_URL: http://localhost + FRONTEND_PORT: 5173 + USE_PORTS: false + AUTHENTICATED_ROOMS: false + volumes: + - ./server/auth_config.json:/usr/src/app/serveur/config/auth_config.json + depends_on: + - mongo + - keycloak + restart: always + + # Ce conteneur sert de routeur pour assurer le bon fonctionnement de l'application + nginx: + image: fuhrmanator/evaluetonsavoir-routeur:latest + container_name: nginx + ports: + - "80:80" + depends_on: + - backend + - frontend + restart: always + + # Ce conteneur est la base de données principale pour l'application + mongo: + image: mongo + container_name: mongo + ports: + - "27017:27017" + tty: true + volumes: + - mongodb_data:/data/db + restart: always + + # Ce conteneur assure que l'application est à jour en allant chercher s'il y a des mises à jours à chaque heure + watchtower: + image: containrrr/watchtower + container_name: watchtower + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + - TZ=America/Montreal + - WATCHTOWER_CLEANUP=true + - WATCHTOWER_DEBUG=true + - WATCHTOWER_INCLUDE_RESTARTING=true + - WATCHTOWER_SCHEDULE=0 0 5 * * * # At 5 am everyday + restart: always + + keycloak: + container_name: keycloak + image: quay.io/keycloak/keycloak:latest + environment: + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin123 + KC_HEALTH_ENABLED: 'true' + KC_FEATURES: preview + ports: + - "8080:8080" + volumes: + - ./oauth-tester/config.json:/opt/keycloak/data/import/realm-config.json + command: + - start-dev + - --import-realm + - --hostname-strict=false + +volumes: + mongodb_data: + external: false diff --git a/docker-compose.yaml b/docker-compose.yaml index 24bd3a6..0d8d61a 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,19 +1,20 @@ +version: '3' + services: frontend: - image: fuhrmanator/evaluetonsavoir-frontend:latest + build: + context: ./client + dockerfile: Dockerfile container_name: frontend - environment: - # Define empty VITE_BACKEND_URL because it's production - - VITE_BACKEND_URL= - # Define empty VITE_BACKEND_SOCKET_URL so it will default to window.location.host - - VITE_BACKEND_SOCKET_URL= ports: - "5173:5173" restart: always backend: - image: fuhrmanator/evaluetonsavoir-backend:latest + build: + context: ./server + dockerfile: Dockerfile container_name: backend ports: - "3000:3000" @@ -25,9 +26,16 @@ services: SENDER_EMAIL: infoevaluetonsavoir@gmail.com EMAIL_PSW: 'vvml wmfr dkzb vjzb' JWT_SECRET: haQdgd2jp09qb897GeBZyJetC8ECSpbFJe - FRONTEND_URL: "http://localhost:5173" + SESSION_Secret: 'lookMomImQuizzing' + SITE_URL: http://localhost + FRONTEND_PORT: 5173 + USE_PORTS: false + AUTHENTICATED_ROOMS: false + volumes: + - ./server/auth_config.json:/usr/src/app/serveur/config/auth_config.json depends_on: - mongo + - keycloak restart: always # Ce conteneur sert de routeur pour assurer le bon fonctionnement de l'application @@ -79,6 +87,23 @@ services: - WATCHTOWER_INCLUDE_RESTARTING=true restart: "no" + keycloak: + container_name: keycloak + image: quay.io/keycloak/keycloak:latest + environment: + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin123 + KC_HEALTH_ENABLED: 'true' + KC_FEATURES: preview + ports: + - "8080:8080" + volumes: + - ./oauth-tester/config.json:/opt/keycloak/data/import/realm-config.json + command: + - start-dev + - --import-realm + - --hostname-strict=false + volumes: mongodb_data: external: false diff --git a/oauth-tester/config.json b/oauth-tester/config.json new file mode 100644 index 0000000..ef8f778 --- /dev/null +++ b/oauth-tester/config.json @@ -0,0 +1,96 @@ +{ + "id": "test-realm", + "realm": "EvalueTonSavoir", + "enabled": true, + "users": [ + { + "username": "teacher", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "teacher123", + "temporary": false + } + ], + "groups": ["teachers"] + }, + { + "username": "student", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "student123", + "temporary": false + } + ], + "groups": ["students"] + } + ], + "groups": [ + { + "name": "teachers", + "attributes": { + "role": ["teacher"] + } + }, + { + "name": "students", + "attributes": { + "role": ["student"] + } + } + ], + "roles": { + "realm": [ + { + "name": "teacher", + "description": "Teacher role" + }, + { + "name": "student", + "description": "Student role" + } + ] + }, + "clients": [ + { + "clientId": "evaluetonsavoir-client", + "enabled": true, + "publicClient": false, + "clientAuthenticatorType": "client-secret", + "secret": "your-secret-key-123", + "redirectUris": ["http://localhost:5173/*","http://localhost/*"], + "webOrigins": ["http://localhost:5173","http://localhost/"] + } + ], + "clientScopes": [ + { + "name": "group", + "description": "Group scope for access control", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "name": "group mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "group", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "group", + "jsonType.label": "String" + } + } + ] + } + ], + "defaultDefaultClientScopes": ["group"] +} \ No newline at end of file diff --git a/server/.env.example b/server/.env.example index 8608d36..3ab7212 100644 --- a/server/.env.example +++ b/server/.env.example @@ -14,4 +14,10 @@ EMAIL_PSW='vvml wmfr dkzb vjzb' JWT_SECRET=TOKEN! # Pour creer les liens images -FRONTEND_URL=http://localhost:5173 +SESSION_Secret='session_secret' + +SITE_URL=http://localhost +FRONTEND_PORT=5173 +USE_PORTS=false + +AUTHENTICATED_ROOMS=false diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..47c9c3b --- /dev/null +++ b/server/.gitignore @@ -0,0 +1 @@ +auth_config.json \ No newline at end of file diff --git a/server/__tests__/auth.test.js b/server/__tests__/auth.test.js new file mode 100644 index 0000000..7a97c69 --- /dev/null +++ b/server/__tests__/auth.test.js @@ -0,0 +1,244 @@ +const request = require("supertest"); +const AuthConfig = require("../config/auth.js"); +const AuthManager = require("../auth/auth-manager.js"); + +const mockConfig = { + auth: { + passportjs: [ + { + provider1: { + type: "oauth", + OAUTH_AUTHORIZATION_URL: "https://www.testurl.com/oauth2/authorize", + OAUTH_TOKEN_URL: "https://www.testurl.com/oauth2/token", + OAUTH_USERINFO_URL: "https://www.testurl.com/oauth2/userinfo/", + OAUTH_CLIENT_ID: "your_oauth_client_id", + OAUTH_CLIENT_SECRET: "your_oauth_client_secret", + OAUTH_ADD_SCOPE: "scopes", + OAUTH_ROLE_TEACHER_VALUE: "teacher-claim-value", + OAUTH_ROLE_STUDENT_VALUE: "student-claim-value", + }, + }, + { + provider2: { + type: "oidc", + OIDC_CLIENT_ID: "your_oidc_client_id", + OIDC_CLIENT_SECRET: "your_oidc_client_secret", + OIDC_CONFIG_URL: "https://your-issuer.com", + OIDC_ADD_SCOPE: "groups", + OIDC_ROLE_TEACHER_VALUE: "teacher-claim-value", + OIDC_ROLE_STUDENT_VALUE: "student-claim-value", + }, + }, + ], + "simpleauth": { + enabled: true, + name: "provider3", + SESSION_SECRET: "your_session_secret", + }, + }, +}; + +// Créez une instance de AuthConfig en utilisant la configuration mockée +describe( + "AuthConfig Class Tests", + () => { + let authConfigInstance; + + // Initialisez l'instance avec la configuration mockée + beforeAll(() => { + authConfigInstance = new AuthConfig(); + authConfigInstance.loadConfigTest(mockConfig); // On injecte la configuration mockée + }); + + it("devrait retourner la configuration PassportJS", () => { + const config = authConfigInstance.getPassportJSConfig(); + expect(config).toHaveProperty("provider1"); + expect(config).toHaveProperty("provider2"); + }); + + it("devrait retourner la configuration Simple Login", () => { + const config = authConfigInstance.getSimpleLoginConfig(); + expect(config).toHaveProperty("name", "provider3"); + expect(config).toHaveProperty("SESSION_SECRET", "your_session_secret"); + }); + + it("devrait retourner les providers OAuth", () => { + const oauthProviders = authConfigInstance.getOAuthProviders(); + expect(Array.isArray(oauthProviders)).toBe(true); + expect(oauthProviders.length).toBe(1); // Il y a un seul provider OAuth + expect(oauthProviders[0]).toHaveProperty("provider1"); + }); + + it("devrait valider la configuration des providers", () => { + expect(() => authConfigInstance.validateProvidersConfig()).not.toThrow(); + }); + + it("devrait lever une erreur si une configuration manque", () => { + const invalidMockConfig = { + auth: { + passportjs: [ + { + provider1: { + type: "oauth", + OAUTH_CLIENT_ID: "your_oauth_client_id", // Il manque des champs nécessaires + }, + }, + ], + }, + }; + + const instanceWithInvalidConfig = new AuthConfig(); + instanceWithInvalidConfig.loadConfigTest(invalidMockConfig); + + // Vérifiez que l'erreur est lancée avec les champs manquants corrects + expect(() => instanceWithInvalidConfig.validateProvidersConfig()).toThrow( + new Error(`Configuration invalide pour les providers suivants : [ + { + "provider": "provider1", + "missingFields": [ + "OAUTH_AUTHORIZATION_URL", + "OAUTH_TOKEN_URL", + "OAUTH_USERINFO_URL", + "OAUTH_CLIENT_SECRET", + "OAUTH_ROLE_TEACHER_VALUE", + "OAUTH_ROLE_STUDENT_VALUE" + ] + } +]`) + ); + }); + }, + + describe("Auth Module Registration", () => { + let expressMock = jest.mock("express"); + expressMock.use = () => {} + expressMock.get = () => {} + + let authConfigInstance; + let logSpy; + + // Initialisez l'instance avec la configuration mockée + beforeAll(() => { + authConfigInstance = new AuthConfig(); + }); + + it("should load valid modules", () => { + const logSpy = jest.spyOn(global.console, "error"); + const validModule = { + auth: { + passportjs: [ + { + provider1: { + type: "oauth", + OAUTH_AUTHORIZATION_URL: + "https://www.testurl.com/oauth2/authorize", + OAUTH_TOKEN_URL: "https://www.testurl.com/oauth2/token", + OAUTH_USERINFO_URL: "https://www.testurl.com/oauth2/userinfo/", + OAUTH_CLIENT_ID: "your_oauth_client_id", + OAUTH_CLIENT_SECRET: "your_oauth_client_secret", + OAUTH_ADD_SCOPE: "scopes", + OAUTH_ROLE_TEACHER_VALUE: "teacher-claim-value", + OAUTH_ROLE_STUDENT_VALUE: "student-claim-value", + }, + provider2: { + type: "oauth", + OAUTH_AUTHORIZATION_URL: + "https://www.testurl.com/oauth2/authorize", + OAUTH_TOKEN_URL: "https://www.testurl.com/oauth2/token", + OAUTH_USERINFO_URL: "https://www.testurl.com/oauth2/userinfo/", + OAUTH_CLIENT_ID: "your_oauth_client_id", + OAUTH_CLIENT_SECRET: "your_oauth_client_secret", + OAUTH_ADD_SCOPE: "scopes", + OAUTH_ROLE_TEACHER_VALUE: "teacher-claim-value", + OAUTH_ROLE_STUDENT_VALUE: "student-claim-value", + }, + }, + ], + }, + }; + authConfigInstance.loadConfigTest(validModule); // On injecte la configuration mockée + authmanagerInstance = new AuthManager(expressMock,authConfigInstance.config); + expect(logSpy).toHaveBeenCalledTimes(0); + logSpy.mockClear(); + }); + + it("should not load invalid modules", () => { + const logSpy = jest.spyOn(global.console, "error"); + const invalidModule = { + auth: { + ModuleX:{} + }, + }; + authConfigInstance.loadConfigTest(invalidModule); // On injecte la configuration mockée + authmanagerInstance = new AuthManager(expressMock,authConfigInstance.config); + expect(logSpy).toHaveBeenCalledTimes(1); + logSpy.mockClear(); + }); + + + it("should not load invalid provider from passport", () => { + const logSpy = jest.spyOn(global.console, "error"); + const validModuleInvalidProvider = { + auth: { + passportjs: [ + { + provider1: { + type: "x", + OAUTH_AUTHORIZATION_URL: + "https://www.testurl.com/oauth2/authorize", + OAUTH_TOKEN_URL: "https://www.testurl.com/oauth2/token", + OAUTH_USERINFO_URL: "https://www.testurl.com/oauth2/userinfo/", + OAUTH_CLIENT_ID: "your_oauth_client_id", + OAUTH_CLIENT_SECRET: "your_oauth_client_secret", + OAUTH_ADD_SCOPE: "scopes", + OAUTH_ROLE_TEACHER_VALUE: "teacher-claim-value", + OAUTH_ROLE_STUDENT_VALUE: "student-claim-value", + }, + }, + ], + }, + }; + authConfigInstance.loadConfigTest(validModuleInvalidProvider); // On injecte la configuration mockée + authmanagerInstance = new AuthManager(expressMock,authConfigInstance.config); + expect(logSpy).toHaveBeenCalledTimes(2); + logSpy.mockClear(); + }); + }) +); + +describe( + "Rooms requiring authentication", () => { + // Making a copy of env variables to restore them later + const OLD_ENV_VARIABLES = process.env; + + let authConfigInstance; + + beforeAll(() => { + authConfigInstance = new AuthConfig(); + }); + + // Clearing cache just in case + beforeEach(() => { + jest.resetModules(); + process.env = { ...OLD_ENV_VARIABLES }; + }); + + // Resetting the old values + afterAll(() => { + process.env = OLD_ENV_VARIABLES; + }); + + // tests cases as [environment variable value, expected value] + const cases = [["true", true], ["false", false], ["", false], ["other_than_true_false", false]]; + test.each(cases)( + "Given %p as AUTHENTICATED_ROOMS environment variable value, returns %p", + (envVarArg, expectedResult) => { + process.env.AUTHENTICATED_ROOMS = envVarArg; + const isAuthRequired = authConfigInstance.getRoomsRequireAuth(); + + expect(isAuthRequired).toEqual(expectedResult); + } + ); + + } +) diff --git a/server/app.js b/server/app.js index 570ee8b..cba0f12 100644 --- a/server/app.js +++ b/server/app.js @@ -39,17 +39,25 @@ module.exports.images = imagesControllerInstance; const userRouter = require('./routers/users.js'); const folderRouter = require('./routers/folders.js'); const quizRouter = require('./routers/quiz.js'); -const imagesRouter = require('./routers/images.js'); +const imagesRouter = require('./routers/images.js') +const AuthManager = require('./auth/auth-manager.js') +const authRouter = require('./routers/auth.js') // Setup environment dotenv.config(); -const isDev = process.env.NODE_ENV === 'development'; + +// Setup urls from configs +const use_ports = (process.env['USE_PORTS'] || 'false').toLowerCase() == "true" +process.env['FRONTEND_URL'] = process.env['SITE_URL'] + (use_ports ? `:${process.env['FRONTEND_PORT']}`:"") +process.env['BACKEND_URL'] = process.env['SITE_URL'] + (use_ports ? `:${process.env['PORT']}`:"") + const errorHandler = require("./middleware/errorHandler.js"); // Start app const app = express(); const cors = require("cors"); const bodyParser = require('body-parser'); +let isDev = process.env.NODE_ENV === 'development'; const configureServer = (httpServer, isDev) => { console.log(`Configuring server with isDev: ${isDev}`); @@ -84,8 +92,19 @@ app.use('/api/user', userRouter); app.use('/api/folder', folderRouter); app.use('/api/quiz', quizRouter); app.use('/api/image', imagesRouter); +app.use('/api/auth', authRouter); -app.use(errorHandler); +// Add Auths methods +const session = require('express-session'); +app.use(session({ + secret: process.env['SESSION_Secret'], + resave: false, + saveUninitialized: false, + cookie: { secure: process.env.NODE_ENV === 'production' } +})); + +authManager = new AuthManager(app,null,userModel) +app.use(errorHandler) // Start server async function start() { diff --git a/server/auth/auth-manager.js b/server/auth/auth-manager.js new file mode 100644 index 0000000..f3288f4 --- /dev/null +++ b/server/auth/auth-manager.js @@ -0,0 +1,66 @@ +const fs = require('fs'); +const AuthConfig = require('../config/auth.js'); +const jwt = require('../middleware/jwtToken.js'); +const emailer = require('../config/email.js'); +const model = require('../controllers/users.js'); + +class AuthManager{ + constructor(expressapp,configs=null,userModel){ + this.modules = [] + this.app = expressapp + + this.configs = configs ?? (new AuthConfig()).loadConfig() + this.addModules() + this.registerAuths() + this.simpleregister = userModel; + } + + getUserModel(){ + return this.simpleregister; + } + + async addModules(){ + for(const module in this.configs.auth){ + this.addModule(module) + } + } + + async addModule(name){ + const modulePath = `${process.cwd()}/auth/modules/${name}.js` + + if(fs.existsSync(modulePath)){ + const Module = require(modulePath); + this.modules.push(new Module(this,this.configs.auth[name])); + console.info(`Module d'authentification '${name}' ajouté`) + } else{ + console.error(`Le module d'authentification ${name} n'as pas été chargé car il est introuvable`) + } + } + + async registerAuths(){ + for(const module of this.modules){ + try{ + module.registerAuth(this.app) + } catch(error){ + console.error(`L'enregistrement du module ${module} a échoué.`) + } + } + } + + async login(userInfo,req,res,next){ + const tokenToSave = jwt.create(userInfo.email, userInfo._id,userInfo.roles); + res.redirect(`/auth/callback?user=${tokenToSave}&username=${userInfo.name}`); + console.info(`L'utilisateur '${userInfo.name}' vient de se connecter`) + } + + async register(userInfos){ + if (!userInfos.email || !userInfos.password) { + throw new AppError(MISSING_REQUIRED_PARAMETER); + } + const user = await this.simpleregister.register(userInfos); + emailer.registerConfirmation(user.email) + return user + } +} + +module.exports = AuthManager; \ No newline at end of file diff --git a/server/auth/modules/passport-providers/oauth.js b/server/auth/modules/passport-providers/oauth.js new file mode 100644 index 0000000..fe76922 --- /dev/null +++ b/server/auth/modules/passport-providers/oauth.js @@ -0,0 +1,101 @@ +var OAuth2Strategy = require('passport-oauth2') +var authUserAssoc = require('../../../models/authUserAssociation') +var users = require('../../../models/users') +var { hasNestedValue } = require('../../../utils') +var jwt = require('../../../middleware/jwtToken') + +class PassportOAuth { + constructor(passportjs, auth_name) { + this.passportjs = passportjs + this.auth_name = auth_name + } + + register(app, passport, endpoint, name, provider) { + const cb_url = `${process.env['BACKEND_URL']}${endpoint}/${name}/callback` + const self = this + const scope = 'openid profile email offline_access' + ` ${provider.OAUTH_ADD_SCOPE}`; + + passport.use(name, new OAuth2Strategy({ + authorizationURL: provider.OAUTH_AUTHORIZATION_URL, + tokenURL: provider.OAUTH_TOKEN_URL, + clientID: provider.OAUTH_CLIENT_ID, + clientSecret: provider.OAUTH_CLIENT_SECRET, + callbackURL: cb_url, + passReqToCallback: true + }, + async function (req, accessToken, refreshToken, params, profile, done) { + try { + const userInfoResponse = await fetch(provider.OAUTH_USERINFO_URL, { + headers: { 'Authorization': `Bearer ${accessToken}` } + }); + const userInfo = await userInfoResponse.json(); + + let received_user = { + auth_id: userInfo.sub, + email: userInfo.email, + name: userInfo.name, + roles: [] + }; + + if (hasNestedValue(userInfo, provider.OAUTH_ROLE_TEACHER_VALUE)) received_user.roles.push('teacher') + if (hasNestedValue(userInfo, provider.OAUTH_ROLE_STUDENT_VALUE)) received_user.roles.push('student') + + const user_association = await authUserAssoc.find_user_association(self.auth_name, received_user.auth_id) + + let user_account + if (user_association) { + user_account = await users.getById(user_association.user_id) + } + else { + let user_id = await users.getId(received_user.email) + if (user_id) { + user_account = await users.getById(user_id); + } else { + received_user.password = users.generatePassword() + user_account = await self.passportjs.register(received_user) + } + await authUserAssoc.link(self.auth_name, received_user.auth_id, user_account._id) + } + + user_account.name = received_user.name + user_account.roles = received_user.roles + await users.editUser(user_account) + + // Store the tokens in the session + req.session.oauth2Tokens = { + accessToken: accessToken, + refreshToken: refreshToken, + expiresIn: params.expires_in + }; + + return done(null, user_account); + } catch (error) { + console.error(`Erreur dans la strategie OAuth2 '${name}' : ${error}`); + return done(error); + } + })); + + app.get(`${endpoint}/${name}`, (req, res, next) => { + passport.authenticate(name, { + scope: scope, + prompt: 'consent' + })(req, res, next); + }); + + app.get(`${endpoint}/${name}/callback`, + (req, res, next) => { + passport.authenticate(name, { failureRedirect: '/login' })(req, res, next); + }, + (req, res) => { + if (req.user) { + self.passportjs.authenticate(req.user, req, res) + } else { + res.status(401).json({ error: "L'authentification a échoué" }); + } + } + ); + console.info(`Ajout de la connexion : ${name}(OAuth)`) + } +} + +module.exports = PassportOAuth; diff --git a/server/auth/modules/passport-providers/oidc.js b/server/auth/modules/passport-providers/oidc.js new file mode 100644 index 0000000..77a557c --- /dev/null +++ b/server/auth/modules/passport-providers/oidc.js @@ -0,0 +1,103 @@ +var OpenIDConnectStrategy = require('passport-openidconnect') +var authUserAssoc = require('../../../models/authUserAssociation') +var users = require('../../../models/users') +var { hasNestedValue } = require('../../../utils') +var jwt = require('../../../middleware/jwtToken') + +class PassportOpenIDConnect { + constructor(passportjs, auth_name) { + this.passportjs = passportjs + this.auth_name = auth_name + } + + async getConfigFromConfigURL(name, provider) { + try { + const config = await fetch(provider.OIDC_CONFIG_URL) + return await config.json() + } catch (error) { + console.error(`Les informations de connexions de la connexion OIDC ${name} n'ont pu être chargées.`) + } + } + + async register(app, passport, endpoint, name, provider) { + + const config = await this.getConfigFromConfigURL(name, provider) + const cb_url = `${process.env['BACKEND_URL']}${endpoint}/${name}/callback` + const self = this + const scope = 'openid profile email ' + `${provider.OIDC_ADD_SCOPE}` + + passport.use(name, new OpenIDConnectStrategy({ + issuer: config.issuer, + authorizationURL: config.authorization_endpoint, + tokenURL: config.token_endpoint, + userInfoURL: config.userinfo_endpoint, + clientID: provider.OIDC_CLIENT_ID, + clientSecret: provider.OIDC_CLIENT_SECRET, + callbackURL: cb_url, + passReqToCallback: true, + scope: scope, + }, + // patch pour la librairie permet d'obtenir les groupes, PR en cours mais "morte" : https://github.com/jaredhanson/passport-openidconnect/pull/101 + async function (req, issuer, profile, times, tok, done) { + try { + const received_user = { + auth_id: profile.id, + email: profile.emails[0].value, + name: profile.name.givenName, + roles: [] + }; + + + if (hasNestedValue(profile, provider.OIDC_ROLE_TEACHER_VALUE)) received_user.roles.push('teacher') + if (hasNestedValue(profile, provider.OIDC_ROLE_STUDENT_VALUE)) received_user.roles.push('student') + + const user_association = await authUserAssoc.find_user_association(self.auth_name, received_user.auth_id) + + let user_account + if (user_association) { + user_account = await users.getById(user_association.user_id) + } + else { + let user_id = await users.getId(received_user.email) + if (user_id) { + user_account = await users.getById(user_id); + } else { + received_user.password = users.generatePassword() + user_account = await self.passportjs.register(received_user) + } + await authUserAssoc.link(self.auth_name, received_user.auth_id, user_account._id) + } + + user_account.name = received_user.name + user_account.roles = received_user.roles + await users.editUser(user_account) + + return done(null, user_account); + } catch (error) { + } + })); + + app.get(`${endpoint}/${name}`, (req, res, next) => { + passport.authenticate(name, { + scope: scope, + prompt: 'consent' + })(req, res, next); + }); + + app.get(`${endpoint}/${name}/callback`, + (req, res, next) => { + passport.authenticate(name, { failureRedirect: '/login' })(req, res, next); + }, + (req, res) => { + if (req.user) { + self.passportjs.authenticate(req.user, req, res) + } else { + res.status(401).json({ error: "L'authentification a échoué" }); + } + } + ); + console.info(`Ajout de la connexion : ${name}(OIDC)`) + } +} + +module.exports = PassportOpenIDConnect; diff --git a/server/auth/modules/passportjs.js b/server/auth/modules/passportjs.js new file mode 100644 index 0000000..3d2d46c --- /dev/null +++ b/server/auth/modules/passportjs.js @@ -0,0 +1,63 @@ +var passport = require('passport') +var authprovider = require('../../models/authProvider') + +class PassportJs{ + constructor(authmanager,settings){ + this.authmanager = authmanager + this.registeredProviders = {} + this.providers = settings + this.endpoint = "/api/auth" + } + + async registerAuth(expressapp){ + expressapp.use(passport.initialize()); + expressapp.use(passport.session()); + + for(const p of this.providers){ + for(const [name,provider] of Object.entries(p)){ + const auth_id = `passportjs_${provider.type}_${name}` + + if(!(provider.type in this.registeredProviders)){ + this.registerProvider(provider.type,auth_id) + } + try{ + this.registeredProviders[provider.type].register(expressapp,passport,this.endpoint,name,provider) + authprovider.create(auth_id) + } catch(error){ + console.error(`La connexion ${name} de type ${provider.type} n'as pu être chargé.`) + } + } + } + + passport.serializeUser(function(user, done) { + done(null, user); + }); + + passport.deserializeUser(function(user, done) { + done(null, user); + }); + } + + async registerProvider(providerType,auth_id){ + try{ + const providerPath = `${process.cwd()}/auth/modules/passport-providers/${providerType}.js` + const Provider = require(providerPath); + this.registeredProviders[providerType]= new Provider(this,auth_id) + console.info(`Le type de connexion '${providerType}' a été ajouté dans passportjs.`) + } catch(error){ + console.error(`Le type de connexion '${providerType}' n'as pas pu être chargé dans passportjs.`) + } + } + + + register(userInfos){ + return this.authmanager.register(userInfos) + } + + authenticate(userInfo,req,res,next){ + return this.authmanager.login(userInfo,req,res,next) + } + +} + +module.exports = PassportJs; \ No newline at end of file diff --git a/server/auth/modules/simpleauth.js b/server/auth/modules/simpleauth.js new file mode 100644 index 0000000..6836c4c --- /dev/null +++ b/server/auth/modules/simpleauth.js @@ -0,0 +1,125 @@ +const jwt = require('../../middleware/jwtToken.js'); +const emailer = require('../../config/email.js'); + +const model = require('../../models/users.js'); +const AppError = require('../../middleware/AppError.js'); +const { MISSING_REQUIRED_PARAMETER, LOGIN_CREDENTIALS_ERROR, GENERATE_PASSWORD_ERROR, UPDATE_PASSWORD_ERROR } = require('../../constants/errorCodes'); +const { name } = require('../../models/authProvider.js'); + +class SimpleAuth { + constructor(authmanager, settings) { + this.authmanager = authmanager + this.providers = settings + this.endpoint = "/api/auth/simple-auth" + } + + async registerAuth(expressapp) { + try { + expressapp.post(`${this.endpoint}/register`, (req, res, next) => this.register(this, req, res)); + expressapp.post(`${this.endpoint}/login`, (req, res, next) => this.authenticate(this, req, res)); + expressapp.post(`${this.endpoint}/reset-password`, (req, res, next) => this.resetPassword(this, req, res)); + expressapp.post(`${this.endpoint}/change-password`, jwt.authenticate, (req, res, next) => this.changePassword(this, req, res)); + } catch (error) { + console.error(`La connexion ${name} de type ${provider.type} n'as pu être chargé.`) + } + } + + async register(self, req, res) { + try { + let userInfos = { + name: req.body.name, + email: req.body.email, + password: req.body.password, + roles: req.body.roles + } + let user = await self.authmanager.register(userInfos) + if (user) res.redirect("/login") + } + catch (error) { + return res.status(400).json({ + message: error.message + }); + } + } + + async authenticate(self, req, res, next) { + try { + const { email, password } = req.body; + + if (!email || !password) { + const error = new Error("Email or password is missing"); + error.statusCode = 400; + throw error; + } + + const userModel = self.authmanager.getUserModel(); + const user = userModel.login(email, password); + + await self.authmanager.login(user, req, res, next); + } catch (error) { + const statusCode = error.statusCode || 500; + const message = error.message || "An internal server error occurred"; + + console.error(error); + return res.status(statusCode).json({ message }); + } + } + + async resetPassword(self, req, res, next) { + try { + const { email } = req.body; + + if (!email) { + throw new AppError(MISSING_REQUIRED_PARAMETER); + } + + const newPassword = await model.resetPassword(email); + + if (!newPassword) { + throw new AppError(GENERATE_PASSWORD_ERROR); + } + + emailer.newPasswordConfirmation(email, newPassword); + + return res.status(200).json({ + message: 'Nouveau mot de passe envoyé par courriel.' + }); + } + catch (error) { + return next(error); + } + } + + async changePassword(self, req, res, next) { + try { + const { email, oldPassword, newPassword } = req.body; + + if (!email || !oldPassword || !newPassword) { + throw new AppError(MISSING_REQUIRED_PARAMETER); + } + + // verify creds first + const user = await model.login(email, oldPassword); + + if (!user) { + throw new AppError(LOGIN_CREDENTIALS_ERROR); + } + + const password = await model.changePassword(email, newPassword) + + if (!password) { + throw new AppError(UPDATE_PASSWORD_ERROR); + } + + return res.status(200).json({ + message: 'Mot de passe changé avec succès.' + }); + } + catch (error) { + return next(error); + } + } + +} + +module.exports = SimpleAuth; \ No newline at end of file diff --git a/server/auth_config.json.example b/server/auth_config.json.example new file mode 100644 index 0000000..2a8fb11 --- /dev/null +++ b/server/auth_config.json.example @@ -0,0 +1,26 @@ +{ + "auth": { + "passportjs": + [ + { + "oidc_local": { + "type": "oidc", + "OIDC_CONFIG_URL": "http://localhost:8080/realms/EvalueTonSavoir/.well-known/openid-configuration", + "OIDC_CLIENT_ID": "evaluetonsavoir-client", + "OIDC_CLIENT_SECRET": "your-secret-key-123", + "OIDC_ADD_SCOPE": "group", + "OIDC_ROLE_TEACHER_VALUE": "teachers", + "OIDC_ROLE_STUDENT_VALUE": "students" + } + } + ], + "simple-login": { + "enabled": true, + "name": "provider3", + "SESSION_SECRET": "your_session_secret" + }, + "Module X":{ + + } + } +} \ No newline at end of file diff --git a/server/config/auth.js b/server/config/auth.js new file mode 100644 index 0000000..2b7c4df --- /dev/null +++ b/server/config/auth.js @@ -0,0 +1,192 @@ +const fs = require('fs'); +const path = require('path'); +const pathAuthConfig = './auth_config.json'; + +const configPath = path.join(process.cwd(), pathAuthConfig); + +class AuthConfig { + + config = null; + + + // Méthode pour lire le fichier de configuration JSON + loadConfig() { + try { + const configData = fs.readFileSync(configPath, 'utf-8'); + this.config = JSON.parse(configData); + } catch (error) { + console.error("Erreur lors de la lecture du fichier de configuration. Ne pas se fier si vous n'avez pas mit de fichier de configuration."); + this.config = {} + } + return this.config + } + + // Méthode pour load le fichier de test + loadConfigTest(mockConfig) { + this.config = mockConfig; + } + + // Méthode pour retourner la configuration des fournisseurs PassportJS + getPassportJSConfig() { + if (this.config && this.config.auth && this.config.auth.passportjs) { + const passportConfig = {}; + + this.config.auth.passportjs.forEach(provider => { + const providerName = Object.keys(provider)[0]; + passportConfig[providerName] = provider[providerName]; + }); + + return passportConfig; + } else { + return { error: "Aucune configuration PassportJS disponible." }; + } + } + + // Méthode pour retourner la configuration de Simple Login + getSimpleLoginConfig() { + if (this.config && this.config.auth && this.config.auth["simpleauth"]) { + return this.config.auth["simpleauth"]; + } else { + return { error: "Aucune configuration Simple Login disponible." }; + } + } + + // Méthode pour retourner tous les providers de type OAuth + getOAuthProviders() { + if (this.config && this.config.auth && this.config.auth.passportjs) { + const oauthProviders = this.config.auth.passportjs.filter(provider => { + const providerName = Object.keys(provider)[0]; + return provider[providerName].type === 'oauth'; + }); + + if (oauthProviders.length > 0) { + return oauthProviders; + } else { + return { error: "Aucun fournisseur OAuth disponible." }; + } + } else { + return { error: "Aucune configuration PassportJS disponible." }; + } + } + + // Méthode pour retourner tous les providers de type OIDC + getOIDCProviders() { + if (this.config && this.config.auth && this.config.auth.passportjs) { + const oidcProviders = this.config.auth.passportjs.filter(provider => { + const providerName = Object.keys(provider)[0]; + return provider[providerName].type === 'oidc'; + }); + + if (oidcProviders.length > 0) { + return oidcProviders; + } else { + return { error: "Aucun fournisseur OIDC disponible." }; + } + } else { + return { error: "Aucune configuration PassportJS disponible." }; + } + } + + // Méthode pour vérifier si tous les providers ont les variables nécessaires + validateProvidersConfig() { + const requiredOAuthFields = [ + 'OAUTH_AUTHORIZATION_URL', 'OAUTH_TOKEN_URL','OAUTH_USERINFO_URL', 'OAUTH_CLIENT_ID', 'OAUTH_CLIENT_SECRET', 'OAUTH_ROLE_TEACHER_VALUE', 'OAUTH_ROLE_STUDENT_VALUE' + ]; + + const requiredOIDCFields = [ + 'OIDC_CLIENT_ID', 'OIDC_CLIENT_SECRET', 'OIDC_CONFIG_URL', 'OIDC_ROLE_TEACHER_VALUE', 'OIDC_ROLE_STUDENT_VALUE','OIDC_ADD_SCOPE' + ]; + + const missingFieldsReport = []; + + if (this.config && this.config.auth && this.config.auth.passportjs) { + this.config.auth.passportjs.forEach(provider => { + const providerName = Object.keys(provider)[0]; + const providerConfig = provider[providerName]; + + let missingFields = []; + + // Vérification des providers de type OAuth + if (providerConfig.type === 'oauth') { + missingFields = requiredOAuthFields.filter(field => !(field in providerConfig)); + } + // Vérification des providers de type OIDC + else if (providerConfig.type === 'oidc') { + missingFields = requiredOIDCFields.filter(field => !(field in providerConfig)); + } + + // Si des champs manquent, on les ajoute au rapport + if (missingFields.length > 0) { + missingFieldsReport.push({ + provider: providerName, + missingFields: missingFields + }); + } + }); + + // Si des champs manquent, lever une exception + if (missingFieldsReport.length > 0) { + throw new Error(`Configuration invalide pour les providers suivants : ${JSON.stringify(missingFieldsReport, null, 2)}`); + } else { + console.log("Configuration auth_config.json: Tous les providers ont les variables nécessaires.") + return { success: "Tous les providers ont les variables nécessaires." }; + } + } else { + throw new Error("Aucune configuration PassportJS disponible."); + } + } + + // Méthode pour retourner la configuration des fournisseurs PassportJS pour le frontend + getActiveAuth() { + if (this.config && this.config.auth) { + const passportConfig = {}; + + // Gestion des providers PassportJS + if (this.config.auth.passportjs) { + this.config.auth.passportjs.forEach(provider => { + const providerName = Object.keys(provider)[0]; + const providerConfig = provider[providerName]; + + passportConfig[providerName] = {}; + + if (providerConfig.type === 'oauth') { + passportConfig[providerName] = { + type: providerConfig.type + }; + } else if (providerConfig.type === 'oidc') { + passportConfig[providerName] = { + type: providerConfig.type, + }; + } + }); + } + + // Gestion du Simple Login + if (this.config.auth["simpleauth"] && this.config.auth["simpleauth"].enabled) { + passportConfig['simpleauth'] = { + type: "simpleauth", + name: this.config.auth["simpleauth"].name + }; + } + + return passportConfig; + } else { + return { error: "Aucune configuration d'authentification disponible." }; + } + } + + // Check if students must be authenticated to join a room + getRoomsRequireAuth() { + const roomRequireAuth = process.env.AUTHENTICATED_ROOMS; + + if (!roomRequireAuth || roomRequireAuth !== "true") { + return false; + } + + return true; + } + + +} + +module.exports = AuthConfig; diff --git a/server/controllers/auth.js b/server/controllers/auth.js new file mode 100644 index 0000000..76769fb --- /dev/null +++ b/server/controllers/auth.js @@ -0,0 +1,36 @@ +const AuthConfig = require('../config/auth.js'); + +class authController { + + async getActive(req, res, next) { + try { + + const authC = new AuthConfig(); + authC.loadConfig(); + + const authActive = authC.getActiveAuth(); + + const response = { + authActive + }; + return res.json(response); + } + catch (error) { + return next(error); // Gérer l'erreur + } + } + + async getRoomsRequireAuth(req, res, next) { + const authC = new AuthConfig(); + const roomsRequireAuth = authC.getRoomsRequireAuth(); + + const response = { + roomsRequireAuth + } + + return res.json(response); + } + +} + +module.exports = new authController; \ No newline at end of file diff --git a/server/middleware/jwtToken.js b/server/middleware/jwtToken.js index 292e591..75ad458 100644 --- a/server/middleware/jwtToken.js +++ b/server/middleware/jwtToken.js @@ -7,8 +7,8 @@ dotenv.config(); class Token { - create(email, userId) { - return jwt.sign({ email, userId }, process.env.JWT_SECRET); + create(email, userId, roles) { + return jwt.sign({ email, userId, roles }, process.env.JWT_SECRET); } authenticate(req, res, next) { @@ -25,11 +25,11 @@ class Token { req.user = payload; }); - + } catch (error) { return next(error); } - + return next(); } } diff --git a/server/models/authProvider.js b/server/models/authProvider.js new file mode 100644 index 0000000..ab92da4 --- /dev/null +++ b/server/models/authProvider.js @@ -0,0 +1,44 @@ +const db = require('../config/db.js') +const { ObjectId } = require('mongodb'); + +class AuthProvider { + constructor(name) { + this._id = new ObjectId(); + this.name = name; + } + + async getId(name){ + await db.connect() + const conn = db.getConnection(); + + const collection = conn.collection('authprovider'); + + const existingauth = await collection.findOne({ name:name }); + + if(existingauth){ + return existingauth._id + } + return null + } + + async create(name) { + await db.connect() + const conn = db.getConnection(); + + const collection = conn.collection('authprovider'); + + const existingauth = await collection.findOne({ name:name }); + + if(existingauth){ + return existingauth._id; + } + + const newProvider = { + name:name + } + const result = await collection.insertOne(newProvider); + return result.insertedId; + } +} + +module.exports = new AuthProvider; \ No newline at end of file diff --git a/server/models/authUserAssociation.js b/server/models/authUserAssociation.js new file mode 100644 index 0000000..3c64644 --- /dev/null +++ b/server/models/authUserAssociation.js @@ -0,0 +1,59 @@ +const authProvider = require('./authProvider.js') +const db = require('../config/db.js') +const { ObjectId } = require('mongodb'); + + +class AuthUserAssociation { + constructor(authProviderId, authId, userId) { + this._id = new ObjectId(); + this.authProvider_id = authProviderId; + this.auth_id = authId; + this.user_id = userId; + this.connected = false; + } + + async find_user_association(provider_name,auth_id){ + await db.connect() + const conn = db.getConnection(); + + const collection = conn.collection('authUserAssociation'); + const provider_id = await authProvider.getId(provider_name) + + const userAssociation = await collection.findOne({ authProvider_id: provider_id,auth_id,auth_id }); + return userAssociation + } + + async link(provider_name,auth_id,user_id){ + await db.connect() + const conn = db.getConnection(); + + const collection = conn.collection('authUserAssociation'); + const provider_id = await authProvider.getId(provider_name) + + const userAssociation = await collection.findOne({ authProvider_id: provider_id, user_id: user_id }); + + if(!userAssociation){ + return await collection.insertOne({ + _id:ObjectId, + authProvider_id:provider_id, + auth_id:auth_id, + user_id:user_id, + }) + } + } + + async unlink(provider_name,user_id){ + await db.connect() + const conn = db.getConnection(); + + const collection = conn.collection('authUserAssociation'); + const provider_id = await authProvider.getId(provider_name) + + const userAssociation = await collection.findOne({ authProvider_id: provider_id, user_id: user_id }); + + if(userAssociation){ + return await collection.deleteOne(userAssociation) + } else return null + } + } +module.exports = new AuthUserAssociation; \ No newline at end of file diff --git a/server/models/userAuthAssociation.js b/server/models/userAuthAssociation.js new file mode 100644 index 0000000..8e12717 --- /dev/null +++ b/server/models/userAuthAssociation.js @@ -0,0 +1,13 @@ +const db = require('../config/db.js') +const { ObjectId } = require('mongodb'); + + +class AuthUserAssoc { + constructor(authProviderId, authId, userId) { + this._id = new ObjectId(); + this.authProvider_id = authProviderId; + this.auth_id = authId; + this.user_id = userId; + } + } + \ No newline at end of file diff --git a/server/models/users.js b/server/models/users.js index 1a04d86..6b96bdb 100644 --- a/server/models/users.js +++ b/server/models/users.js @@ -1,125 +1,195 @@ -//user -const bcrypt = require('bcrypt'); -const AppError = require('../middleware/AppError.js'); -const { USER_ALREADY_EXISTS } = require('../constants/errorCodes'); +const bcrypt = require("bcrypt"); +const AppError = require("../middleware/AppError.js"); +const { USER_ALREADY_EXISTS } = require("../constants/errorCodes"); +const Folders = require("../models/folders.js"); class Users { - constructor(db, foldersModel) { - // console.log("Users constructor: db", db) - this.db = db; - this.folders = foldersModel; + + constructor(db, foldersModel) { + this.db = db; + this.folders = foldersModel; + } + + async hashPassword(password) { + return await bcrypt.hash(password, 10); + } + + generatePassword() { + return Math.random().toString(36).slice(-8); + } + + async verify(password, hash) { + return await bcrypt.compare(password, hash); + } + + async register(userInfos) { + await this.db.connect(); + const conn = this.db.getConnection(); + + const userCollection = conn.collection("users"); + + const existingUser = await userCollection.findOne({ email: userInfos.email }); + + if (existingUser) { + throw new AppError(USER_ALREADY_EXISTS); } + + let newUser = { + name: userInfos.name ?? userInfos.email, + email: userInfos.email, + password: await this.hashPassword(userInfos.password), + created_at: new Date(), + roles: userInfos.roles + }; + + let created_user = await userCollection.insertOne(newUser); + let user = await this.getById(created_user.insertedId) + + const folderTitle = "Dossier par Défaut"; - async hashPassword(password) { - return await bcrypt.hash(password, 10) + const userId = newUser._id ? newUser._id.toString() : 'x'; + await this.folders.create(folderTitle, userId); + + // TODO: verif if inserted properly... + return user; + } + + async login(userid) { + await this.db.connect(); + const conn = this.db.getConnection(); + + const userCollection = conn.collection("users"); + const user = await userCollection.findOne({ _id: userid }); + + if (!user) { + return false; } - generatePassword() { - return Math.random().toString(36).slice(-8); + return user; + } + + async login(email, password) { + try { + await this.db.connect(); + const conn = this.db.getConnection(); + const userCollection = conn.collection("users"); + + const user = await userCollection.findOne({ email: email }); + + if (!user) { + const error = new Error("User not found"); + error.statusCode = 404; + throw error; + } + + const passwordMatch = await this.verify(password, user.password); + + if (!passwordMatch) { + const error = new Error("Password does not match"); + error.statusCode = 401; + throw error; + } + + return user; + } catch (error) { + console.error(error); + throw error; + } + } + + async resetPassword(email) { + const newPassword = this.generatePassword(); + + return await this.changePassword(email, newPassword); + } + + async changePassword(email, newPassword) { + await this.db.connect(); + const conn = this.db.getConnection(); + + const userCollection = conn.collection("users"); + + const hashedPassword = await this.hashPassword(newPassword); + + const result = await userCollection.updateOne( + { email }, + { $set: { password: hashedPassword } } + ); + + if (result.modifiedCount != 1) return null; + + return newPassword; + } + + async delete(email) { + await this.db.connect(); + const conn = this.db.getConnection(); + + const userCollection = conn.collection("users"); + + const result = await userCollection.deleteOne({ email }); + + if (result.deletedCount != 1) return false; + + return true; + } + + async getId(email) { + await this.db.connect(); + const conn = this.db.getConnection(); + + const userCollection = conn.collection("users"); + + const user = await userCollection.findOne({ email: email }); + + if (!user) { + return false; } - async verify(password, hash) { - return await bcrypt.compare(password, hash) + return user._id; + } + + async getById(id) { + await this.db.connect(); + const conn = this.db.getConnection(); + + const userCollection = conn.collection("users"); + + const user = await userCollection.findOne({ _id: id }); + + if (!user) { + return false; } - async register(email, password) { - await this.db.connect() - const conn = this.db.getConnection(); - - const userCollection = conn.collection('users'); + return user; + } - const existingUser = await userCollection.findOne({ email: email }); + async editUser(userInfo) { + await this.db.connect(); + const conn = this.db.getConnection(); - if (existingUser) { - throw new AppError(USER_ALREADY_EXISTS); - } + const userCollection = conn.collection("users"); - const newUser = { - email: email, - password: await this.hashPassword(password), - created_at: new Date() - }; + const user = await userCollection.findOne({ _id: userInfo.id }); - const result = await userCollection.insertOne(newUser); - // console.log("userCollection.insertOne() result", result); - const userId = result.insertedId.toString(); - - const folderTitle = 'Dossier par Défaut'; - await this.folders.create(folderTitle, userId); - - return result; + if (!user) { + return false; } - async login(email, password) { - await this.db.connect() - const conn = this.db.getConnection(); + const updatedFields = { ...userInfo }; + delete updatedFields.id; - const userCollection = conn.collection('users'); + const result = await userCollection.updateOne( + { _id: userInfo.id }, + { $set: updatedFields } + ); - const user = await userCollection.findOne({ email: email }); - - if (!user) { - return false; - } - - const passwordMatch = await this.verify(password, user.password); - - if (!passwordMatch) { - return false; - } - - return user; - } - - async resetPassword(email) { - const newPassword = this.generatePassword(); - - return await this.changePassword(email, newPassword); - } - - async changePassword(email, newPassword) { - await this.db.connect() - const conn = this.db.getConnection(); - - const userCollection = conn.collection('users'); - - const hashedPassword = await this.hashPassword(newPassword); - - const result = await userCollection.updateOne({ email }, { $set: { password: hashedPassword } }); - - if (result.modifiedCount != 1) return null; - - return newPassword - } - - async delete(email) { - await this.db.connect() - const conn = this.db.getConnection(); - - const userCollection = conn.collection('users'); - - const result = await userCollection.deleteOne({ email }); - - if (result.deletedCount != 1) return false; - - return true; - } - - async getId(email) { - await this.db.connect() - const conn = this.db.getConnection(); - - const userCollection = conn.collection('users'); - - const user = await userCollection.findOne({ email: email }); - - if (!user) { - return false; - } - - return user._id; + if (result.modifiedCount === 1) { + return true; } + return false; + } } module.exports = Users; diff --git a/server/package-lock.json b/server/package-lock.json index 9b44c36..56ccead 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -7,16 +7,22 @@ "": { "name": "ets-pfe004-evaluetonsavoir-backend", "version": "1.0.0", + "hasInstallScript": true, "license": "MIT", "dependencies": { "bcrypt": "^5.1.1", "cors": "^2.8.5", "dotenv": "^16.4.4", "express": "^4.18.2", + "express-session": "^1.18.0", "jsonwebtoken": "^9.0.2", "mongodb": "^6.3.0", "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.9", + "passport": "^0.7.0", + "passport-oauth2": "^1.8.0", + "passport-openidconnect": "^0.1.2", + "patch-package": "^8.0.0", "socket.io": "^4.7.2", "socket.io-client": "^4.7.2" }, @@ -1618,6 +1624,11 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1734,7 +1745,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" }, @@ -1806,6 +1816,14 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -1935,6 +1953,14 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -1993,7 +2019,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -2139,7 +2164,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" @@ -2155,7 +2179,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" } @@ -2164,7 +2187,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" }, @@ -2220,7 +2242,6 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, "funding": [ { "type": "github", @@ -2271,7 +2292,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" }, @@ -2282,8 +2302,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", @@ -2449,9 +2468,14 @@ }, "node_modules/cross-env": { "version": "7.0.3", +<<<<<<< HEAD "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", "dev": true, +======= + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", +>>>>>>> dev-it2/dev-it2-PFEA2024 "dependencies": { "cross-spawn": "^7.0.1" }, @@ -3182,12 +3206,37 @@ "url": "https://opencollective.com/express" } }, +<<<<<<< HEAD "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" +======= + "node_modules/express-session": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz", + "integrity": "sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==", + "dependencies": { + "cookie": "0.6.0", + "cookie-signature": "1.0.7", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" +>>>>>>> dev-it2/dev-it2-PFEA2024 }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", @@ -3234,7 +3283,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -3272,6 +3320,7 @@ "node": ">=8" } }, +<<<<<<< HEAD "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -3293,6 +3342,16 @@ "dev": true, "license": "ISC" }, +======= + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dependencies": { + "micromatch": "^4.0.2" + } + }, +>>>>>>> dev-it2/dev-it2-PFEA2024 "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -3338,6 +3397,20 @@ "node": ">= 0.6" } }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -3521,8 +3594,7 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/has-flag": { "version": "3.0.0", @@ -3788,6 +3860,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3830,7 +3916,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -3847,6 +3932,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -3855,8 +3951,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", @@ -4670,6 +4765,7 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, +<<<<<<< HEAD "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4683,6 +4779,29 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, "license": "MIT" +======= + "node_modules/json-stable-stringify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", + "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", + "dependencies": { + "call-bind": "^1.0.5", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/json-stable-stringify/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" +>>>>>>> dev-it2/dev-it2-PFEA2024 }, "node_modules/json5": { "version": "2.2.3", @@ -4696,6 +4815,25 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -4741,6 +4879,7 @@ "safe-buffer": "^5.0.1" } }, +<<<<<<< HEAD "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4749,6 +4888,14 @@ "license": "MIT", "dependencies": { "json-buffer": "3.0.1" +======= + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dependencies": { + "graceful-fs": "^4.1.11" +>>>>>>> dev-it2/dev-it2-PFEA2024 } }, "node_modules/kleur": { @@ -4917,7 +5064,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -5280,6 +5426,11 @@ "set-blocking": "^2.0.0" } }, + "node_modules/oauth": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.0.tgz", + "integrity": "sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q==" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -5299,6 +5450,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -5310,6 +5469,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5333,6 +5500,7 @@ "url": "https://github.com/sponsors/sindresorhus" } }, +<<<<<<< HEAD "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5349,6 +5517,29 @@ }, "engines": { "node": ">= 0.8.0" +======= + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "engines": { + "node": ">=0.10.0" +>>>>>>> dev-it2/dev-it2-PFEA2024 } }, "node_modules/p-limit": { @@ -5426,6 +5617,115 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-oauth2": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", + "dependencies": { + "base64url": "3.x.x", + "oauth": "0.10.x", + "passport-strategy": "1.x.x", + "uid2": "0.0.x", + "utils-merge": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-openidconnect": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/passport-openidconnect/-/passport-openidconnect-0.1.2.tgz", + "integrity": "sha512-JX3rTyW+KFZ/E9OF/IpXJPbyLO9vGzcmXB5FgSP2jfL3LGKJPdV7zUE8rWeKeeI/iueQggOeFa3onrCmhxXZTg==", + "dependencies": { + "oauth": "0.10.x", + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/patch-package/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/patch-package/node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "engines": { + "node": ">=6" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5447,7 +5747,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -5464,6 +5763,11 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -5474,7 +5778,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -5613,6 +5916,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -5854,7 +6165,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -5866,7 +6176,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -6305,6 +6614,17 @@ "node": ">=8" } }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -6324,7 +6644,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -6414,6 +6733,22 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uid2": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -6425,6 +6760,14 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -6541,7 +6884,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -6655,6 +6997,17 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yaml": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", + "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/server/package.json b/server/package.json index da602ee..4eb536d 100644 --- a/server/package.json +++ b/server/package.json @@ -7,7 +7,8 @@ "build": "webpack --config webpack.config.js", "start": "node app.js", "dev": "cross-env NODE_ENV=development nodemon app.js", - "test": "jest --colors" + "test": "jest", + "postinstall": "patch-package" }, "keywords": [], "author": "", @@ -17,10 +18,15 @@ "cors": "^2.8.5", "dotenv": "^16.4.4", "express": "^4.18.2", + "express-session": "^1.18.0", "jsonwebtoken": "^9.0.2", "mongodb": "^6.3.0", "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.9", + "passport": "^0.7.0", + "passport-oauth2": "^1.8.0", + "passport-openidconnect": "^0.1.2", + "patch-package": "^8.0.0", "socket.io": "^4.7.2", "socket.io-client": "^4.7.2" }, diff --git a/server/patches/passport-openidconnect+0.1.2.patch b/server/patches/passport-openidconnect+0.1.2.patch new file mode 100644 index 0000000..e386741 --- /dev/null +++ b/server/patches/passport-openidconnect+0.1.2.patch @@ -0,0 +1,12 @@ +diff --git a/node_modules/passport-openidconnect/lib/profile.js b/node_modules/passport-openidconnect/lib/profile.js +index eeabf4e..8abe391 100644 +--- a/node_modules/passport-openidconnect/lib/profile.js ++++ b/node_modules/passport-openidconnect/lib/profile.js +@@ -17,6 +17,7 @@ exports.parse = function(json) { + if (json.middle_name) { profile.name.middleName = json.middle_name; } + } + if (json.email) { profile.emails = [ { value: json.email } ]; } ++ if (json.groups) { profile.groups = [ { value: json.groups } ]; } + + return profile; + }; diff --git a/server/routers/auth.js b/server/routers/auth.js new file mode 100644 index 0000000..7260669 --- /dev/null +++ b/server/routers/auth.js @@ -0,0 +1,10 @@ +const express = require('express'); +const router = express.Router(); +const jwt = require('../middleware/jwtToken.js'); + +const authController = require('../controllers/auth.js') + +router.get("/getActiveAuth",authController.getActive); +router.get("/getRoomsRequireAuth", authController.getRoomsRequireAuth); + +module.exports = router; \ No newline at end of file diff --git a/server/routers/users.js b/server/routers/users.js index d1f81b7..f88436d 100644 --- a/server/routers/users.js +++ b/server/routers/users.js @@ -3,11 +3,12 @@ const router = express.Router(); const users = require('../app.js').users; const jwt = require('../middleware/jwtToken.js'); const asyncHandler = require('./routerUtils.js'); +const usersController = require('../controllers/users.js') router.post("/register", asyncHandler(users.register)); router.post("/login", asyncHandler(users.login)); router.post("/reset-password", asyncHandler(users.resetPassword)); router.post("/change-password", jwt.authenticate, asyncHandler(users.changePassword)); -router.post("/delete-user", jwt.authenticate, asyncHandler(users.delete)); +router.post("/delete-user", jwt.authenticate, usersController); module.exports = router; diff --git a/server/utils.js b/server/utils.js new file mode 100644 index 0000000..91f5972 --- /dev/null +++ b/server/utils.js @@ -0,0 +1,35 @@ +function hasNestedValue(obj, path, delimiter = "_") { + const keys = path.split(delimiter); + let current = obj; + + for (const key of keys) { + while(Array.isArray(current) && current.length == 1 && current[0]){ + current = current[0] + } + while(current['value']){ + current = current.value + } + + if (current && typeof current === "object") { + if (Array.isArray(current)) { + const index = current.findIndex(x => x == key) + if (index != -1) { + current = current[index]; + } else { + return false; + } + } else if (key in current) { + current = current[key]; + } else { + return false; + } + } else { + return false; + } + } + + return true; +} + + +module.exports = { hasNestedValue}; \ No newline at end of file From 29d9ed31acb721c8fac90af3bcd9f798d406abeb Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Fri, 31 Jan 2025 15:22:37 -0500 Subject: [PATCH 04/37] npm install (client et server) pour que l'action roule sur GitHub --- client/package-lock.json | 214 ++++++++++++----------------- server/package-lock.json | 281 ++++++++++++++++++++++----------------- 2 files changed, 246 insertions(+), 249 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 4ee571d..9b6d01c 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -2550,7 +2550,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -2569,7 +2569,7 @@ "version": "4.12.1", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2579,7 +2579,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.5", @@ -2594,7 +2594,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2605,7 +2605,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2618,7 +2618,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -2631,7 +2631,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -2655,7 +2655,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2666,7 +2666,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=18" @@ -2679,7 +2679,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2692,7 +2692,7 @@ "version": "9.18.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2702,7 +2702,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2712,7 +2712,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.10.0", @@ -2819,7 +2819,7 @@ "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -2829,7 +2829,7 @@ "version": "0.16.6", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -2843,7 +2843,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -2857,7 +2857,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -3854,7 +3854,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3868,7 +3867,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3882,7 +3880,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3896,7 +3893,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3910,7 +3906,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3924,7 +3919,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3938,7 +3932,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3952,7 +3945,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3966,7 +3958,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3980,7 +3971,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3994,7 +3984,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4008,7 +3997,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4022,7 +4010,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4036,7 +4023,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4050,7 +4036,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4064,7 +4049,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4078,7 +4062,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4092,7 +4075,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4133,7 +4115,7 @@ "version": "1.7.40", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.7.40.tgz", "integrity": "sha512-0HIzM5vigVT5IvNum+pPuST9p8xFhN6mhdIKju7qYYeNuZG78lwms/2d8WgjTJJlzp6JlPguXGrMMNzjQw0qNg==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -4175,7 +4157,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4192,7 +4173,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4209,7 +4189,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -4226,7 +4205,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4243,7 +4221,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4260,7 +4237,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4277,7 +4253,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4294,7 +4269,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4311,7 +4285,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4328,7 +4301,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -4342,14 +4314,14 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@swc/types": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.13.tgz", "integrity": "sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" @@ -4543,7 +4515,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, "license": "MIT" }, "node_modules/@types/graceful-fs": { @@ -4643,7 +4614,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/katex": { @@ -5024,7 +4995,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -5058,7 +5029,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -5133,7 +5104,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, + "devOptional": true, "license": "Python-2.0" }, "node_modules/aria-query": { @@ -5947,7 +5918,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -6118,7 +6089,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -6668,7 +6639,7 @@ "version": "9.18.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -6826,7 +6797,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -6843,7 +6814,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6856,7 +6827,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -6870,7 +6841,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -6881,7 +6852,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6894,7 +6865,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -6907,7 +6878,7 @@ "version": "10.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.14.0", @@ -6925,7 +6896,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6951,7 +6922,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -6964,7 +6935,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -7057,7 +7028,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -7092,14 +7063,14 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fastq": { @@ -7125,7 +7096,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -7179,7 +7150,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -7196,7 +7167,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -7210,7 +7181,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/follow-redirects": { @@ -7475,7 +7446,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -7763,7 +7734,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 4" @@ -7809,7 +7780,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -8312,7 +8283,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -9350,7 +9321,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -9420,7 +9391,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -9433,14 +9404,14 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/json5": { @@ -9477,6 +9448,21 @@ "node": ">= 10.0.0" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/jwt-decode": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", @@ -9504,7 +9490,7 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -9534,7 +9520,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -9554,7 +9540,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -9591,7 +9577,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/longest-streak": { @@ -10368,7 +10354,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/node-int64": { @@ -10548,7 +10534,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -10584,7 +10570,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -10600,7 +10586,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -10668,7 +10654,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -10830,7 +10816,6 @@ "version": "8.4.47", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -10859,7 +10844,6 @@ "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "dev": true, "funding": [ { "type": "github", @@ -10878,7 +10862,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -11367,7 +11351,6 @@ "version": "4.24.3", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.3.tgz", "integrity": "sha512-HBW896xR5HGmoksbi3JBDtmVzWiPAYqp7wip50hjQ67JbDz61nyoMPdqu1DvVW9asYb2M65Z20ZHsyJCMqMyDg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.6" @@ -11569,7 +11552,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -11582,7 +11565,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -11728,7 +11711,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -11959,7 +11941,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -12227,7 +12209,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -12339,7 +12321,6 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -12573,7 +12554,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -12662,7 +12643,6 @@ "version": "5.4.14", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", - "dev": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -12800,7 +12780,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12817,7 +12796,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12834,7 +12812,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12851,7 +12828,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12868,7 +12844,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12885,7 +12860,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12902,7 +12876,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12919,7 +12892,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12936,7 +12908,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12953,7 +12924,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12970,7 +12940,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -12987,7 +12956,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13004,7 +12972,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13021,7 +12988,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13038,7 +13004,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13055,7 +13020,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13072,7 +13036,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13089,7 +13052,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13106,7 +13068,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13123,7 +13084,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13140,7 +13100,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13157,7 +13116,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13174,7 +13132,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -13188,7 +13145,6 @@ "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -13398,7 +13354,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -13502,7 +13458,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13659,7 +13615,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/server/package-lock.json b/server/package-lock.json index 56ccead..1de4aeb 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -37,7 +37,7 @@ "supertest": "^6.3.4" }, "engines": { - "node": "18.x" + "node": "20.x" } }, "node_modules/@ampproject/remapping": { @@ -2105,15 +2105,41 @@ } }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -2468,14 +2494,9 @@ }, "node_modules/cross-env": { "version": "7.0.3", -<<<<<<< HEAD "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", "dev": true, -======= - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", ->>>>>>> dev-it2/dev-it2-PFEA2024 "dependencies": { "cross-spawn": "^7.0.1" }, @@ -2493,7 +2514,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2636,6 +2656,19 @@ "url": "https://dotenvx.com" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -2780,12 +2813,9 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "engines": { "node": ">= 0.4" } @@ -2798,6 +2828,17 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -3206,20 +3247,12 @@ "url": "https://opencollective.com/express" } }, -<<<<<<< HEAD - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" -======= "node_modules/express-session": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz", - "integrity": "sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==", + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", + "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", "dependencies": { - "cookie": "0.6.0", + "cookie": "0.7.2", "cookie-signature": "1.0.7", "debug": "2.6.9", "depd": "~2.0.0", @@ -3232,11 +3265,25 @@ "node": ">= 0.8.0" } }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/express-session/node_modules/cookie-signature": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" ->>>>>>> dev-it2/dev-it2-PFEA2024 + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", @@ -3320,7 +3367,14 @@ "node": ">=8" } }, -<<<<<<< HEAD + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dependencies": { + "micromatch": "^4.0.2" + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -3342,16 +3396,6 @@ "dev": true, "license": "ISC" }, -======= - "node_modules/find-yarn-workspace-root": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", - "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", - "dependencies": { - "micromatch": "^4.0.2" - } - }, ->>>>>>> dev-it2/dev-it2-PFEA2024 "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -3438,20 +3482,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3498,15 +3528,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -3524,6 +3559,18 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -3581,11 +3628,11 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3616,21 +3663,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "engines": { "node": ">= 0.4" }, @@ -3644,9 +3680,9 @@ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, "node_modules/hasown": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", - "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dependencies": { "function-bind": "^1.1.2" }, @@ -4765,7 +4801,6 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, -<<<<<<< HEAD "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4773,19 +4808,13 @@ "dev": true, "license": "MIT" }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" -======= "node_modules/json-stable-stringify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.1.1.tgz", - "integrity": "sha512-SU/971Kt5qVQfJpyDveVhQ/vya+5hvrjClFOcr8c0Fq5aODJjMwutrOfCU+eCnVD5gpx1Q3fEqkyom77zH1iIg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.2.1.tgz", + "integrity": "sha512-Lp6HbbBgosLmJbjx0pBLbgvx68FaFU1sdkmBuckmhhJ88kL13OA51CDtR2yJB50eCNMH9wRqtQNNiAqQH4YXnA==", "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "isarray": "^2.0.5", "jsonify": "^0.0.1", "object-keys": "^1.1.1" @@ -4797,11 +4826,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json-stable-stringify/node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" ->>>>>>> dev-it2/dev-it2-PFEA2024 }, "node_modules/json5": { "version": "2.2.3", @@ -4879,7 +4914,6 @@ "safe-buffer": "^5.0.1" } }, -<<<<<<< HEAD "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4888,14 +4922,14 @@ "license": "MIT", "dependencies": { "json-buffer": "3.0.1" -======= + } + }, "node_modules/klaw-sync": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", "dependencies": { "graceful-fs": "^4.1.11" ->>>>>>> dev-it2/dev-it2-PFEA2024 } }, "node_modules/kleur": { @@ -5025,6 +5059,14 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -5500,7 +5542,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, -<<<<<<< HEAD + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5517,20 +5573,6 @@ }, "engines": { "node": ">= 0.8.0" -======= - "node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/os-tmpdir": { @@ -5539,7 +5581,6 @@ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "engines": { "node": ">=0.10.0" ->>>>>>> dev-it2/dev-it2-PFEA2024 } }, "node_modules/p-limit": { @@ -6998,9 +7039,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", - "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", "bin": { "yaml": "bin.mjs" }, From e88dafa01c7966aeb029de5913060e20505d6d6e Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Fri, 31 Jan 2025 15:23:20 -0500 Subject: [PATCH 05/37] Workspace in VSCode (c'est pratique pour ouvrir un mono repo) --- EvalueTonSavoir.code-workspace | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 EvalueTonSavoir.code-workspace diff --git a/EvalueTonSavoir.code-workspace b/EvalueTonSavoir.code-workspace new file mode 100644 index 0000000..f18d936 --- /dev/null +++ b/EvalueTonSavoir.code-workspace @@ -0,0 +1,20 @@ +{ + "folders": [ + { + "path": "." + }, + { + "name": "server", + "path": "server" + }, + { + "name": "client", + "path": "client" + } + ], + "settings": { + "jest.disabledWorkspaceFolders": [ + "EvalueTonSavoir" + ] + } +} From 896bd588bddac86147536d1027791b5c3ec95b2d Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Fri, 31 Jan 2025 15:48:38 -0500 Subject: [PATCH 06/37] ignorer dist pour eslint --- client/eslint.config.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/eslint.config.js b/client/eslint.config.js index ed8593c..56b57bd 100644 --- a/client/eslint.config.js +++ b/client/eslint.config.js @@ -6,7 +6,11 @@ import pluginReact from "eslint-plugin-react"; /** @type {import('eslint').Linter.Config[]} */ export default [ { - files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"], + ignores: ["dist/**"], // Place this at the root level + }, + { + files: ["src/**/*.{js,mjs,cjs,ts,jsx,tsx}"], + // ignores: ["./dist/**"], languageOptions: { globals: globals.browser, }, From 23e053e24ff661f406b47270336b3b557afc114a Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Sun, 2 Feb 2025 12:40:12 -0500 Subject: [PATCH 07/37] =?UTF-8?q?normaliser=20config=20eslint=20M=C3=AAme?= =?UTF-8?q?=20config=20pour=20extension=20VS=20Code=20et=20npx=20eslint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- EvalueTonSavoir.code-workspace | 15 +++++- client/.eslintrc.cjs | 19 ------- client/eslint.config.js | 97 +++++++++++++++++++++++++--------- client/package-lock.json | 71 +++++++++++++++++++++++++ client/package.json | 3 ++ 5 files changed, 159 insertions(+), 46 deletions(-) delete mode 100644 client/.eslintrc.cjs diff --git a/EvalueTonSavoir.code-workspace b/EvalueTonSavoir.code-workspace index f18d936..2ee3b1c 100644 --- a/EvalueTonSavoir.code-workspace +++ b/EvalueTonSavoir.code-workspace @@ -16,5 +16,18 @@ "jest.disabledWorkspaceFolders": [ "EvalueTonSavoir" ] - } + }, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "eslint.validate": [ + "javascript", + "typescript", + "javascriptreact", + "typescriptreact" + ], + // use the same eslint config as `npx eslint` + "eslint.experimental.useFlatConfig": true, + "eslint.nodePath": "./node_modules" + } diff --git a/client/.eslintrc.cjs b/client/.eslintrc.cjs deleted file mode 100644 index 43f6c4c..0000000 --- a/client/.eslintrc.cjs +++ /dev/null @@ -1,19 +0,0 @@ -// eslint-disable-next-line no-undef -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', - ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, -} diff --git a/client/eslint.config.js b/client/eslint.config.js index 56b57bd..4da1290 100644 --- a/client/eslint.config.js +++ b/client/eslint.config.js @@ -1,33 +1,78 @@ +import react from "eslint-plugin-react"; +import typescriptEslint from "@typescript-eslint/eslint-plugin"; +import typescriptParser from "@typescript-eslint/parser"; import globals from "globals"; import pluginJs from "@eslint/js"; -import tseslint from "typescript-eslint"; -import pluginReact from "eslint-plugin-react"; +import jest from "eslint-plugin-jest"; +import reactRefresh from "eslint-plugin-react-refresh"; +import unusedImports from "eslint-plugin-unused-imports"; +import eslintComments from "eslint-plugin-eslint-comments"; /** @type {import('eslint').Linter.Config[]} */ export default [ - { - ignores: ["dist/**"], // Place this at the root level - }, - { - files: ["src/**/*.{js,mjs,cjs,ts,jsx,tsx}"], - // ignores: ["./dist/**"], - languageOptions: { - globals: globals.browser, + { + ignores: ["node_modules", "dist/**/*"], }, - rules: { - "no-unused-vars": ["error", { - "argsIgnorePattern": "^_", - "varsIgnorePattern": "^_", - "caughtErrorsIgnorePattern": "^_" // Ignore catch clause parameters that start with _ - }], - }, - settings: { - react: { - version: "detect", // Automatically detect the React version - }, - }, - }, - pluginJs.configs.recommended, - ...tseslint.configs.recommended, - pluginReact.configs.flat.recommended, + { + files: ["**/*.{js,jsx,mjs,cjs,ts,tsx}"], + languageOptions: { + parser: typescriptParser, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + globals: { + ...globals.serviceworker, + ...globals.browser, + ...globals.jest, + ...globals.node, + process: "readonly", + }, + }, + plugins: { + "@typescript-eslint": typescriptEslint, + react, + jest, + "react-refresh": reactRefresh, + "unused-imports": unusedImports, + "eslint-comments": eslintComments + }, + rules: { + // Auto-fix unused variables + "@typescript-eslint/no-unused-vars": "off", + "no-unused-vars": "off", + "unused-imports/no-unused-vars": [ + "warn", + { + "vars": "all", + "varsIgnorePattern": "^_", + "args": "after-used", + "argsIgnorePattern": "^_", + "destructuredArrayIgnorePattern": "^_" + } + ], + + // Handle directive comments + "eslint-comments/no-unused-disable": "warn", + "eslint-comments/no-unused-enable": "warn", + + // Jest configurations + "jest/valid-expect": ["error", { "alwaysAwait": true }], + "jest/prefer-to-have-length": "warn", + "jest/no-disabled-tests": "warn", + "jest/no-focused-tests": "error", + "jest/no-identical-title": "error", + + // React refresh + "react-refresh/only-export-components": ["warn", { + allowConstantExport: true + }], + }, + settings: { + react: { + version: "detect", + }, + }, + } ]; diff --git a/client/package-lock.json b/client/package-lock.json index 9b6d01c..534304c 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -54,9 +54,12 @@ "@typescript-eslint/parser": "^8.5.0", "@vitejs/plugin-react-swc": "^3.7.2", "eslint": "^9.18.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-jest": "^28.11.0", "eslint-plugin-react": "^7.37.3", "eslint-plugin-react-hooks": "^5.1.0-rc-206df66e-20240912", "eslint-plugin-react-refresh": "^0.4.12", + "eslint-plugin-unused-imports": "^4.1.4", "globals": "^15.14.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", @@ -6695,6 +6698,59 @@ } } }, + "node_modules/eslint-plugin-eslint-comments": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz", + "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5", + "ignore": "^5.0.5" + }, + "engines": { + "node": ">=6.5.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-eslint-comments/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-plugin-jest": { + "version": "28.11.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.11.0.tgz", + "integrity": "sha512-QAfipLcNCWLVocVbZW8GimKn5p5iiMcgGbRzz8z/P5q7xw+cNEpYqyzFMtIF/ZgF2HLOyy+dYBut+DoYolvqig==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "engines": { + "node": "^16.10.0 || ^18.12.0 || >=20.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0 || ^8.0.0", + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, "node_modules/eslint-plugin-react": { "version": "7.37.4", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.4.tgz", @@ -6793,6 +6849,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/eslint-plugin-unused-imports": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz", + "integrity": "sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==", + "dev": true, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", + "eslint": "^9.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } + } + }, "node_modules/eslint-scope": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", diff --git a/client/package.json b/client/package.json index 939257a..1888165 100644 --- a/client/package.json +++ b/client/package.json @@ -58,9 +58,12 @@ "@typescript-eslint/parser": "^8.5.0", "@vitejs/plugin-react-swc": "^3.7.2", "eslint": "^9.18.0", + "eslint-plugin-eslint-comments": "^3.2.0", + "eslint-plugin-jest": "^28.11.0", "eslint-plugin-react": "^7.37.3", "eslint-plugin-react-hooks": "^5.1.0-rc-206df66e-20240912", "eslint-plugin-react-refresh": "^0.4.12", + "eslint-plugin-unused-imports": "^4.1.4", "globals": "^15.14.0", "identity-obj-proxy": "^3.0.0", "jest": "^29.7.0", From 2de7671666b0d150f5436ed4119ec03349425cf1 Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Sun, 2 Feb 2025 12:42:05 -0500 Subject: [PATCH 08/37] =?UTF-8?q?Corriger=20des=20tests=20et=20auto-fix=20?= =?UTF-8?q?des=20probl=C3=A8mes=20de=20style?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/babel.config.cjs | 2 +- client/jest.config.cjs | 2 +- client/jest.setup.cjs | 2 +- .../src/__tests__/Types/StudentType.test.tsx | 2 +- .../GiftTemplate/GIFTTemplatePreview.test.tsx | 75 ++++++++++++------- .../components/GiftTemplate/TextType.test.ts | 4 +- .../GiftTemplate/constants/styles.test.tsx | 2 +- .../StudentModeQuiz/StudentModeQuiz.tsx | 2 +- .../StudentWaitPage/StudentWaitPage.tsx | 2 +- .../TeacherModeQuiz/TeacherModeQuiz.tsx | 2 +- .../src/pages/Teacher/Dashboard/Dashboard.tsx | 3 +- .../pages/Teacher/EditorQuiz/EditorQuiz.tsx | 3 +- .../pages/Teacher/ManageRoom/ManageRoom.tsx | 1 - 13 files changed, 58 insertions(+), 44 deletions(-) diff --git a/client/babel.config.cjs b/client/babel.config.cjs index 2bda178..eae7944 100644 --- a/client/babel.config.cjs +++ b/client/babel.config.cjs @@ -1,4 +1,4 @@ -/* eslint-disable no-undef */ + module.exports = { presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'] }; diff --git a/client/jest.config.cjs b/client/jest.config.cjs index 6c635c8..b2d35cc 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -1,4 +1,4 @@ -/* eslint-disable no-undef */ + /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { diff --git a/client/jest.setup.cjs b/client/jest.setup.cjs index 30fd66a..3b56b65 100644 --- a/client/jest.setup.cjs +++ b/client/jest.setup.cjs @@ -1,3 +1,3 @@ -/* eslint-disable no-undef */ + process.env.VITE_BACKEND_URL = 'http://localhost:4000/'; process.env.VITE_BACKEND_SOCKET_URL = 'https://ets-glitch-backend.glitch.me/'; diff --git a/client/src/__tests__/Types/StudentType.test.tsx b/client/src/__tests__/Types/StudentType.test.tsx index 4e7c849..2f9efbe 100644 --- a/client/src/__tests__/Types/StudentType.test.tsx +++ b/client/src/__tests__/Types/StudentType.test.tsx @@ -12,6 +12,6 @@ describe('StudentType', () => { expect(user.name).toBe('Student'); expect(user.id).toBe('123'); - expect(user.answers.length).toBe(0); + expect(user.answers).toHaveLength(0); }); }); diff --git a/client/src/__tests__/components/GiftTemplate/GIFTTemplatePreview.test.tsx b/client/src/__tests__/components/GiftTemplate/GIFTTemplatePreview.test.tsx index 586c8d1..967d33c 100644 --- a/client/src/__tests__/components/GiftTemplate/GIFTTemplatePreview.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/GIFTTemplatePreview.test.tsx @@ -5,47 +5,64 @@ import GIFTTemplatePreview from 'src/components/GiftTemplate/GIFTTemplatePreview describe('GIFTTemplatePreview Component', () => { test('renders error message when questions contain invalid syntax', () => { - render(); - const errorMessage = screen.findByText(/Erreur inconnue/i, {}, { timeout: 5000 }); - expect(errorMessage).resolves.toBeInTheDocument(); + render(); + const errorMessage = screen.getByText(/Title ::, Category, Description, or Question formatted stem but ":" found./i); + expect(errorMessage).toBeInTheDocument(); }); + test('renders preview when valid questions are provided', () => { const questions = [ - 'Question 1 { A | B | C }', - 'Question 2 { D | E | F }', + 'Stem1 {=ans1 ~ans2 ~ans3}', ]; render(); const previewContainer = screen.getByTestId('preview-container'); expect(previewContainer).toBeInTheDocument(); + // const question1 = screen.getByText('Stem1'); + const mcQuestion1 = screen.getByText('Stem1'); + const ans1 = screen.getByText('ans1'); + const ans2 = screen.getByText('ans2'); + const ans3 = screen.getByText('ans3'); + expect(mcQuestion1).toBeInTheDocument(); + expect(ans1).toBeInTheDocument(); + expect(ans2).toBeInTheDocument(); + expect(ans3).toBeInTheDocument(); + + // each answer should have a radio button before it + const radioButtons = screen.getAllByRole('radio'); + expect(radioButtons).toHaveLength(3); + // ans1 should be the