From c077d6dad719a31110fb455de1bfbae4404feb7c Mon Sep 17 00:00:00 2001 From: DIALLO SAAD BOUH Date: Mon, 21 Oct 2024 17:30:08 +0200 Subject: [PATCH 01/20] feat: SSO module implementation --- .env.example | 37 +++ .gitignore | 1 + Dockerfile.label-backend | 2 +- DockerfileLocalDev | 1 + INSTALL.md | 2 + README.fr.md | 35 +++ README.md | 32 ++ ROADMAP.md | 1 - docker.env.example | 33 +++ docs/images/LABEL_auth_workflow.png | Bin 0 -> 47770 bytes package.json | 6 +- .../courDeCassation/src/scripts/insertUser.ts | 12 +- .../src/sderApi/sderApiType.ts | 3 +- packages/generic/backend/babel.config.json | 8 +- packages/generic/backend/jest.config.json | 3 + packages/generic/backend/package.json | 5 + packages/generic/backend/src/api/buildApi.ts | 95 +++++- .../buildAuthenticatedController.ts | 55 ++-- .../generic/backend/src/api/controllerType.ts | 1 + .../generic/backend/src/api/controllers.ts | 66 +---- .../generic/backend/src/app/buildRunServer.ts | 20 ++ .../backend/src/app/express-session.d.ts | 21 ++ .../src/app/scripts/insertTestUsers.ts | 3 - .../backend/src/app/scripts/insertUser.ts | 10 - .../documentService/fetchDocumentsForUser.ts | 4 +- .../generic/backend/src/modules/sso/index.ts | 3 + .../modules/sso/service/express-session.d.ts | 22 ++ .../backend/src/modules/sso/service/index.ts | 22 ++ .../modules/sso/service/ssoService.spec.ts | 260 +++++++++++++++++ .../src/modules/sso/service/ssoService.ts | 166 +++++++++++ .../repository/buildFakeUserRepository.ts | 18 -- .../user/repository/buildUserRepository.ts | 17 +- .../repository/customUserRepositoryType.ts | 8 - .../userService/changePassword.spec.ts | 134 --------- .../service/userService/changePassword.ts | 42 --- .../user/service/userService/createUser.ts | 7 +- .../service/userService/fetchWorkingUsers.ts | 30 +- .../modules/user/service/userService/index.ts | 58 ++-- .../user/service/userService/login.spec.ts | 162 ----------- .../modules/user/service/userService/login.ts | 48 --- .../user/service/userService/resetPassword.ts | 23 -- .../userService/setDeletionDateForUser.ts | 11 - .../userService/setIsActivatedForUser.ts | 15 - .../user/service/userService/signUpUser.ts | 3 - packages/generic/client/src/App.tsx | 5 +- packages/generic/client/src/api/apiCaller.ts | 11 +- .../business/AdminMenu/AdminMenu.tsx | 9 - .../business/MainHeader/MainHeader.tsx | 14 +- .../PasswordChangeConfirmationPopup.tsx | 36 --- .../business/MainHeader/SettingsDrawer.tsx | 133 ++++----- .../ResetPasswordForm/ResetPasswordForm.tsx | 112 ------- .../business/ResetPasswordForm/index.ts | 1 - .../client/src/components/business/index.ts | 2 - .../generic/client/src/components/index.ts | 2 - .../client/src/contexts/user.context.tsx | 65 +++++ .../DocumentInspector/DocumentInspector.tsx | 5 +- .../ProblemReportsTable.tsx | 10 +- .../TreatedDocumentsTable.tsx | 5 +- .../UntreatedDocumentsTable.tsx | 10 +- .../AddUserDrawer/AddUserButton.tsx | 22 -- .../AddUserDrawer/AddUserDrawer.tsx | 197 ------------- .../AddUserDrawer/UserCreatedPopUp.tsx | 49 ---- .../Admin/WorkingUsers/AddUserDrawer/index.ts | 1 - .../PasswordResetSuccessPopup.tsx | 47 --- .../pages/Admin/WorkingUsers/WorkingUsers.tsx | 76 ----- .../Admin/WorkingUsers/WorkingUsersTable.tsx | 115 -------- .../src/pages/Admin/WorkingUsers/index.ts | 1 - .../generic/client/src/pages/DataFetcher.tsx | 3 - .../client/src/pages/ErrorPage/ErrorPage.tsx | 8 +- .../generic/client/src/pages/Login/Login.tsx | 25 +- .../src/pages/ResetPassword/ResetPassword.tsx | 65 ----- .../client/src/pages/ResetPassword/index.ts | 1 - packages/generic/client/src/pages/router.tsx | 146 +++++----- packages/generic/client/src/pages/routes.ts | 2 - .../src/services/localStorage/userHandler.ts | 24 +- .../generic/client/src/utils/urlHandler.ts | 8 + packages/generic/client/src/wordings/fr.ts | 70 ----- packages/generic/core/src/api/apiSchema.ts | 71 ----- packages/generic/core/src/modules/index.ts | 4 +- .../modules/user/generator/userGenerator.ts | 12 +- .../generic/core/src/modules/user/index.ts | 6 +- .../core/src/modules/user/lib/buildUser.ts | 7 +- .../core/src/modules/user/lib/index.ts | 3 - .../generic/core/src/modules/user/userType.ts | 17 +- packages/generic/sso/.eslintrc.json | 35 +++ packages/generic/sso/.prettierrc.js | 7 + packages/generic/sso/README.fr.md | 269 +++++++++++++++++ packages/generic/sso/README.md | 275 ++++++++++++++++++ packages/generic/sso/babel.config.json | 21 ++ packages/generic/sso/jest.config.json | 20 ++ packages/generic/sso/package.json | 66 +++++ .../generic/sso/src/.jest/setupEnvVars.ts | 11 + .../sso/src/api/saml/saml.service.spec.ts | 168 +++++++++++ .../generic/sso/src/api/saml/saml.service.ts | 151 ++++++++++ packages/generic/sso/src/index.ts | 1 + packages/generic/sso/tsconfig.json | 16 + 96 files changed, 2178 insertions(+), 1767 deletions(-) create mode 100644 docs/images/LABEL_auth_workflow.png create mode 100644 packages/generic/backend/src/app/express-session.d.ts create mode 100644 packages/generic/backend/src/modules/sso/index.ts create mode 100644 packages/generic/backend/src/modules/sso/service/express-session.d.ts create mode 100644 packages/generic/backend/src/modules/sso/service/index.ts create mode 100644 packages/generic/backend/src/modules/sso/service/ssoService.spec.ts create mode 100644 packages/generic/backend/src/modules/sso/service/ssoService.ts delete mode 100644 packages/generic/backend/src/modules/user/service/userService/changePassword.spec.ts delete mode 100644 packages/generic/backend/src/modules/user/service/userService/changePassword.ts delete mode 100644 packages/generic/backend/src/modules/user/service/userService/login.spec.ts delete mode 100644 packages/generic/backend/src/modules/user/service/userService/login.ts delete mode 100644 packages/generic/backend/src/modules/user/service/userService/resetPassword.ts delete mode 100644 packages/generic/backend/src/modules/user/service/userService/setDeletionDateForUser.ts delete mode 100644 packages/generic/backend/src/modules/user/service/userService/setIsActivatedForUser.ts delete mode 100644 packages/generic/client/src/components/business/MainHeader/PasswordChangeConfirmationPopup.tsx delete mode 100644 packages/generic/client/src/components/business/ResetPasswordForm/ResetPasswordForm.tsx delete mode 100644 packages/generic/client/src/components/business/ResetPasswordForm/index.ts create mode 100644 packages/generic/client/src/contexts/user.context.tsx delete mode 100644 packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/AddUserButton.tsx delete mode 100644 packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/AddUserDrawer.tsx delete mode 100644 packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/UserCreatedPopUp.tsx delete mode 100644 packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/index.ts delete mode 100644 packages/generic/client/src/pages/Admin/WorkingUsers/PasswordResetSuccessPopup.tsx delete mode 100644 packages/generic/client/src/pages/Admin/WorkingUsers/WorkingUsers.tsx delete mode 100644 packages/generic/client/src/pages/Admin/WorkingUsers/WorkingUsersTable.tsx delete mode 100644 packages/generic/client/src/pages/Admin/WorkingUsers/index.ts delete mode 100644 packages/generic/client/src/pages/ResetPassword/ResetPassword.tsx delete mode 100644 packages/generic/client/src/pages/ResetPassword/index.ts create mode 100644 packages/generic/sso/.eslintrc.json create mode 100644 packages/generic/sso/.prettierrc.js create mode 100644 packages/generic/sso/README.fr.md create mode 100644 packages/generic/sso/README.md create mode 100644 packages/generic/sso/babel.config.json create mode 100644 packages/generic/sso/jest.config.json create mode 100644 packages/generic/sso/package.json create mode 100644 packages/generic/sso/src/.jest/setupEnvVars.ts create mode 100644 packages/generic/sso/src/api/saml/saml.service.spec.ts create mode 100644 packages/generic/sso/src/api/saml/saml.service.ts create mode 100644 packages/generic/sso/src/index.ts create mode 100644 packages/generic/sso/tsconfig.json diff --git a/.env.example b/.env.example index 673cbe44b..c9f0cb926 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,40 @@ NLP_PSEUDONYMISATION_API_ENABLED=false JWT_PRIVATE_KEY=myPrivateKey SDER_DB_URL=http://127.0.0.1:55433 RUN_MODE=LOCAL + +##SSO VARIABLES +COOKIE_PRIVATE_KEY=myPrivateKey +#Service Provider (SP) +SSO_SP_ENTITY_ID=http://localhost:55430/label/api/sso/metadata +SSO_SP_ASSERTION_CONSUMER_SERVICE_LOCATION=http://localhost:55430/label/api/sso/acs +#Identity Provider (IdP) +SSO_IDP_METADATA=sso_files/sso_idp_metadata.xml +SSO_IDP_SINGLE_SIGN_ON_SERVICE_LOCATION=http://test.sso.intranet.justice.gouv.fr:9000/saml/singleSignOn +SSO_IDP_SINGLE_LOGOUT_SERVICE_LOCATION=http://test.sso.intranet.justice.gouv.fr:9000/saml/singleLogout +SSO_CERTIFICAT=certificate-example.pem +SSO_SP_PRIVATE_KEY=privatekey-example.pem +# Les valeurs possibles du SSO_NAME_ID_FORMAT sont le IDP metadata.xml +SSO_NAME_ID_FORMAT=urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress +SSO_SIGNATURE_ALGORITHM="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" +# Authentication +#604800=7*24*60*60 +SESSION_DURATION=604800 +# FRONT END +SSO_FRONT_SUCCESS_CONNEXION_ANNOTATOR_URL=http://localhost:55432/label/annotation +SSO_FRONT_SUCCESS_CONNEXION_ADMIN_SCRUTATOR_URL=http://localhost:55432/label/admin/main/summary +SSO_FRONT_SUCCESS_CONNEXION_PUBLICATOR_URL=http://localhost:55432/label/publishable-documents +# SSO URL du back à setter dans le serveur du client (front react) +REACT_APP_BACKEND_API_URL=http://localhost:55430 + +#ATTRIBUTS KEYS +SSO_ATTRIBUTE_NAME=nom +SSO_ATTRIBUTE_FIRSTNAME=prenom +SSO_ATTRIBUTE_FULLNAME=name +SSO_ATTRIBUTE_MAIL=email +SSO_ATTRIBUTE_ROLE=role + +#APPLICATION NAME +SSO_APP_NAME=LABEL + +#APPLICATION ROLES +SSO_APP_ROLES=admin,annotator,publicator,scrutator \ No newline at end of file diff --git a/.gitignore b/.gitignore index bef1fb032..83581282e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ package-lock.json /packages/courDeCassation/yarn-error.log *.env *coverage +.history/* .ash-history diff --git a/Dockerfile.label-backend b/Dockerfile.label-backend index 776ed66bc..27eb829e6 100644 --- a/Dockerfile.label-backend +++ b/Dockerfile.label-backend @@ -22,7 +22,7 @@ COPY . . # Do not bring client dependencies to backend prod # Workaround to rewrite 'workspaces' in packages.json file to not run 'yarn install' in all packages -RUN cat package.json | sed 's|"packages/generic/\*"|"packages/generic/backend", "packages/generic/core"|' > package.json.new && \ +RUN cat package.json | sed 's|"packages/generic/\*"|"packages/generic/backend", "packages/generic/core", "packages/generic/sso"|' > package.json.new && \ mv package.json.new package.json # Install dependencies diff --git a/DockerfileLocalDev b/DockerfileLocalDev index c184a66ef..ec5be0417 100644 --- a/DockerfileLocalDev +++ b/DockerfileLocalDev @@ -10,6 +10,7 @@ RUN apk add git # Copy context files COPY ./package.json ./ COPY packages/generic/core/package.json ./packages/generic/core/ +COPY packages/generic/sso/package.json ./packages/generic/sso/ COPY packages/generic/backend/package.json ./packages/generic/backend/ COPY packages/courDeCassation/package.json ./packages/courDeCassation/ COPY yarn.lock ./ diff --git a/INSTALL.md b/INSTALL.md index 3271004fa..8fc4d3f7d 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -89,3 +89,5 @@ docker container exec -it label-backend-1 sh -c "cd packages/courDeCassation; sh ```sh scripts/runScriptLocally.sh "myScript.js --myArgument" ``` +### SSO configuration +Follow the [installation guide](packages/generic/sso/README.md). \ No newline at end of file diff --git a/README.fr.md b/README.fr.md index d2c6c8da9..d0b53aff4 100644 --- a/README.fr.md +++ b/README.fr.md @@ -99,6 +99,7 @@ LABEL permet de lier des annotations. Parfois les noms sont écrits en minuscule - Avril 2022 : Ajout des décisions des Cours d'appel en matière civile, sociale et commerciale (base de données JuriCA). - Décembre 2023 : Ajout des décisions de 9 tribunaux judiciaires. - Courant 2024-2025 : Ajout des décisions de l'ensemble les tribunaux judiciaires. +- Septembre 2024: Connection de LABEL au SSO/LDAP du ministère Prochaines étapes: @@ -125,3 +126,37 @@ LABEL a été conçu pour être réutiliser dans n'importe quel contexte d'annot - `generic` : tout ce qui n'est pas lié aux besoins propres à la Cour de cassation Lisez [le guide de réutilisation](docs/reuserGuide.md) pour en savoir plus. + + +## Authentification + +
+ +LABEL s'interface avec le SSO Pages Blanches pour assurer l'authentification des utilisateurs via le protocole SAML 2. + +Le schéma ci-dessous illustre le flux d'authentification. + +Label auth workflow + +1. Lorsqu'un utilisateur accède à LABEL, le frontend interroge le backend pour vérifier son authentification. Si l'utilisateur n'est pas authentifié, le backend redirige vers le fournisseur d'identité (IdP) avec les paramètres nécessaires. + + +2. L'utilisateur se connecte sur la page SSO et, après authentification, le fournisseur d'identité génère et envoie une assertion SAML au backend de LABEL via l'URL de l'ACS. + + +3. Le backend valide l'assertion SAML pour garantir l'intégrité des données et la conformité de la signature numérique. + + +4. Après validation, l'accès aux ressources sécurisées est accordé, permettant à l'utilisateur de poursuivre sa session authentifiée.
+ + +> L'application LABEL utilise le module SSO comme dépendance pour son intégration avec le système d'authentification unique (SSO). Les spécificités de cette intégration sont documentées dans le [readme](packages/generic/sso/README.md) du module SSO. + +Le backend expose les URLs suivantes pour interagir avec le SSO : + +> 1. **/api/sso/login** : Endpoint pour initier le processus de connexion via SSO +> 2. **/api/sso/acs** : Endpoint pour le traitement des assertions SAML suite à une authentification réussie. +> 2. **/api/sso/logout** : Endpoint pour déconnecter l'utilisateur du SSO. +> 3. **/api/sso/metadata** : Endpoint pour récupérer les métadonnées du service SSO, incluant les configurations nécessaires pour l'authentification. + +Les attributs renvoyés par le SSO, ainsi que les rôles utilisés par l'application, sont spécifiés dans le fichier de configuration \ No newline at end of file diff --git a/README.md b/README.md index b1e22eef4..1fb84d876 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ LABEL allow you to link annotations. Sometimes names are written lowercase or in - April 2022: Addition of appeal court's decisions (JuriCA database). - December 2023: Addition of 9 first judicial courts' decisions. - During 2024-2025: Addition of other judicial courts' decisions. +- September 2024: Connecting label to the ministry's SSO/LDAP. Next steps: @@ -125,3 +126,34 @@ LABEL has been designed to be reused whatever the annotation context. There are - `generic`: what is not linked to the specific needs of the Cour de cassation Learn more in the [reuser guide](docs/reuserGuide.md). + +## Authentication +
+LABEL integrates with the Pages Blanches SSO to facilitate user authentication using the SAML 2.0 protocol. + +The diagram below illustrates the authentication flow. + +Label auth workflow + +1. When a user initiates access to LABEL, the frontend communicates with the backend to check the user’s authentication status. If the user is not authenticated, the backend initiates a redirect to the identity provider (IdP), passing the necessary authentication parameters. + + +2. The user is then prompted to log in via the SSO page. Upon successful authentication, the identity provider generates a SAML assertion and sends it to LABEL's backend via the Assertion Consumer Service (ACS) URL. + + +3. The backend processes and validates the SAML assertion to ensure data integrity and verify the authenticity of the digital signature + + +4. After validation, access to secured resources is granted, allowing the user to continue their authenticated session. + +
+ +>The LABEL application leverages the SSO module as a dependency for its integration with the Single Sign-On (SSO) system. The details of this integration are documented in the [README](packages/generic/sso/README.md) of the SSO module. + +The backend exposes the following URLs to interact with the SSO: + +1. /api/sso/login: Endpoint to initiate the login process via SSO. +2. /api/sso/acs: Endpoint for processing SAML assertions following a successful authentication. +3. /api/sso/logout: Endpoint to disconnect the user from the SSO. + +***The attributes returned by the SSO, as well as the roles used by the application, are specified in the configuration file.*** \ No newline at end of file diff --git a/ROADMAP.md b/ROADMAP.md index fffae8e94..71f387cf3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -8,7 +8,6 @@ Here are the current roadmaps: - Fix security issues and update dependencies - Improve reusability - Improve test coverage -- Connect label to the ministry's SSO/LDAP - Use dbsder api and dbsder-api-type instead of `sder` repository - Rethinking the use of the pelta design system - Use mongoose diff --git a/docker.env.example b/docker.env.example index 3405bd2d1..85a9b9ab9 100644 --- a/docker.env.example +++ b/docker.env.example @@ -11,3 +11,36 @@ NLP_PSEUDONYMISATION_API_ENABLED=false JWT_PRIVATE_KEY=myPrivateKey SDER_DB_URL=http://127.0.0.1:55433 RUN_MODE=LOCAL + +##SSO VARIABLES +COOKIE_PRIVATE_KEY=myPrivateKey +#Service Provider (SP) +SSO_SP_ENTITY_ID=http://localhost:55430/label/api/sso/metadata +SSO_SP_ASSERTION_CONSUMER_SERVICE_LOCATION=http://localhost:55430/label/api/sso/acs +#Identity Provider (IdP) +SSO_IDP_METADATA=sso_files/sso_idp_metadata.xml +SSO_IDP_SINGLE_SIGN_ON_SERVICE_LOCATION=http://test.sso.intranet.justice.gouv.fr:9000/saml/singleSignOn +SSO_IDP_SINGLE_LOGOUT_SERVICE_LOCATION=http://test.sso.intranet.justice.gouv.fr:9000/saml/singleLogout +SSO_CERTIFICAT=certificate-example.pem +SSO_SP_PRIVATE_KEY=privatekey-example.pem +# Les valeurs possibles du SSO_NAME_ID_FORMAT sont le IDP metadata.xml +SSO_NAME_ID_FORMAT=urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress +SSO_SIGNATURE_ALGORITHM="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" +# Authentication +#604800=7*24*60*60 +SESSION_DURATION=604800 +# FRONT END +SSO_FRONT_SUCCESS_CONNEXION_ANNOTATOR_URL=http://localhost:55432/label/annotation +SSO_FRONT_SUCCESS_CONNEXION_ADMIN_SCRUTATOR_URL=http://localhost:55432/label/admin/main/summary +SSO_FRONT_SUCCESS_CONNEXION_PUBLICATOR_URL=http://localhost:55432/label/publishable-documents +# SSO URL du back à setter dans le serveur du client (front react) +REACT_APP_BACKEND_API_URL=http://localhost:55430 +#ATTRIBUTS KEYS +SSO_ATTRIBUTE_NAME=nom +SSO_ATTRIBUTE_FIRSTNAME=prenom +SSO_ATTRIBUTE_FULLNAME=name +SSO_ATTRIBUTE_MAIL=email +SSO_ATTRIBUTE_ROLE=role +#APPLICATION NAME +SSO_APP_NAME=LABEL +SSO_APP_ROLES=admin,annotator,publicator,scrutator \ No newline at end of file diff --git a/docs/images/LABEL_auth_workflow.png b/docs/images/LABEL_auth_workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..413a74cf062ef264a7cd3a1d02e6c9f5ca731c1f GIT binary patch literal 47770 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 Vi)Wps9d{Ujz|+;wWt~$(696X0K{)^b literal 0 HcmV?d00001 diff --git a/package.json b/package.json index bd76c8d31..d7d712d27 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,14 @@ "scripts": { "build": "lerna run build --stream && cp -r packages/generic/client/build/ packages/generic/backend/", "build:backend": "lerna run --ignore @label/client build --stream", - "build:client": "lerna run --ignore @label/backend --ignore @label/cour-de-cassation build --stream", + "build:client": "lerna run --ignore @label/backend --ignore @label/cour-de-cassation --ignore @label/sso build --stream", + "build:sso": "lerna run --ignore @label/backend --ignore @label/client --ignore @label/cour-de-cassation build --stream", "clean": "lerna run clean", "clean:all": "lerna run clean:all && rimraf -rf ./node_modules", "compile": "lerna run compile", "compile:backend": "lerna run --ignore @label/client compile", - "compile:client": "lerna run --ignore @label/backend --ignore @label/cour-de-cassation compile ", + "compile:client": "lerna run --ignore @label/backend --ignore @label/cour-de-cassation --ignore @label/sso compile ", + "compile:sso": "lerna run --ignore @label/backend --ignore @label/cour-de-cassation --ignore @label/client compile", "coverage": "lerna run test:coverage", "docker:build:backend": "docker compose -f docker-compose-dev.yml build", "docker:start:backend": "docker compose -f docker-compose-dev.yml up", diff --git a/packages/courDeCassation/src/scripts/insertUser.ts b/packages/courDeCassation/src/scripts/insertUser.ts index 48792d7d1..288109ab3 100644 --- a/packages/courDeCassation/src/scripts/insertUser.ts +++ b/packages/courDeCassation/src/scripts/insertUser.ts @@ -4,11 +4,11 @@ import { parametersHandler } from '../lib/parametersHandler'; (async () => { const { settings } = await parametersHandler.getParameters(); - const { email, name, password, role } = parseArgv(); + const { email, name, role } = parseArgv(); const backend = buildBackend(settings); await backend.runScript( - () => backend.scripts.insertUser.run({ email, name, password, role }), + () => backend.scripts.insertUser.run({ email, name, role }), backend.scripts.insertUser.option, ); })(); @@ -26,11 +26,6 @@ function parseArgv() { description: 'Name of the new user', type: 'array', }, - password: { - demandOption: true, - description: 'Password of the new user', - type: 'string', - }, role: { demandOption: true, description: 'Role of the new user (must be "admin" or "annotator")', @@ -46,8 +41,7 @@ function parseArgv() { return { email: argv.email as string, - name: (argv.name as string[]).join(' ') as string, - password: argv.password as string, + name: (argv.name as string[]).join(' '), role: argv.role as 'admin' | 'annotator', }; } diff --git a/packages/courDeCassation/src/sderApi/sderApiType.ts b/packages/courDeCassation/src/sderApi/sderApiType.ts index 78ff3a6ee..b6bb24eeb 100644 --- a/packages/courDeCassation/src/sderApi/sderApiType.ts +++ b/packages/courDeCassation/src/sderApi/sderApiType.ts @@ -1,6 +1,5 @@ -import { decisionType, publishStatusType } from 'sder'; +import { decisionType, publishStatusType, labelTreatmentsType } from 'sder'; import { documentType } from '@label/core'; -import { labelTreatmentsType } from 'sder'; export type { sderApiType }; diff --git a/packages/generic/backend/babel.config.json b/packages/generic/backend/babel.config.json index 3313ff9ef..1a1e5f849 100644 --- a/packages/generic/backend/babel.config.json +++ b/packages/generic/backend/babel.config.json @@ -1,3 +1,9 @@ { - "presets": ["@babel/preset-env", "@babel/preset-typescript"] + "presets": [ + "@babel/preset-env", + "@babel/preset-typescript" + ], + "plugins": [ + "@babel/plugin-transform-runtime" + ] } diff --git a/packages/generic/backend/jest.config.json b/packages/generic/backend/jest.config.json index 368ed5a21..dcc3dd5ef 100644 --- a/packages/generic/backend/jest.config.json +++ b/packages/generic/backend/jest.config.json @@ -11,6 +11,9 @@ "ts" ], "rootDir": "src", + "setupFiles": [ + "core-js" + ], "setupFilesAfterEnv": [ "./test/setupTests.ts" ], diff --git a/packages/generic/backend/package.json b/packages/generic/backend/package.json index 852e25517..d83b26dcd 100644 --- a/packages/generic/backend/package.json +++ b/packages/generic/backend/package.json @@ -36,8 +36,10 @@ }, "dependencies": { "@label/core": "*", + "@label/sso": "*", "@types/cors": "^2.8.7", "@types/express": "^4.17.8", + "@types/express-session": "^1.18.0", "@types/jest": "^26.0.13", "@types/lodash": "^4.14.161", "@types/mongodb": "^3.5.27", @@ -46,6 +48,7 @@ "body-parser": "^1.19.0", "cors": "^2.8.5", "express": "^4.17.1", + "express-session": "^1.18.0", "fast-xml-parser": "^3.17.4", "iconv-lite": "^0.6.2", "joi": "17.11.0", @@ -58,6 +61,7 @@ "devDependencies": { "@babel/cli": "^7.11.6", "@babel/core": "^7.11.6", + "@babel/plugin-transform-runtime": "^7.10.4", "@babel/polyfill": "^7.11.5", "@babel/preset-env": "^7.11.5", "@babel/preset-typescript": "^7.10.4", @@ -67,6 +71,7 @@ "@types/supertest": "^2.0.8", "@typescript-eslint/eslint-plugin": "^4.4.1", "@typescript-eslint/parser": "^4.4.1", + "dotenv": "^16.4.5", "core-js": "^3.6.5", "eslint": "7.7.0", "eslint-config-prettier": "^6.12.0", diff --git a/packages/generic/backend/src/api/buildApi.ts b/packages/generic/backend/src/api/buildApi.ts index caa08fd9c..b883ccb58 100644 --- a/packages/generic/backend/src/api/buildApi.ts +++ b/packages/generic/backend/src/api/buildApi.ts @@ -4,6 +4,7 @@ import { CustomError, httpStatusCodeHandler } from 'sder-core'; import { apiSchema, apiSchemaMethodNameType } from '@label/core'; import { logger } from '../utils'; import { controllers } from './controllers'; +import { ssoService } from '../modules/sso'; export { buildApi }; @@ -15,6 +16,9 @@ function buildApi(app: Express) { ) as any) as apiSchemaMethodNameType[]; methodNames.map((methodName) => buildMethod(app, methodName)); + + // urls SSO + buildApiSso(app); } function buildMethod(app: Express, methodName: apiSchemaMethodNameType) { @@ -56,7 +60,12 @@ function buildPostRoutes(app: Express) { function buildController( method: apiSchemaMethodNameType, - controller: (param: { headers: any; args: any }) => Promise, + controller: (param: { + headers: any; + args: any; + session: any; + path: string; + }) => Promise, ) { /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ @@ -92,15 +101,97 @@ function buildController( data: await controller({ headers: req.headers, args: sanitizedQuery, + session: req.session, + path: req.path, }), statusCode: httpStatusCodeHandler.HTTP_STATUS_CODE.SUCCESS.OK, }; case 'post': return { - data: await controller({ headers: req.headers, args: req.body }), + data: await controller({ + headers: req.headers, + args: req.body, + session: req.session, + path: req.path, + }), statusCode: httpStatusCodeHandler.HTTP_STATUS_CODE.SUCCESS.CREATED, }; } } }; } + +function buildApiSso(app: Express) { + app.get(`${API_BASE_URL}/sso/metadata`, async (req, res) => { + try { + const xml = await ssoService.getMetadata(); + res.type('application/xml').send(xml); + } catch (err) { + res + .status(httpStatusCodeHandler.HTTP_STATUS_CODE.ERROR.SERVER_ERROR) + .send(`Metadata SAML protocol error ${err}`); + } + }); + + app.get(`${API_BASE_URL}/sso/login`, async (req, res) => { + try { + const context = await ssoService.login(); + res.redirect(context); + } catch (err) { + await logger.error({ + operationName: 'login SSO ', + msg: `${err}`, + }); + res + .status( + httpStatusCodeHandler.HTTP_STATUS_CODE.ERROR.AUTHENTICATION_ERROR, + ) + .json({ status: 401, message: err.message }); + } + }); + + app.get(`${API_BASE_URL}/sso/logout`, (req, res) => { + const nameID = String(req.session.user?.email); + const sessionIndex = String(req.session.user?.sessionIndex); + req.session.destroy(async (err) => { + if (err) { + res.status(httpStatusCodeHandler.HTTP_STATUS_CODE.ERROR.SERVER_ERROR); + } + try { + const context = await ssoService.logout({ nameID, sessionIndex }); + res.redirect(context); + } catch (err) { + await logger.error({ + operationName: 'logoutSso SamlService ', + msg: `${err}`, + }); + res + .status(httpStatusCodeHandler.HTTP_STATUS_CODE.ERROR.SERVER_ERROR) + .json({ status: 500, message: err.message }); + } + }); + }); + + app.get(`${API_BASE_URL}/sso/whoami`, (req, res) => { + const user = req.session?.user ?? null; + if (!user) { + return res + .status( + httpStatusCodeHandler.HTTP_STATUS_CODE.ERROR.AUTHENTICATION_ERROR, + ) + .send({ status: 401, message: `Session invalid or expired` }); + } + res.type('application/json').send(user); + }); + + app.post(`${API_BASE_URL}/sso/acs`, async (req, res) => { + try { + const url = await ssoService.acs(req); + res.redirect(url); + } catch (err) { + res + .status(httpStatusCodeHandler.HTTP_STATUS_CODE.ERROR.SERVER_ERROR) + res.redirect(`${API_BASE_URL}/sso/logout`); + } + }); +} diff --git a/packages/generic/backend/src/api/buildAuthenticatedController/buildAuthenticatedController.ts b/packages/generic/backend/src/api/buildAuthenticatedController/buildAuthenticatedController.ts index 16198a08a..99f06ef25 100644 --- a/packages/generic/backend/src/api/buildAuthenticatedController/buildAuthenticatedController.ts +++ b/packages/generic/backend/src/api/buildAuthenticatedController/buildAuthenticatedController.ts @@ -1,14 +1,8 @@ -import { userModule, userType } from '@label/core'; -import { userService } from '../../modules/user'; +import { idModule, userModule, userType } from '@label/core'; +import { errorHandlers } from 'sder-core'; export { buildAuthenticatedController }; -export type { authorizationHeaderType }; - -type authorizationHeaderType = { - authorization: string; -}; - function buildAuthenticatedController({ permissions, controllerWithUser, @@ -16,16 +10,43 @@ function buildAuthenticatedController({ permissions: Array; controllerWithUser: ( user: userType, - req: { args: inT; headers: authorizationHeaderType }, + req: { args: inT; headers: any; session?: any; path?: string }, ) => Promise; -}): (req: { args: inT; headers: authorizationHeaderType }) => Promise { - return async (req: { args: inT; headers: authorizationHeaderType }) => { - const user = await userService.fetchAuthenticatedUserFromAuthorizationHeader( - req.headers.authorization, - ); - userModule.lib.assertAuthorization(user); - userModule.lib.assertPermissions(user, permissions); +}): (req: { + args: inT; + headers: any; + session?: any; + path?: string; +}) => Promise { + return async (req: { + args: inT; + headers: any; + session?: any; + path?: string; + }) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + let currentUser = req.session?.user ?? null; + if (!currentUser) { + throw errorHandlers.authenticationErrorHandler.build( + `user session has expired or is invalid`, + ); + } + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const isAnnotator = currentUser.role === 'annotator'; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const isAdminAccessingDocs = + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + currentUser.role === 'admin' && req.path?.includes('documentsForUser'); - return controllerWithUser(user, req); + if (isAnnotator || isAdminAccessingDocs) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + currentUser = { + ...currentUser, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + _id: idModule.lib.buildId(currentUser._id), + }; + } + userModule.lib.assertPermissions(currentUser, permissions); + return controllerWithUser(currentUser, req); }; } diff --git a/packages/generic/backend/src/api/controllerType.ts b/packages/generic/backend/src/api/controllerType.ts index 16effe602..a1aa48246 100644 --- a/packages/generic/backend/src/api/controllerType.ts +++ b/packages/generic/backend/src/api/controllerType.ts @@ -14,6 +14,7 @@ type controllersFromSchemaType = { [controllerName in keyof apiSchemaT[methodName]]: (req: { args: networkType>; headers: any; + session?: any; }) => Promise>; } : never; diff --git a/packages/generic/backend/src/api/controllers.ts b/packages/generic/backend/src/api/controllers.ts index 092753998..c668a43c2 100644 --- a/packages/generic/backend/src/api/controllers.ts +++ b/packages/generic/backend/src/api/controllers.ts @@ -1,4 +1,4 @@ -import { apiSchema, documentType, idModule } from '@label/core'; +import { apiSchema, documentType, idModule, replacementTermType } from '@label/core'; import { errorHandlers } from 'sder-core'; import { settingsLoader } from '../lib/settingsLoader'; import { assignationService } from '../modules/assignation'; @@ -12,7 +12,6 @@ import { buildAuthenticatedController } from './buildAuthenticatedController'; import { controllersFromSchemaType } from './controllerType'; import { annotationReportService } from '../modules/annotationReport'; import { preAssignationService } from '../modules/preAssignation'; -import { replacementTermType } from '@label/core'; export { controllers }; @@ -80,7 +79,7 @@ const controllers: controllersFromSchemaType = { await cacheService.fetchAllByKey('availableStatisticFilters') )[0]; if (cache) { - return JSON.parse(cache.content as string) as { + return JSON.parse(cache.content) as { publicationCategories: string[]; maxDate: number | undefined; minDate: number | undefined; @@ -128,7 +127,7 @@ const controllers: controllersFromSchemaType = { documentsForUser: buildAuthenticatedController({ permissions: ['admin', 'annotator'], controllerWithUser: async (user, { args: { documentsMaxCount } }) => - documentService.fetchDocumentsForUser(user._id, documentsMaxCount), + documentService.fetchDocumentsForUser(idModule.lib.buildId(user._id), documentsMaxCount), }), async health() { @@ -237,14 +236,6 @@ const controllers: controllersFromSchemaType = { }, }), - changePassword: buildAuthenticatedController({ - permissions: ['admin', 'annotator', 'publicator', 'scrutator'], - controllerWithUser: async ( - user, - { args: { previousPassword, newPassword } }, - ) => userService.changePassword({ user, previousPassword, newPassword }), - }), - deleteProblemReport: buildAuthenticatedController({ permissions: ['admin'], controllerWithUser: async (_, { args: { problemReportId } }) => @@ -275,27 +266,6 @@ const controllers: controllersFromSchemaType = { }, }), - async login({ args: { email, password } }) { - const { - _id, - email: userEmail, - name, - role, - token, - passwordTimeValidityStatus, - } = await userService.login({ - email, - password, - }); - return { - email: userEmail, - name, - role, - token, - _id, - passwordTimeValidityStatus, - }; - }, problemReport: buildAuthenticatedController({ permissions: ['admin', 'annotator', 'scrutator'], @@ -304,7 +274,7 @@ const controllers: controllersFromSchemaType = { { args: { documentId, problemText, problemType } }, ) => { await problemReportService.createProblemReport({ - userId: user._id, + userId: idModule.lib.buildId(user._id), documentId: idModule.lib.buildId(documentId), problemText, problemType, @@ -319,11 +289,6 @@ const controllers: controllersFromSchemaType = { }, }), - resetPassword: buildAuthenticatedController({ - permissions: ['admin'], - controllerWithUser: async (_, { args: { userId } }) => - userService.resetPassword(idModule.lib.buildId(userId)), - }), resetTreatmentLastUpdateDate: buildAuthenticatedController({ permissions: ['admin', 'annotator'], @@ -332,7 +297,7 @@ const controllers: controllersFromSchemaType = { idModule.lib.buildId(assignationId), ); - if (!idModule.lib.equalId(user._id, assignation.userId)) { + if (!idModule.lib.equalId(idModule.lib.buildId(user._id), assignation.userId)) { throw errorHandlers.permissionErrorHandler.build( `User ${idModule.lib.convertToString( user._id, @@ -346,20 +311,7 @@ const controllers: controllersFromSchemaType = { }, }), - setDeletionDateForUser: buildAuthenticatedController({ - permissions: ['admin'], - controllerWithUser: async (_, { args: { userId } }) => - userService.setDeletionDateForUser(idModule.lib.buildId(userId)), - }), - setIsActivatedForUser: buildAuthenticatedController({ - permissions: ['admin'], - controllerWithUser: async (_, { args: { userId, isActivated } }) => - userService.setIsActivatedForUser({ - userId: idModule.lib.buildId(userId), - isActivated, - }), - }), updateAssignationDocumentStatus: buildAuthenticatedController({ permissions: ['admin'], @@ -377,7 +329,7 @@ const controllers: controllersFromSchemaType = { if (user.role !== 'admin' && user.role !== 'publicator') { await assignationService.assertDocumentIsAssignatedToUser({ documentId: idModule.lib.buildId(documentId), - userId: user._id, + userId: idModule.lib.buildId(user._id), }); } @@ -431,7 +383,7 @@ const controllers: controllersFromSchemaType = { idModule.lib.buildId(assignationId), ); - if (!idModule.lib.equalId(user._id, assignation.userId)) { + if (!idModule.lib.equalId(idModule.lib.buildId(user._id), assignation.userId)) { throw errorHandlers.permissionErrorHandler.build( `User ${idModule.lib.convertToString( user._id, @@ -454,7 +406,7 @@ const controllers: controllersFromSchemaType = { const assignation = await assignationService.fetchAssignation( idModule.lib.buildId(assignationId), ); - if (!idModule.lib.equalId(user._id, assignation.userId)) { + if (!idModule.lib.equalId(idModule.lib.buildId(user._id), assignation.userId)) { throw errorHandlers.permissionErrorHandler.build( `User ${idModule.lib.convertToString( user._id, @@ -482,7 +434,7 @@ const controllers: controllersFromSchemaType = { { annotationsDiff, documentId: idModule.lib.buildId(documentId), - userId: user._id, + userId: idModule.lib.buildId(user._id), }, settings, ); diff --git a/packages/generic/backend/src/app/buildRunServer.ts b/packages/generic/backend/src/app/buildRunServer.ts index 7c493a3da..b034d2fb4 100644 --- a/packages/generic/backend/src/app/buildRunServer.ts +++ b/packages/generic/backend/src/app/buildRunServer.ts @@ -7,6 +7,9 @@ import { buildApi } from '../api'; import { setup } from './setup'; import { envSchema } from './envSchema'; +import session from 'express-session'; + + export { buildRunServer }; function buildRunServer(settings: settingsType) { @@ -24,10 +27,27 @@ function buildRunServer(settings: settingsType) { app.use( cors({ origin: [`${process.env.LABEL_CLIENT_URL}`], + credentials: true, }), ); app.use(bodyParser.json({ limit: '1mb' })); + app.use(bodyParser.urlencoded({ extended: true })); + + // Configuration de la session + const sessionMiddleware = session({ + secret: `${process.env.COOKIE_PRIVATE_KEY}`, + resave: false, + saveUninitialized: false, + cookie: { + maxAge: Number(process.env.SESSION_DURATION), + secure: false, + }, + }); + + app.use((req, res, next) => { + sessionMiddleware(req, res, next); + }); buildApi(app); diff --git a/packages/generic/backend/src/app/express-session.d.ts b/packages/generic/backend/src/app/express-session.d.ts new file mode 100644 index 000000000..c01604860 --- /dev/null +++ b/packages/generic/backend/src/app/express-session.d.ts @@ -0,0 +1,21 @@ +import session from 'express-session'; + +declare module 'express-session' { + interface SessionData { + user: { + _id: string; + name: string; + role: string; + email: string; + sessionIndex: string; + }; + } +} + +declare global { + namespace Express { + interface Request { + session: session.Session & Partial; + } + } +} diff --git a/packages/generic/backend/src/app/scripts/insertTestUsers.ts b/packages/generic/backend/src/app/scripts/insertTestUsers.ts index d1895adcb..86d07f9e6 100644 --- a/packages/generic/backend/src/app/scripts/insertTestUsers.ts +++ b/packages/generic/backend/src/app/scripts/insertTestUsers.ts @@ -6,19 +6,16 @@ async function insertTestUsers() { await userService.signUpUser({ email: 'test.annotator@label.fr', name: 'Test Annotator', - password: 'annotator', role: 'annotator', }); await userService.signUpUser({ email: 'test.scrutator@label.fr', name: 'Test Scrutator', - password: 'scrutator', role: 'scrutator', }); await userService.signUpUser({ email: 'test.admin@label.fr', name: 'Test Admin', - password: 'admin', role: 'admin', }); } diff --git a/packages/generic/backend/src/app/scripts/insertUser.ts b/packages/generic/backend/src/app/scripts/insertUser.ts index 74fabdd23..6d54ac224 100644 --- a/packages/generic/backend/src/app/scripts/insertUser.ts +++ b/packages/generic/backend/src/app/scripts/insertUser.ts @@ -1,5 +1,4 @@ import { userType } from '@label/core'; -import { userService } from '../../modules/user'; import { logger } from '../../utils'; export { insertUser }; @@ -7,12 +6,10 @@ export { insertUser }; async function insertUser({ email, name, - password, role, }: { email: string; name: string; - password: string; role: userType['role']; }) { logger.log({ @@ -25,12 +22,5 @@ async function insertUser({ }, }); - await userService.signUpUser({ - email, - name, - password, - role, - }); - logger.log({ operationName: 'insertUser', msg: 'DONE' }); } diff --git a/packages/generic/backend/src/modules/document/service/documentService/fetchDocumentsForUser.ts b/packages/generic/backend/src/modules/document/service/documentService/fetchDocumentsForUser.ts index bc5c934fc..09011ad0d 100644 --- a/packages/generic/backend/src/modules/document/service/documentService/fetchDocumentsForUser.ts +++ b/packages/generic/backend/src/modules/document/service/documentService/fetchDocumentsForUser.ts @@ -32,7 +32,7 @@ function buildFetchDocumentsForUser( // Documents already assignated to the user are fetched const alreadyAssignatedDocuments = await fetchAlreadyAssignatedDocuments( - userId, + idModule.lib.buildId(userId), ); for ( let i = 0; @@ -53,7 +53,7 @@ function buildFetchDocumentsForUser( for (let i = documents.length; i < documentsMaxCount; i++) { try { const assignatedDocument = await fetchDocumentForUser( - userId, + idModule.lib.buildId(userId), documentIdsWithAnnotations, ); documents.push(assignatedDocument); diff --git a/packages/generic/backend/src/modules/sso/index.ts b/packages/generic/backend/src/modules/sso/index.ts new file mode 100644 index 000000000..9d67c6efe --- /dev/null +++ b/packages/generic/backend/src/modules/sso/index.ts @@ -0,0 +1,3 @@ +import { ssoService } from './service'; + +export { ssoService }; diff --git a/packages/generic/backend/src/modules/sso/service/express-session.d.ts b/packages/generic/backend/src/modules/sso/service/express-session.d.ts new file mode 100644 index 000000000..d01414d7a --- /dev/null +++ b/packages/generic/backend/src/modules/sso/service/express-session.d.ts @@ -0,0 +1,22 @@ +import 'express-session'; +import session from 'express-session'; + +declare module 'express-session' { + interface SessionData { + user: { + _id: string; + name: string; + role: string; + email: string; + sessionIndex: string; + }; + } +} + +declare global { + namespace Express { + interface Request { + session: session.Session & Partial; + } + } +} diff --git a/packages/generic/backend/src/modules/sso/service/index.ts b/packages/generic/backend/src/modules/sso/service/index.ts new file mode 100644 index 000000000..41e055bcc --- /dev/null +++ b/packages/generic/backend/src/modules/sso/service/index.ts @@ -0,0 +1,22 @@ +import { buildCallAttemptsRegulator } from 'sder-core'; +import { acs, getMetadata, login, logout } from './ssoService'; + +const DELAY_BETWEEN_LOGIN_ATTEMPTS_IN_SECONDS = 1 * 1000; + +const MAX_LOGIN_ATTEMPTS = 1; + +function buildSsoService() { + buildCallAttemptsRegulator( + MAX_LOGIN_ATTEMPTS, + DELAY_BETWEEN_LOGIN_ATTEMPTS_IN_SECONDS, + ); + + return { + acs, + getMetadata, + login, + logout, + }; +} + +export const ssoService = buildSsoService(); diff --git a/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts b/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts new file mode 100644 index 000000000..fa85ab467 --- /dev/null +++ b/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts @@ -0,0 +1,260 @@ +import { + acs, + getMetadata, + login, + logout, + samlService, + setUserSessionAndReturnRedirectUrl, +} from './ssoService'; +import { buildUserRepository } from '../../user'; +import { buildUserService } from '../../user/service/userService'; + +jest.mock('@label/sso', () => ({ + SamlService: jest.fn().mockImplementation(() => ({ + generateMetadata: jest.fn().mockReturnValue(''), + createLoginRequestUrl: jest + .fn() + .mockResolvedValue({ context: 'login-url' }), + createLogoutRequestUrl: jest + .fn() + .mockResolvedValue({ context: 'logout-url' }), + parseResponse: jest.fn().mockResolvedValue({ + extract: { + nameID: 'test@example.com', + name: 'Test User', + role: 'annotator', + sessionIndex: 'session123', + }, + }), + })), +})); +jest.mock('../../../utils/logger', () => ({ + logger: { + log: jest.fn(), + error: jest.fn(), + }, +})); +jest.mock('../../user', () => ({ + userService: { + createUser: jest.fn(), + }, +})); +jest.mock('../../user', () => ({ + buildUserRepository: jest.fn().mockImplementation(() => ({ + findByEmail: jest.fn().mockResolvedValue({ + _id: '123', + email: 'test@example.com', + name: 'Test User', + role: 'annotator', + }), + })), +})); + +process.env.SSO_FRONT_SUCCESS_CONNEXION_ANNOTATOR_URL = + 'http://localhost:55432/label/annotation'; +(process.env.SSO_FRONT_SUCCESS_CONNEXION_ADMIN_SCRUTATOR_URL = + 'http://localhost:55432/label/admin/main/summary'), + (process.env.SSO_FRONT_SUCCESS_CONNEXION_PUBLICATOR_URL = + 'http://localhost:55432/label/publishable-documents'); + +describe('SSO CNX functions', () => { + describe('getMetadataSso', () => { + it('should return SAML metadata', async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const metadata = await getMetadata(); + expect(metadata).toBe(''); + }); + }); + + describe('loginSso', () => { + it('should return login URL', async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const loginUrl = await login(); + expect(loginUrl).toBe('login-url'); + }); + }); + + describe('logoutSso', () => { + it('should return logout URL', async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const logoutUrl = await logout({ + nameID: 'test-user-id', + sessionIndex: 'test-session-index', + }); + expect(logoutUrl).toBe('logout-url'); + }); + }); + + describe('acsSso', () => { + it('should handle ACS SSO and return a redirect URL', async () => { + const mockReq = { + body: { SAMLResponse: 'mock-saml-response' }, + session: { + user: { + _id: '123', + email: 'test@example.com', + name: 'Test User', + role: 'annotator', + }, + }, + }; + const redirectUrl = await acs(mockReq); + expect(redirectUrl).toContain( + process.env.SSO_FRONT_SUCCESS_CONNEXION_ANNOTATOR_URL, + ); + expect(mockReq.session.user).toBeDefined(); + expect(mockReq.session.user.email).toBe('test@example.com'); + }); + + const mockReq = { + body: { SAMLResponse: 'dummyResponse' }, + }; + + it('should handle the case where user does not exist and is auto-provisioned', async () => { + const mockNewUser = { email: 'newuser@example.com' }; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + (samlService.parseResponse as jest.Mock).mockResolvedValue({ + extract: { + nameID: 'newuser@example.com', + sessionIndex: 'session123', + attributes: { + role: ['ANNOTATEUR'], + email: 'newuser@example.com', + name: 'New User', + }, + }, + }); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment + const userRepository = buildUserRepository(); + jest + .spyOn(userRepository, 'findByEmail') + .mockRejectedValue(new Error('No matching user for email newuser@example.com')); + + const userService = buildUserService(); + jest + .spyOn(userService, 'createUser') + .mockResolvedValue('User created successfully'); + + jest.spyOn(userRepository, 'findByEmail').mockReturnValue(mockNewUser as any); + + const result = await acs(mockReq); + + expect(result).toBeDefined(); + }); + + }); + + describe('setUserSessionAndReturnRedirectUrl', () => { + const mockRequest = { + session: { + user: {}, + }, + }; + + it('should return the correct URL for annotator role', () => { + const user = { + _id: '1', + name: 'Annotator', + role: 'annotator', + email: 'annotator@test.com', + }; + const result = setUserSessionAndReturnRedirectUrl( + mockRequest, + user, + 'test-session-index', + ); + expect(mockRequest.session.user).toEqual({ + ...user, + sessionIndex: 'test-session-index', + }); + expect(result).toEqual( + process.env.SSO_FRONT_SUCCESS_CONNEXION_ANNOTATOR_URL, + ); + }); + + it('should return the correct URL for admin role', () => { + const user = { + _id: '2', + name: 'Admin', + role: 'admin', + email: 'admin@test.com', + }; + const result = setUserSessionAndReturnRedirectUrl( + mockRequest, + user, + 'test-session-index', + ); + + expect(mockRequest.session.user).toEqual({ + ...user, + sessionIndex: 'test-session-index', + }); + expect(result).toEqual( + process.env.SSO_FRONT_SUCCESS_CONNEXION_ADMIN_SCRUTATOR_URL, + ); + }); + + it('should return the correct URL for scrutator role', () => { + const user = { + _id: '3', + name: 'Scrutator', + role: 'scrutator', + email: 'scrutator@test.com', + }; + const result = setUserSessionAndReturnRedirectUrl( + mockRequest, + user, + 'test-session-index', + ); + + expect(mockRequest.session.user).toEqual({ + ...user, + sessionIndex: 'test-session-index', + }); + expect(result).toEqual( + process.env.SSO_FRONT_SUCCESS_CONNEXION_ADMIN_SCRUTATOR_URL, + ); + }); + + it('should return the correct URL for publicator role', () => { + const user = { + _id: '4', + name: 'Publicator', + role: 'publicator', + email: 'publicator@test.com', + }; + const result = setUserSessionAndReturnRedirectUrl( + mockRequest, + user, + 'test-session-index', + ); + + expect(mockRequest.session.user).toEqual({ + ...user, + sessionIndex: 'test-session-index', + }); + expect(result).toEqual( + process.env.SSO_FRONT_SUCCESS_CONNEXION_PUBLICATOR_URL, + ); + }); + + it('should throw an error for an invalid role', () => { + const user = { + _id: '5', + name: 'InvalidRole', + role: 'invalidRole', + email: 'invalid@test.com', + }; + + expect(() => { + setUserSessionAndReturnRedirectUrl( + mockRequest, + user, + 'test-session-index', + ); + }).toThrowError("Role doesn't exist in label"); + }); + }); +}); diff --git a/packages/generic/backend/src/modules/sso/service/ssoService.ts b/packages/generic/backend/src/modules/sso/service/ssoService.ts new file mode 100644 index 000000000..f023d7f2a --- /dev/null +++ b/packages/generic/backend/src/modules/sso/service/ssoService.ts @@ -0,0 +1,166 @@ +import { SamlService } from '@label/sso'; +import { buildUserRepository, userService } from '../../user'; +import { logger } from '../../../utils'; +import every from "lodash/every"; +import includes from "lodash/includes"; + +export { samlService }; + +function ssoSamlService() { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return + return new SamlService(); +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment +const samlService = ssoSamlService(); + +export async function getMetadata() { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-return + return samlService.generateMetadata(); +} + +export async function login() { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + const { context } = await samlService.createLoginRequestUrl(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return context; +} + +export async function logout(user: { nameID: string; sessionIndex: string }) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + const { context } = await samlService.createLogoutRequestUrl(user); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return context; +} + +export async function acs(req: any) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call + const response = await samlService.parseResponse(req); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const { extract } = response; + + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + const user = await getUserByEmail(extract?.nameID); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + await logger.log({ + operationName: 'user connected', + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + msg: user.email, + }); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return setUserSessionAndReturnRedirectUrl(req, user, extract?.sessionIndex); + } catch (err) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + await logger.log({ + operationName: `catch autoprovision user ${JSON.stringify(err)}`, + msg: `${err}`, + }); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + if (err.message.includes(`No matching user for email ${extract?.nameID}`)) { + const { attributes } = extract as Record; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const roles = attributes[`${process.env.SSO_ATTRIBUTE_ROLE}`].map((item: string) => item.toLowerCase()) as string[]; + + const appRoles = (process.env.SSO_APP_ROLES as string).toLowerCase().split(','); + const userRolesInAppRoles = every(roles, (element) => includes(appRoles, element)); + + if (!roles.length || !userRolesInAppRoles) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const errorMsg = `User ${extract.nameID}, role ${roles} doesn't exist in application ${process.env.SSO_APP_NAME}`; + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + logger.error({ operationName: 'ssoService', msg: errorMsg }); + throw new Error(errorMsg); + } + + const newUser = { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + name: + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + attributes[`${process.env.SSO_ATTRIBUTE_FULLNAME}`] || + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + `${attributes[`${process.env.SSO_ATTRIBUTE_NAME}`]} ${ + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + attributes[`${process.env.SSO_ATTRIBUTE_FIRSTNAME}`] + }`, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + email: attributes[`${process.env.SSO_ATTRIBUTE_MAIL}`], + role: roles[0] as + | 'annotator' + | 'scrutator' + | 'admin' + | 'publicator', + }; + + await userService.createUser(newUser); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const createdUser = await getUserByEmail(newUser.email); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + await logger.log({ + operationName: `Auto-provided user`, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + msg: `successfully created user ${createdUser.email}`, + }); + + return setUserSessionAndReturnRedirectUrl( + req, + createdUser, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + extract?.sessionIndex, + ); + } else { + throw new Error(`Error in acsSso: ${err}`); + } + } +} + +export async function getUserByEmail(email: string) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call + const userRepository = buildUserRepository(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + return await userRepository.findByEmail(email); +} + +export function setUserSessionAndReturnRedirectUrl( + req: any, + user: any, + sessionIndex: string, +) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (req.session) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + req.session.user = { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + _id: user._id, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + name: user.name, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + role: user.role, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + email: user.email, + sessionIndex: sessionIndex, + }; + } + + const roleToUrlMap: Record = { + annotator: process.env.SSO_FRONT_SUCCESS_CONNEXION_ANNOTATOR_URL as string, + admin: process.env + .SSO_FRONT_SUCCESS_CONNEXION_ADMIN_SCRUTATOR_URL as string, + scrutator: process.env + .SSO_FRONT_SUCCESS_CONNEXION_ADMIN_SCRUTATOR_URL as string, + publicator: process.env + .SSO_FRONT_SUCCESS_CONNEXION_PUBLICATOR_URL as string, + }; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (!roleToUrlMap[user.role]) { + throw new Error(`Role doesn't exist in label`); + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return roleToUrlMap[user.role]; +} diff --git a/packages/generic/backend/src/modules/user/repository/buildFakeUserRepository.ts b/packages/generic/backend/src/modules/user/repository/buildFakeUserRepository.ts index b7c75d561..c32310469 100644 --- a/packages/generic/backend/src/modules/user/repository/buildFakeUserRepository.ts +++ b/packages/generic/backend/src/modules/user/repository/buildFakeUserRepository.ts @@ -1,7 +1,6 @@ import { userModule, userType } from '@label/core'; import { buildFakeRepositoryBuilder, - projectFakeObjects, } from '../../../repository'; import { customUserRepositoryType } from './customUserRepositoryType'; @@ -13,11 +12,6 @@ const buildFakeUserRepository = buildFakeRepositoryBuilder< >({ collectionName: 'users', buildCustomFakeRepository: (collection) => ({ - async findAllWithNoDeletionDateProjection(projection) { - return collection - .filter((user) => !user.deletionDate) - .map((user) => projectFakeObjects(user, projection)); - }, async findByEmail(email) { const formattedEmail = userModule.lib.formatEmail(email); const result = collection.find((user) => user.email === formattedEmail); @@ -26,17 +20,5 @@ const buildFakeUserRepository = buildFakeRepositoryBuilder< } return result; }, - async updateHashedPassword(userId, hashedPassword) { - const storedUserIndex = collection.findIndex(({ _id }) => _id === userId); - if (storedUserIndex === -1) { - return { success: false }; - } - collection[storedUserIndex] = { - ...collection[storedUserIndex], - passwordLastUpdateDate: Date.now(), - hashedPassword, - }; - return { success: true }; - }, }), }); diff --git a/packages/generic/backend/src/modules/user/repository/buildUserRepository.ts b/packages/generic/backend/src/modules/user/repository/buildUserRepository.ts index eeef3d711..60dedf140 100644 --- a/packages/generic/backend/src/modules/user/repository/buildUserRepository.ts +++ b/packages/generic/backend/src/modules/user/repository/buildUserRepository.ts @@ -1,5 +1,5 @@ import { userModule, userType } from '@label/core'; -import { buildProjection, buildRepositoryBuilder } from '../../../repository'; +import { buildRepositoryBuilder } from '../../../repository'; import { customUserRepositoryType } from './customUserRepositoryType'; export { buildUserRepository }; @@ -18,12 +18,6 @@ const buildUserRepository = buildRepositoryBuilder< } as const, ], buildCustomRepository: (collection) => ({ - async findAllWithNoDeletionDateProjection(projection) { - return collection - .find({ deletionDate: undefined }) - .project(buildProjection(projection as string[])) - .toArray(); - }, async findByEmail(email) { const formattedEmail = userModule.lib.formatEmail(email); const result = await collection.findOne({ email: formattedEmail }); @@ -32,14 +26,5 @@ const buildUserRepository = buildRepositoryBuilder< } return result; }, - async updateHashedPassword(userId, hashedPassword) { - const { result } = await collection.updateOne( - { _id: userId }, - { $set: { hashedPassword, passwordLastUpdateDate: Date.now() } }, - ); - return { - success: result.ok === 1, - }; - }, }), }); diff --git a/packages/generic/backend/src/modules/user/repository/customUserRepositoryType.ts b/packages/generic/backend/src/modules/user/repository/customUserRepositoryType.ts index fb05e0875..95c369bf1 100644 --- a/packages/generic/backend/src/modules/user/repository/customUserRepositoryType.ts +++ b/packages/generic/backend/src/modules/user/repository/customUserRepositoryType.ts @@ -1,15 +1,7 @@ import { userType } from '@label/core'; -import { projectedType } from '../../../repository'; export type { customUserRepositoryType }; type customUserRepositoryType = { - findAllWithNoDeletionDateProjection: ( - projection: Array, - ) => Promise>>; findByEmail: (email: userType['email']) => Promise; - updateHashedPassword: ( - userId: userType['_id'], - hashedPassword: string, - ) => Promise<{ success: boolean }>; }; diff --git a/packages/generic/backend/src/modules/user/service/userService/changePassword.spec.ts b/packages/generic/backend/src/modules/user/service/userService/changePassword.spec.ts deleted file mode 100644 index 7ae871343..000000000 --- a/packages/generic/backend/src/modules/user/service/userService/changePassword.spec.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { userModule } from '@label/core'; -import { buildUserRepository } from '../../repository'; -import { buildUserService } from './index'; - -describe('changePassword', () => { - it('should change the password of the given user', async () => { - const userService = buildUserService(); - const userRepository = buildUserRepository(); - const userEmail = 'MAIL@MAIL.MAIL'; - const userName = 'NAME'; - const userPassword = userModule.lib.passwordHandler.generate(); - const userNewPassword = userModule.lib.passwordHandler.generate(); - const userRole = 'admin'; - await userService.signUpUser({ - email: userEmail, - name: userName, - password: userPassword, - role: userRole, - }); - const user = await userRepository.findByEmail(userEmail); - - const result = await userService.changePassword({ - user, - previousPassword: userPassword, - newPassword: userNewPassword, - }); - - const { email, name, role, token } = await userService.login({ - email: userEmail, - password: userNewPassword, - }); - expect(result).toEqual('passwordUpdated'); - expect(email).toEqual('mail@mail.mail'); - expect(name).toEqual(user.name); - expect(role).toEqual(user.role); - expect(token).toBeDefined; - }); - - it('should not change the password of the given user if the previous password is wrong', async () => { - const userService = buildUserService(); - const userRepository = buildUserRepository(); - const userEmail = 'MAIL@MAIL.MAIL'; - const userName = 'NAME'; - const userPassword = 'PASSWORD'; - const userRole = 'admin'; - await userService.signUpUser({ - email: userEmail, - name: userName, - password: userPassword, - role: userRole, - }); - const user = await userRepository.findByEmail(userEmail); - - const result = await userService.changePassword({ - user, - previousPassword: 'WRONG_PASSWORD', - newPassword: 'NEW_PASSWORD', - }); - - const { email, name, role, token } = await userService.login({ - email: userEmail, - password: userPassword, - }); - expect(result).toEqual('wrongPassword'); - expect(email).toEqual('mail@mail.mail'); - expect(name).toEqual(user.name); - expect(role).toEqual(user.role); - expect(token).toBeDefined; - }); - - it('should not change the password of the given user if the new password is not valid (less than 8 characters)', async () => { - const userService = buildUserService(); - const userRepository = buildUserRepository(); - const userEmail = 'MAIL@MAIL.MAIL'; - const userName = 'NAME'; - const userPassword = 'PASSWORD'; - const userRole = 'admin'; - await userService.signUpUser({ - email: userEmail, - name: userName, - password: userPassword, - role: userRole, - }); - const user = await userRepository.findByEmail(userEmail); - - const result = await userService.changePassword({ - user, - previousPassword: 'PASSWORD', - newPassword: 'P', - }); - - const { email, name, role, token } = await userService.login({ - email: userEmail, - password: userPassword, - }); - expect(result).toEqual('notValidNewPassword'); - expect(email).toEqual('mail@mail.mail'); - expect(name).toEqual(user.name); - expect(role).toEqual(user.role); - expect(token).toBeDefined; - }); - - it('should not change the password of the given user if the new password is not changed', async () => { - const userService = buildUserService(); - const userRepository = buildUserRepository(); - const userEmail = 'MAIL@MAIL.MAIL'; - const userName = 'NAME'; - const userPassword = userModule.lib.passwordHandler.generate(); - const userRole = 'admin'; - await userService.signUpUser({ - email: userEmail, - name: userName, - password: userPassword, - role: userRole, - }); - const user = await userRepository.findByEmail(userEmail); - - const result = await userService.changePassword({ - user, - previousPassword: userPassword, - newPassword: userPassword, - }); - - const { email, name, role, token } = await userService.login({ - email: userEmail, - password: userPassword, - }); - expect(result).toEqual('notValidNewPassword'); - expect(email).toEqual('mail@mail.mail'); - expect(name).toEqual(user.name); - expect(role).toEqual(user.role); - expect(token).toBeDefined; - }); -}); diff --git a/packages/generic/backend/src/modules/user/service/userService/changePassword.ts b/packages/generic/backend/src/modules/user/service/userService/changePassword.ts deleted file mode 100644 index 35ca8e136..000000000 --- a/packages/generic/backend/src/modules/user/service/userService/changePassword.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { hasher, userModule, userType } from '@label/core'; -import { buildUserRepository } from '../../repository'; - -export { changePassword }; - -async function changePassword({ - user, - previousPassword, - newPassword, -}: { - user: userType; - previousPassword: string; - newPassword: string; -}) { - const userRepository = buildUserRepository(); - - const isPreviousPasswordValid = await hasher.compare( - previousPassword, - user.hashedPassword, - ); - - const isNewPasswordEqualToCurrent = await hasher.compare( - newPassword, - user.hashedPassword, - ); - - if (!isPreviousPasswordValid) { - return 'wrongPassword'; - } else if ( - isNewPasswordEqualToCurrent || - !userModule.lib.passwordHandler.isValid(newPassword) - ) { - return 'notValidNewPassword'; - } else { - await userRepository.updateHashedPassword( - user._id, - await userModule.lib.computeHashedPassword(newPassword), - ); - - return 'passwordUpdated'; - } -} diff --git a/packages/generic/backend/src/modules/user/service/userService/createUser.ts b/packages/generic/backend/src/modules/user/service/userService/createUser.ts index a9c24b09e..8f6867734 100644 --- a/packages/generic/backend/src/modules/user/service/userService/createUser.ts +++ b/packages/generic/backend/src/modules/user/service/userService/createUser.ts @@ -1,4 +1,4 @@ -import { userModule, userType } from '@label/core'; +import { userType } from '@label/core'; import { signUpUser } from './signUpUser'; export { createUser }; @@ -12,7 +12,6 @@ async function createUser({ email: string; role: userType['role']; }) { - const password = userModule.lib.passwordHandler.generate(); - await signUpUser({ name, email, role, password }); - return password; + await signUpUser({ name, email, role }); + return `User created successfully `; } diff --git a/packages/generic/backend/src/modules/user/service/userService/fetchWorkingUsers.ts b/packages/generic/backend/src/modules/user/service/userService/fetchWorkingUsers.ts index 2ce01bac0..869ddd1e0 100644 --- a/packages/generic/backend/src/modules/user/service/userService/fetchWorkingUsers.ts +++ b/packages/generic/backend/src/modules/user/service/userService/fetchWorkingUsers.ts @@ -4,18 +4,20 @@ export { fetchWorkingUsers }; async function fetchWorkingUsers() { const userRepository = buildUserRepository(); - const users = await userRepository.findAllWithNoDeletionDateProjection([ - '_id', - 'email', - 'isActivated', - 'name', - 'role', - ]); - return users.map(({ _id, email, isActivated, name, role }) => ({ - _id, - email, - isActivated, - name, - role, - })); + const users = await userRepository.findAll(); // This returns an array of users + + if (!users || users.length === 0) { + throw new Error('No users found'); + } + + return users.map(user => { + const { _id, email, name, role } = user; + return { + _id, + email, + name, + role, + }; + }); } + diff --git a/packages/generic/backend/src/modules/user/service/userService/index.ts b/packages/generic/backend/src/modules/user/service/userService/index.ts index 99b369cd7..4f87ff781 100644 --- a/packages/generic/backend/src/modules/user/service/userService/index.ts +++ b/packages/generic/backend/src/modules/user/service/userService/index.ts @@ -1,19 +1,14 @@ -import { buildCallAttemptsRegulator } from 'sder-core'; -import { changePassword } from './changePassword'; -import { createUser } from './createUser'; -import { fetchAuthenticatedUserFromAuthorizationHeader } from './fetchAuthenticatedUserFromAuthorizationHeader'; -import { fetchUserRole } from './fetchUserRole'; -import { fetchUsers } from './fetchUsers'; -import { fetchUsersByAssignations } from './fetchUsersByAssignations'; -import { fetchUsersByIds } from './fetchUsersByIds'; -import { fetchWorkingUsers } from './fetchWorkingUsers'; -import { buildLogin } from './login'; -import { resetPassword } from './resetPassword'; -import { setDeletionDateForUser } from './setDeletionDateForUser'; -import { setIsActivatedForUser } from './setIsActivatedForUser'; -import { signUpUser } from './signUpUser'; +import {buildCallAttemptsRegulator} from 'sder-core'; +import {createUser} from './createUser'; +import {fetchAuthenticatedUserFromAuthorizationHeader} from './fetchAuthenticatedUserFromAuthorizationHeader'; +import {fetchUserRole} from './fetchUserRole'; +import {fetchUsers} from './fetchUsers'; +import {fetchUsersByAssignations} from './fetchUsersByAssignations'; +import {fetchUsersByIds} from './fetchUsersByIds'; +import {fetchWorkingUsers} from './fetchWorkingUsers'; +import {signUpUser} from './signUpUser'; -export { userService, buildUserService }; +export {userService, buildUserService}; const DELAY_BETWEEN_LOGIN_ATTEMPTS_IN_SECONDS = 1 * 1000; @@ -22,23 +17,18 @@ const MAX_LOGIN_ATTEMPTS = 1; const userService = buildUserService(); function buildUserService() { - const { checkCallAttempts } = buildCallAttemptsRegulator( - MAX_LOGIN_ATTEMPTS, - DELAY_BETWEEN_LOGIN_ATTEMPTS_IN_SECONDS, - ); - return { - changePassword, - createUser, - fetchAuthenticatedUserFromAuthorizationHeader, - fetchUsers, - fetchUsersByIds, - fetchUsersByAssignations, - fetchWorkingUsers, - fetchUserRole, - login: buildLogin(checkCallAttempts), - resetPassword, - setIsActivatedForUser, - setDeletionDateForUser, - signUpUser, - }; + buildCallAttemptsRegulator( + MAX_LOGIN_ATTEMPTS, + DELAY_BETWEEN_LOGIN_ATTEMPTS_IN_SECONDS + ); + return { + createUser, + fetchAuthenticatedUserFromAuthorizationHeader, + fetchUsers, + fetchUsersByIds, + fetchUsersByAssignations, + fetchWorkingUsers, + fetchUserRole, + signUpUser, + }; } diff --git a/packages/generic/backend/src/modules/user/service/userService/login.spec.ts b/packages/generic/backend/src/modules/user/service/userService/login.spec.ts deleted file mode 100644 index b589c0a59..000000000 --- a/packages/generic/backend/src/modules/user/service/userService/login.spec.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { dateBuilder, userModule } from '@label/core'; -import { buildUserService } from './index'; -import { buildUserRepository } from '../../repository'; - -describe('login/signUpUser', () => { - describe('success', () => { - it('should login and return a token', async () => { - const userService = buildUserService(); - const userEmail = 'MAIL@MAIL.MAIL'; - const userName = 'NAME'; - const userPassword = 'PASSWORD'; - const userRole = 'admin'; - await userService.signUpUser({ - email: userEmail, - name: userName, - password: userPassword, - role: userRole, - }); - - const { email, name, role, token } = await userService.login({ - email: userEmail, - password: userPassword, - }); - - expect(email).toEqual('mail@mail.mail'); - expect(name).toEqual(userName); - expect(role).toEqual(userRole); - expect(token).toBeDefined; - }); - }); - describe('failure', () => { - it('should fail when no user has been signed up', async () => { - const userService = buildUserService(); - - const userEmail = 'MAIL@MAIL.MAIL'; - const userPassword = 'PASSWORD'; - - const promise = () => - userService.login({ - email: userEmail, - password: userPassword, - }); - - await expect(promise()).rejects.toThrow('Login failed'); - }); - it('should fail when only another user has been signed up', async () => { - const userService = buildUserService(); - - const userEmail = 'MAIL@MAIL.MAIL'; - const userPassword = 'PASSWORD'; - await userService.signUpUser({ - email: 'ANOTHER_MAIL@ANOTHER_MAIL.ANOTHER_MAIL', - name: 'NAME', - password: 'ANOTHER_PASSWORD', - }); - - const promise = () => - userService.login({ email: userEmail, password: userPassword }); - - await expect(promise()).rejects.toThrow('Login failed'); - }); - it('should fail when the user has been signed up but the password is not the right one', async () => { - const userService = buildUserService(); - - const userEmail = 'MAIL@MAIL.MAIL'; - const userName = 'NAME'; - const userPassword = 'PASSWORD'; - const userRole = 'admin'; - await userService.signUpUser({ - email: userEmail, - name: userName, - password: userPassword, - role: userRole, - }); - - const promise = () => - userService.login({ email: userEmail, password: 'WRONG_PASSWORD' }); - - await expect(promise()).rejects.toThrow('Login failed'); - }); - - it('should fail when the user has tried too many times to log in', async () => { - const userService = buildUserService(); - - const userEmail = 'MAIL@MAIL.MAIL'; - const userName = 'NAME'; - const userPassword = 'PASSWORD'; - const userRole = 'admin'; - await userService.signUpUser({ - email: userEmail, - name: userName, - password: userPassword, - role: userRole, - }); - try { - await userService.login({ - email: userEmail, - password: 'WRONG_PASSWORD', - }); - } catch (error) {} - - const promise = () => - userService.login({ email: userEmail, password: 'WRONG_PASSWORD' }); - - await expect(promise()).rejects.toThrow('Login failed'); - }); - - it('should succeed when the user has only tried once with email/password', async () => { - const userService = buildUserService(); - - const userEmail = 'MAIL@MAIL.MAIL'; - const otherUserEmail = 'OTHER@MAIL.MAIL'; - const userName = 'NAME'; - const userPassword = 'PASSWORD'; - const userRole = 'admin'; - await userService.signUpUser({ - email: userEmail, - name: userName, - password: userPassword, - role: userRole, - }); - try { - await userService.login({ - email: otherUserEmail, - password: 'WRONG_PASSWORD', - }); - } catch (error) {} - - const { email, passwordTimeValidityStatus } = await userService.login({ - email: userEmail, - password: userPassword, - }); - - expect(email).toBe('mail@mail.mail'); - expect(passwordTimeValidityStatus).toBe('valid'); - }); - - it('should succeed when the user has only tried once with email/password', async () => { - const userService = buildUserService(); - const userRepository = buildUserRepository(); - const userPassword = 'password'; - const hashedPassword = await userModule.lib.computeHashedPassword( - userPassword, - ); - const userEmail = 'mail@mail.mail'; - const user = userModule.generator.generate({ - email: userEmail, - hashedPassword, - passwordLastUpdateDate: dateBuilder.monthsAgo(7), - }); - await userRepository.insert(user); - - const { email, passwordTimeValidityStatus } = await userService.login({ - email: userEmail, - password: userPassword, - }); - - expect(email).toBe('mail@mail.mail'); - expect(passwordTimeValidityStatus).toBe('outdated'); - }); - }); -}); diff --git a/packages/generic/backend/src/modules/user/service/userService/login.ts b/packages/generic/backend/src/modules/user/service/userService/login.ts deleted file mode 100644 index 3d596b10f..000000000 --- a/packages/generic/backend/src/modules/user/service/userService/login.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { errorHandlers } from 'sder-core'; -import { userModule, userType } from '@label/core'; -import { buildUserRepository } from '../../repository'; - -export { buildLogin }; - -function buildLogin(checkCallAttempts: (identifier: string) => void) { - return login; - - async function login({ - email, - password, - }: { - email: userType['email']; - password: string; - }) { - try { - checkCallAttempts(userModule.lib.formatEmail(email)); - const userRepository = buildUserRepository(); - const user = await userRepository.findByEmail(email); - - if (!(await userModule.lib.passwordHandler.checkUser(user, password))) { - throw new Error( - `The received password does not match the stored one for ${user.email}`, - ); - } - - const token = await userModule.lib.getTokenForUser(user); - - const passwordTimeValidityStatus = userModule.lib.passwordHandler.getPasswordTimeValidityStatus( - user, - ); - - return { - _id: user._id, - email: user.email, - name: user.name, - role: user.role, - token, - passwordTimeValidityStatus, - }; - } catch (err) { - throw errorHandlers.authenticationErrorHandler.build( - `Login failed using ${email} email.`, - ); - } - } -} diff --git a/packages/generic/backend/src/modules/user/service/userService/resetPassword.ts b/packages/generic/backend/src/modules/user/service/userService/resetPassword.ts deleted file mode 100644 index a8662d554..000000000 --- a/packages/generic/backend/src/modules/user/service/userService/resetPassword.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { errorHandlers } from 'sder-core'; -import { userModule, userType } from '@label/core'; -import { buildUserRepository } from '../../repository'; - -export { resetPassword }; - -async function resetPassword(userId: userType['_id']) { - const userRepository = buildUserRepository(); - const password = userModule.lib.passwordHandler.generate(); - const hashedPassword = await userModule.lib.computeHashedPassword(password); - const { success } = await userRepository.updateHashedPassword( - userId, - hashedPassword, - ); - - if (!success) { - throw errorHandlers.notFoundErrorHandler.build( - `No user found for id ${userId}`, - ); - } - - return password; -} diff --git a/packages/generic/backend/src/modules/user/service/userService/setDeletionDateForUser.ts b/packages/generic/backend/src/modules/user/service/userService/setDeletionDateForUser.ts deleted file mode 100644 index 114d57218..000000000 --- a/packages/generic/backend/src/modules/user/service/userService/setDeletionDateForUser.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { userType } from '@label/core'; -import { buildUserRepository } from '../../repository'; - -export { setDeletionDateForUser }; - -async function setDeletionDateForUser(userId: userType['_id']) { - const userRepository = buildUserRepository(); - await userRepository.updateOne(userId, { - deletionDate: new Date().getTime(), - }); -} diff --git a/packages/generic/backend/src/modules/user/service/userService/setIsActivatedForUser.ts b/packages/generic/backend/src/modules/user/service/userService/setIsActivatedForUser.ts deleted file mode 100644 index 7df9e7d0b..000000000 --- a/packages/generic/backend/src/modules/user/service/userService/setIsActivatedForUser.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { userType } from '@label/core'; -import { buildUserRepository } from '../../repository'; - -export { setIsActivatedForUser }; - -async function setIsActivatedForUser({ - userId, - isActivated, -}: { - userId: userType['_id']; - isActivated: userType['isActivated']; -}) { - const userRepository = buildUserRepository(); - await userRepository.updateOne(userId, { isActivated }); -} diff --git a/packages/generic/backend/src/modules/user/service/userService/signUpUser.ts b/packages/generic/backend/src/modules/user/service/userService/signUpUser.ts index d4c9e0d70..fb3956f58 100644 --- a/packages/generic/backend/src/modules/user/service/userService/signUpUser.ts +++ b/packages/generic/backend/src/modules/user/service/userService/signUpUser.ts @@ -8,19 +8,16 @@ export { signUpUser }; async function signUpUser({ email, name, - password, role = DEFAULT_ROLE, }: { email: string; name: string; - password: string; role?: userType['role']; }) { const userRepository = buildUserRepository(); const newUser = await userModule.lib.buildUser({ email, name, - password, role, }); diff --git a/packages/generic/client/src/App.tsx b/packages/generic/client/src/App.tsx index e62e6b767..a4e878359 100644 --- a/packages/generic/client/src/App.tsx +++ b/packages/generic/client/src/App.tsx @@ -3,12 +3,15 @@ import { ThemeProvider } from 'pelta-design-system'; import { Router } from './pages'; import { AlertHandlerContextProvider } from './services/alert'; import { PopupHandlerContextProvider } from './services/popup'; +import { UserProvider } from './contexts/user.context'; const App = () => ( - + + + diff --git a/packages/generic/client/src/api/apiCaller.ts b/packages/generic/client/src/api/apiCaller.ts index 357220943..d6cfc69e8 100644 --- a/packages/generic/client/src/api/apiCaller.ts +++ b/packages/generic/client/src/api/apiCaller.ts @@ -1,6 +1,5 @@ import { errorHandlers, httpStatusCodeHandler } from 'sder-core'; import { apiSchema, apiRouteInType, apiRouteOutType, networkType } from '@label/core'; -import { localStorage } from '../services/localStorage'; import { urlHandler } from '../utils'; export { apiCaller }; @@ -15,13 +14,12 @@ const apiCaller = { data: networkType>; statusCode: number; }> { - const bearerToken = localStorage.bearerTokenHandler.get(); - const response = await fetch(buildUrlWithParams(`${urlHandler.getApiUrl()}/label/api/${routeName}`, args), { cache: 'default', - headers: bearerToken ? { ...DEFAULT_HEADER, authorization: `Bearer ${bearerToken}` } : DEFAULT_HEADER, + headers: DEFAULT_HEADER, method: 'get', mode: 'cors', + credentials: 'include', }); const data = (await computeDataFromResponse(response)) as networkType>; @@ -39,14 +37,13 @@ const apiCaller = { data: networkType>; statusCode: number; }> { - const bearerToken = localStorage.bearerTokenHandler.get(); - const response = await fetch(`${urlHandler.getApiUrl()}/label/api/${routeName}`, { body: JSON.stringify(args), cache: 'default', - headers: bearerToken ? { ...DEFAULT_HEADER, authorization: `Bearer ${bearerToken}` } : DEFAULT_HEADER, + headers: DEFAULT_HEADER, method: 'post', mode: 'cors', + credentials: 'include', }); const data = (await computeDataFromResponse(response)) as networkType>; diff --git a/packages/generic/client/src/components/business/AdminMenu/AdminMenu.tsx b/packages/generic/client/src/components/business/AdminMenu/AdminMenu.tsx index 26de4d4ee..b9188bb2a 100644 --- a/packages/generic/client/src/components/business/AdminMenu/AdminMenu.tsx +++ b/packages/generic/client/src/components/business/AdminMenu/AdminMenu.tsx @@ -90,14 +90,6 @@ function getMenuIcons({ /> ); - const WORKING_USERS_ICON = ( - - ); - return { admin: [ SUMMARY_ICON, @@ -107,7 +99,6 @@ function getMenuIcons({ TREATED_DOCUMENTS_ICON, PROBLEM_REPORTS_ICON, PRE_ASSIGN_DOCUMENTS_ICON, - WORKING_USERS_ICON, ], scrutator: [ SUMMARY_ICON, diff --git a/packages/generic/client/src/components/business/MainHeader/MainHeader.tsx b/packages/generic/client/src/components/business/MainHeader/MainHeader.tsx index 11abe78eb..cc6af111a 100644 --- a/packages/generic/client/src/components/business/MainHeader/MainHeader.tsx +++ b/packages/generic/client/src/components/business/MainHeader/MainHeader.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { customThemeType, heights, useCustomTheme, Header, IconButton, MenuBar, Text } from 'pelta-design-system'; -import { localStorage } from '../../../services/localStorage'; import { wordings } from '../../../wordings'; import { AdminViewDropdown } from './AdminViewDropdown'; import { PersonalStatisticsButton } from './PersonalStatisticsButton'; import { SettingsButton } from './SettingsButton'; +import { useCtxUser } from '../../../contexts/user.context'; export { MainHeader }; @@ -18,11 +18,16 @@ function MainHeader(props: { const styles = buildStyles(theme); const leftHeaderComponents = buildLeftHeaders(); + const { user, loading } = useCtxUser(); + if (loading) { + return
Loading...
; + } + return (
); - function buildRightHeaderComponents() { - const userRole = localStorage.userHandler.getRole(); + function buildRightHeaderComponents(user?: any) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + const userRole = user?.role; if (userRole === 'admin') { return [ , diff --git a/packages/generic/client/src/components/business/MainHeader/PasswordChangeConfirmationPopup.tsx b/packages/generic/client/src/components/business/MainHeader/PasswordChangeConfirmationPopup.tsx deleted file mode 100644 index a01ac951e..000000000 --- a/packages/generic/client/src/components/business/MainHeader/PasswordChangeConfirmationPopup.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { customThemeType, useCustomTheme, ButtonWithIcon, PopUp, Text } from 'pelta-design-system'; -import { wordings } from '../../../wordings'; - -export { PasswordChangeConfirmationPopup }; - -function PasswordChangeConfirmationPopup(props: { onClose: () => void }) { - const theme = useCustomTheme(); - const styles = buildStyles(theme); - - return ( - -
- {wordings.shared.passwordChangedConfirmation.text} -
-
- -
-
- ); -} - -function buildStyles(theme: customThemeType) { - return { - textContainer: { marginBottom: theme.spacing * 9 }, - buttonContainer: { - display: 'flex', - justifyContent: 'flex-end', - }, - }; -} diff --git a/packages/generic/client/src/components/business/MainHeader/SettingsDrawer.tsx b/packages/generic/client/src/components/business/MainHeader/SettingsDrawer.tsx index 89504967b..beadb40b8 100644 --- a/packages/generic/client/src/components/business/MainHeader/SettingsDrawer.tsx +++ b/packages/generic/client/src/components/business/MainHeader/SettingsDrawer.tsx @@ -1,84 +1,69 @@ -import React, { useState } from 'react'; -import { useHistory } from 'react-router-dom'; -import { useDisplayMode, ButtonWithIcon, Drawer, RadioButton, Text } from 'pelta-design-system'; -import { routes } from '../../../pages'; -import { localStorage } from '../../../services/localStorage'; -import { wordings } from '../../../wordings'; -import { ResetPasswordForm } from '../ResetPasswordForm'; -import { PasswordChangeConfirmationPopup } from './PasswordChangeConfirmationPopup'; -import { SettingsSection } from './SettingsSection'; +import React from 'react'; +import {useHistory} from 'react-router-dom'; +import {useDisplayMode, ButtonWithIcon, Drawer, RadioButton, Text} from 'pelta-design-system'; +import {localStorage} from '../../../services/localStorage'; +import {wordings} from '../../../wordings'; +import {SettingsSection} from './SettingsSection'; +import {useCtxUser} from "../../../contexts/user.context"; +import {urlHandler} from "../../../utils"; -export { SettingsDrawer }; +export {SettingsDrawer}; -function SettingsDrawer(props: { close: () => void; isOpen: boolean }) { - const { displayMode, setDisplayMode } = useDisplayMode(); - const styles = buildStyles(); - const history = useHistory(); +function SettingsDrawer(props: { close: () => void; isOpen: boolean }) { + const {displayMode, setDisplayMode} = useDisplayMode(); + const styles = buildStyles(); + const history = useHistory(); + const {user, loading} = useCtxUser(); - const [isPasswordConfirmationPopupShown, setIsPasswordConfirmationPopupShown] = useState(false); + if (loading) { + return
Loading...
; + } + const userEmail = user?.email; + const userName = user?.name; - const userEmail = localStorage.userHandler.getEmail(); - const userName = localStorage.userHandler.getName(); - - return ( - <> - {isPasswordConfirmationPopupShown && ( - setIsPasswordConfirmationPopupShown(false)} /> - )} - -
- -
- - {userName} - - {userEmail} -
- -
- } - title={wordings.business.account} - /> - - setDisplayMode('lightMode')} + return ( + +
+ +
+ + {userName} + + {userEmail} +
+ +
+ } + title={wordings.business.account} /> - setDisplayMode('darkMode')} + + setDisplayMode('lightMode')} + /> + setDisplayMode('darkMode')} + /> + + } + title={wordings.shared.settingsDrawer.displayMode} /> - - } - title={wordings.shared.settingsDrawer.displayMode} - /> - - } - title={wordings.business.changePassword} - /> - -
- - ); + +
+ ); - function onUpdatePassword() { - setIsPasswordConfirmationPopupShown(true); - props.close(); - } - - function logout() { - localStorage.bearerTokenHandler.remove(); - localStorage.userHandler.remove(); - localStorage.adminViewHandler.remove(); - history.push(routes.DEFAULT.getPath()); - } + function logout() { + localStorage.adminViewHandler.remove(); + window.location.replace(urlHandler.getSsoLogoutUrl()); + } function buildStyles() { return { diff --git a/packages/generic/client/src/components/business/ResetPasswordForm/ResetPasswordForm.tsx b/packages/generic/client/src/components/business/ResetPasswordForm/ResetPasswordForm.tsx deleted file mode 100644 index 4ea16226a..000000000 --- a/packages/generic/client/src/components/business/ResetPasswordForm/ResetPasswordForm.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React, { useState } from 'react'; -import { customThemeType, useCustomTheme, ButtonWithIcon, RichTextInput } from 'pelta-design-system'; -import { apiCaller } from '../../../api'; -import { localStorage } from '../../../services/localStorage'; -import { wordings } from '../../../wordings'; - -export { ResetPasswordForm }; - -type passwordChangeValidityType = 'valid' | 'notValidNewPassword' | 'wrongPassword' | 'newPasswordsDontMatch'; - -function ResetPasswordForm(props: { onUpdatePassword: () => void }) { - const theme = useCustomTheme(); - const styles = buildStyles(theme); - const [passwordChangeValidity, setPasswordChangeValidity] = useState('valid'); - const [previousPassword, setPreviousPassword] = useState(''); - const [confirmedNewPassword, setConfirmedNewPassword] = useState(''); - - const [newPassword, setNewPassword] = useState(''); - return ( - <> - - - -
- -
- - ); - - function shouldShowErrorForPreviousPassword() { - return passwordChangeValidity === 'wrongPassword'; - } - - function shouldShowErrorForNewPassword() { - return passwordChangeValidity === 'notValidNewPassword' || passwordChangeValidity === 'newPasswordsDontMatch'; - } - - function computeErrorMessageForNewPassword() { - if (passwordChangeValidity === 'newPasswordsDontMatch') { - return wordings.business.newPasswordsDontMatch; - } - - return wordings.business.newPasswordInstructions; - } - - async function updatePassword() { - if (newPassword !== confirmedNewPassword) { - setPasswordChangeValidity('newPasswordsDontMatch'); - return; - } - - const result = await apiCaller.post<'changePassword'>('changePassword', { previousPassword, newPassword }); - - switch (result.data) { - case 'notValidNewPassword': - setPasswordChangeValidity('notValidNewPassword'); - break; - case 'passwordUpdated': - setPreviousPassword(''); - setNewPassword(''); - setConfirmedNewPassword(''); - setPasswordChangeValidity('valid'); - localStorage.userHandler.setPasswordTimeValidityStatus('valid'); - props.onUpdatePassword(); - break; - case 'wrongPassword': - setPasswordChangeValidity('wrongPassword'); - break; - } - } -} - -function buildStyles(theme: customThemeType) { - return { - passwordTextInput: { - margin: `${theme.spacing * 2}px 0`, - width: '100%', - }, - passwordUpdateButtonContainer: { - display: 'flex', - justifyContent: 'flex-end' as const, - }, - } as const; -} diff --git a/packages/generic/client/src/components/business/ResetPasswordForm/index.ts b/packages/generic/client/src/components/business/ResetPasswordForm/index.ts deleted file mode 100644 index aa5db8594..000000000 --- a/packages/generic/client/src/components/business/ResetPasswordForm/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ResetPasswordForm } from './ResetPasswordForm'; diff --git a/packages/generic/client/src/components/business/index.ts b/packages/generic/client/src/components/business/index.ts index bebcb53a3..1a2d836c0 100644 --- a/packages/generic/client/src/components/business/index.ts +++ b/packages/generic/client/src/components/business/index.ts @@ -13,7 +13,6 @@ import { MainHeader } from './MainHeader'; import { ProblemReportIcon } from './ProblemReportIcon'; import { PublicationCategoryBadge } from './PublicationCategoryBadge'; import { UnlinkAnnotationDropdown } from './UnlinkAnnotationDropdown'; -import { ResetPasswordForm } from './ResetPasswordForm'; export { AdminMenu, @@ -31,7 +30,6 @@ export { MainHeader, ProblemReportIcon, PublicationCategoryBadge, - ResetPasswordForm, UnlinkAnnotationDropdown, }; diff --git a/packages/generic/client/src/components/index.ts b/packages/generic/client/src/components/index.ts index e6e235c2c..063ffab97 100644 --- a/packages/generic/client/src/components/index.ts +++ b/packages/generic/client/src/components/index.ts @@ -15,7 +15,6 @@ import { MainHeader, ProblemReportIcon, PublicationCategoryBadge, - ResetPasswordForm, UnlinkAnnotationDropdown, } from './business'; @@ -35,7 +34,6 @@ export { MainHeader, ProblemReportIcon, PublicationCategoryBadge, - ResetPasswordForm, UnlinkAnnotationDropdown, }; diff --git a/packages/generic/client/src/contexts/user.context.tsx b/packages/generic/client/src/contexts/user.context.tsx new file mode 100644 index 000000000..7e03062f6 --- /dev/null +++ b/packages/generic/client/src/contexts/user.context.tsx @@ -0,0 +1,65 @@ +import React, { createContext, FC, ReactNode, useContext, useEffect, useState } from 'react'; +import { urlHandler } from '../utils'; + +export interface CurrentUser { + _id?: string; + email?: string; + name?: string; + role?: string; + passwordTimeValidityStatus?: string; +} +interface UserContextType { + user: CurrentUser | null; + loading: boolean; + onLogout: () => void; +} +const UserContext = createContext(null); + +export const UserProvider: FC<{ children: ReactNode }> = ({ children }) => { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + async function fetchUser() { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const userData = await whoami(); + setUser(userData); + setLoading(false); + } + fetchUser(); + }, []); + const onLogout = () => { + setUser(null); // Remettre l'utilisateur à null + }; + return {children}; +}; + +export const useCtxUser = () => { + const context = useContext(UserContext); + if (!context) { + throw new Error(`useCtxUser must be used within a UserProvider `); + } + return context; +}; + +async function whoami() { + try { + const response = await fetch(`${urlHandler.getApiUrl()}/label/api/sso/whoami`, { + headers: { 'Content-Type': 'application/json' }, + method: 'GET', + credentials: 'include', + mode: 'cors', + }); + + console.warn('#################### ',JSON.stringify(await response), ' #################### '); + if (!response.ok) { + return null; + } + console.warn('**************** ', JSON.stringify(await response), ' ****************'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return await response.json(); + } catch (error) { + console.error('Error fetching authentication status:', error); + return null; + } +} diff --git a/packages/generic/client/src/pages/Admin/DocumentInspector/DocumentInspector.tsx b/packages/generic/client/src/pages/Admin/DocumentInspector/DocumentInspector.tsx index 5f2e7769e..c08c0080a 100644 --- a/packages/generic/client/src/pages/Admin/DocumentInspector/DocumentInspector.tsx +++ b/packages/generic/client/src/pages/Admin/DocumentInspector/DocumentInspector.tsx @@ -14,8 +14,8 @@ import { wordings } from '../../../wordings'; import { ChecklistDataFetcher } from './ChecklistDataFetcher'; import { AnnotationsDataFetcher } from './AnnotationsDataFetcher'; import { DocumentDataFetcher } from './DocumentDataFetcher'; -import { localStorage } from '../../../services/localStorage'; import { MandatoryReplacementTermsDataFetcher } from './MandatoryReplacementTermsDataFetcher'; +import { useCtxUser } from '../../../contexts/user.context'; export { DocumentInspector }; @@ -27,9 +27,10 @@ function DocumentInspector(props: { settings: settingsType }) { const params = useParams(); const history = useHistory(); const { displayAlert } = useAlert(); + const { user } = useCtxUser(); useEffect(() => { - const userRole = localStorage.userHandler.getRole(); + const userRole = user?.role; if (userRole === 'scrutator') { displayScrutatorInfo(); } diff --git a/packages/generic/client/src/pages/Admin/ProblemReports/ProblemReportsTable/ProblemReportsTable.tsx b/packages/generic/client/src/pages/Admin/ProblemReports/ProblemReportsTable/ProblemReportsTable.tsx index b51b43aec..10ac6961e 100644 --- a/packages/generic/client/src/pages/Admin/ProblemReports/ProblemReportsTable/ProblemReportsTable.tsx +++ b/packages/generic/client/src/pages/Admin/ProblemReports/ProblemReportsTable/ProblemReportsTable.tsx @@ -11,6 +11,7 @@ import { localStorage } from '../../../../services/localStorage'; import { sendMail } from '../../../../services/sendMail'; import { wordings } from '../../../../wordings'; import { routes } from '../../../routes'; +import { useCtxUser } from '../../../../contexts/user.context'; import { annotationDiffDocumentInfoType, AnnotationsDiffDrawer } from '../../TreatedDocuments/AnnotationsDiffDrawer'; export { ProblemReportsTable }; @@ -33,7 +34,12 @@ function ProblemReportsTable(props: { const styles = buildStyles(theme); const problemReportsFields = buildProblemReportsFields(); - const userRole = localStorage.userHandler.getRole(); + + const { user, loading } = useCtxUser(); + if (loading) { + return
Loading...
; + } + const userRole = user?.role; const adminView = localStorage.adminViewHandler.get(); return ( @@ -135,7 +141,7 @@ function ProblemReportsTable(props: { } function buildOptionItems(problemReportWithDetails: apiRouteOutType<'get', 'problemReportsWithDetails'>[number]) { - const userRole = localStorage.userHandler.getRole(); + const userRole = user?.role; const validateDocumentOptionItem = { kind: 'text' as const, diff --git a/packages/generic/client/src/pages/Admin/TreatedDocuments/TreatedDocumentsTable.tsx b/packages/generic/client/src/pages/Admin/TreatedDocuments/TreatedDocumentsTable.tsx index 8ef0495b4..704defe29 100644 --- a/packages/generic/client/src/pages/Admin/TreatedDocuments/TreatedDocumentsTable.tsx +++ b/packages/generic/client/src/pages/Admin/TreatedDocuments/TreatedDocumentsTable.tsx @@ -8,6 +8,7 @@ import { localStorage, treatedDocumentOrderByProperties } from '../../../service import { AnnotationsDiffDrawer, annotationDiffDocumentInfoType } from './AnnotationsDiffDrawer'; import { useAlert } from '../../../services/alert'; import { routes } from '../../routes'; +import { useCtxUser } from '../../../contexts/user.context'; export { TreatedDocumentsTable }; @@ -32,6 +33,8 @@ function TreatedDocumentsTable(props: { const orderDirection = localStorage.treatedDocumentsStateHandler.getOrderDirection(); const styles = buildStyles(); + const { user } = useCtxUser(); + return (
{!!documentIdToReset && ( @@ -91,7 +94,7 @@ function TreatedDocumentsTable(props: { } function buildOptionItems(treatmentWithDetails: apiRouteOutType<'get', 'treatedDocuments'>[number]) { - const userRole = localStorage.userHandler.getRole(); + const userRole = user?.role; const adminView = localStorage.adminViewHandler.get(); const openDocumentOption = { diff --git a/packages/generic/client/src/pages/Admin/UntreatedDocuments/UntreatedDocumentsTable.tsx b/packages/generic/client/src/pages/Admin/UntreatedDocuments/UntreatedDocumentsTable.tsx index ec25a7a41..41aef48a0 100644 --- a/packages/generic/client/src/pages/Admin/UntreatedDocuments/UntreatedDocumentsTable.tsx +++ b/packages/generic/client/src/pages/Admin/UntreatedDocuments/UntreatedDocumentsTable.tsx @@ -15,6 +15,7 @@ import { useAlert } from '../../../services/alert'; import { localStorage, untreatedDocumentOrderByProperties } from '../../../services/localStorage'; import { wordings } from '../../../wordings'; import { routes } from '../../routes'; +import { useCtxUser } from '../../../contexts/user.context'; export { UntreatedDocumentsTable }; @@ -35,6 +36,11 @@ function UntreatedDocumentsTable(props: { const styles = buildStyles(theme); const fields = buildUntreatedDocumentsFields(); + const { user, loading } = useCtxUser(); + if (loading) { + return
Loading...
; + } + return (
{!!documentIdToUpdateStatus && ( @@ -65,7 +71,7 @@ function UntreatedDocumentsTable(props: { } function buildOptionItems(untreatedDocument: apiRouteOutType<'get', 'untreatedDocuments'>[number]) { - const userRole = localStorage.userHandler.getRole(); + const userRole = user?.role; const adminView = localStorage.adminViewHandler.get(); const openAnonymizedDocumentOptionItem = { @@ -132,7 +138,7 @@ function UntreatedDocumentsTable(props: { async function onConfirmUpdateDocumentStatus(documentIdToUpdateStatus: documentType['_id']) { setDocumentIdToUpdateStatus(undefined); - const userId = localStorage.userHandler.getId(); + const userId = (user?._id as unknown) as any; if (!userId) { displayAlert({ text: wordings.business.errors.noUserIdFound, variant: 'alert', autoHide: true }); return; diff --git a/packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/AddUserButton.tsx b/packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/AddUserButton.tsx deleted file mode 100644 index ad65fa8bc..000000000 --- a/packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/AddUserButton.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React, { useState } from 'react'; -import { ButtonWithIcon } from 'pelta-design-system'; -import { wordings } from '../../../../wordings'; -import { AddWorkingUserDrawer } from './AddUserDrawer'; - -export { AddWorkingUserButton }; - -function AddWorkingUserButton(props: { refetch: () => void }) { - const [isDrawerOpen, setIsDrawerOpen] = useState(false); - - return ( - <> - setIsDrawerOpen(true)} - text={wordings.workingUsersPage.createWorkingUserDrawer.title} - iconName="addPerson" - /> - - setIsDrawerOpen(false)} refetch={props.refetch} /> - - ); -} diff --git a/packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/AddUserDrawer.tsx b/packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/AddUserDrawer.tsx deleted file mode 100644 index c638c8aac..000000000 --- a/packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/AddUserDrawer.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import React, { useState } from 'react'; -import { userModule, userType } from '@label/core'; -import { apiCaller } from '../../../../api'; -import { - customThemeType, - useCustomTheme, - ButtonWithIcon, - Drawer, - LabelledDropdown, - RichTextInput, -} from 'pelta-design-system'; -import { wordings } from '../../../../wordings'; -import { WorkingUserCreatedPopUp } from './UserCreatedPopUp'; -import { processNames } from '../../../../utils/'; - -export { AddWorkingUserDrawer }; - -const FIELD_WIDTH = 400; - -const DRAWER_WIDTH = 600; - -type formErrorType = { - firstName?: boolean; - lastName?: boolean; - email?: boolean; - role?: boolean; -}; - -type formValuesType = { - firstName: string | undefined; - lastName: string | undefined; - email: string | undefined; - role: userType['role'] | undefined; -}; - -const INITIAL_FORM_VALUES = { - firstName: undefined, - lastName: undefined, - email: undefined, - role: undefined, -}; - -function AddWorkingUserDrawer(props: { isOpen: boolean; onClose: () => void; refetch: () => void }) { - const [formValues, setFormValues] = useState(INITIAL_FORM_VALUES); - const [temporaryPassword, setTemporaryPassword] = useState(); - const [formErrors, setFormErrors] = useState({}); - const [isLoading, setIsLoading] = useState(false); - const theme = useCustomTheme(); - const styles = buildStyles(theme); - - return ( - <> - {!!temporaryPassword && } - -
-
-
- updateField('firstName', firstName)} - placeholder={wordings.workingUsersPage.createWorkingUserDrawer.fields.firstName} - style={styles.field} - /> -
-
- updateField('lastName', lastName)} - placeholder={wordings.workingUsersPage.createWorkingUserDrawer.fields.lastName} - style={styles.field} - /> -
-
- updateField('email', email)} - placeholder={wordings.workingUsersPage.createWorkingUserDrawer.fields.email} - style={styles.field} - /> -
-
- - label={wordings.workingUsersPage.createWorkingUserDrawer.fields.role} - error={!!formErrors.role} - items={userModule.models.user.content.role.content.map((role) => ({ - text: wordings.business.userRoles[role], - value: role, - }))} - onChange={(role: userType['role']) => updateField('role', role)} - width={FIELD_WIDTH} - /> -
-
- -
-
-
-
- - ); - - function onClose() { - setTemporaryPassword(undefined); - setFormErrors({}); - setFormValues(INITIAL_FORM_VALUES); - } - - function updateField(field: T, value: formValuesType[T]) { - setFormValues({ ...formValues, [field]: value }); - if (formErrors[field]) { - setFormErrors({ ...formErrors, [field]: undefined }); - } - } - - async function addWorkingUser() { - const { firstName, lastName, email, role } = formValues; - if (!firstName || !lastName || !email || !role) { - updateFormErrors(); - return; - } - setIsLoading(true); - try { - const { data: temporaryPassword } = await apiCaller.post<'createUser'>('createUser', { - email, - name: processNames(firstName, lastName), - role, - }); - props.refetch(); - props.onClose(); - setTemporaryPassword(temporaryPassword); - } catch (error) { - console.warn(error); - } finally { - setIsLoading(false); - } - } - - function updateFormErrors() { - setFormErrors({ - firstName: !formValues.firstName, - lastName: !formValues.lastName, - email: !formValues.email, - role: !formValues.role, - }); - } -} - -function buildStyles(theme: customThemeType) { - return { - drawer: { - display: 'flex', - flexDirection: 'column' as const, - alignItems: 'center', - justifyContent: 'space-between', - width: DRAWER_WIDTH, - padding: theme.spacing * 6, - }, - header: { - display: 'flex', - justifyContent: 'space-between', - paddingBottom: theme.spacing * 5, - borderBottom: 'solid 1px', - borderBottomColor: theme.colors.separator, - width: '100%', - }, - formContainer: { - paddingTop: theme.spacing * 6, - width: '100%', - display: 'flex', - flexDirection: 'column', - }, - fieldContainer: { - marginBottom: theme.spacing * 5, - }, - field: { - width: FIELD_WIDTH, - }, - submitButtonContainer: { - alignSelf: 'flex-end', - }, - } as const; -} diff --git a/packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/UserCreatedPopUp.tsx b/packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/UserCreatedPopUp.tsx deleted file mode 100644 index 3158b0715..000000000 --- a/packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/UserCreatedPopUp.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import { customThemeType, useCustomTheme, ButtonWithIcon, PopUp, Text } from 'pelta-design-system'; -import { wordings } from '../../../../wordings'; - -export { WorkingUserCreatedPopUp }; - -function WorkingUserCreatedPopUp(props: { password: string; onClose: () => void }) { - const theme = useCustomTheme(); - const styles = buildStyles(theme); - - return ( - -
- - {wordings.workingUsersPage.createWorkingUserDrawer.createdWorkingUserPopup.createdWorkingUserConfirmation} - -
- {wordings.workingUsersPage.createWorkingUserDrawer.createdWorkingUserPopup.passwordIndication} -
-
- {props.password} -
-
- -
-
- ); -} - -function buildStyles(theme: customThemeType) { - return { - textContainer: { marginBottom: theme.spacing * 9 }, - passwordContainer: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - marginBottom: theme.spacing * 8, - }, - buttonContainer: { - display: 'flex', - justifyContent: 'flex-end', - }, - }; -} diff --git a/packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/index.ts b/packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/index.ts deleted file mode 100644 index 16a70a5b8..000000000 --- a/packages/generic/client/src/pages/Admin/WorkingUsers/AddUserDrawer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { AddWorkingUserDrawer } from './AddUserDrawer'; diff --git a/packages/generic/client/src/pages/Admin/WorkingUsers/PasswordResetSuccessPopup.tsx b/packages/generic/client/src/pages/Admin/WorkingUsers/PasswordResetSuccessPopup.tsx deleted file mode 100644 index 005b5259f..000000000 --- a/packages/generic/client/src/pages/Admin/WorkingUsers/PasswordResetSuccessPopup.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { customThemeType, useCustomTheme, ButtonWithIcon, PopUp, Text } from 'pelta-design-system'; -import { wordings } from '../../../wordings'; - -export { PasswordResetSuccessPopup }; - -function PasswordResetSuccessPopup(props: { password: string; onClose: () => void }) { - const theme = useCustomTheme(); - const styles = buildStyles(theme); - - return ( - -
- {wordings.workingUsersPage.table.passwordResetSuccessPopup.passwordResetConfirmation} -
- {wordings.workingUsersPage.table.passwordResetSuccessPopup.passwordIndication} -
-
- {props.password} -
-
- -
-
- ); -} - -function buildStyles(theme: customThemeType) { - return { - textContainer: { marginBottom: theme.spacing * 9 }, - passwordContainer: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - marginBottom: theme.spacing * 8, - }, - buttonContainer: { - display: 'flex', - justifyContent: 'flex-end', - }, - }; -} diff --git a/packages/generic/client/src/pages/Admin/WorkingUsers/WorkingUsers.tsx b/packages/generic/client/src/pages/Admin/WorkingUsers/WorkingUsers.tsx deleted file mode 100644 index 08e77025b..000000000 --- a/packages/generic/client/src/pages/Admin/WorkingUsers/WorkingUsers.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import { apiRouteOutType } from '@label/core'; -import { customThemeType, useCustomTheme, tableRowFieldType } from 'pelta-design-system'; -import { heights, widths } from '../../../styles'; -import { wordings } from '../../../wordings'; -import { AddWorkingUserButton } from './AddUserDrawer/AddUserButton'; -import { WorkingUsersTable } from './WorkingUsersTable'; - -export { WorkingUsers }; - -function WorkingUsers(props: { workingUsers: apiRouteOutType<'get', 'workingUsers'>; refetch: () => void }) { - const theme = useCustomTheme(); - const styles = buildStyles(theme); - const userFields = buildUserFields(); - return ( -
-
-
- -
-
-
- -
-
- ); - - function buildUserFields() { - const usersFields: Array[number]>> = [ - { - id: 'name', - title: wordings.workingUsersPage.table.columnTitles.name, - canBeSorted: true, - extractor: (workingUser) => workingUser.name, - width: 10, - }, - { - id: 'email', - title: wordings.workingUsersPage.table.columnTitles.email, - canBeSorted: true, - extractor: (workingUser) => workingUser.email, - width: 10, - }, - { - id: 'role', - title: wordings.workingUsersPage.table.columnTitles.role, - canBeSorted: true, - extractor: (workingUser) => wordings.business.userRoles[workingUser.role], - width: 10, - }, - ]; - return usersFields; - } - - function buildStyles(theme: customThemeType) { - return { - tableHeaderContainer: { - height: heights.adminTreatmentsTableHeader, - }, - tableHeader: { - paddingTop: theme.spacing * 4, - display: 'flex', - justifyContent: 'flex-end', - }, - tableContentContainer: { - height: heights.adminTreatmentsTable, - overflowY: 'auto', - }, - table: { - width: widths.adminContent, - paddingLeft: theme.spacing * 3, - paddingRight: theme.spacing * 2, - }, - } as const; - } -} diff --git a/packages/generic/client/src/pages/Admin/WorkingUsers/WorkingUsersTable.tsx b/packages/generic/client/src/pages/Admin/WorkingUsers/WorkingUsersTable.tsx deleted file mode 100644 index 8cd77d563..000000000 --- a/packages/generic/client/src/pages/Admin/WorkingUsers/WorkingUsersTable.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React, { useState } from 'react'; -import { apiRouteOutType, userType } from '@label/core'; -import { ConfirmationPopup, Table, tableRowFieldType } from 'pelta-design-system'; -import { wordings } from '../../../wordings'; -import { apiCaller } from '../../../api'; -import { PasswordResetSuccessPopup } from './PasswordResetSuccessPopup'; - -export { WorkingUsersTable }; - -function WorkingUsersTable(props: { - fields: Array[number]>>; - refetch: () => void; - workingUsers: apiRouteOutType<'get', 'workingUsers'>; -}) { - const [newPassword, setNewPassword] = useState(); - const [resetPasswordUserId, setResetPasswordUserId] = useState(); - const [deleteUserId, setDeleteUserId] = useState(); - const styles = buildStyles(); - - return ( -
- {!!newPassword && setNewPassword(undefined)} />} - {!!resetPasswordUserId && ( - setResetPasswordUserId(undefined)} - onConfirm={buildOnConfirmResetPassword(resetPasswordUserId)} - text={wordings.workingUsersPage.table.passwordResetConfirmationPopup.text} - /> - )} - {!!deleteUserId && ( - setDeleteUserId(undefined)} - onConfirm={buildOnConfirmDeleteUser(deleteUserId)} - text={wordings.workingUsersPage.table.deleteUserConfirmationPopup.text} - /> - )} - - - ); - - function isRowMinored(workingUser: apiRouteOutType<'get', 'workingUsers'>[number]) { - return !workingUser.isActivated; - } - - function buildOnConfirmResetPassword(resetPasswordUserId: userType['_id']) { - return () => { - resetPasswordForUserId(resetPasswordUserId); - setResetPasswordUserId(undefined); - }; - } - - function buildOnConfirmDeleteUser(deleteUserId: userType['_id']) { - return async () => { - await apiCaller.post<'setDeletionDateForUser'>('setDeletionDateForUser', { userId: deleteUserId }); - setDeleteUserId(undefined); - props.refetch(); - }; - } - - async function resetPasswordForUserId(userId: userType['_id']) { - const { data: newPassword } = await apiCaller.post<'resetPassword'>('resetPassword', { - userId, - }); - setNewPassword(newPassword); - } - - function buildOptionItems(workingUser: apiRouteOutType<'get', 'workingUsers'>[number]) { - const toggleIsActivatedOptionItem = buildToggleIsActivatedOptionItem(workingUser); - return [ - { - kind: 'text' as const, - text: wordings.workingUsersPage.table.optionItems.resetPassword, - onClick: async () => setResetPasswordUserId(workingUser._id), - iconName: 'key' as const, - }, - toggleIsActivatedOptionItem, - { - kind: 'text' as const, - text: wordings.workingUsersPage.table.optionItems.delete, - onClick: () => { - setDeleteUserId(workingUser._id); - }, - iconName: 'delete' as const, - }, - ]; - } - - function buildToggleIsActivatedOptionItem(user: apiRouteOutType<'get', 'workingUsers'>[number]) { - const iconName: 'lock' | 'unlock' = user.isActivated ? 'lock' : 'unlock'; - const onClick = async () => { - await apiCaller.post<'setIsActivatedForUser'>('setIsActivatedForUser', { - isActivated: !user.isActivated, - userId: user._id, - }); - props.refetch(); - }; - const text = user.isActivated - ? wordings.workingUsersPage.table.optionItems.deactivate - : wordings.workingUsersPage.table.optionItems.activate; - return { kind: 'text' as const, iconName, text, onClick }; - } - - function buildStyles() { - return { - container: { - height: '100%', - }, - }; - } -} diff --git a/packages/generic/client/src/pages/Admin/WorkingUsers/index.ts b/packages/generic/client/src/pages/Admin/WorkingUsers/index.ts deleted file mode 100644 index 16e24a5ea..000000000 --- a/packages/generic/client/src/pages/Admin/WorkingUsers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { WorkingUsers } from './WorkingUsers'; diff --git a/packages/generic/client/src/pages/DataFetcher.tsx b/packages/generic/client/src/pages/DataFetcher.tsx index 6624113b9..f1c08386b 100644 --- a/packages/generic/client/src/pages/DataFetcher.tsx +++ b/packages/generic/client/src/pages/DataFetcher.tsx @@ -1,7 +1,6 @@ import React, { ReactElement } from 'react'; import { Route, Redirect } from 'react-router-dom'; import { handleFetchedData } from '../api'; -import { localStorage } from '../services/localStorage'; import { ErrorPage } from './ErrorPage'; import { LoadingPage } from './LoadingPage'; @@ -23,8 +22,6 @@ function DataFetcher(props: { case 'error': switch (fetchedData.error) { case 'authentication': - localStorage.bearerTokenHandler.remove(); - localStorage.userHandler.remove(); return buildLoginRedirectionPage(); case 'unknown': return buildErrorPage(); diff --git a/packages/generic/client/src/pages/ErrorPage/ErrorPage.tsx b/packages/generic/client/src/pages/ErrorPage/ErrorPage.tsx index 8f9ab7e4e..a16ca04c7 100644 --- a/packages/generic/client/src/pages/ErrorPage/ErrorPage.tsx +++ b/packages/generic/client/src/pages/ErrorPage/ErrorPage.tsx @@ -1,10 +1,10 @@ import React from 'react'; import { useHistory } from 'react-router'; import { customThemeType, useCustomTheme, ButtonWithIcon, Icon, Text } from 'pelta-design-system'; -import { localStorage } from '../../services/localStorage'; import { wordings } from '../../wordings'; -import { routes } from '../routes'; import format from 'string-template'; +import { urlHandler } from '../../utils'; +import { localStorage } from '../../services/localStorage'; export { ErrorPage }; @@ -40,10 +40,8 @@ function ErrorPage(props: { route?: string; errorCode?: number }) { ); function logout() { - localStorage.bearerTokenHandler.remove(); - localStorage.userHandler.remove(); localStorage.adminViewHandler.remove(); - history.push(routes.LOGIN.getPath()); + window.location.replace(urlHandler.getSsoLogoutUrl()); } function reload() { diff --git a/packages/generic/client/src/pages/Login/Login.tsx b/packages/generic/client/src/pages/Login/Login.tsx index d205b60af..54be3db7d 100644 --- a/packages/generic/client/src/pages/Login/Login.tsx +++ b/packages/generic/client/src/pages/Login/Login.tsx @@ -1,11 +1,8 @@ -import { idModule } from '@label/core'; -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, useEffect } from 'react'; import { useHistory } from 'react-router-dom'; -import { customThemeType, useCustomTheme, LoginForm } from 'pelta-design-system'; -import { apiCaller } from '../../api'; +import { customThemeType, useCustomTheme } from 'pelta-design-system'; import { Logo } from '../../components'; -import { localStorage } from '../../services/localStorage'; -import { routes } from '../routes'; +import { urlHandler } from '../../utils'; export { Login }; @@ -13,26 +10,20 @@ const Login: FunctionComponent = () => { const history = useHistory(); const theme = useCustomTheme(); const styles = buildStyles(theme); + useEffect(() => { + // URL backend qui déclenche la redirection SAML (à mettre en variable d'env) + window.location.href = urlHandler.getSsoLoginUrl(); + }, [history]); return (
+ Redirection vers SSO...
-
); - async function handleSubmit({ email, password }: { email: string; password: string }) { - const { - data: { _id, email: userEmail, name, role, token, passwordTimeValidityStatus }, - } = await apiCaller.post<'login'>('login', { email, password }); - localStorage.bearerTokenHandler.set(token); - localStorage.userHandler.set({ _id: idModule.lib.buildId(_id), email: userEmail, name, role }); - localStorage.userHandler.setPasswordTimeValidityStatus(passwordTimeValidityStatus); - history.push(routes.DEFAULT.getPath()); - } - function buildStyles(theme: customThemeType) { return { mainContainer: { diff --git a/packages/generic/client/src/pages/ResetPassword/ResetPassword.tsx b/packages/generic/client/src/pages/ResetPassword/ResetPassword.tsx deleted file mode 100644 index 76e8c8c83..000000000 --- a/packages/generic/client/src/pages/ResetPassword/ResetPassword.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useState } from 'react'; -import { useHistory } from 'react-router'; -import { customThemeType, useCustomTheme, ConfirmationPopup } from 'pelta-design-system'; -import { ResetPasswordForm } from '../../components'; -import { localStorage } from '../../services/localStorage'; -import { wordings } from '../../wordings'; -import { routes } from '../routes'; - -export { ResetPassword }; - -const FORM_WIDTH = 400; - -function ResetPassword() { - const theme = useCustomTheme(); - const history = useHistory(); - const styles = buildStyles(theme); - const [isInformationPopupOpen, setIsInformationPopupOpen] = useState(true); - - return ( -
- {isInformationPopupOpen && ( - setIsInformationPopupOpen(false)} - /> - )} -
- -
-
- ); - - function logout() { - localStorage.userHandler.remove(); - localStorage.bearerTokenHandler.remove(); - history.push(routes.DEFAULT.getPath()); - } - - function onUpdatePassword() { - history.push(routes.DEFAULT.getPath()); - } -} - -function buildStyles(theme: customThemeType) { - return { - container: { - flexDirection: 'column', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - height: '100vh', - width: '100vw', - }, - formContainer: { - display: 'flex', - flexDirection: 'column', - minWidth: FORM_WIDTH, - padding: theme.spacing * 6, - paddingBottom: theme.spacing * 3, - borderRadius: theme.shape.borderRadius.m, - boxShadow: theme.boxShadow.major.out, - }, - } as const; -} diff --git a/packages/generic/client/src/pages/ResetPassword/index.ts b/packages/generic/client/src/pages/ResetPassword/index.ts deleted file mode 100644 index 966b77748..000000000 --- a/packages/generic/client/src/pages/ResetPassword/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ResetPassword } from './ResetPassword'; diff --git a/packages/generic/client/src/pages/router.tsx b/packages/generic/client/src/pages/router.tsx index 002927999..94aee5785 100644 --- a/packages/generic/client/src/pages/router.tsx +++ b/packages/generic/client/src/pages/router.tsx @@ -4,7 +4,6 @@ import { localStorage } from '../services/localStorage'; import { wordings } from '../wordings'; import { AdminPage } from './Admin/AdminPage'; import { AdminInfosDataFetcher } from './Admin/AdminInfosDataFetcher'; -import { WorkingUsers } from './Admin/WorkingUsers'; import { DocumentInspector } from './Admin/DocumentInspector'; import { TreatedDocuments } from './Admin/TreatedDocuments'; import { ProblemReports } from './Admin/ProblemReports'; @@ -13,26 +12,24 @@ import { UntreatedDocuments } from './Admin/UntreatedDocuments'; import { AnonymizedDocument } from './AnonymizedDocument'; import { Home } from './Home'; import { Login } from './Login'; -import { ResetPassword } from './ResetPassword'; import { PublishableDocuments } from './PublishableDocuments'; import { SettingsDataFetcher } from './SettingsDataFetcher'; import { Statistics } from './Admin/Statistics'; import { defaultRoutes, routes } from './routes'; import { ToBeConfirmedDocuments } from './Admin/ToBeConfirmedDocuments'; import { Summary } from './Admin/Summary'; +import { CurrentUser, useCtxUser } from '../contexts/user.context'; export { Router }; function Router() { + const { user } = useCtxUser(); return ( - - - {({ settings }) => } @@ -44,7 +41,8 @@ function Router() { ({ problemReport }) => !problemReport.hasBeenRead, ).length; const toBeConfirmedDocumentsCount = adminInfos.toBeConfirmedDocuments.length; - const userRole = localStorage.adminViewHandler.get() || localStorage.userHandler.getRole(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + const userRole = localStorage.adminViewHandler.get() || (user?.role as 'admin' | 'scrutator'); if (userRole !== 'admin' && userRole !== 'scrutator') { return <>; } @@ -77,18 +75,6 @@ function Router() { /> - {userRole === 'admin' && ( - - - - - - )} = ({ children, ...rest }: RouteProps) => ( - - isAuthenticated() ? ( - children - ) : ( +const AuthenticatedRoute: FunctionComponent = ({ children, ...rest }: RouteProps) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment + const { user, loading } = useCtxUser(); + if (loading) { + return
Loading...
; + } + return ( + + user ? ( + children + ) : ( + + ) + } + /> + ); +}; + +const HomeRoute: FunctionComponent = ({ ...props }: RouteProps) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment + const { user, loading } = useCtxUser(); + + if (loading) { + return
Loading...
; + } + return ( + ( - ) - } - /> -); - -const HomeRoute: FunctionComponent = ({ ...props }: RouteProps) => ( - ( - - )} - /> -); + )} + /> + ); +}; -function getRedirectionRoute() { - const passwordTimeValidityStatus = localStorage.userHandler.getPasswordTimeValidityStatus(); - if (passwordTimeValidityStatus === 'outdated') { - return routes.RESET_PASSWORD.getPath(); - } - const userRole = localStorage.userHandler.getRole(); +function getRedirectionRoute(user: CurrentUser | null) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + const userRole = user?.role; if (!userRole) { return routes.LOGIN.getPath(); @@ -236,27 +235,30 @@ function getRedirectionRoute() { } } - return defaultRoutes[userRole]; + return defaultRoutes[userRole as 'annotator' | 'scrutator' | 'admin']; } -const UnauthenticatedRoute: FunctionComponent = ({ children, ...rest }: RouteProps) => ( - - !isAuthenticated() ? ( - children - ) : ( - - ) - } - /> -); - -function isAuthenticated() { - return !!localStorage.bearerTokenHandler.get() && !!localStorage.userHandler.getRole(); -} +const UnauthenticatedRoute: FunctionComponent = ({ children, ...rest }: RouteProps) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment + const { user, loading } = useCtxUser(); + if (loading) { + return
Loading...
; + } + return ( + + !user ? ( + children + ) : ( + + ) + } + /> + ); +}; diff --git a/packages/generic/client/src/pages/routes.ts b/packages/generic/client/src/pages/routes.ts index 55921084d..d91336a06 100644 --- a/packages/generic/client/src/pages/routes.ts +++ b/packages/generic/client/src/pages/routes.ts @@ -3,7 +3,6 @@ export { defaultRoutes, routes }; const routes = { ADMIN: { getPath: () => '/label/admin' }, ADMIN_MAIN: { getPath: () => '/label/admin/main/' }, - WORKING_USERS: { getPath: () => '/label/admin/main/working-users' }, ANNOTATION: { getPath: () => '/label/annotation' }, ANONYMIZED_DOCUMENT: { getPath: (documentId?: string) => `/label/anonymized-document/${documentId || ':documentId'}`, @@ -13,7 +12,6 @@ const routes = { LOGIN: { getPath: () => '/label/login' }, PROBLEM_REPORTS: { getPath: () => '/label/admin/main/problem-reports' }, PRE_ASSIGN_DOCUMENTS: { getPath: () => '/label/admin/main/pre-assign-documents' }, - RESET_PASSWORD: { getPath: () => '/label/reset-password' }, PUBLISHABLE_DOCUMENTS: { getPath: () => '/label/publishable-documents' }, STATISTICS: { getPath: () => '/label/admin/main/statistics' }, SUMMARY: { getPath: () => '/label/admin/main/summary' }, diff --git a/packages/generic/client/src/services/localStorage/userHandler.ts b/packages/generic/client/src/services/localStorage/userHandler.ts index fe85593b8..1066baa23 100644 --- a/packages/generic/client/src/services/localStorage/userHandler.ts +++ b/packages/generic/client/src/services/localStorage/userHandler.ts @@ -1,4 +1,4 @@ -import { passwordTimeValidityStatusType, userType } from '@label/core'; +import { userType } from '@label/core'; import { localStorageHandler } from './localStorageHandler'; import { localStorageMappers } from './localStorageMappers'; @@ -8,17 +8,14 @@ const USER_ID_STORAGE_KEY = 'USER_ID'; const USER_EMAIL_STORAGE_KEY = 'USER_EMAIL'; const USER_NAME_STORAGE_KEY = 'USER_NAME'; const USER_ROLE_STORAGE_KEY = 'USER_ROLE'; -const USER_PASSWORD_TIME_VALIDITY_STATUS_STORAGE_KEY = 'USER_PASSWORD_TIME_VALIDITY_STATUS'; const userHandler = { set, - setPasswordTimeValidityStatus, remove, getId, getEmail, getName, getRole, - getPasswordTimeValidityStatus, }; function set({ _id, email, name, role }: Pick) { @@ -28,24 +25,12 @@ function set({ _id, email, name, role }: Pick = { - generate: ({ deletionDate, email, hashedPassword, _id, isActivated, name, passwordLastUpdateDate, role } = {}) => ({ - email: email ? email : 'EMAIL', - deletionDate: deletionDate || undefined, - hashedPassword: hashedPassword ? hashedPassword : 'HASHED_PASSWORD', + generate: ({ email, _id, name, role } = {}) => ({ + email: email || 'EMAIL', _id: _id ? idModule.lib.buildId(_id) : idModule.lib.buildId(), - isActivated: isActivated !== undefined ? isActivated : true, - name: name ? name : 'NAME', - passwordLastUpdateDate: passwordLastUpdateDate ? passwordLastUpdateDate : new Date().getTime(), - role: role ? role : 'annotator', + name: name || 'NAME', + role: role || 'annotator', }), }; diff --git a/packages/generic/core/src/modules/user/index.ts b/packages/generic/core/src/modules/user/index.ts index e7a925985..e9227d179 100644 --- a/packages/generic/core/src/modules/user/index.ts +++ b/packages/generic/core/src/modules/user/index.ts @@ -1,13 +1,13 @@ import { userGenerator } from './generator'; import { userLib } from './lib'; -import { userModel, userType, passwordTimeValidityStatusType, passwordTimeValidityStatusModel } from './userType'; +import { userModel, userType } from './userType'; export { userModule }; -export type { userType, passwordTimeValidityStatusType }; +export type { userType }; const userModule = { - models: { user: userModel, passwordTimeValidityStatus: passwordTimeValidityStatusModel }, + models: { user: userModel }, generator: userGenerator, lib: userLib, }; diff --git a/packages/generic/core/src/modules/user/lib/buildUser.ts b/packages/generic/core/src/modules/user/lib/buildUser.ts index 4b8063526..4a7517943 100644 --- a/packages/generic/core/src/modules/user/lib/buildUser.ts +++ b/packages/generic/core/src/modules/user/lib/buildUser.ts @@ -1,24 +1,21 @@ import { idModule } from '../../id'; import { userType } from '../userType'; -import { authenticator } from './authenticator'; export { buildUser }; async function buildUser({ email, name, - password, role, }: { email: string; name: string; - password: string; role: userType['role']; }): Promise { - const baseUser = await authenticator.buildBaseUser({ email, name, password }); return { - ...baseUser, _id: idModule.lib.buildId(), + email, + name, role, }; } diff --git a/packages/generic/core/src/modules/user/lib/index.ts b/packages/generic/core/src/modules/user/lib/index.ts index b3af134a9..72e0cb0dd 100644 --- a/packages/generic/core/src/modules/user/lib/index.ts +++ b/packages/generic/core/src/modules/user/lib/index.ts @@ -6,11 +6,8 @@ const userLib = { assertAuthorization: authenticator.assertAuthorization, assertPermissions, buildUser, - computeHashedPassword: authenticator.computeHashedPassword, extractUserIdFromAuthorizationHeader: authenticator.extractUserIdFromAuthorizationHeader, formatEmail: authenticator.formatEmail, - getTokenForUser: authenticator.getTokenForUser, - passwordHandler: authenticator.passwordHandler, }; export { userLib }; diff --git a/packages/generic/core/src/modules/user/userType.ts b/packages/generic/core/src/modules/user/userType.ts index 56988c567..0acc14ebe 100644 --- a/packages/generic/core/src/modules/user/userType.ts +++ b/packages/generic/core/src/modules/user/userType.ts @@ -1,27 +1,16 @@ -import { passwordTimeValidityStatus } from 'sder-core'; import { idType } from '../id'; import { buildModel, buildType } from '../modelType'; -export { userModel, passwordTimeValidityStatusModel }; +export { userModel }; -export type { userType, passwordTimeValidityStatusType }; +export type { userType }; const userModel = buildModel({ kind: 'object', content: { _id: { kind: 'custom', content: 'id' }, - deletionDate: { - kind: 'or', - content: [ - { kind: 'primitive', content: 'number' }, - { kind: 'primitive', content: 'undefined' }, - ], - }, email: { kind: 'primitive', content: 'string' }, - hashedPassword: { kind: 'primitive', content: 'string' }, - isActivated: { kind: 'primitive', content: 'boolean' }, name: { kind: 'primitive', content: 'string' }, - passwordLastUpdateDate: { kind: 'primitive', content: 'number' }, role: { kind: 'constant', content: ['admin', 'annotator', 'publicator', 'scrutator'] as const, @@ -29,7 +18,5 @@ const userModel = buildModel({ }, } as const); -const passwordTimeValidityStatusModel = buildModel({ kind: 'constant', content: passwordTimeValidityStatus }); type userType = buildType; -type passwordTimeValidityStatusType = buildType; diff --git a/packages/generic/sso/.eslintrc.json b/packages/generic/sso/.eslintrc.json new file mode 100644 index 000000000..fc8016b6b --- /dev/null +++ b/packages/generic/sso/.eslintrc.json @@ -0,0 +1,35 @@ +{ + "extends": [ + "plugin:react/recommended", // Uses the recommended rules from @eslint-plugin-react + "plugin:@typescript-eslint/recommended", // Uses the recommended rules from @typescript-eslint/eslint-plugin + "prettier/@typescript-eslint", // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier + "plugin:prettier/recommended" // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. + ], + "parser": "@typescript-eslint/parser", // Specifies the ESLint parser + "parserOptions": { + "ecmaFeatures": { + "jsx": true // Allows for the parsing of JSX + }, + "project": "tsconfig.json", + "ecmaVersion": 2020, // Allows for the parsing of modern ECMAScript features + "sourceType": "module" // Allows for the use of imports + }, + "rules": { + // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs + // e.g. "@typescript-eslint/explicit-function-return-type": "off", + "no-console": ["error", { "allow": ["warn", "error"] }], + "react/jsx-key": "off", + "react/display-name": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-unused-vars": 2, + "@typescript-eslint/no-unsafe-assignment": 2, + "@typescript-eslint/no-unsafe-call": 2, + "@typescript-eslint/no-unsafe-member-access": 2, + "@typescript-eslint/no-unsafe-return": 2 + }, + "settings": { + "react": { + "version": "detect" // Tells eslint-plugin-react to automatically detect the version of React to use + } + } +} diff --git a/packages/generic/sso/.prettierrc.js b/packages/generic/sso/.prettierrc.js new file mode 100644 index 000000000..73313fa59 --- /dev/null +++ b/packages/generic/sso/.prettierrc.js @@ -0,0 +1,7 @@ +module.exports = { + printWidth: 120, + semi: true, + singleQuote: true, + tabWidth: 2, + trailingComma: 'all', +}; diff --git a/packages/generic/sso/README.fr.md b/packages/generic/sso/README.fr.md new file mode 100644 index 000000000..5ece2f740 --- /dev/null +++ b/packages/generic/sso/README.fr.md @@ -0,0 +1,269 @@ +# Module SSO +Ce service implémente l'authentification unique (SSO) via **SAML 2** en de basant sur le framework **NestJS** et la bibliothèque `samlify`. +Il gère l'authentification avec un fournisseur d'identité (IdP) tel que **Keycloak**, **Pages Blanches** ou tout autre fournisseur compatible SAML. +Le service utilise la librairie **SAMLify** pour s'interfacer avec le SSO et faciliter la gestion des requêtes et des réponses SAML. + +## Prérequis + +- **Node.js** (version 16 ou plus récente) +- **NestJS** +- **SAMLify** : Bibliothèque pour gérer les interactions avec SAML 2. +- Un **Identity Provider (IdP)** compatible SAML 2.0 (par ex. Keycloak, Pages Blanches) +- Le fichier `sso_idp_metadata.xml` contient la configuration de l'Identity Provider. +- Le fichier `.env` contient les variables d'environnement nécessaires au fonctionnement du module. + +## Technologies + +- **NestJS** : Framework pour construire des applications Node.js performantes. +- **TypeScript** : Langage de programmation pour un typage statique. +- **Jest** : Framework de tests pour JavaScript. +- **Prettier** : Outil de formatage de code. +- **ESLint** : Outil de linting pour identifier et reporter les motifs dans le code JavaScript. + +## Dépendances +### Le module SSO utilise les dépendances suivantes : +- `@nestjs/common` +- `@nestjs/core` +- `@nestjs/config` +- `@authenio/samlify-node-xmllint` +- `@nestjs/platform-express` +- `body-parser` +- `dotenv` +- `reflect-metadata` +- `samlify` + +### Pré-requis + +- Installer [nvm](https://github.com/nvm-sh/nvm) afin d'avoir la version utilisée pour cette application et lancer la commande : + +```bash +nvm install +``` + +### Installation + +Pour installer les packages nécessaires au bon fonctionnement de l'application, ouvrir un terminal et executer la commande suivante : + +```bash +npm install +``` + +## Configuration +### Configurer les variables d'environnement: + + Les variables d'environnement nécessaires au fonctionnement du SSO doivent être configurées dans le fichier [docker.env.example](../../../docker.env.example) / [.env.example](../../../.env.example), situé à la racine du projet principal. Ce fichier doit ensuite être renommé en docker.env ou .env, selon le besoin, adapter les variables d'environnement si besoin + +# Configuration du Service Provider (SP) + Les instructions spécifiques à la configuration du fournisseur d'identité (IdP) et du fournisseur de services (SP) sont détaillées dans le document suivant : `MANU_SSO_Configuration_IDP_SAML_VM_Test_V1.1.odt` + +- SSO_SP_ENTITY_ID: Identifiant unique du SP (ie: SP-LABEL) +- SSO_SP_ASSERTION_CONSUMER_SERVICE_LOCATION: URL où le SP reçoit les assertions SAML après authentification +- SSO_SP_PRIVATE_KEY: Clé privée du Service Provider utilisée pour signer et déchiffrer les messages SAML échangés avec l'Identity Provider + +### Génération d'une Clé Privée et d'un Certificat Auto-Signé pour SAML + +Cette partie décrit les étapes nécessaires pour générer une clé privée et un certificat auto-signé à l'aide d'OpenSSL, qui peuvent être utilisés pour configurer un Service Provider (SP) dans un environnement SAML. + +**Veuillez noter que l'utilisation de certificats auto-signés est généralement destinée aux environnements de développement ou de test. En production, il est fortement recommandé d'utiliser un certificat émis par une autorité de certification (CA) de confiance pour garantir la sécurité et la validité des communications.** + +#### Prérequis + +- **OpenSSL**: Assurez-vous qu'OpenSSL est installé sur votre système. Vous pouvez le télécharger et l'installer à partir du site officiel ou utiliser un gestionnaire de paquets. + +#### Étapes de Génération + +1. **Installer OpenSSL**: + +```sh + sudo apt-get update + sudo apt-get install openssl +``` +2. **Générer une Clé Privée** +```sh + openssl genrsa -out privatekey.pem 2048 +``` +3. **Générer un Certificat Auto-Signé** + +Utilisez la clé privée pour créer un certificat auto-signé +```sh + openssl req -new -x509 -key privatekey.pem -out certificate.pem +``` +Intégrez la clé privée ainsi que le certificat auto-signé dans la configuration de votre application. Par exemple, cela peut être réalisé dans votre fichier de configuration SAML +```typescript +const spProps = { + entityID: process.env.SSO_SP_ENTITY_ID, + assertionConsumerService: [ + { + Binding: samlify.Constants.namespace.binding.post, + Location: process.env.SSO_SP_ASSERTION_CONSUMER_SERVICE_LOCATION, + }, + ], + privateKey: fs.readFileSync('path/to/privatekey.pem', 'utf8'), + signingCert: fs.readFileSync('path/to/certificate.pem', 'utf8'), + authnRequestsSigned: true, + wantAssertionsSigned: true, +}; +``` + +# Configuration de l'Identity Provider (IdP) +- SSO_IDP_METADATA: Fichier XML contenant les métadonnées du IdP (Entity ID, certificats, endpoints). +- SSO_IDP_SINGLE_SIGN_ON_SERVICE_LOCATION: URL du SSO où le Service Provider redirige les utilisateurs pour l'authentification. +- SSO_IDP_SINGLE_LOGOUT_SERVICE_LOCATION: URL permettant la déconnexion des utilisateurs + + +## Scripts +- Voici une liste des scripts disponibles dans `package.json` : + +| Commande | Description | +|--------------------|-----------------------------------------------------------------------------------------------| +| `build` | Clean et compile l'application NestJS. | +| `compile` | Compile l'application NestJS. | +| `format` | Vérifie le formatage du code avec Prettier. | +| `lint` | Vérifie le code TypeScript avec ESLint. | +| `test` | Exécute les tests unitaires et d'intégration. | +| `test:watch` | Exécute les tests en mode interactif avec surveillance des changements. | +| `test:coverage` | Exécute les tests avec génération de rapports de couverture. | +| `fix` | Corrige les erreurs de formatage avec ESLint et Prettier. | + +## Tests +- Pour exécuter les tests, utilisez la commande suivante : + +```bash +npm run test +``` + +- Pour exécuter les tests en mode interactif : + +```bash + npm run test:watch + ``` + +- Pour exécuter les tests avec un rapport de couverture : + +```bash + npm run test:cov + ``` + +## Fonctionnalités + +### Génération des métadonnées + + + +>La méthode ***generateMetadata()*** permet de générer et de récupérer les métadonnées du Service Provider (SP). + + +### Création d'une URL de demande de connexion + + +>La méthode ***createLoginRequestUrl()*** génère une URL pour initier la procédure de connexion via SAML 2. + + +### Analyse et traitement de la réponse SAML 2 + + +>La méthode ***parseResponse()*** permet d'analyser et de traiter la réponse SAML reçue du IdP après une tentative de connexion. + + +### Création d'une URL de demande de déconnexion + + +>La méthode ***createLogoutRequestUrl(user)*** permet de créer une URL de déconnexion basée sur les informations d'utilisateur (nameID). + + +## Utilisation +L'exemple ci-dessous illustre l'utilisation du module SSO dans une application NestJs. + + +### Gestion de la session +Exemple d'implémentation de la gestion des sessions utilisateurs avec express-session. + +#### Installation +```sh +npm install express-session +``` + +#### Configuration de la session +Dans votre application NestJS, procédez à la configuration d'express-session dans le fichier principal (par exemple, main.ts) comme suit : +```typescript +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; +import * as session from 'express-session'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + app.use(session({ + secret: 'votre_secret', // Remplacez par un secret fort + resave: false, + saveUninitialized: false, + cookie: { secure: false }, // Mettez à true en production si vous utilisez HTTPS + })); + + await app.listen(3000); +} +bootstrap(); + +``` + +#### Initialisation du service +Intégrez SamlService en l'injectant dans votre contrôleur ou service NestJS afin de l'utiliser de manière optimale. +```typescript +import { SamlService } from './saml.service'; + +@Controller('auth') +export class AuthController { + constructor(private readonly samlService: SamlService) {} + + @Get('login') + async login(@Res() res: Response) { + const loginUrl = await this.samlService.createLoginRequestUrl(); + return res.redirect(loginUrl); + } + + @Post('sso/acs') + async handleSSO(@Req() req: Request) { + const response = await this.samlService.parseResponse(req); + req.session.user = response; // Enregistrer l'utilisateur dans la session + return response; // Traiter la réponse selon votre logique + } + + @Get('logout') + async logout(@Req() req: Request, @Res() res: Response) { + const logoutUrl = await this.samlService.createLogoutRequestUrl(req.session.user); + req.session.destroy(); // Détruire la session + return res.redirect(logoutUrl); + } +} +``` + +1. **Méthode de Connexion** + + Endpoint : ***GET /auth/login*** + + Cette méthode initie le processus de connexion en redirigeant l'utilisateur vers l'URL de demande de connexion SAML. + +>##### Fonctionnement +>Lorsqu'un utilisateur accède à l'endpoint /auth/login, la méthode login est appelée. +La méthode utilise le service SAML (SamlService) pour générer une URL de demande de connexion. +L'utilisateur est ensuite redirigé vers cette URL pour procéder à l'authentification. + +2. **Méthode de Gestion des Réponses SSO** + Endpoint : ***POST /auth/sso/acs*** + + Cette méthode gère les réponses du SSO après que l'utilisateur se soit authentifié. + +>#### Fonctionnement +>Lorsque l'utilisateur est authentifié, le SSO redirige vers l'endpoint /auth/sso/acs avec une réponse SAML. +La méthode handleSSO est appelée, qui utilise le service SAML pour analyser la réponse. +Les informations de l'utilisateur sont extraites de la réponse et stockées dans la session. + +3. **Méthode de Déconnexion** + Endpoint : ***GET /auth/logout*** + + Cette méthode permet de déconnecter l'utilisateur et de gérer le processus de déconnexion avec le SSO. + +>#### Fonctionnement +>Lorsque l'utilisateur souhaite se déconnecter, il accède à l'endpoint /auth/logout. +La méthode logout est appelée, qui génère une URL de demande de déconnexion SAML. +La session de l'utilisateur est détruite, et l'utilisateur est redirigé vers l'URL de déconnexion pour finaliser le processus. diff --git a/packages/generic/sso/README.md b/packages/generic/sso/README.md new file mode 100644 index 000000000..a810ec9a0 --- /dev/null +++ b/packages/generic/sso/README.md @@ -0,0 +1,275 @@ + +**EN** | [FR](README.fr.md) + +# SSO Module + +This service implements Single Sign-On (SSO) using **SAML 2**, built on the **NestJS** framework and the `samlify` library. +It facilitates authentication with Identity Providers (IdP) such as **Keycloak**, **Pages Blanches**, or any other SAML-compatible provider. +The service utilizes the **SAMLify** library to interface with SSO, simplifying the management of SAML requests and responses. + +## Prerequisites + +- **Node.js** (version 16 or higher) +- **NestJS** +- **SAMLify**: Library for managing interactions with SAML 2. +- A **SAML 2.0 compatible Identity Provider (IdP)** (e.g., Keycloak, Pages Blanches). +- The `sso_idp_metadata.xml` file containing the Identity Provider configuration. +- The `.env` file that holds the necessary environment variables for the module. + +## Technologies + +- **NestJS**: A framework for building efficient Node.js applications. +- **TypeScript**: A programming language that adds static typing. +- **Jest**: A testing framework for JavaScript. +- **Prettier**: A code formatting tool. +- **ESLint**: A linting tool for identifying and reporting patterns in JavaScript code. + +## Dependencies + +The SSO module requires the following dependencies: +- `@nestjs/common` +- `@nestjs/core` +- `@nestjs/config` +- `@authenio/samlify-node-xmllint` +- `@nestjs/platform-express` +- `body-parser` +- `dotenv` +- `reflect-metadata` +- `samlify` + +### Installation Prerequisites + +1. Install [nvm](https://github.com/nvm-sh/nvm) to manage Node.js versions. +2. Run the following command to install the required Node.js version: + + ```bash + nvm install + +### Installation + +To install the necessary packages for the application, open a terminal and execute the following command: + +```bash +npm install +``` + +## Configuration +### Environment Variables +Configure the environment variables required for the SSO module in the [docker.env.example](../../../docker.env.example) / [.env.example](../../../.env.example) file located at the root of the main project. Rename this file to docker.env or .env as necessary, and adjust the environment variables as needed. + + +# Service Provider (SP) Configuration +Specific instructions for configuring the Identity Provider (IdP) and the Service Provider (SP) can be found in the following document: `MANU_SSO_Configuration_IDP_SAML_VM_Test_V1.1.odt` + +- SSO_SP_ENTITY_ID: Unique identifier for the SP (e.g., SP-LABEL). +- SSO_SP_ASSERTION_CONSUMER_SERVICE_LOCATION: URL where the SP receives SAML assertions after authentication. +- SSO_SP_PRIVATE_KEY: Private key of the Service Provider used for signing and decrypting SAML messages exchanged with the Identity Provider. + +### Generating a Private Key and Self-Signed Certificate for SAML + +This section details the steps needed to generate a private key and a self-signed certificate using OpenSSL, which can be used to set up a Service Provider (SP) in a SAML environment. + +**Self-signed certificates are typically suitable for development or testing environments. For production use, it is highly recommended to obtain a certificate from a trusted Certificate Authority (CA) to ensure secure and trusted communication.** + +#### Prérequis + +- **OpenSSL**: Verify that OpenSSL is installed on your system. It can be downloaded from the official website or installed via a package manager. + +#### Étapes de Génération + +1. **Installer OpenSSL**: + +```sh + sudo apt-get update + sudo apt-get install openssl +``` +2. **Generate a Private Key** +```sh + openssl genrsa -out privatekey.pem 2048 +``` +3. **Generate a Self-Signed Certificate** + +Use the private key to create a self-signed certificate +```sh + openssl req -new -x509 -key privatekey.pem -out certificate.pem +``` +Integrate the private key and the self-signed certificate into your application configuration. For example, in your SAML configuration file +```typescript +const spProps = { + entityID: process.env.SSO_SP_ENTITY_ID, + assertionConsumerService: [ + { + Binding: samlify.Constants.namespace.binding.post, + Location: process.env.SSO_SP_ASSERTION_CONSUMER_SERVICE_LOCATION, + }, + ], + privateKey: fs.readFileSync('path/to/privatekey.pem', 'utf8'), + signingCert: fs.readFileSync('path/to/certificate.pem', 'utf8'), + authnRequestsSigned: true, + wantAssertionsSigned: true, +}; +``` + +## Identity Provider (IdP) Configuration +- SSO_IDP_METADATA: XML file containing the IdP metadata (Entity ID, certificates, endpoints). +- SSO_IDP_SINGLE_SIGN_ON_SERVICE_LOCATION: URL where the Service Provider redirects users for authentication. +- SSO_IDP_SINGLE_LOGOUT_SERVICE_LOCATION: URL for user logout. + + +## Scripts +- The following scripts are available in `package.json` : + +| Commande | Description | +|--------------------|---------------------------------------------------------------------------------| +| `build` | Cleans and compiles the NestJS application. | +| `compile` | Compiles the NestJS application. | +| `format` | Checks the code formatting with Prettier. | +| `lint` | Checks TypeScript code with ESLint. | +| `test` | Runs unit and integration tests. | +| `test:watch` | Executes tests in interactive mode with change monitoring. | +| `test:coverage` | Runs tests while generating a coverage report. | +| `fix` | Corrects formatting errors with ESLint and Prettier. | + +## Testing +- To run the tests, use the following command: + +```bash +npm run test +``` + +- To run the tests in interactive mode, execute: + +```bash + npm run test:watch + ``` + +- To run the tests with coverage reporting, execute: + +```bash + npm run test:cov + ``` + +## Features + +### Metadata Generation + + + +>The ***generateMetadata()*** method allows for the generation and retrieval of the Service Provider (SP) metadata. + + +### Creating a Login Request URL + + +>The ***createLoginRequestUrl()*** method generates a URL to initiate the login process via SAML 2.. + + +### Parsing and Handling SAML 2 Responses + + +>The ***parseResponse()*** method parses and processes the SAML response received from the IdP after a login attempt. + + +### Creating a Logout Request URL + + +>The ***createLogoutRequestUrl(user)*** method generates a logout URL based on user information (nameID). + + +## Usage +The following example illustrates the use of the SSO module within a NestJS application. + + +### Session Management +Example implementation of user session management with express-session. + +#### Installation +```sh +npm install express-session +``` + +#### Configuring the Session +In your NestJS application, configure express-session in the main file (e.g., main.ts) as follows:: +```typescript +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; +import * as session from 'express-session'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + app.use(session({ + secret: 'votre_secret', // Replace with a strong secret. + resave: false, + saveUninitialized: false, + cookie: { secure: false }, // Set to true in production if you are using HTTPS. + })); + + await app.listen(3000); +} +bootstrap(); + +``` + +#### Initialisation du service +Integrate SamlService by injecting it into your NestJS controller or service for optimal usage. +```typescript +import { SamlService } from './saml.service'; + +@Controller('auth') +export class AuthController { + constructor(private readonly samlService: SamlService) {} + + @Get('login') + async login(@Res() res: Response) { + const loginUrl = await this.samlService.createLoginRequestUrl(); + return res.redirect(loginUrl); + } + + @Post('sso/acs') + async handleSSO(@Req() req: Request) { + const response = await this.samlService.parseResponse(req); + req.session.user = response; // Register the user in the session. + return response; // Process the response according to your logic. + } + + @Get('logout') + async logout(@Req() req: Request, @Res() res: Response) { + const logoutUrl = await this.samlService.createLogoutRequestUrl(req.session.user); + req.session.destroy(); // Destroy the session. + return res.redirect(logoutUrl); + } + +} +``` + +1. **Login method** + + Endpoint : ***GET /auth/login*** + + This method initiates the login process by redirecting the user to the SAML login request URL + +>##### Mechanism +>When a user accesses the endpoint /auth/login, the login method is called. +The method uses the SAML service (SamlService) to generate a login request URL. +The user is then redirected to this URL to proceed with authentication. + +2. **SSO Response Handling Method** + Endpoint : ***POST /auth/sso/acs*** + + This method handles SSO responses after the user has authenticated. + +>#### Mechanism +>When the user is authenticated, the SSO redirects to the endpoint /auth/sso/acs with a SAML response. +The handleSSO method is called, which uses the SAML service to parse the response. +The user's information is extracted from the response and stored in the session. + +3. **Logout Method** + Endpoint : ***GET /auth/logout*** + + This method allows the user to log out and manages the logout process with the SSO. + +>#### Mechanism +>When the user wishes to log out, they access the endpoint /auth/logout. +The logout method is called, which generates a SAML logout request URL. +The user's session is destroyed, and the user is redirected to the logout URL to finalize the process. diff --git a/packages/generic/sso/babel.config.json b/packages/generic/sso/babel.config.json new file mode 100644 index 000000000..a1292bd52 --- /dev/null +++ b/packages/generic/sso/babel.config.json @@ -0,0 +1,21 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "node": "current" + } + } + ], + "@babel/preset-typescript" + ], + "plugins": [ + [ + "@babel/plugin-proposal-decorators", + { + "legacy": true + } + ] + ] +} diff --git a/packages/generic/sso/jest.config.json b/packages/generic/sso/jest.config.json new file mode 100644 index 000000000..c56e60304 --- /dev/null +++ b/packages/generic/sso/jest.config.json @@ -0,0 +1,20 @@ +{ + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node", + "setupFiles": [ + "/.jest/setupEnvVars.ts" + ] +} \ No newline at end of file diff --git a/packages/generic/sso/package.json b/packages/generic/sso/package.json new file mode 100644 index 000000000..4e9aa2fbd --- /dev/null +++ b/packages/generic/sso/package.json @@ -0,0 +1,66 @@ +{ + "name": "@label/sso", + "version": "1.0.0", + "description": "Module d'interface d'authentification SSO", + "author": "@open-groupe", + "license": "ISC", + "main": "dist/index", + "types": "dist/index", + "repository": { + "type": "git", + "url": "ssh://git@git.boost.open.global:443/cour_de_cassation/cassation_lab/label.git" + }, + "files": [ + "dist" + ], + "scripts": { + "test": "RUN_MODE=TEST jest --passWithNoTests", + "test:watch": "RUN_MODE=TEST jest --passWithNoTests --watch", + "test:coverage": "RUN_MODE=TEST jest --passWithNoTests --coverage", + "build": "yarn clean && yarn compile", + "clean": "rimraf -rf ./dist", + "clean:all": "rimraf -rf ./dist && rimraf -rf ./node_modules", + "compile": "tsc -p tsconfig.json", + "fix": "eslint 'src/**/*.{js,ts,tsx}' --quiet --fix", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "lint": "eslint 'src/**/*.{js,ts,tsx}' --quiet" + }, + "eslintConfig": { + "extends": "react-app" + }, + "dependencies": { + "@authenio/samlify-node-xmllint": "^1.0.1", + "@nestjs/common": "^8.4.7", + "@nestjs/config": "^0.5.0", + "@nestjs/core": "8.4.7", + "@nestjs/platform-express": "7.6.18", + "body-parser": "^1.19.0", + "dotenv": "^16.4.5", + "reflect-metadata": "^0.2.1", + "samlify": "2.6.0" + }, + "devDependencies": { + "@babel/cli": "^7.11.6", + "@babel/core": "^7.11.6", + "@babel/polyfill": "^7.11.5", + "@babel/preset-env": "^7.11.5", + "@babel/preset-typescript": "^7.10.4", + "@babel/plugin-proposal-decorators": "^7.10.5", + "@nestjs/testing": "^8.4.7", + "@types/body-parser": "^1.19.0", + "@types/jest": "^26.0.13", + "@types/supertest": "^2.0.8", + "@typescript-eslint/eslint-plugin": "^4.4.1", + "@typescript-eslint/parser": "^4.4.1", + "core-js": "^3.6.5", + "eslint": "7.7.0", + "eslint-config-prettier": "^6.12.0", + "eslint-plugin-prettier": "^3.1.4", + "jest": "24.9.0", + "prettier": "^2.1.2", + "rimraf": "^3.0.2", + "supertest": "^4.0.2", + "ts-jest": "^29.2.5", + "typescript": "~4.0.0" + } +} diff --git a/packages/generic/sso/src/.jest/setupEnvVars.ts b/packages/generic/sso/src/.jest/setupEnvVars.ts new file mode 100644 index 000000000..3fdf6a487 --- /dev/null +++ b/packages/generic/sso/src/.jest/setupEnvVars.ts @@ -0,0 +1,11 @@ +// @ts-ignore +process.env = { + SSO_SP_ENTITY_ID: 'SP-LABEL-DEV', + SSO_SP_ASSERTION_CONSUMER_SERVICE_LOCATION: 'http://localhost:3005/saml/acs', + SSO_IDP_METADATA: 'sso_idp_metadata.xml', + SSO_IDP_SINGLE_SIGN_ON_SERVICE_LOCATION : 'http://localhost:8080/realms/nouveau-realm/protocol/saml', + SSO_IDP_SINGLE_LOGOUT_SERVICE_LOCATION : 'http://localhost:8080/realms/nouveau-realm/protocol/saml', + PUBLIC_URL: '', + SSO_ATTRIBUTE_ROLE: 'role', + SSO_APP_NAME: 'LABEL' +} diff --git a/packages/generic/sso/src/api/saml/saml.service.spec.ts b/packages/generic/sso/src/api/saml/saml.service.spec.ts new file mode 100644 index 000000000..d45375ee6 --- /dev/null +++ b/packages/generic/sso/src/api/saml/saml.service.spec.ts @@ -0,0 +1,168 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { SamlService } from './saml.service'; +import * as fs from 'fs'; +import * as samlify from 'samlify'; +import * as validator from '@authenio/samlify-node-xmllint'; + +jest.mock('fs'); +jest.mock('samlify', () => { + const Extractor = { + loginResponseFields: jest.fn().mockReturnValue([]), + extract: jest.fn().mockReturnValue({ + // Simuler la réponse extraite ici + samlContent: 'mock-saml-content', + fields: [], + attributes: { nom: 'Xx', prenom: 'Alain', role: ['ANNOTATEUR'], email: 'mail.573@justice.fr', name: 'Xx Alain' }, + }), + }; + jest.mock('@authenio/samlify-node-xmllint', () => ({ + validate: jest.fn(() => true), + })); + + return { + // Autres méthodes de samlify + ServiceProvider: jest.fn().mockReturnValue({ + getMetadata: jest.fn().mockReturnValue(''), + createLoginRequest: jest.fn().mockResolvedValue('http://login-url'), + parseLoginResponse: jest.fn().mockResolvedValue({ + samlContent: 'parsed-response', + }), + createLogoutRequest: jest.fn().mockResolvedValue('http://logout-url'), + }), + IdentityProvider: jest.fn().mockReturnValue({}), + Extractor, + Constants: { + namespace: { + binding: { + post: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', + redirect: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect', + }, + }, + }, + setSchemaValidator: jest.fn().mockReturnValue({ + validate: () => jest.fn().mockResolvedValue(true), + }), + }; +}); + +describe('SamlService', () => { + let service: SamlService; + + beforeEach(async () => { + (fs.readFileSync as jest.Mock).mockReturnValue(''); + + const module: TestingModule = await Test.createTestingModule({ + providers: [SamlService], + }).compile(); + + service = module.get(SamlService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should generate metadata', () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const metadata = service.generateMetadata(); + expect(metadata).toEqual(''); + expect(samlify.ServiceProvider).toHaveBeenCalled(); + }); + + it('should create login request URL', async () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const loginUrl = await service.createLoginRequestUrl(); + expect(loginUrl).toEqual('http://login-url'); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(samlify.ServiceProvider().createLoginRequest).toHaveBeenCalled(); + }); + + describe('parseResponse', () => { + it('should throw an error if SAMLResponse is missing', async () => { + const request = {}; + await expect(service.parseResponse(request)).rejects.toThrow('Missing SAMLResponse in request body'); + }); + + it('should decode the SAMLResponse correctly', async () => { + const base64Response = Buffer.from('').toString('base64'); + const request = { body: { SAMLResponse: base64Response } }; + + const samlContent = await service.parseResponse(request); + + expect(samlContent.samlContent).toBe(''); + }); + + it('should extract fields correctly', async () => { + const base64Response = Buffer.from('').toString('base64'); + const request = { body: { SAMLResponse: base64Response } }; + + const mockExtract = { + attributes: { + role: ['app:role1', 'other:role2'], + }, + }; + (samlify.Extractor.extract as jest.Mock).mockReturnValue(mockExtract); + + process.env.SSO_ATTRIBUT_ROLE = 'role'; + process.env.SSO_APP_NAME = 'app'; + + const result = await service.parseResponse(request); + + expect(result.extract).toEqual(mockExtract); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(result.extract.attributes.role).toEqual(['role1']); + }); + + it('should handle roles correctly when role is a string', async () => { + const base64Response = Buffer.from('').toString('base64'); + const request = { body: { SAMLResponse: base64Response } }; + + const mockExtract = { + attributes: { + role: 'app:role1', + }, + }; + (samlify.Extractor.extract as jest.Mock).mockReturnValue(mockExtract); + + process.env.SSO_ATTRIBUT_ROLE = 'role'; + process.env.SSO_APP_NAME = 'app'; + + const result = await service.parseResponse(request); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(result.extract.attributes.role).toEqual(['role1']); // Vérifie que le rôle est bien traité + }); + + it('should not modify roles if appName is empty', async () => { + const base64Response = Buffer.from('').toString('base64'); + const request = { body: { SAMLResponse: base64Response } }; + + const mockExtract = { + attributes: { + role: ['app:role1', 'other:role2'], + }, + }; + (samlify.Extractor.extract as jest.Mock).mockReturnValue(mockExtract); + + process.env.SSO_ATTRIBUT_ROLE = 'role'; + process.env.SSO_APP_NAME = ''; + + const result = await service.parseResponse(request); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(result.extract.attributes.role.length).toEqual(2); + }); + }); + + it('should create logout request URL', async () => { + const mockUser = { nameID: 'test.user@label.fr', sessionIndex: undefined }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const logoutUrl = await service.createLogoutRequestUrl(mockUser); + expect(logoutUrl).toEqual('http://logout-url'); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(samlify.ServiceProvider().createLogoutRequest).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/generic/sso/src/api/saml/saml.service.ts b/packages/generic/sso/src/api/saml/saml.service.ts new file mode 100644 index 000000000..d955c494b --- /dev/null +++ b/packages/generic/sso/src/api/saml/saml.service.ts @@ -0,0 +1,151 @@ +import { Injectable } from '@nestjs/common'; +import * as samlify from 'samlify'; +import * as fs from 'fs'; +import * as validator from '@authenio/samlify-node-xmllint'; + +samlify.setSchemaValidator(validator); + +@Injectable() +export class SamlService { + private sp; + private idp; + + constructor() { + // Initialiser le Service Provider (SP) + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const spProps = { + entityID: process.env.SSO_SP_ENTITY_ID, + assertionConsumerService: [ + { + Binding: samlify.Constants.namespace.binding.post, + Location: process.env.SSO_SP_ASSERTION_CONSUMER_SERVICE_LOCATION, + }, + ], + authnRequestsSigned: true, + wantAssertionsSigned: true, + isAssertionEncrypted: true, + + privateKey: fs.readFileSync(String(process.env.SSO_SP_PRIVATE_KEY), 'utf8'), + encPrivateKey: fs.readFileSync(String(process.env.SSO_SP_PRIVATE_KEY), 'utf8'), + signingCert: fs.readFileSync(String(process.env.SSO_CERTIFICAT), 'utf8'), + signatureConfig: { + prefix: 'ds', + location: { + reference: '/EntityDescriptor', + action: 'append', + }, + signatureAlgorithm: 'http://www.w3.org/2000/09/xmldsig#', + digestAlgorithm: 'http://www.w3.org/2000/09/xmldsig#', + }, + } as any; + + this.sp = samlify.ServiceProvider(spProps); + // Initialiser l'Identity Provider (IdP) + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const idpProps = { + metadata: fs.readFileSync(String(process.env.SSO_IDP_METADATA), 'utf8'), + encCert: fs.readFileSync(String(process.env.SSO_CERTIFICAT), 'utf8'), + signingCert: fs.readFileSync(String(process.env.SSO_CERTIFICAT), 'utf8'), + wantAuthnRequestsSigned: true, + singleSignOnService: [ + { + Binding: samlify.Constants.namespace.binding.redirect, + Location: process.env.SSO_IDP_SINGLE_SIGN_ON_SERVICE_LOCATION, + }, + ], + singleLogoutService: [ + { + Binding: samlify.Constants.namespace.binding.redirect, + Location: process.env.SSO_IDP_SINGLE_LOGOUT_SERVICE_LOCATION, + }, + ], + wantLogoutRequestSigned: true, + } as any; + this.idp = samlify.IdentityProvider(idpProps); + } + + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + generateMetadata() { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-member-access + return this.sp.getMetadata(); + } + + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + createLoginRequestUrl() { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + return this.sp.createLoginRequest(this.idp, 'redirect'); + } + + async parseResponse(request: any) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + const samlResponse = request?.body?.SAMLResponse; + if (!samlResponse) throw new Error('Missing SAMLResponse in request body'); + + const samlContent = Buffer.from(samlResponse, 'base64').toString(); + + const extractFields = [ + ...samlify.Extractor.loginResponseFields(samlContent), + { + key: 'nameID', + localPath: ['Response', 'Assertion', 'Subject', 'NameID'], + attributes: [], + shortcut: samlContent as unknown, + }, + { + key: 'sessionIndex', + localPath: ['Response', 'Assertion', 'AuthnStatement'], + attributes: ['SessionIndex'], + shortcut: samlContent as unknown, + }, + { + key: 'attributes', + localPath: ['Response', 'Assertion', 'AttributeStatement', 'Attribute'], + index: ['Name'], + attributePath: ['AttributeValue'], + attributes: [], + shortcut: samlContent as unknown, + }, + ]; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const extract = samlify.Extractor.extract(samlContent, extractFields); + // eslint-disable-next-line no-console + //console.log('extract ', extract); + const roleKey = process.env.SSO_ATTRIBUTE_ROLE || 'role'; + const appName = process.env.SSO_APP_NAME || ''; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (typeof extract.attributes?.[roleKey] === 'string') { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + extract.attributes[roleKey] = [extract.attributes[roleKey]]; + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + extract.attributes[roleKey] = extract.attributes[roleKey] + ?.filter((role: string) => role.includes(appName)) + .map((role: string) => role.replace(`${appName}:`, '')); + + return { + samlContent, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + extract, + }; + } + + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + async createLogoutRequestUrl(user: any) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + return this.sp.createLogoutRequest(this.idp, 'redirect', { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + logoutNameID: user.nameID, + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + id: user.sessionIndex, + nameIDFormat: process.env.SSO_NAME_ID_FORMAT, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + sessionIndex: user.sessionIndex, + signRequest: true, + signatureAlgorithm: process.env.SSO_SIGNATURE_ALGORITHM, + }); + } +} diff --git a/packages/generic/sso/src/index.ts b/packages/generic/sso/src/index.ts new file mode 100644 index 000000000..36336d050 --- /dev/null +++ b/packages/generic/sso/src/index.ts @@ -0,0 +1 @@ +export * from './api/saml/saml.service'; diff --git a/packages/generic/sso/tsconfig.json b/packages/generic/sso/tsconfig.json new file mode 100644 index 000000000..5680259f7 --- /dev/null +++ b/packages/generic/sso/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2020", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true + } +} \ No newline at end of file From 6202039236315724bfa3146cad86feff7e540faf Mon Sep 17 00:00:00 2001 From: elhadj Date: Tue, 22 Oct 2024 16:31:06 +0200 Subject: [PATCH 02/20] fix: lint and clear --- nodemon.json | 2 +- package.json | 2 +- packages/courDeCassation/babel.config.json | 5 +- packages/courDeCassation/jest.config.json | 5 +- packages/courDeCassation/package.json | 1 + packages/courDeCassation/src/test/crt.txt | 1 + .../courDeCassation/src/test/idp_metadata.xml | 6 + packages/courDeCassation/src/test/pk.txt | 1 + .../courDeCassation/src/test/setupEnvVars.ts | 29 + packages/generic/backend/src/api/buildApi.ts | 3 +- .../generic/backend/src/api/controllers.ts | 37 +- .../generic/backend/src/app/buildRunServer.ts | 1 - .../src/{app => }/express-session.d.ts | 0 .../modules/sso/service/express-session.d.ts | 22 - .../modules/sso/service/ssoService.spec.ts | 9 +- .../src/modules/sso/service/ssoService.ts | 26 +- .../repository/buildFakeUserRepository.ts | 4 +- .../service/userService/fetchWorkingUsers.ts | 3 +- .../modules/user/service/userService/index.ts | 48 +- .../business/MainHeader/SettingsDrawer.tsx | 115 ++- .../client/src/contexts/user.context.tsx | 3 +- .../UntreatedDocumentsTable.tsx | 2 +- .../src/services/localStorage/userHandler.ts | 1 - .../generic/client/src/utils/urlHandler.ts | 5 +- .../generic/core/src/modules/user/userType.ts | 1 - packages/generic/sso/jest.config.json | 4 +- .../generic/sso/src/.jest/setupEnvVars.ts | 11 - .../sso/src/api/saml/saml.service.spec.ts | 2 + yarn.lock | 841 +++++++++++++++++- 29 files changed, 1018 insertions(+), 172 deletions(-) create mode 100644 packages/courDeCassation/src/test/crt.txt create mode 100644 packages/courDeCassation/src/test/idp_metadata.xml create mode 100644 packages/courDeCassation/src/test/pk.txt create mode 100644 packages/courDeCassation/src/test/setupEnvVars.ts rename packages/generic/backend/src/{app => }/express-session.d.ts (100%) delete mode 100644 packages/generic/backend/src/modules/sso/service/express-session.d.ts delete mode 100644 packages/generic/sso/src/.jest/setupEnvVars.ts diff --git a/nodemon.json b/nodemon.json index 1da89108c..214432afc 100644 --- a/nodemon.json +++ b/nodemon.json @@ -5,5 +5,5 @@ "packages/generic/core/src" ], "ext": "ts,json", - "exec": "yarn compile:backend && node packages/courDeCassation/dist/labelServer.js --settings packages/courDeCassation/settings/settings.json" + "exec": "node packages/courDeCassation/dist/labelServer.js --settings packages/courDeCassation/settings/settings.json" } diff --git a/package.json b/package.json index d7d712d27..b7c3b6eb6 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "init:db": "scripts/initializeTestDb.sh", "lint": "lerna run lint", "start:backend": "lerna run --scope @label/cour-de-cassation start --stream", - "start:backend:dev": "nodemon", + "start:backend:dev": "RUN_MODE=LOCAL nodemon", "start:client:dev": "yarn compile:client && cd packages/generic/client && yarn start", "test": "lerna run test", "type": "lerna run type" diff --git a/packages/courDeCassation/babel.config.json b/packages/courDeCassation/babel.config.json index 3313ff9ef..08e775f2d 100644 --- a/packages/courDeCassation/babel.config.json +++ b/packages/courDeCassation/babel.config.json @@ -1,3 +1,6 @@ { - "presets": ["@babel/preset-env", "@babel/preset-typescript"] + "presets": ["@babel/preset-env", "@babel/preset-typescript"], + "plugins": [ + "@babel/plugin-transform-runtime" + ] } diff --git a/packages/courDeCassation/jest.config.json b/packages/courDeCassation/jest.config.json index 368ed5a21..f72448dff 100644 --- a/packages/courDeCassation/jest.config.json +++ b/packages/courDeCassation/jest.config.json @@ -15,5 +15,8 @@ "./test/setupTests.ts" ], "testRegex": ".spec.ts$", - "testEnvironment": "node" + "testEnvironment": "node", + "setupFiles": [ + "./test/setupEnvVars.ts" + ] } diff --git a/packages/courDeCassation/package.json b/packages/courDeCassation/package.json index 46e099d87..365cd6786 100644 --- a/packages/courDeCassation/package.json +++ b/packages/courDeCassation/package.json @@ -34,6 +34,7 @@ "devDependencies": { "@babel/cli": "^7.11.6", "@babel/core": "^7.11.6", + "@babel/plugin-transform-runtime": "^7.10.4", "@babel/polyfill": "^7.11.5", "@babel/preset-env": "^7.11.5", "@babel/preset-typescript": "^7.10.4", diff --git a/packages/courDeCassation/src/test/crt.txt b/packages/courDeCassation/src/test/crt.txt new file mode 100644 index 000000000..96236f815 --- /dev/null +++ b/packages/courDeCassation/src/test/crt.txt @@ -0,0 +1 @@ +example \ No newline at end of file diff --git a/packages/courDeCassation/src/test/idp_metadata.xml b/packages/courDeCassation/src/test/idp_metadata.xml new file mode 100644 index 000000000..72a3046df --- /dev/null +++ b/packages/courDeCassation/src/test/idp_metadata.xml @@ -0,0 +1,6 @@ + + + urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + + + \ No newline at end of file diff --git a/packages/courDeCassation/src/test/pk.txt b/packages/courDeCassation/src/test/pk.txt new file mode 100644 index 000000000..96236f815 --- /dev/null +++ b/packages/courDeCassation/src/test/pk.txt @@ -0,0 +1 @@ +example \ No newline at end of file diff --git a/packages/courDeCassation/src/test/setupEnvVars.ts b/packages/courDeCassation/src/test/setupEnvVars.ts new file mode 100644 index 000000000..99a4dcb9d --- /dev/null +++ b/packages/courDeCassation/src/test/setupEnvVars.ts @@ -0,0 +1,29 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +process.env = { + LABEL_DB_URL: 'mongodb://localhost:55431', + LABEL_DB_NAME: 'labelDb', + LABEL_CLIENT_URL: 'http://localhost:55432', + LABEL_API_PORT: '55430', + DBSDER_API_URL: 'http://localhost:3008', + DBSDER_API_KEY: '3d8767ff-ed2a-47bd-91c2-f5ebac712f2c', + DBSDER_API_VERSION: 'v1', + DBSDER_API_ENABLED: 'false', + NLP_PSEUDONYMISATION_API_URL: 'http://localhost:8081', + NLP_PSEUDONYMISATION_API_ENABLED: 'false', + JWT_PRIVATE_KEY: 'myPrivateKey', + SDER_DB_URL: 'http://127.0.0.1:55433', + SSO_SP_ENTITY_ID: 'SP-LABEL-DEV', + SSO_SP_ASSERTION_CONSUMER_SERVICE_LOCATION: + 'http://localhost:55430/label/api/sso/acs', + SSO_IDP_METADATA: 'src/test/idp_metadata.xml', + SSO_IDP_SINGLE_SIGN_ON_SERVICE_LOCATION: + 'http://test.sso.intranet.justice.gouv.fr:9000/saml/singleSignOn', + SSO_IDP_SINGLE_LOGOUT_SERVICE_LOCATION: + 'http://test.sso.intranet.justice.gouv.fr:9000/saml/singleLogout', + PUBLIC_URL: '', + SSO_ATTRIBUTE_ROLE: 'role', + SSO_APP_NAME: 'LABEL', + SSO_SP_PRIVATE_KEY: 'src/test/pk.txt', + SSO_CERTIFICAT: 'src/test/crt.txt', +}; diff --git a/packages/generic/backend/src/api/buildApi.ts b/packages/generic/backend/src/api/buildApi.ts index b883ccb58..185188195 100644 --- a/packages/generic/backend/src/api/buildApi.ts +++ b/packages/generic/backend/src/api/buildApi.ts @@ -189,8 +189,7 @@ function buildApiSso(app: Express) { const url = await ssoService.acs(req); res.redirect(url); } catch (err) { - res - .status(httpStatusCodeHandler.HTTP_STATUS_CODE.ERROR.SERVER_ERROR) + res.status(httpStatusCodeHandler.HTTP_STATUS_CODE.ERROR.SERVER_ERROR); res.redirect(`${API_BASE_URL}/sso/logout`); } }); diff --git a/packages/generic/backend/src/api/controllers.ts b/packages/generic/backend/src/api/controllers.ts index c668a43c2..19d69de39 100644 --- a/packages/generic/backend/src/api/controllers.ts +++ b/packages/generic/backend/src/api/controllers.ts @@ -1,4 +1,9 @@ -import { apiSchema, documentType, idModule, replacementTermType } from '@label/core'; +import { + apiSchema, + documentType, + idModule, + replacementTermType, +} from '@label/core'; import { errorHandlers } from 'sder-core'; import { settingsLoader } from '../lib/settingsLoader'; import { assignationService } from '../modules/assignation'; @@ -127,7 +132,10 @@ const controllers: controllersFromSchemaType = { documentsForUser: buildAuthenticatedController({ permissions: ['admin', 'annotator'], controllerWithUser: async (user, { args: { documentsMaxCount } }) => - documentService.fetchDocumentsForUser(idModule.lib.buildId(user._id), documentsMaxCount), + documentService.fetchDocumentsForUser( + idModule.lib.buildId(user._id), + documentsMaxCount, + ), }), async health() { @@ -266,7 +274,6 @@ const controllers: controllersFromSchemaType = { }, }), - problemReport: buildAuthenticatedController({ permissions: ['admin', 'annotator', 'scrutator'], controllerWithUser: async ( @@ -289,7 +296,6 @@ const controllers: controllersFromSchemaType = { }, }), - resetTreatmentLastUpdateDate: buildAuthenticatedController({ permissions: ['admin', 'annotator'], controllerWithUser: async (user, { args: { assignationId } }) => { @@ -297,7 +303,12 @@ const controllers: controllersFromSchemaType = { idModule.lib.buildId(assignationId), ); - if (!idModule.lib.equalId(idModule.lib.buildId(user._id), assignation.userId)) { + if ( + !idModule.lib.equalId( + idModule.lib.buildId(user._id), + assignation.userId, + ) + ) { throw errorHandlers.permissionErrorHandler.build( `User ${idModule.lib.convertToString( user._id, @@ -311,8 +322,6 @@ const controllers: controllersFromSchemaType = { }, }), - - updateAssignationDocumentStatus: buildAuthenticatedController({ permissions: ['admin'], controllerWithUser: async (_, { args: { assignationId, status } }) => { @@ -383,7 +392,12 @@ const controllers: controllersFromSchemaType = { idModule.lib.buildId(assignationId), ); - if (!idModule.lib.equalId(idModule.lib.buildId(user._id), assignation.userId)) { + if ( + !idModule.lib.equalId( + idModule.lib.buildId(user._id), + assignation.userId, + ) + ) { throw errorHandlers.permissionErrorHandler.build( `User ${idModule.lib.convertToString( user._id, @@ -406,7 +420,12 @@ const controllers: controllersFromSchemaType = { const assignation = await assignationService.fetchAssignation( idModule.lib.buildId(assignationId), ); - if (!idModule.lib.equalId(idModule.lib.buildId(user._id), assignation.userId)) { + if ( + !idModule.lib.equalId( + idModule.lib.buildId(user._id), + assignation.userId, + ) + ) { throw errorHandlers.permissionErrorHandler.build( `User ${idModule.lib.convertToString( user._id, diff --git a/packages/generic/backend/src/app/buildRunServer.ts b/packages/generic/backend/src/app/buildRunServer.ts index b034d2fb4..682dafa47 100644 --- a/packages/generic/backend/src/app/buildRunServer.ts +++ b/packages/generic/backend/src/app/buildRunServer.ts @@ -9,7 +9,6 @@ import { envSchema } from './envSchema'; import session from 'express-session'; - export { buildRunServer }; function buildRunServer(settings: settingsType) { diff --git a/packages/generic/backend/src/app/express-session.d.ts b/packages/generic/backend/src/express-session.d.ts similarity index 100% rename from packages/generic/backend/src/app/express-session.d.ts rename to packages/generic/backend/src/express-session.d.ts diff --git a/packages/generic/backend/src/modules/sso/service/express-session.d.ts b/packages/generic/backend/src/modules/sso/service/express-session.d.ts deleted file mode 100644 index d01414d7a..000000000 --- a/packages/generic/backend/src/modules/sso/service/express-session.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -import 'express-session'; -import session from 'express-session'; - -declare module 'express-session' { - interface SessionData { - user: { - _id: string; - name: string; - role: string; - email: string; - sessionIndex: string; - }; - } -} - -declare global { - namespace Express { - interface Request { - session: session.Session & Partial; - } - } -} diff --git a/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts b/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts index fa85ab467..cdadd2d31 100644 --- a/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts +++ b/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts @@ -130,20 +130,23 @@ describe('SSO CNX functions', () => { const userRepository = buildUserRepository(); jest .spyOn(userRepository, 'findByEmail') - .mockRejectedValue(new Error('No matching user for email newuser@example.com')); + .mockRejectedValue( + new Error('No matching user for email newuser@example.com'), + ); const userService = buildUserService(); jest .spyOn(userService, 'createUser') .mockResolvedValue('User created successfully'); - jest.spyOn(userRepository, 'findByEmail').mockReturnValue(mockNewUser as any); + jest + .spyOn(userRepository, 'findByEmail') + .mockReturnValue(mockNewUser as any); const result = await acs(mockReq); expect(result).toBeDefined(); }); - }); describe('setUserSessionAndReturnRedirectUrl', () => { diff --git a/packages/generic/backend/src/modules/sso/service/ssoService.ts b/packages/generic/backend/src/modules/sso/service/ssoService.ts index f023d7f2a..afa088136 100644 --- a/packages/generic/backend/src/modules/sso/service/ssoService.ts +++ b/packages/generic/backend/src/modules/sso/service/ssoService.ts @@ -1,8 +1,8 @@ import { SamlService } from '@label/sso'; import { buildUserRepository, userService } from '../../user'; import { logger } from '../../../utils'; -import every from "lodash/every"; -import includes from "lodash/includes"; +import every from 'lodash/every'; +import includes from 'lodash/includes'; export { samlService }; @@ -62,11 +62,17 @@ export async function acs(req: any) { // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access if (err.message.includes(`No matching user for email ${extract?.nameID}`)) { const { attributes } = extract as Record; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const roles = attributes[`${process.env.SSO_ATTRIBUTE_ROLE}`].map((item: string) => item.toLowerCase()) as string[]; - - const appRoles = (process.env.SSO_APP_ROLES as string).toLowerCase().split(','); - const userRolesInAppRoles = every(roles, (element) => includes(appRoles, element)); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call + const roles = attributes[ + `${process.env.SSO_ATTRIBUTE_ROLE}` + ].map((item: string) => item.toLowerCase()) as string[]; + + const appRoles = (process.env.SSO_APP_ROLES as string) + .toLowerCase() + .split(','); + const userRolesInAppRoles = every(roles, (element) => + includes(appRoles, element), + ); if (!roles.length || !userRolesInAppRoles) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access @@ -88,11 +94,7 @@ export async function acs(req: any) { }`, // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment email: attributes[`${process.env.SSO_ATTRIBUTE_MAIL}`], - role: roles[0] as - | 'annotator' - | 'scrutator' - | 'admin' - | 'publicator', + role: roles[0] as 'annotator' | 'scrutator' | 'admin' | 'publicator', }; await userService.createUser(newUser); diff --git a/packages/generic/backend/src/modules/user/repository/buildFakeUserRepository.ts b/packages/generic/backend/src/modules/user/repository/buildFakeUserRepository.ts index c32310469..d4fc55683 100644 --- a/packages/generic/backend/src/modules/user/repository/buildFakeUserRepository.ts +++ b/packages/generic/backend/src/modules/user/repository/buildFakeUserRepository.ts @@ -1,7 +1,5 @@ import { userModule, userType } from '@label/core'; -import { - buildFakeRepositoryBuilder, -} from '../../../repository'; +import { buildFakeRepositoryBuilder } from '../../../repository'; import { customUserRepositoryType } from './customUserRepositoryType'; export { buildFakeUserRepository }; diff --git a/packages/generic/backend/src/modules/user/service/userService/fetchWorkingUsers.ts b/packages/generic/backend/src/modules/user/service/userService/fetchWorkingUsers.ts index 869ddd1e0..5bad054e1 100644 --- a/packages/generic/backend/src/modules/user/service/userService/fetchWorkingUsers.ts +++ b/packages/generic/backend/src/modules/user/service/userService/fetchWorkingUsers.ts @@ -10,7 +10,7 @@ async function fetchWorkingUsers() { throw new Error('No users found'); } - return users.map(user => { + return users.map((user) => { const { _id, email, name, role } = user; return { _id, @@ -20,4 +20,3 @@ async function fetchWorkingUsers() { }; }); } - diff --git a/packages/generic/backend/src/modules/user/service/userService/index.ts b/packages/generic/backend/src/modules/user/service/userService/index.ts index 4f87ff781..1942cfca0 100644 --- a/packages/generic/backend/src/modules/user/service/userService/index.ts +++ b/packages/generic/backend/src/modules/user/service/userService/index.ts @@ -1,14 +1,14 @@ -import {buildCallAttemptsRegulator} from 'sder-core'; -import {createUser} from './createUser'; -import {fetchAuthenticatedUserFromAuthorizationHeader} from './fetchAuthenticatedUserFromAuthorizationHeader'; -import {fetchUserRole} from './fetchUserRole'; -import {fetchUsers} from './fetchUsers'; -import {fetchUsersByAssignations} from './fetchUsersByAssignations'; -import {fetchUsersByIds} from './fetchUsersByIds'; -import {fetchWorkingUsers} from './fetchWorkingUsers'; -import {signUpUser} from './signUpUser'; +import { buildCallAttemptsRegulator } from 'sder-core'; +import { createUser } from './createUser'; +import { fetchAuthenticatedUserFromAuthorizationHeader } from './fetchAuthenticatedUserFromAuthorizationHeader'; +import { fetchUserRole } from './fetchUserRole'; +import { fetchUsers } from './fetchUsers'; +import { fetchUsersByAssignations } from './fetchUsersByAssignations'; +import { fetchUsersByIds } from './fetchUsersByIds'; +import { fetchWorkingUsers } from './fetchWorkingUsers'; +import { signUpUser } from './signUpUser'; -export {userService, buildUserService}; +export { userService, buildUserService }; const DELAY_BETWEEN_LOGIN_ATTEMPTS_IN_SECONDS = 1 * 1000; @@ -17,18 +17,18 @@ const MAX_LOGIN_ATTEMPTS = 1; const userService = buildUserService(); function buildUserService() { - buildCallAttemptsRegulator( - MAX_LOGIN_ATTEMPTS, - DELAY_BETWEEN_LOGIN_ATTEMPTS_IN_SECONDS - ); - return { - createUser, - fetchAuthenticatedUserFromAuthorizationHeader, - fetchUsers, - fetchUsersByIds, - fetchUsersByAssignations, - fetchWorkingUsers, - fetchUserRole, - signUpUser, - }; + buildCallAttemptsRegulator( + MAX_LOGIN_ATTEMPTS, + DELAY_BETWEEN_LOGIN_ATTEMPTS_IN_SECONDS, + ); + return { + createUser, + fetchAuthenticatedUserFromAuthorizationHeader, + fetchUsers, + fetchUsersByIds, + fetchUsersByAssignations, + fetchWorkingUsers, + fetchUserRole, + signUpUser, + }; } diff --git a/packages/generic/client/src/components/business/MainHeader/SettingsDrawer.tsx b/packages/generic/client/src/components/business/MainHeader/SettingsDrawer.tsx index beadb40b8..b2b5f84e8 100644 --- a/packages/generic/client/src/components/business/MainHeader/SettingsDrawer.tsx +++ b/packages/generic/client/src/components/business/MainHeader/SettingsDrawer.tsx @@ -1,69 +1,66 @@ import React from 'react'; -import {useHistory} from 'react-router-dom'; -import {useDisplayMode, ButtonWithIcon, Drawer, RadioButton, Text} from 'pelta-design-system'; -import {localStorage} from '../../../services/localStorage'; -import {wordings} from '../../../wordings'; -import {SettingsSection} from './SettingsSection'; -import {useCtxUser} from "../../../contexts/user.context"; -import {urlHandler} from "../../../utils"; +import { useDisplayMode, ButtonWithIcon, Drawer, RadioButton, Text } from 'pelta-design-system'; +import { localStorage } from '../../../services/localStorage'; +import { wordings } from '../../../wordings'; +import { SettingsSection } from './SettingsSection'; +import { useCtxUser } from '../../../contexts/user.context'; +import { urlHandler } from '../../../utils'; -export {SettingsDrawer}; +export { SettingsDrawer }; -function SettingsDrawer(props: { close: () => void; isOpen: boolean }) { - const {displayMode, setDisplayMode} = useDisplayMode(); - const styles = buildStyles(); - const history = useHistory(); - const {user, loading} = useCtxUser(); +function SettingsDrawer(props: { close: () => void; isOpen: boolean }) { + const { displayMode, setDisplayMode } = useDisplayMode(); + const styles = buildStyles(); + const { user, loading } = useCtxUser(); - if (loading) { - return
Loading...
; - } - const userEmail = user?.email; - const userName = user?.name; + if (loading) { + return
Loading...
; + } + const userEmail = user?.email; + const userName = user?.name; - return ( - -
- -
- - {userName} - - {userEmail} -
- -
- } - title={wordings.business.account} - /> - - setDisplayMode('lightMode')} - /> - setDisplayMode('darkMode')} - /> - - } - title={wordings.shared.settingsDrawer.displayMode} - /> + return ( + +
+ +
+ + {userName} + + {userEmail} +
+
-
- ); + } + title={wordings.business.account} + /> + + setDisplayMode('lightMode')} + /> + setDisplayMode('darkMode')} + /> + + } + title={wordings.shared.settingsDrawer.displayMode} + /> + +
+ ); - function logout() { - localStorage.adminViewHandler.remove(); - window.location.replace(urlHandler.getSsoLogoutUrl()); - } + function logout() { + localStorage.adminViewHandler.remove(); + window.location.replace(urlHandler.getSsoLogoutUrl()); + } function buildStyles() { return { diff --git a/packages/generic/client/src/contexts/user.context.tsx b/packages/generic/client/src/contexts/user.context.tsx index 7e03062f6..a14be886a 100644 --- a/packages/generic/client/src/contexts/user.context.tsx +++ b/packages/generic/client/src/contexts/user.context.tsx @@ -15,6 +15,7 @@ interface UserContextType { } const UserContext = createContext(null); +// eslint-disable-next-line react/prop-types export const UserProvider: FC<{ children: ReactNode }> = ({ children }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); @@ -51,11 +52,9 @@ async function whoami() { mode: 'cors', }); - console.warn('#################### ',JSON.stringify(await response), ' #################### '); if (!response.ok) { return null; } - console.warn('**************** ', JSON.stringify(await response), ' ****************'); // eslint-disable-next-line @typescript-eslint/no-unsafe-return return await response.json(); } catch (error) { diff --git a/packages/generic/client/src/pages/Admin/UntreatedDocuments/UntreatedDocumentsTable.tsx b/packages/generic/client/src/pages/Admin/UntreatedDocuments/UntreatedDocumentsTable.tsx index 41aef48a0..a64742d07 100644 --- a/packages/generic/client/src/pages/Admin/UntreatedDocuments/UntreatedDocumentsTable.tsx +++ b/packages/generic/client/src/pages/Admin/UntreatedDocuments/UntreatedDocumentsTable.tsx @@ -138,7 +138,7 @@ function UntreatedDocumentsTable(props: { async function onConfirmUpdateDocumentStatus(documentIdToUpdateStatus: documentType['_id']) { setDocumentIdToUpdateStatus(undefined); - const userId = (user?._id as unknown) as any; + const userId = (user?._id as unknown) as userType['_id']; if (!userId) { displayAlert({ text: wordings.business.errors.noUserIdFound, variant: 'alert', autoHide: true }); return; diff --git a/packages/generic/client/src/services/localStorage/userHandler.ts b/packages/generic/client/src/services/localStorage/userHandler.ts index 1066baa23..e66339194 100644 --- a/packages/generic/client/src/services/localStorage/userHandler.ts +++ b/packages/generic/client/src/services/localStorage/userHandler.ts @@ -25,7 +25,6 @@ function set({ _id, email, name, role }: Pick; diff --git a/packages/generic/sso/jest.config.json b/packages/generic/sso/jest.config.json index c56e60304..4438bafb9 100644 --- a/packages/generic/sso/jest.config.json +++ b/packages/generic/sso/jest.config.json @@ -14,7 +14,5 @@ ], "coverageDirectory": "../coverage", "testEnvironment": "node", - "setupFiles": [ - "/.jest/setupEnvVars.ts" - ] + "setupFiles": [] } \ No newline at end of file diff --git a/packages/generic/sso/src/.jest/setupEnvVars.ts b/packages/generic/sso/src/.jest/setupEnvVars.ts deleted file mode 100644 index 3fdf6a487..000000000 --- a/packages/generic/sso/src/.jest/setupEnvVars.ts +++ /dev/null @@ -1,11 +0,0 @@ -// @ts-ignore -process.env = { - SSO_SP_ENTITY_ID: 'SP-LABEL-DEV', - SSO_SP_ASSERTION_CONSUMER_SERVICE_LOCATION: 'http://localhost:3005/saml/acs', - SSO_IDP_METADATA: 'sso_idp_metadata.xml', - SSO_IDP_SINGLE_SIGN_ON_SERVICE_LOCATION : 'http://localhost:8080/realms/nouveau-realm/protocol/saml', - SSO_IDP_SINGLE_LOGOUT_SERVICE_LOCATION : 'http://localhost:8080/realms/nouveau-realm/protocol/saml', - PUBLIC_URL: '', - SSO_ATTRIBUTE_ROLE: 'role', - SSO_APP_NAME: 'LABEL' -} diff --git a/packages/generic/sso/src/api/saml/saml.service.spec.ts b/packages/generic/sso/src/api/saml/saml.service.spec.ts index d45375ee6..521af5f9f 100644 --- a/packages/generic/sso/src/api/saml/saml.service.spec.ts +++ b/packages/generic/sso/src/api/saml/saml.service.spec.ts @@ -67,6 +67,8 @@ describe('SamlService', () => { const metadata = service.generateMetadata(); expect(metadata).toEqual(''); expect(samlify.ServiceProvider).toHaveBeenCalled(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + expect(validator.validate()).toBeTruthy(); }); it('should create login request URL', async () => { diff --git a/yarn.lock b/yarn.lock index 6d648e1ba..aceef0d7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,24 @@ # yarn lockfile v1 +"@authenio/samlify-node-xmllint@^1.0.1": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@authenio/samlify-node-xmllint/-/samlify-node-xmllint-1.0.2.tgz#2175cdc123257c569739b4ba3765b994a81a83dd" + integrity sha512-+NJroTmkuGWQLiHp5X+CL0DMO36cot8aw9A8d6luFFpylfNVHlKFjFfrp4YW7C3Zgvh7biIRmQE1Pnq/1jOG/Q== + dependencies: + node-xmllint "^1.0.0" + +"@authenio/xml-encryption@^0.11.3": + version "0.11.3" + resolved "https://registry.yarnpkg.com/@authenio/xml-encryption/-/xml-encryption-0.11.3.tgz#35379a8a87bbed3f34907a0359e8e1c30ce24bdd" + integrity sha512-zFLGBw/iNmLq5Udg+OJfsuERsTjcfvWtP6ZzL55EVwGu3bYsxuq1ARRNfNLHCXv4cNP49o8nLwpgA6VmXzRp/w== + dependencies: + async "^2.1.5" + ejs "^2.5.6" + node-forge "^0.7.4" + xmldom "~0.1.15" + xpath "0.0.27" + "@babel/cli@^7.11.6": version "7.11.6" resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.11.6.tgz#1fcbe61c2a6900c3539c06ee58901141f3558482" @@ -46,6 +64,14 @@ dependencies: "@babel/highlight" "^7.16.7" +"@babel/code-frame@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7" + integrity sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g== + dependencies: + "@babel/highlight" "^7.25.7" + picocolors "^1.0.0" + "@babel/compat-data@^7.10.4", "@babel/compat-data@^7.11.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.11.0.tgz#e9f73efe09af1355b723a7f39b11bad637d7c99c" @@ -60,6 +86,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.7": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.8.tgz#0376e83df5ab0eb0da18885c0140041f0747a402" + integrity sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA== + "@babel/core@7.9.0": version "7.9.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e" @@ -152,6 +183,16 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.7.tgz#de86acbeb975a3e11ee92dd52223e6b03b479c56" + integrity sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA== + dependencies: + "@babel/types" "^7.25.7" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz#5bf0d495a3f757ac3bda48b5bf3b3ba309c72ba3" @@ -173,6 +214,13 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-annotate-as-pure@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.7.tgz#63f02dbfa1f7cb75a9bdb832f300582f30bb8972" + integrity sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA== + dependencies: + "@babel/types" "^7.25.7" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz#bb0b75f31bf98cbf9ff143c1ae578b87274ae1a3" @@ -210,6 +258,17 @@ browserslist "^4.17.5" semver "^6.3.0" +"@babel/helper-compilation-targets@^7.22.6": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz#11260ac3322dda0ef53edfae6e97b961449f5fa4" + integrity sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A== + dependencies: + "@babel/compat-data" "^7.25.7" + "@babel/helper-validator-option" "^7.25.7" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.10.4", "@babel/helper-create-class-features-plugin@^7.10.5": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz#9f61446ba80e8240b0a5c85c6fdac8459d6f259d" @@ -235,6 +294,19 @@ "@babel/helper-replace-supers" "^7.16.5" "@babel/helper-split-export-declaration" "^7.16.0" +"@babel/helper-create-class-features-plugin@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.7.tgz#5d65074c76cae75607421c00d6bd517fe1892d6b" + integrity sha512-bD4WQhbkx80mAyj/WCm4ZHcF4rDxkoLFO6ph8/5/mQ3z4vAzltQXAmbc7GvVJx5H+lk5Mi5EmbTeox5nMGCsbw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.7" + "@babel/helper-member-expression-to-functions" "^7.25.7" + "@babel/helper-optimise-call-expression" "^7.25.7" + "@babel/helper-replace-supers" "^7.25.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.7" + "@babel/traverse" "^7.25.7" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz#fdd60d88524659a0b6959c0579925e425714f3b8" @@ -275,6 +347,17 @@ resolve "^1.14.2" semver "^6.1.2" +"@babel/helper-define-polyfill-provider@^0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d" + integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + "@babel/helper-environment-visitor@^7.16.5": version "7.16.5" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz#f6a7f38b3c6d8b07c88faea083c46c09ef5451b8" @@ -386,6 +469,14 @@ dependencies: "@babel/types" "^7.16.0" +"@babel/helper-member-expression-to-functions@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.7.tgz#541a33b071f0355a63a0fa4bdf9ac360116b8574" + integrity sha512-O31Ssjd5K6lPbTX9AAYpSKrZmLeagt9uwschJd+Ixo6QiRyfpvgtVQp8qrDR9UNFjZ8+DO34ZkdrN+BnPXemeA== + dependencies: + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" + "@babel/helper-module-imports@^7.0.0": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" @@ -414,6 +505,14 @@ dependencies: "@babel/types" "^7.22.15" +"@babel/helper-module-imports@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz#dba00d9523539152906ba49263e36d7261040472" + integrity sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw== + dependencies: + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" + "@babel/helper-module-transforms@^7.10.4", "@babel/helper-module-transforms@^7.10.5", "@babel/helper-module-transforms@^7.11.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz#b16f250229e47211abdd84b34b64737c2ab2d359" @@ -455,6 +554,13 @@ dependencies: "@babel/types" "^7.16.0" +"@babel/helper-optimise-call-expression@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.7.tgz#1de1b99688e987af723eed44fa7fc0ee7b97d77a" + integrity sha512-VAwcwuYhv/AT+Vfr28c9y6SHzTan1ryqrydSTFGjU0uDJHw3uZ+PduI8plCLkRsDnqK2DMEDmwrOQRsK/Ykjng== + dependencies: + "@babel/types" "^7.25.7" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" @@ -470,6 +576,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== +"@babel/helper-plugin-utils@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.7.tgz#8ec5b21812d992e1ef88a9b068260537b6f0e36c" + integrity sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw== + "@babel/helper-regex@^7.10.4": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.10.5.tgz#32dfbb79899073c415557053a19bd055aae50ae0" @@ -517,6 +628,15 @@ "@babel/traverse" "^7.16.5" "@babel/types" "^7.16.0" +"@babel/helper-replace-supers@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.7.tgz#38cfda3b6e990879c71d08d0fef9236b62bd75f5" + integrity sha512-iy8JhqlUW9PtZkd4pHM96v6BdJ66Ba9yWSE4z0W4TvSZwLBPkyDsiIU3ENe4SmrzRBs76F7rQXTy1lYC49n6Lw== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.25.7" + "@babel/helper-optimise-call-expression" "^7.25.7" + "@babel/traverse" "^7.25.7" + "@babel/helper-simple-access@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz#0f5ccda2945277a2a7a2d3a821e15395edcf3461" @@ -546,6 +666,14 @@ dependencies: "@babel/types" "^7.16.0" +"@babel/helper-skip-transparent-expression-wrappers@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.7.tgz#382831c91038b1a6d32643f5f49505b8442cb87c" + integrity sha512-pPbNbchZBkPMD50K0p3JGcFMNLVUCuU/ABybm/PGNj4JiHrpmNyqqCphBk4i19xXtNV0JhldQJJtbSW5aUvbyA== + dependencies: + "@babel/traverse" "^7.25.7" + "@babel/types" "^7.25.7" + "@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0": version "7.11.0" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" @@ -572,6 +700,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== +"@babel/helper-string-parser@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54" + integrity sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g== + "@babel/helper-validator-identifier@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" @@ -592,11 +725,21 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== +"@babel/helper-validator-identifier@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5" + integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg== + "@babel/helper-validator-option@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== +"@babel/helper-validator-option@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz#97d1d684448228b30b506d90cace495d6f492729" + integrity sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ== + "@babel/helper-wrap-function@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87" @@ -662,6 +805,16 @@ chalk "^2.0.0" js-tokens "^4.0.0" +"@babel/highlight@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.7.tgz#20383b5f442aa606e7b5e3043b0b1aafe9f37de5" + integrity sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw== + dependencies: + "@babel/helper-validator-identifier" "^7.25.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.5", "@babel/parser@^7.4.3", "@babel/parser@^7.7.0": version "7.11.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037" @@ -677,6 +830,13 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.10.tgz#aba1b1cb9696a24a19f59c41af9cf17d1c716a88" integrity sha512-Sm/S9Or6nN8uiFsQU1yodyDW3MWXQhFeqzMPM+t8MJjM+pLsnFVxFZzkpXKvUXh+Gz9cbMoYYs484+Jw/NTEFQ== +"@babel/parser@^7.25.7": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.8.tgz#f6aaf38e80c36129460c1657c0762db584c9d5e2" + integrity sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ== + dependencies: + "@babel/types" "^7.25.8" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.2": version "7.16.2" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.2.tgz#2977fca9b212db153c195674e57cfab807733183" @@ -753,6 +913,15 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-syntax-decorators" "^7.8.3" +"@babel/plugin-proposal-decorators@^7.10.5": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.7.tgz#dabfd82df5dff3a8fc61a434233bf8227c88402c" + integrity sha512-q1mqqqH0e1lhmsEQHV5U8OmdueBC2y0RFr2oUzZoFRtN3MvPmt2fsFRcNQAoGLTSNdHBFUYGnlgcRFhkBbKjPw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/plugin-syntax-decorators" "^7.25.7" + "@babel/plugin-proposal-dynamic-import@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz#ba57a26cb98b37741e9d5bca1b8b0ddf8291f17e" @@ -997,6 +1166,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" +"@babel/plugin-syntax-decorators@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.7.tgz#cf26fdde4e750688e133c0e33ead2506377e88f7" + integrity sha512-oXduHo642ZhstLVYTe2z2GSJIruU0c/W3/Ghr6A5yGMsVrvdnxO1z+3pbTcT7f3/Clnt+1z8D/w1r1f1SHaCHw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.7" + "@babel/plugin-syntax-decorators@^7.8.3": version "7.16.5" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.16.5.tgz#8d397dee482716a79f1a22314f0b4770a5b67427" @@ -1598,6 +1774,18 @@ resolve "^1.8.1" semver "^5.5.1" +"@babel/plugin-transform-runtime@^7.10.4": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.7.tgz#435a4fab67273f00047dc806e05069c9c6344e12" + integrity sha512-Y9p487tyTzB0yDYQOtWnC+9HGOuogtP3/wNpun1xJXEEvI6vip59BSBTsHnekZLqxmPcgsrAKt46HAAb//xGhg== + dependencies: + "@babel/helper-module-imports" "^7.25.7" + "@babel/helper-plugin-utils" "^7.25.7" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.6" + babel-plugin-polyfill-regenerator "^0.6.1" + semver "^6.3.1" + "@babel/plugin-transform-shorthand-properties@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz#9fd25ec5cdd555bb7f473e5e6ee1c971eede4dd6" @@ -2074,6 +2262,15 @@ "@babel/parser" "^7.16.7" "@babel/types" "^7.16.7" +"@babel/template@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.7.tgz#27f69ce382855d915b14ab0fe5fb4cbf88fa0769" + integrity sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA== + dependencies: + "@babel/code-frame" "^7.25.7" + "@babel/parser" "^7.25.7" + "@babel/types" "^7.25.7" + "@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.11.5", "@babel/traverse@^7.4.3", "@babel/traverse@^7.7.0": version "7.11.5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3" @@ -2105,6 +2302,19 @@ debug "^4.1.0" globals "^11.1.0" +"@babel/traverse@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.7.tgz#83e367619be1cab8e4f2892ef30ba04c26a40fa8" + integrity sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg== + dependencies: + "@babel/code-frame" "^7.25.7" + "@babel/generator" "^7.25.7" + "@babel/parser" "^7.25.7" + "@babel/template" "^7.25.7" + "@babel/types" "^7.25.7" + debug "^4.3.1" + globals "^11.1.0" + "@babel/traverse@^7.4.5": version "7.16.10" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.10.tgz#448f940defbe95b5a8029975b051f75993e8239f" @@ -2155,6 +2365,15 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.25.7", "@babel/types@^7.25.8": + version "7.25.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.8.tgz#5cf6037258e8a9bcad533f4979025140cb9993e1" + integrity sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg== + dependencies: + "@babel/helper-string-parser" "^7.25.7" + "@babel/helper-validator-identifier" "^7.25.7" + to-fast-properties "^2.0.0" + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -2424,6 +2643,13 @@ source-map "^0.6.0" string-length "^2.0.0" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/source-map@^24.3.0", "@jest/source-map@^24.9.0": version "24.9.0" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-24.9.0.tgz#0e263a94430be4b41da683ccc1e6bffe2a191714" @@ -2504,6 +2730,50 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@lerna/add@3.21.0": version "3.21.0" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.21.0.tgz#27007bde71cc7b0a2969ab3c2f0ae41578b4577b" @@ -3334,6 +3604,58 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@nestjs/common@^8.4.7": + version "8.4.7" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-8.4.7.tgz#fc4a575b797e230bb5a0bcab6da8b796aa88d605" + integrity sha512-m/YsbcBal+gA5CFrDpqXqsSfylo+DIQrkFY3qhVIltsYRfu8ct8J9pqsTO6OPf3mvqdOpFGrV5sBjoyAzOBvsw== + dependencies: + axios "0.27.2" + iterare "1.2.1" + tslib "2.4.0" + uuid "8.3.2" + +"@nestjs/config@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-0.5.0.tgz#ad1110d937ec26941b8fce5f575859046f8d7b4b" + integrity sha512-8vgakV722qNt3YBHbvhfwcNRLlwOv37pr5/1RUn5mYMmUh7JKobVNqHgTRWEuJK4BK3dCrhnjxPMPvHOCaA4LQ== + dependencies: + dotenv "8.2.0" + dotenv-expand "5.1.0" + lodash.get "4.4.2" + lodash.set "4.3.2" + uuid "8.1.0" + +"@nestjs/core@8.4.7": + version "8.4.7" + resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-8.4.7.tgz#fbec7fa744ac8749a4b966f759a6656c1cf43883" + integrity sha512-XB9uexHqzr2xkPo6QSiQWJJttyYYLmvQ5My64cFvWFi7Wk2NIus0/xUNInwX3kmFWB6pF1ab5Y2ZBvWdPwGBhw== + dependencies: + "@nuxtjs/opencollective" "0.3.2" + fast-safe-stringify "2.1.1" + iterare "1.2.1" + object-hash "3.0.0" + path-to-regexp "3.2.0" + tslib "2.4.0" + uuid "8.3.2" + +"@nestjs/platform-express@7.6.18": + version "7.6.18" + resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-7.6.18.tgz#cdf442dfd85948fc7b67bbc4007dddef83cdd4b9" + integrity sha512-Dty2bBhsW7EInMRPS1pkXKJ3GBBusEj6fnEpb0UfkaT3E7asay9c64kCmZE+8hU430qQjY+fhBb1RNWWPnUiwQ== + dependencies: + body-parser "1.19.0" + cors "2.8.5" + express "4.17.1" + multer "1.4.2" + tslib "2.2.0" + +"@nestjs/testing@^8.4.7": + version "8.4.7" + resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-8.4.7.tgz#fe4f356c0e081e25fe8c899a65e91dd88947fd13" + integrity sha512-aedpeJFicTBeiTCvJWUG45WMMS53f5eu8t2fXsfjsU1t+WdDJqYcZyrlCzA4dL1B7MfbqaTURdvuVVHTmJO8ag== + dependencies: + tslib "2.4.0" + "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" @@ -3376,6 +3698,15 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@nuxtjs/opencollective@0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz#620ce1044f7ac77185e825e1936115bb38e2681c" + integrity sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA== + dependencies: + chalk "^4.1.0" + consola "^2.15.0" + node-fetch "^2.6.1" + "@octokit/auth-token@^2.4.0": version "2.4.2" resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.2.tgz#10d0ae979b100fa6b72fa0e8e63e27e6d0dbff8a" @@ -3507,6 +3838,11 @@ resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -3785,6 +4121,13 @@ "@types/qs" "*" "@types/range-parser" "*" +"@types/express-session@^1.18.0": + version "1.18.0" + resolved "https://registry.yarnpkg.com/@types/express-session/-/express-session-1.18.0.tgz#7c6f25c3604b28d6bc08a2e3929997bbc7672fa2" + integrity sha512-27JdDRgor6PoYlURY+Y5kCakqp5ulC0kmf7y+QwaY+hv9jEFuQOThgkjyA53RP3jmKuBsH5GR6qEfFmvb8mwOA== + dependencies: + "@types/express" "*" + "@types/express@*", "@types/express@^4.17.8": version "4.17.8" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.8.tgz#3df4293293317e61c60137d273a2e96cd8d5f27a" @@ -4062,6 +4405,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yargs@^17.0.8": + version "17.0.33" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" + integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== + dependencies: + "@types/yargs-parser" "*" + "@typescript-eslint/eslint-plugin@3.9.1", "@typescript-eslint/eslint-plugin@^2.10.0", "@typescript-eslint/eslint-plugin@^4.4.1": version "4.4.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.4.1.tgz#b8acea0373bd2a388ac47df44652f00bf8b368f5" @@ -4278,6 +4628,11 @@ "@webassemblyjs/wast-parser" "1.8.5" "@xtuc/long" "4.2.2" +"@xmldom/xmldom@^0.7.0": + version "0.7.13" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.13.tgz#ff34942667a4e19a9f4a0996a76814daac364cf3" + integrity sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g== + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -4560,6 +4915,11 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +append-field@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" + integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw== + aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -4766,6 +5126,13 @@ asn1.js@^5.2.0: minimalistic-assert "^1.0.0" safer-buffer "^2.1.0" +asn1@^0.2.4: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -4823,6 +5190,13 @@ async@^1.3.0: resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= +async@^2.1.5: + version "2.6.4" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + async@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -4830,6 +5204,11 @@ async@^2.6.2: dependencies: lodash "^4.17.14" +async@^3.2.3: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -4875,6 +5254,14 @@ axios@*: dependencies: follow-redirects "^1.10.0" +axios@0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + axios@^0.24.0: version "0.24.0" resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" @@ -4986,6 +5373,23 @@ babel-plugin-polyfill-corejs2@^0.3.0: "@babel/helper-define-polyfill-provider" "^0.3.0" semver "^6.1.1" +babel-plugin-polyfill-corejs2@^0.4.10: + version "0.4.11" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz#30320dfe3ffe1a336c15afdcdafd6fd615b25e33" + integrity sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.6.2" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.10.6: + version "0.10.6" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz#2deda57caef50f59c525aeb4964d3b2f867710c7" + integrity sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.2" + core-js-compat "^3.38.0" + babel-plugin-polyfill-corejs3@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.4.0.tgz#0b571f4cf3d67f911512f5c04842a7b8e8263087" @@ -5001,6 +5405,13 @@ babel-plugin-polyfill-regenerator@^0.3.0: dependencies: "@babel/helper-define-polyfill-provider" "^0.3.0" +babel-plugin-polyfill-regenerator@^0.6.1: + version "0.6.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz#addc47e240edd1da1058ebda03021f382bba785e" + integrity sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.2" + "babel-plugin-styled-components@>= 1": version "2.1.4" resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz#9a1f37c7f32ef927b4b008b529feb4a2c82b1092" @@ -5244,6 +5655,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^2.3.1, braces@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" @@ -5376,6 +5794,23 @@ browserslist@^4.17.5, browserslist@^4.18.1, browserslist@^4.9.1: node-releases "^2.0.1" picocolors "^1.0.0" +browserslist@^4.23.3, browserslist@^4.24.0: + version "4.24.0" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.0.tgz#a1325fe4bc80b64fda169629fc01b3d6cecd38d4" + integrity sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A== + dependencies: + caniuse-lite "^1.0.30001663" + electron-to-chromium "^1.5.28" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" + +bs-logger@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" + integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== + dependencies: + fast-json-stable-stringify "2.x" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -5432,6 +5867,14 @@ builtins@^1.0.3: resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og= +busboy@^0.2.11: + version "0.2.14" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" + integrity sha512-InWFDomvlkEj+xWLBfU3AvnbVYqeTWmQopiW0tWWEy5yehYm2YkGEc59sUmw/4ty5Zj/b0WHGs1LgecuBSBGrg== + dependencies: + dicer "0.2.5" + readable-stream "1.1.x" + byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" @@ -5665,6 +6108,11 @@ caniuse-lite@^1.0.30001286: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001286.tgz#3e9debad420419618cfdf52dc9b6572b28a8fff6" integrity sha512-zaEMRH6xg8ESMi2eQ3R4eZ5qw/hJiVsO/HlLwniIwErij0JDr9P+8V4dtx1l+kLq6j3yy8l8W4fst1lBnat5wQ== +caniuse-lite@^1.0.30001663: + version "1.0.30001669" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz#fda8f1d29a8bfdc42de0c170d7f34a9cf19ed7a3" + integrity sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w== + capture-exit@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" @@ -5718,6 +6166,14 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" @@ -5794,6 +6250,11 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +ci-info@^3.2.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== + cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -5993,7 +6454,7 @@ columnify@^1.5.4: strip-ansi "^3.0.0" wcwidth "^1.0.0" -combined-stream@^1.0.6, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -6072,7 +6533,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0: +concat-stream@^1.5.0, concat-stream@^1.5.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -6132,6 +6593,11 @@ connect@^3.6.2: parseurl "~1.3.3" utils-merge "1.0.1" +consola@^2.15.0: + version "2.15.3" + resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" + integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== + console-browserify@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" @@ -6264,11 +6730,21 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= +cookie-signature@1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.7.tgz#ab5dd7ab757c54e60f37ef6550f481c426d10454" + integrity sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA== + cookie@0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + cookiejar@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" @@ -6299,6 +6775,13 @@ core-js-compat@^3.18.0, core-js-compat@^3.19.1: browserslist "^4.18.1" semver "7.0.0" +core-js-compat@^3.38.0: + version "3.38.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.38.1.tgz#2bc7a298746ca5a7bcb9c164bcb120f2ebc09a09" + integrity sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw== + dependencies: + browserslist "^4.23.3" + core-js-compat@^3.6.2: version "3.6.5" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.5.tgz#2a51d9a4e25dfd6e690251aa81f99e3c05481f1c" @@ -6337,7 +6820,7 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cors@^2.8.5: +cors@2.8.5, cors@^2.8.5: version "2.8.5" resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== @@ -6923,6 +7406,11 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" +deflate-js@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/deflate-js/-/deflate-js-0.2.3.tgz#f85abb58ebc5151a306147473d57c3e4f7e4426b" + integrity sha512-r5KgHJ/yTiWQs23nVeQz5dSL/kmW0MBszsssNyEqDCjjFDj4XG/c6QUN/I0JtY3ZHwwcaNBtGE8s+oV33acTfQ== + del@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" @@ -7010,6 +7498,14 @@ dezalgo@^1.0.0: asap "^2.0.0" wrappy "1" +dicer@0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" + integrity sha512-FDvbtnq7dzlPz0wyYlOExifDEZcu8h+rErEXgfxqmLfRfC/kJidEFh4+effJRO3P0xmfqyPbSMG0LveNRfTKVg== + dependencies: + readable-stream "1.1.x" + streamsearch "0.1.2" + diff-sequences@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5" @@ -7248,6 +7744,18 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +ejs@^2.5.6: + version "2.7.4" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba" + integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA== + +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.562: version "1.3.564" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.564.tgz#e9c319ae437b3eb8bbf3e3bae4bead5a21945961" @@ -7258,6 +7766,11 @@ electron-to-chromium@^1.4.17: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.17.tgz#16ec40f61005582d5d41fac08400a254dccfb85f" integrity sha512-zhk1MravPtq/KBhmGB7TLBILmXTgRG9TFSI3qS3DbgyfHzIl72iiTE37r/BHIbPCJJlWIo5rySyxiH4vWhu2ZA== +electron-to-chromium@^1.5.28: + version "1.5.41" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz#eae1ba6c49a1a61d84cf8263351d3513b2bcc534" + integrity sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ== + elliptic@^6.5.3: version "6.5.3" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" @@ -7488,6 +8001,11 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + escape-goat@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" @@ -7944,7 +8462,21 @@ expect@^24.9.0: jest-message-util "^24.9.0" jest-regex-util "^24.9.0" -express@^4.17.1: +express-session@^1.18.0: + version "1.18.1" + resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.18.1.tgz#88d0bbd41878882840f24ec6227493fcb167e8d5" + integrity sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA== + dependencies: + cookie "0.7.2" + 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" + +express@4.17.1, express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== @@ -8074,7 +8606,7 @@ fast-glob@^3.1.1: micromatch "^4.0.2" picomatch "^2.2.1" -fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -8084,6 +8616,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-safe-stringify@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== + fast-url-parser@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" @@ -8170,6 +8707,13 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + filesize@6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/filesize/-/filesize-6.0.1.tgz#f850b509909c7c86f7e450ea19006c31c2ed3d2f" @@ -8309,6 +8853,11 @@ follow-redirects@^1.14.4: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381" integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA== +follow-redirects@^1.14.9: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + for-in@^0.1.3: version "0.1.8" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" @@ -8354,6 +8903,15 @@ form-data@^2.3.1: combined-stream "^1.0.6" mime-types "^2.1.12" +form-data@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" + integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -8824,6 +9382,11 @@ graceful-fs@^4.2.6: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== +graceful-fs@^4.2.9: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" @@ -10083,6 +10646,21 @@ istanbul-reports@^2.2.6: dependencies: html-escaper "^2.0.0" +iterare@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" + integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== + +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + jest-changed-files@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039" @@ -10423,6 +11001,18 @@ jest-util@^24.0.0, jest-util@^24.9.0: slash "^2.0.0" source-map "^0.6.0" +jest-util@^29.0.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.7.0.tgz#23c2b62bfb22be82b44de98055802ff3710fc0bc" + integrity sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA== + dependencies: + "@jest/types" "^29.6.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + jest-validate@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-24.9.0.tgz#0775c55360d173cd854e40180756d4ff52def8ab" @@ -10597,6 +11187,11 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== +jsesc@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== + jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" @@ -10663,6 +11258,11 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -11134,7 +11734,7 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= -lodash.get@^4.4.2: +lodash.get@4.4.2, lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= @@ -11215,7 +11815,7 @@ lodash.once@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash.set@^4.3.2: +lodash.set@4.3.2, lodash.set@^4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23" integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM= @@ -11340,6 +11940,11 @@ make-dir@^3.0.0, make-dir@^3.0.2: dependencies: semver "^6.0.0" +make-error@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + make-fetch-happen@^5.0.0: version "5.0.2" resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz#aa8387104f2687edca01c8687ee45013d02d19bd" @@ -11681,6 +12286,13 @@ minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -11899,6 +12511,20 @@ ms@2.1.2, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +multer@1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.2.tgz#2f1f4d12dbaeeba74cb37e623f234bf4d3d2057a" + integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg== + dependencies: + append-field "^1.0.0" + busboy "^0.2.11" + concat-stream "^1.5.2" + mkdirp "^0.5.1" + object-assign "^4.1.1" + on-finished "^2.3.0" + type-is "^1.6.4" + xtend "^4.0.0" + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -12036,11 +12662,28 @@ node-fetch@^2.3.0, node-fetch@^2.5.0: dependencies: whatwg-url "^5.0.0" +node-fetch@^2.6.1: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-forge@0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== +node-forge@^0.7.4: + version "0.7.6" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" + integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== + +node-forge@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.5.tgz#57906f07614dc72762c84cef442f427c0e1b86ee" + integrity sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q== + node-gyp@^5.0.2: version "5.1.1" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-5.1.1.tgz#eb915f7b631c937d282e33aed44cb7a025f62a3e" @@ -12134,6 +12777,23 @@ node-releases@^2.0.1: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + +node-rsa@^1.0.5: + version "1.1.1" + resolved "https://registry.yarnpkg.com/node-rsa/-/node-rsa-1.1.1.tgz#efd9ad382097782f506153398496f79e4464434d" + integrity sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw== + dependencies: + asn1 "^0.2.4" + +node-xmllint@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-xmllint/-/node-xmllint-1.0.0.tgz#f680457eb0166a3d06996f1242d34d47000fe55b" + integrity sha512-71UV2HRUP+djvHpdyatiuv+Y1o8hI4ZI7bMfuuoACMLR1JJCErM4WXAclNeHd6BgHXkqeqnnAk3wpDkSQWmFXw== + nodemon@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-3.1.0.tgz#ff7394f2450eb6a5e96fe4180acd5176b29799c9" @@ -12334,6 +12994,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-hash@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + object-hash@^2.0.1: version "2.2.0" resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" @@ -12464,6 +13129,13 @@ on-finished@^2.2.0, on-finished@~2.3.0: dependencies: ee-first "1.1.1" +on-finished@^2.3.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + on-headers@^1.0.0, on-headers@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" @@ -12884,6 +13556,11 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.2.0.tgz#fa7877ecbc495c601907562222453c43cc204a5f" + integrity sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA== + path-to-regexp@^1.7.0, path-to-regexp@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" @@ -12957,12 +13634,17 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== -picomatch@^2.3.1: +picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -14046,6 +14728,11 @@ raf@^3.4.1: dependencies: performance-now "^2.1.0" +random-bytes@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" + integrity sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ== + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -14392,6 +15079,16 @@ read@1, read@~1.0.1: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@1.1.x: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + "readable-stream@2 || 3", readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" @@ -14472,6 +15169,11 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +reflect-metadata@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" + integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== + regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -14931,7 +15633,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -14948,6 +15650,22 @@ safe-regex@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +samlify@2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/samlify/-/samlify-2.6.0.tgz#50010ae27b808e500c88c705763ecfd05a6ab519" + integrity sha512-/g1ELo/vF4Apw2kq4akQme5eaHyFmAXXZgZFgPjfhHoJ3I0W1MncbknpKNRbVQ7z2hRJd3x3Ngjw3k5RCSFQjA== + dependencies: + "@authenio/xml-encryption" "^0.11.3" + camelcase "^5.3.1" + deflate-js "^0.2.3" + node-forge "^0.8.5" + node-rsa "^1.0.5" + uuid "^3.3.2" + xml "^1.0.1" + xml-crypto "^1.4.0" + xmldom "^0.1.27" + xpath "^0.0.27" + sane@^4.0.3: version "4.1.0" resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" @@ -15080,6 +15798,11 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + semver@^7.2.1, semver@^7.3.2: version "7.3.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" @@ -15097,6 +15820,11 @@ semver@^7.5.3: resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== +semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -15636,6 +16364,11 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== +streamsearch@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" + integrity sha512-jos8u++JKm0ARcSUTAZXOVC0mSox7Bhn6sBgty73P1f3JGf7yG2clTbBNHUdde/kdvP2FESam+vM6l8jBrNxHA== + strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -15764,6 +16497,11 @@ string_decoder@^1.0.0, string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== + string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -16356,6 +17094,21 @@ trim-off-newlines@^1.0.0: resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= +ts-jest@^29.2.5: + version "29.2.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" + integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== + dependencies: + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" + jest-util "^29.0.0" + json5 "^2.2.3" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.6.3" + yargs-parser "^21.1.1" + ts-pnp@1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.1.6.tgz#389a24396d425a0d3162e96d2b4638900fdc289a" @@ -16381,6 +17134,16 @@ tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" +tslib@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" + integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== + +tslib@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: version "1.13.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" @@ -16449,7 +17212,7 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@~1.6.17, type-is@~1.6.18: +type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -16499,6 +17262,13 @@ uid-number@0.0.6: resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" integrity sha1-DqEOgDXo61uOREnwbaHHMGY7qoE= +uid-safe@~2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" + integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== + dependencies: + random-bytes "~1.0.0" + umask@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" @@ -16646,6 +17416,14 @@ upath@^1.1.1, upath@^1.2.0: resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== +update-browserslist-db@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.0" + update-notifier@^4.1.1: version "4.1.3" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.3.tgz#be86ee13e8ce48fb50043ff72057b5bd598e1ea3" @@ -16773,6 +17551,16 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +uuid@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d" + integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg== + +uuid@8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + uuid@^3.0.1, uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" @@ -17397,16 +18185,44 @@ xdg-basedir@^4.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== +xml-crypto@^1.4.0: + version "1.5.6" + resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-1.5.6.tgz#e8122f0fe8489d7f444a32a45388798684aa7315" + integrity sha512-LCLvc59uItSD3QZprq+XaJWXb0umi3g8Ks3pZis1qZ9OYzQuHb4U//u5+vHr4gjn2KFAAAzFlja6OnS2LG/gRw== + dependencies: + "@xmldom/xmldom" "^0.7.0" + xpath "0.0.32" + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw== + xmlchars@^2.1.1: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xmldom@^0.1.27, xmldom@~0.1.15: + version "0.1.31" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff" + integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ== + +xpath@0.0.27, xpath@^0.0.27: + version "0.0.27" + resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92" + integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ== + +xpath@0.0.32: + version "0.0.32" + resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.32.tgz#1b73d3351af736e17ec078d6da4b8175405c48af" + integrity sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw== + xregexp@^4.3.0: version "4.4.1" resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.4.1.tgz#c84a88fa79e9ab18ca543959712094492185fe65" @@ -17473,6 +18289,11 @@ yargs-parser@^20.2.2: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== +yargs-parser@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" + integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== + yargs@16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" From a017912dc0a35d7e4c685bf15a212973b49d2988 Mon Sep 17 00:00:00 2001 From: elhadj Date: Tue, 22 Oct 2024 19:14:52 +0200 Subject: [PATCH 03/20] clear: insertUser deletion --- .../courDeCassation/src/scripts/insertUser.ts | 47 ------------------- .../courDeCassation/src/test/setupEnvVars.ts | 1 + .../generic/backend/src/app/buildBackend.ts | 5 -- .../generic/backend/src/app/scripts/index.ts | 2 - .../backend/src/app/scripts/insertUser.ts | 26 ---------- 5 files changed, 1 insertion(+), 80 deletions(-) delete mode 100644 packages/courDeCassation/src/scripts/insertUser.ts delete mode 100644 packages/generic/backend/src/app/scripts/insertUser.ts diff --git a/packages/courDeCassation/src/scripts/insertUser.ts b/packages/courDeCassation/src/scripts/insertUser.ts deleted file mode 100644 index 288109ab3..000000000 --- a/packages/courDeCassation/src/scripts/insertUser.ts +++ /dev/null @@ -1,47 +0,0 @@ -import yargs from 'yargs'; -import { buildBackend } from '@label/backend'; -import { parametersHandler } from '../lib/parametersHandler'; - -(async () => { - const { settings } = await parametersHandler.getParameters(); - const { email, name, role } = parseArgv(); - const backend = buildBackend(settings); - - await backend.runScript( - () => backend.scripts.insertUser.run({ email, name, role }), - backend.scripts.insertUser.option, - ); -})(); - -function parseArgv() { - const argv = yargs - .options({ - email: { - demandOption: true, - description: 'Email of the new user', - type: 'string', - }, - name: { - demandOption: true, - description: 'Name of the new user', - type: 'array', - }, - role: { - demandOption: true, - description: 'Role of the new user (must be "admin" or "annotator")', - type: 'string', - }, - }) - .help() - .alias('help', 'h').argv; - - if (!['admin', 'annotator'].includes(argv.role)) { - throw new Error('Bad role'); - } - - return { - email: argv.email as string, - name: (argv.name as string[]).join(' '), - role: argv.role as 'admin' | 'annotator', - }; -} diff --git a/packages/courDeCassation/src/test/setupEnvVars.ts b/packages/courDeCassation/src/test/setupEnvVars.ts index 99a4dcb9d..67a51eaca 100644 --- a/packages/courDeCassation/src/test/setupEnvVars.ts +++ b/packages/courDeCassation/src/test/setupEnvVars.ts @@ -26,4 +26,5 @@ process.env = { SSO_APP_NAME: 'LABEL', SSO_SP_PRIVATE_KEY: 'src/test/pk.txt', SSO_CERTIFICAT: 'src/test/crt.txt', + RUN_MODE: 'TEST', }; diff --git a/packages/generic/backend/src/app/buildBackend.ts b/packages/generic/backend/src/app/buildBackend.ts index 773550d94..9bcb63dcd 100644 --- a/packages/generic/backend/src/app/buildBackend.ts +++ b/packages/generic/backend/src/app/buildBackend.ts @@ -11,7 +11,6 @@ import { freePendingDocuments, insertTestStatistics, insertTestUsers, - insertUser, listAllDocuments, listAllCaches, listDocumentsWithProblemReports, @@ -68,10 +67,6 @@ function buildBackend(settings: settingsType) { run: insertTestUsers, option: { shouldLoadDb: true, shouldExit: false }, }, - insertUser: { - run: insertUser, - option: { shouldLoadDb: true, shouldExit: false }, - }, listAllCaches: { run: listAllCaches, option: { shouldLoadDb: true, shouldExit: false }, diff --git a/packages/generic/backend/src/app/scripts/index.ts b/packages/generic/backend/src/app/scripts/index.ts index 493328ae0..b8176d5db 100644 --- a/packages/generic/backend/src/app/scripts/index.ts +++ b/packages/generic/backend/src/app/scripts/index.ts @@ -7,7 +7,6 @@ import { dumpDocument } from './dumpDocument'; import { freePendingDocuments } from './freePendingDocuments'; import { insertTestStatistics } from './insertTestStatistics'; import { insertTestUsers } from './insertTestUsers'; -import { insertUser } from './insertUser'; import { listAllCaches } from './listAllCaches'; import { listAllDocuments } from './listAllDocuments'; import { listDocumentsWithProblemReports } from './listDocumentsWithProblemReports'; @@ -26,7 +25,6 @@ export { freePendingDocuments, insertTestStatistics, insertTestUsers, - insertUser, listAllCaches, listAllDocuments, listDocumentsWithProblemReports, diff --git a/packages/generic/backend/src/app/scripts/insertUser.ts b/packages/generic/backend/src/app/scripts/insertUser.ts deleted file mode 100644 index 6d54ac224..000000000 --- a/packages/generic/backend/src/app/scripts/insertUser.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { userType } from '@label/core'; -import { logger } from '../../utils'; - -export { insertUser }; - -async function insertUser({ - email, - name, - role, -}: { - email: string; - name: string; - role: userType['role']; -}) { - logger.log({ - operationName: 'insertUser', - msg: 'START', - data: { - email, - name, - role, - }, - }); - - logger.log({ operationName: 'insertUser', msg: 'DONE' }); -} From 99c6b0299314494cb4323bdef3b9af788d5bd73c Mon Sep 17 00:00:00 2001 From: elhadj Date: Tue, 22 Oct 2024 23:31:42 +0200 Subject: [PATCH 04/20] clear: back to initial config --- nodemon.json | 2 +- package.json | 2 +- packages/generic/client/src/utils/urlHandler.ts | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/nodemon.json b/nodemon.json index 214432afc..1da89108c 100644 --- a/nodemon.json +++ b/nodemon.json @@ -5,5 +5,5 @@ "packages/generic/core/src" ], "ext": "ts,json", - "exec": "node packages/courDeCassation/dist/labelServer.js --settings packages/courDeCassation/settings/settings.json" + "exec": "yarn compile:backend && node packages/courDeCassation/dist/labelServer.js --settings packages/courDeCassation/settings/settings.json" } diff --git a/package.json b/package.json index b7c3b6eb6..d7d712d27 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "init:db": "scripts/initializeTestDb.sh", "lint": "lerna run lint", "start:backend": "lerna run --scope @label/cour-de-cassation start --stream", - "start:backend:dev": "RUN_MODE=LOCAL nodemon", + "start:backend:dev": "nodemon", "start:client:dev": "yarn compile:client && cd packages/generic/client && yarn start", "test": "lerna run test", "type": "lerna run type" diff --git a/packages/generic/client/src/utils/urlHandler.ts b/packages/generic/client/src/utils/urlHandler.ts index e89dab9ce..2321e298a 100644 --- a/packages/generic/client/src/utils/urlHandler.ts +++ b/packages/generic/client/src/utils/urlHandler.ts @@ -2,14 +2,13 @@ export { urlHandler }; const urlHandler = { getApiUrl() { - /*const clientPort = parseInt(window.location.port); + const clientPort = parseInt(window.location.port); const clientProtocol = window.location.protocol; const clientHostname = window.location.hostname; const serverPort = clientPort - 2; - return serverPort ? `${clientProtocol}//${clientHostname}:${serverPort}` : `${clientProtocol}//${clientHostname}`;*/ - return `http://localhost:55430`; + return serverPort ? `${clientProtocol}//${clientHostname}:${serverPort}` : `${clientProtocol}//${clientHostname}`; }, getSsoLoginUrl() { From 8ecf60e1b00b6704a6de6ac2228e23ffc8f9f400 Mon Sep 17 00:00:00 2001 From: DIALLO SAAD BOUH Date: Mon, 16 Dec 2024 13:32:00 +0100 Subject: [PATCH 05/20] fix: CASSLAB-123, CASSLAB-125 and SSO readme refacto --- INSTALL.md | 13 ++- README.fr.md | 36 +----- README.md | 33 +----- .../buildAuthenticatedController.ts | 42 ++++--- .../modules/sso/service/ssoService.spec.ts | 37 +++--- .../src/modules/sso/service/ssoService.ts | 110 ++++++++---------- .../client/src/contexts/user.context.tsx | 13 +-- .../PreAssignDocuments/PreAssignDocuments.tsx | 4 +- packages/generic/client/src/pages/router.tsx | 8 +- .../src/services/localStorage/localStorage.ts | 2 - .../src/services/localStorage/userHandler.ts | 51 -------- packages/generic/sso/README.fr.md | 26 +++++ packages/generic/sso/README.md | 21 ++++ .../sso/docs}/images/LABEL_auth_workflow.png | Bin .../sso/src/api/saml/saml.service.spec.ts | 32 +++-- .../generic/sso/src/api/saml/saml.service.ts | 61 +++++----- 16 files changed, 207 insertions(+), 282 deletions(-) delete mode 100644 packages/generic/client/src/services/localStorage/userHandler.ts rename {docs => packages/generic/sso/docs}/images/LABEL_auth_workflow.png (100%) diff --git a/INSTALL.md b/INSTALL.md index 8fc4d3f7d..c80a00789 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -90,4 +90,15 @@ docker container exec -it label-backend-1 sh -c "cd packages/courDeCassation; sh scripts/runScriptLocally.sh "myScript.js --myArgument" ``` ### SSO configuration -Follow the [installation guide](packages/generic/sso/README.md). \ No newline at end of file +Follow the [installation guide](packages/generic/sso/README.md). + + +>The LABEL application leverages the SSO module as a dependency for its integration with the Single Sign-On (SSO) system. The details of this integration are documented in the [README](packages/generic/sso/README.md) of the SSO module. + +The backend exposes the following URLs to interact with the SSO: + +1. /api/sso/login: Endpoint to initiate the login process via SSO. +2. /api/sso/acs: Endpoint for processing SAML assertions following a successful authentication. +3. /api/sso/logout: Endpoint to disconnect the user from the SSO. + +***The attributes returned by the SSO, as well as the roles used by the application, are specified in the configuration file.*** \ No newline at end of file diff --git a/README.fr.md b/README.fr.md index d0b53aff4..eb6070aeb 100644 --- a/README.fr.md +++ b/README.fr.md @@ -125,38 +125,4 @@ LABEL a été conçu pour être réutiliser dans n'importe quel contexte d'annot - `specific` : ce qui est spécifique à la Cour de cassation (machine learning engine API, database connector, etc.) - `generic` : tout ce qui n'est pas lié aux besoins propres à la Cour de cassation -Lisez [le guide de réutilisation](docs/reuserGuide.md) pour en savoir plus. - - -## Authentification - -
- -LABEL s'interface avec le SSO Pages Blanches pour assurer l'authentification des utilisateurs via le protocole SAML 2. - -Le schéma ci-dessous illustre le flux d'authentification. - -Label auth workflow - -1. Lorsqu'un utilisateur accède à LABEL, le frontend interroge le backend pour vérifier son authentification. Si l'utilisateur n'est pas authentifié, le backend redirige vers le fournisseur d'identité (IdP) avec les paramètres nécessaires. - - -2. L'utilisateur se connecte sur la page SSO et, après authentification, le fournisseur d'identité génère et envoie une assertion SAML au backend de LABEL via l'URL de l'ACS. - - -3. Le backend valide l'assertion SAML pour garantir l'intégrité des données et la conformité de la signature numérique. - - -4. Après validation, l'accès aux ressources sécurisées est accordé, permettant à l'utilisateur de poursuivre sa session authentifiée.
- - -> L'application LABEL utilise le module SSO comme dépendance pour son intégration avec le système d'authentification unique (SSO). Les spécificités de cette intégration sont documentées dans le [readme](packages/generic/sso/README.md) du module SSO. - -Le backend expose les URLs suivantes pour interagir avec le SSO : - -> 1. **/api/sso/login** : Endpoint pour initier le processus de connexion via SSO -> 2. **/api/sso/acs** : Endpoint pour le traitement des assertions SAML suite à une authentification réussie. -> 2. **/api/sso/logout** : Endpoint pour déconnecter l'utilisateur du SSO. -> 3. **/api/sso/metadata** : Endpoint pour récupérer les métadonnées du service SSO, incluant les configurations nécessaires pour l'authentification. - -Les attributs renvoyés par le SSO, ainsi que les rôles utilisés par l'application, sont spécifiés dans le fichier de configuration \ No newline at end of file +Lisez [le guide de réutilisation](docs/reuserGuide.md) pour en savoir plus. \ No newline at end of file diff --git a/README.md b/README.md index 1fb84d876..5fe35a76c 100644 --- a/README.md +++ b/README.md @@ -125,35 +125,4 @@ LABEL has been designed to be reused whatever the annotation context. There are - `specific`: what is specific to the Cour de cassation (machine learning engine API, database connector, etc.) - `generic`: what is not linked to the specific needs of the Cour de cassation -Learn more in the [reuser guide](docs/reuserGuide.md). - -## Authentication -
-LABEL integrates with the Pages Blanches SSO to facilitate user authentication using the SAML 2.0 protocol. - -The diagram below illustrates the authentication flow. - -Label auth workflow - -1. When a user initiates access to LABEL, the frontend communicates with the backend to check the user’s authentication status. If the user is not authenticated, the backend initiates a redirect to the identity provider (IdP), passing the necessary authentication parameters. - - -2. The user is then prompted to log in via the SSO page. Upon successful authentication, the identity provider generates a SAML assertion and sends it to LABEL's backend via the Assertion Consumer Service (ACS) URL. - - -3. The backend processes and validates the SAML assertion to ensure data integrity and verify the authenticity of the digital signature - - -4. After validation, access to secured resources is granted, allowing the user to continue their authenticated session. - -
- ->The LABEL application leverages the SSO module as a dependency for its integration with the Single Sign-On (SSO) system. The details of this integration are documented in the [README](packages/generic/sso/README.md) of the SSO module. - -The backend exposes the following URLs to interact with the SSO: - -1. /api/sso/login: Endpoint to initiate the login process via SSO. -2. /api/sso/acs: Endpoint for processing SAML assertions following a successful authentication. -3. /api/sso/logout: Endpoint to disconnect the user from the SSO. - -***The attributes returned by the SSO, as well as the roles used by the application, are specified in the configuration file.*** \ No newline at end of file +Learn more in the [reuser guide](docs/reuserGuide.md). \ No newline at end of file diff --git a/packages/generic/backend/src/api/buildAuthenticatedController/buildAuthenticatedController.ts b/packages/generic/backend/src/api/buildAuthenticatedController/buildAuthenticatedController.ts index 99f06ef25..34e98aa20 100644 --- a/packages/generic/backend/src/api/buildAuthenticatedController/buildAuthenticatedController.ts +++ b/packages/generic/backend/src/api/buildAuthenticatedController/buildAuthenticatedController.ts @@ -21,32 +21,36 @@ function buildAuthenticatedController({ return async (req: { args: inT; headers: any; - session?: any; + session?: { + user: { + _id: string; + name: string; + role: string; + email: string; + sessionIndex: string; + }; + }; path?: string; }) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - let currentUser = req.session?.user ?? null; + const currentUser = req.session?.user ?? null; if (!currentUser) { throw errorHandlers.authenticationErrorHandler.build( `user session has expired or is invalid`, ); } - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const isAnnotator = currentUser.role === 'annotator'; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const isAdminAccessingDocs = - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - currentUser.role === 'admin' && req.path?.includes('documentsForUser'); - if (isAnnotator || isAdminAccessingDocs) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - currentUser = { - ...currentUser, - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - _id: idModule.lib.buildId(currentUser._id), - }; - } - userModule.lib.assertPermissions(currentUser, permissions); - return controllerWithUser(currentUser, req); + const resolvedUser = { + _id: idModule.lib.buildId(currentUser._id) as userType['_id'], + name: currentUser.name, + role: currentUser.role as + | 'admin' + | 'annotator' + | 'publicator' + | 'scrutator', + email: currentUser.email, + }; + + userModule.lib.assertPermissions(resolvedUser, permissions); + return controllerWithUser(resolvedUser, req); }; } diff --git a/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts b/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts index cdadd2d31..e1dfa89eb 100644 --- a/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts +++ b/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts @@ -8,6 +8,8 @@ import { } from './ssoService'; import { buildUserRepository } from '../../user'; import { buildUserService } from '../../user/service/userService'; +import { Request } from 'express'; +import { userType } from '@label/core'; jest.mock('@label/sso', () => ({ SamlService: jest.fn().mockImplementation(() => ({ @@ -60,7 +62,6 @@ process.env.SSO_FRONT_SUCCESS_CONNEXION_ANNOTATOR_URL = describe('SSO CNX functions', () => { describe('getMetadataSso', () => { it('should return SAML metadata', async () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const metadata = await getMetadata(); expect(metadata).toBe(''); }); @@ -68,7 +69,6 @@ describe('SSO CNX functions', () => { describe('loginSso', () => { it('should return login URL', async () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const loginUrl = await login(); expect(loginUrl).toBe('login-url'); }); @@ -76,7 +76,6 @@ describe('SSO CNX functions', () => { describe('logoutSso', () => { it('should return logout URL', async () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const logoutUrl = await logout({ nameID: 'test-user-id', sessionIndex: 'test-session-index', @@ -113,7 +112,6 @@ describe('SSO CNX functions', () => { it('should handle the case where user does not exist and is auto-provisioned', async () => { const mockNewUser = { email: 'newuser@example.com' }; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access (samlService.parseResponse as jest.Mock).mockResolvedValue({ extract: { nameID: 'newuser@example.com', @@ -126,7 +124,6 @@ describe('SSO CNX functions', () => { }, }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment const userRepository = buildUserRepository(); jest .spyOn(userRepository, 'findByEmail') @@ -150,19 +147,25 @@ describe('SSO CNX functions', () => { }); describe('setUserSessionAndReturnRedirectUrl', () => { - const mockRequest = { + const mockRequest = ({ session: { user: {}, }, - }; + body: { + SAMLResponse: 'mock-saml-response', + }, + params: { + id: '123456789', + }, + } as unknown) as Request; it('should return the correct URL for annotator role', () => { - const user = { + const user = ({ _id: '1', name: 'Annotator', role: 'annotator', email: 'annotator@test.com', - }; + } as unknown) as userType; const result = setUserSessionAndReturnRedirectUrl( mockRequest, user, @@ -178,12 +181,12 @@ describe('SSO CNX functions', () => { }); it('should return the correct URL for admin role', () => { - const user = { + const user = ({ _id: '2', name: 'Admin', role: 'admin', email: 'admin@test.com', - }; + } as unknown) as userType; const result = setUserSessionAndReturnRedirectUrl( mockRequest, user, @@ -200,12 +203,12 @@ describe('SSO CNX functions', () => { }); it('should return the correct URL for scrutator role', () => { - const user = { + const user = ({ _id: '3', name: 'Scrutator', role: 'scrutator', email: 'scrutator@test.com', - }; + } as unknown) as userType; const result = setUserSessionAndReturnRedirectUrl( mockRequest, user, @@ -222,12 +225,12 @@ describe('SSO CNX functions', () => { }); it('should return the correct URL for publicator role', () => { - const user = { + const user = ({ _id: '4', name: 'Publicator', role: 'publicator', email: 'publicator@test.com', - }; + } as unknown) as userType; const result = setUserSessionAndReturnRedirectUrl( mockRequest, user, @@ -244,12 +247,12 @@ describe('SSO CNX functions', () => { }); it('should throw an error for an invalid role', () => { - const user = { + const user = ({ _id: '5', name: 'InvalidRole', role: 'invalidRole', email: 'invalid@test.com', - }; + } as unknown) as userType; expect(() => { setUserSessionAndReturnRedirectUrl( diff --git a/packages/generic/backend/src/modules/sso/service/ssoService.ts b/packages/generic/backend/src/modules/sso/service/ssoService.ts index afa088136..16c712121 100644 --- a/packages/generic/backend/src/modules/sso/service/ssoService.ts +++ b/packages/generic/backend/src/modules/sso/service/ssoService.ts @@ -3,69 +3,81 @@ import { buildUserRepository, userService } from '../../user'; import { logger } from '../../../utils'; import every from 'lodash/every'; import includes from 'lodash/includes'; +import { idModule, userType } from '@label/core'; +import { Request } from 'express'; export { samlService }; +export interface BindingContext { + context: string; + id: string; +} + +export interface ParseResponseResult { + samlContent: string; + extract: { + nameID: string; + sessionIndex: string; + attributes: Record; + }; +} + +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable-next-line @typescript-eslint/no-unsafe-return */ + function ssoSamlService() { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return return new SamlService(); } -// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const samlService = ssoSamlService(); export async function getMetadata() { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-return - return samlService.generateMetadata(); + return samlService.generateMetadata() as string; } export async function login() { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - const { context } = await samlService.createLoginRequestUrl(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return + const { + context, + } = (await samlService.createLoginRequestUrl()) as BindingContext; return context; } export async function logout(user: { nameID: string; sessionIndex: string }) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - const { context } = await samlService.createLogoutRequestUrl(user); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return + const { context } = (await samlService.createLogoutRequestUrl( + user, + )) as BindingContext; return context; } export async function acs(req: any) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call - const response = await samlService.parseResponse(req); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const response = (await samlService.parseResponse( + req, + )) as ParseResponseResult; const { extract } = response; try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - const user = await getUserByEmail(extract?.nameID); + const user = (await getUserByEmail(extract?.nameID)) as userType; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access await logger.log({ operationName: 'user connected', - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment msg: user.email, }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access return setUserSessionAndReturnRedirectUrl(req, user, extract?.sessionIndex); - } catch (err) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + } catch (err: unknown) { await logger.log({ operationName: `catch autoprovision user ${JSON.stringify(err)}`, msg: `${err}`, }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - if (err.message.includes(`No matching user for email ${extract?.nameID}`)) { - const { attributes } = extract as Record; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call - const roles = attributes[ + if ( + err instanceof Error && + err.message.includes(`No matching user for email ${extract?.nameID}`) + ) { + const { attributes } = extract; + const roles = (attributes[ `${process.env.SSO_ATTRIBUTE_ROLE}` - ].map((item: string) => item.toLowerCase()) as string[]; + ] as string[]).map((item: string) => item.toLowerCase()) as string[]; const appRoles = (process.env.SSO_APP_ROLES as string) .toLowerCase() @@ -75,43 +87,33 @@ export async function acs(req: any) { ); if (!roles.length || !userRolesInAppRoles) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const errorMsg = `User ${extract.nameID}, role ${roles} doesn't exist in application ${process.env.SSO_APP_NAME}`; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access logger.error({ operationName: 'ssoService', msg: errorMsg }); throw new Error(errorMsg); } const newUser = { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment name: - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - attributes[`${process.env.SSO_ATTRIBUTE_FULLNAME}`] || - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - `${attributes[`${process.env.SSO_ATTRIBUTE_NAME}`]} ${ - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - attributes[`${process.env.SSO_ATTRIBUTE_FIRSTNAME}`] + (attributes[`${process.env.SSO_ATTRIBUTE_FULLNAME}`] as string) || + `${attributes[`${process.env.SSO_ATTRIBUTE_NAME}`] as string} ${ + attributes[`${process.env.SSO_ATTRIBUTE_FIRSTNAME}`] as string }`, - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - email: attributes[`${process.env.SSO_ATTRIBUTE_MAIL}`], + email: attributes[`${process.env.SSO_ATTRIBUTE_MAIL}`] as string, role: roles[0] as 'annotator' | 'scrutator' | 'admin' | 'publicator', }; await userService.createUser(newUser); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const createdUser = await getUserByEmail(newUser.email); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + const createdUser = (await getUserByEmail(newUser.email)) as userType; + await logger.log({ operationName: `Auto-provided user`, - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access msg: `successfully created user ${createdUser.email}`, }); return setUserSessionAndReturnRedirectUrl( req, createdUser, - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access extract?.sessionIndex, ); } else { @@ -121,29 +123,21 @@ export async function acs(req: any) { } export async function getUserByEmail(email: string) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call const userRepository = buildUserRepository(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - return await userRepository.findByEmail(email); + return (await userRepository.findByEmail(email)) as userType; } export function setUserSessionAndReturnRedirectUrl( - req: any, - user: any, + req: Request, + user: userType, sessionIndex: string, ) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (req.session) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access req.session.user = { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - _id: user._id, - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - name: user.name, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - role: user.role, - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment - email: user.email, + _id: idModule.lib.convertToString(user._id), + name: user.name as string, + role: user.role as string, + email: user.email as string, sessionIndex: sessionIndex, }; } @@ -158,11 +152,9 @@ export function setUserSessionAndReturnRedirectUrl( .SSO_FRONT_SUCCESS_CONNEXION_PUBLICATOR_URL as string, }; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (!roleToUrlMap[user.role]) { throw new Error(`Role doesn't exist in label`); } - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access return roleToUrlMap[user.role]; } diff --git a/packages/generic/client/src/contexts/user.context.tsx b/packages/generic/client/src/contexts/user.context.tsx index a14be886a..80a8b4266 100644 --- a/packages/generic/client/src/contexts/user.context.tsx +++ b/packages/generic/client/src/contexts/user.context.tsx @@ -1,12 +1,13 @@ import React, { createContext, FC, ReactNode, useContext, useEffect, useState } from 'react'; import { urlHandler } from '../utils'; +import { userType } from '@label/core'; export interface CurrentUser { _id?: string; email?: string; name?: string; - role?: string; - passwordTimeValidityStatus?: string; + role?: userType['role']; + sessionIndex?: string; } interface UserContextType { user: CurrentUser | null; @@ -17,12 +18,11 @@ const UserContext = createContext(null); // eslint-disable-next-line react/prop-types export const UserProvider: FC<{ children: ReactNode }> = ({ children }) => { - const [user, setUser] = useState(null); + const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { async function fetchUser() { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const userData = await whoami(); setUser(userData); setLoading(false); @@ -43,7 +43,7 @@ export const useCtxUser = () => { return context; }; -async function whoami() { +async function whoami(): Promise { try { const response = await fetch(`${urlHandler.getApiUrl()}/label/api/sso/whoami`, { headers: { 'Content-Type': 'application/json' }, @@ -55,8 +55,7 @@ async function whoami() { if (!response.ok) { return null; } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return await response.json(); + return (await response.json()) as CurrentUser; } catch (error) { console.error('Error fetching authentication status:', error); return null; diff --git a/packages/generic/client/src/pages/Admin/PreAssignDocuments/PreAssignDocuments.tsx b/packages/generic/client/src/pages/Admin/PreAssignDocuments/PreAssignDocuments.tsx index a74bdf74f..8f0d1ba5b 100644 --- a/packages/generic/client/src/pages/Admin/PreAssignDocuments/PreAssignDocuments.tsx +++ b/packages/generic/client/src/pages/Admin/PreAssignDocuments/PreAssignDocuments.tsx @@ -4,7 +4,6 @@ import { customThemeType, useCustomTheme, RefreshButton } from 'pelta-design-sys import { heights, widths } from '../../../styles'; import { PreAssignDocumentsTable } from './PreAssignDocumentsTable'; import { AddPreAssignationButton } from './AddPreAssignationDrawer/AddPreAssignationButton'; -import { localStorage } from '../../../services/localStorage'; export { PreAssignDocuments }; @@ -13,11 +12,12 @@ function PreAssignDocuments(props: { preAssignations: apiRouteOutType<'get', 'preAssignations'>; refetch: () => void; isLoading: boolean; + userRole: userType['role']; }) { const theme = useCustomTheme(); const styles = buildStyles(theme); - const userRole = localStorage.userHandler.getRole(); + const userRole = props.userRole; return (
diff --git a/packages/generic/client/src/pages/router.tsx b/packages/generic/client/src/pages/router.tsx index 94aee5785..9145f8334 100644 --- a/packages/generic/client/src/pages/router.tsx +++ b/packages/generic/client/src/pages/router.tsx @@ -41,8 +41,7 @@ function Router() { ({ problemReport }) => !problemReport.hasBeenRead, ).length; const toBeConfirmedDocumentsCount = adminInfos.toBeConfirmedDocuments.length; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - const userRole = localStorage.adminViewHandler.get() || (user?.role as 'admin' | 'scrutator'); + const userRole = localStorage.adminViewHandler.get() || user?.role; if (userRole !== 'admin' && userRole !== 'scrutator') { return <>; } @@ -101,6 +100,7 @@ function Router() { refetch={refetch.preAssignDocuments} preAssignations={adminInfos.preAssignations} isLoading={isLoading.preAssignDocuments} + userRole={userRole} /> @@ -173,7 +173,6 @@ function Router() { } const AuthenticatedRoute: FunctionComponent = ({ children, ...rest }: RouteProps) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment const { user, loading } = useCtxUser(); if (loading) { return
Loading...
; @@ -198,7 +197,6 @@ const AuthenticatedRoute: FunctionComponent = ({ children, ...rest } }; const HomeRoute: FunctionComponent = ({ ...props }: RouteProps) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment const { user, loading } = useCtxUser(); if (loading) { @@ -221,7 +219,6 @@ const HomeRoute: FunctionComponent = ({ ...props }: RouteProps) => { }; function getRedirectionRoute(user: CurrentUser | null) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment const userRole = user?.role; if (!userRole) { @@ -239,7 +236,6 @@ function getRedirectionRoute(user: CurrentUser | null) { } const UnauthenticatedRoute: FunctionComponent = ({ children, ...rest }: RouteProps) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment const { user, loading } = useCtxUser(); if (loading) { return
Loading...
; diff --git a/packages/generic/client/src/services/localStorage/localStorage.ts b/packages/generic/client/src/services/localStorage/localStorage.ts index 9ca5b67fe..640e706c4 100644 --- a/packages/generic/client/src/services/localStorage/localStorage.ts +++ b/packages/generic/client/src/services/localStorage/localStorage.ts @@ -20,7 +20,6 @@ import { toBeConfirmedDocumentOrderByProperties, toBeConfirmedDocumentsStateHandler, } from './documentStateHandler/toBeConfirmedDocumentsStateHandler'; -import { userHandler } from './userHandler'; export { adminViews, @@ -41,5 +40,4 @@ const localStorage = { treatedDocumentsStateHandler, untreatedDocumentsStateHandler, toBeConfirmedDocumentsStateHandler, - userHandler, }; diff --git a/packages/generic/client/src/services/localStorage/userHandler.ts b/packages/generic/client/src/services/localStorage/userHandler.ts deleted file mode 100644 index e66339194..000000000 --- a/packages/generic/client/src/services/localStorage/userHandler.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { userType } from '@label/core'; -import { localStorageHandler } from './localStorageHandler'; -import { localStorageMappers } from './localStorageMappers'; - -export { userHandler }; - -const USER_ID_STORAGE_KEY = 'USER_ID'; -const USER_EMAIL_STORAGE_KEY = 'USER_EMAIL'; -const USER_NAME_STORAGE_KEY = 'USER_NAME'; -const USER_ROLE_STORAGE_KEY = 'USER_ROLE'; - -const userHandler = { - set, - remove, - getId, - getEmail, - getName, - getRole, -}; - -function set({ _id, email, name, role }: Pick) { - localStorageHandler.set({ key: USER_ID_STORAGE_KEY, value: _id, mapper: localStorageMappers.id }); - localStorageHandler.set({ key: USER_EMAIL_STORAGE_KEY, value: email, mapper: localStorageMappers.string }); - localStorageHandler.set({ key: USER_NAME_STORAGE_KEY, value: name, mapper: localStorageMappers.string }); - localStorageHandler.set({ key: USER_ROLE_STORAGE_KEY, value: role, mapper: localStorageMappers.string }); -} - -function remove() { - localStorageHandler.set({ key: USER_ID_STORAGE_KEY, value: undefined, mapper: localStorageMappers.id }); - localStorageHandler.set({ key: USER_EMAIL_STORAGE_KEY, value: undefined, mapper: localStorageMappers.string }); - localStorageHandler.set({ key: USER_NAME_STORAGE_KEY, value: undefined, mapper: localStorageMappers.string }); - localStorageHandler.set({ key: USER_ROLE_STORAGE_KEY, value: undefined, mapper: localStorageMappers.string }); -} - -function getId() { - return localStorageHandler.get({ key: USER_ID_STORAGE_KEY, mapper: localStorageMappers.id }); -} - -function getEmail() { - return localStorageHandler.get({ key: USER_EMAIL_STORAGE_KEY, mapper: localStorageMappers.string }); -} - -function getName() { - return localStorageHandler.get({ key: USER_NAME_STORAGE_KEY, mapper: localStorageMappers.string }); -} - -function getRole() { - return localStorageHandler.get({ key: USER_ROLE_STORAGE_KEY, mapper: localStorageMappers.string }) as - | userType['role'] - | undefined; -} diff --git a/packages/generic/sso/README.fr.md b/packages/generic/sso/README.fr.md index 5ece2f740..447fa3ec0 100644 --- a/packages/generic/sso/README.fr.md +++ b/packages/generic/sso/README.fr.md @@ -3,6 +3,32 @@ Ce service implémente l'authentification unique (SSO) via **SAML 2** en de basa Il gère l'authentification avec un fournisseur d'identité (IdP) tel que **Keycloak**, **Pages Blanches** ou tout autre fournisseur compatible SAML. Le service utilise la librairie **SAMLify** pour s'interfacer avec le SSO et faciliter la gestion des requêtes et des réponses SAML. + +## Workflow + +
+ +LABEL s'interface avec le SSO Pages Blanches pour assurer l'authentification des utilisateurs via le protocole SAML 2. + +Le diagramme ci-dessous illustre l'interaction entre l'application Label et le SSO Pages Blanches pour l'authentification. + +Label auth workflow + +1. Lorsqu'un utilisateur accède à LABEL, le frontend interroge le backend pour vérifier son authentification. Si l'utilisateur n'est pas authentifié, le backend redirige vers le fournisseur d'identité (IdP) avec les paramètres nécessaires. + + +2. L'utilisateur se connecte sur la page SSO et, après authentification, le fournisseur d'identité génère et envoie une assertion SAML au backend de LABEL via l'URL de l'ACS. + + +3. Le backend valide l'assertion SAML pour garantir l'intégrité des données et la conformité de la signature numérique. + + +4. Après validation, l'accès aux ressources sécurisées est accordé, permettant à l'utilisateur de poursuivre sa session authentifiée.
+ + +> L'application LABEL utilise le module SSO comme dépendance pour son intégration avec le système d'authentification unique (SSO). Les spécificités de cette intégration sont documentées dans le [readme](packages/generic/sso/README.md) du module SSO. + + ## Prérequis - **Node.js** (version 16 ou plus récente) diff --git a/packages/generic/sso/README.md b/packages/generic/sso/README.md index a810ec9a0..65771e727 100644 --- a/packages/generic/sso/README.md +++ b/packages/generic/sso/README.md @@ -7,6 +7,27 @@ This service implements Single Sign-On (SSO) using **SAML 2**, built on the **Ne It facilitates authentication with Identity Providers (IdP) such as **Keycloak**, **Pages Blanches**, or any other SAML-compatible provider. The service utilizes the **SAMLify** library to interface with SSO, simplifying the management of SAML requests and responses. +## Workflow +
+LABEL integrates with the SSO to facilitate user authentication using the SAML 2.0 protocol. + +The diagram below illustrates the interaction between the Label application and the Pages Blanches SSO for authentication. + +Label auth workflow + +1. When a user initiates access to LABEL, the frontend communicates with the backend to check the user’s authentication status. If the user is not authenticated, the backend initiates a redirect to the identity provider (IdP), passing the necessary authentication parameters. + + +2. The user is then prompted to log in via the SSO page. Upon successful authentication, the identity provider generates a SAML assertion and sends it to LABEL's backend via the Assertion Consumer Service (ACS) URL. + + +3. The backend processes and validates the SAML assertion to ensure data integrity and verify the authenticity of the digital signature + + +4. After validation, access to secured resources is granted, allowing the user to continue their authenticated session. + +
+ ## Prerequisites - **Node.js** (version 16 or higher) diff --git a/docs/images/LABEL_auth_workflow.png b/packages/generic/sso/docs/images/LABEL_auth_workflow.png similarity index 100% rename from docs/images/LABEL_auth_workflow.png rename to packages/generic/sso/docs/images/LABEL_auth_workflow.png diff --git a/packages/generic/sso/src/api/saml/saml.service.spec.ts b/packages/generic/sso/src/api/saml/saml.service.spec.ts index 521af5f9f..965b16515 100644 --- a/packages/generic/sso/src/api/saml/saml.service.spec.ts +++ b/packages/generic/sso/src/api/saml/saml.service.spec.ts @@ -6,7 +6,7 @@ import * as validator from '@authenio/samlify-node-xmllint'; jest.mock('fs'); jest.mock('samlify', () => { - const Extractor = { + const mockExtractor = { loginResponseFields: jest.fn().mockReturnValue([]), extract: jest.fn().mockReturnValue({ // Simuler la réponse extraite ici @@ -15,9 +15,6 @@ jest.mock('samlify', () => { attributes: { nom: 'Xx', prenom: 'Alain', role: ['ANNOTATEUR'], email: 'mail.573@justice.fr', name: 'Xx Alain' }, }), }; - jest.mock('@authenio/samlify-node-xmllint', () => ({ - validate: jest.fn(() => true), - })); return { // Autres méthodes de samlify @@ -30,7 +27,7 @@ jest.mock('samlify', () => { createLogoutRequest: jest.fn().mockResolvedValue('http://logout-url'), }), IdentityProvider: jest.fn().mockReturnValue({}), - Extractor, + Extractor: mockExtractor, Constants: { namespace: { binding: { @@ -45,6 +42,14 @@ jest.mock('samlify', () => { }; }); +jest.mock('@authenio/samlify-node-xmllint', () => ({ + validate: jest.fn(() => true), +})); + +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ + describe('SamlService', () => { let service: SamlService; @@ -63,22 +68,17 @@ describe('SamlService', () => { }); it('should generate metadata', () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const metadata = service.generateMetadata(); expect(metadata).toEqual(''); expect(samlify.ServiceProvider).toHaveBeenCalled(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access - expect(validator.validate()).toBeTruthy(); + expect(validator.validate()).toBe(true); }); it('should create login request URL', async () => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const loginUrl = await service.createLoginRequestUrl(); expect(loginUrl).toEqual('http://login-url'); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(samlify.ServiceProvider().createLoginRequest).toHaveBeenCalled(); + expect(samlify.ServiceProvider({}).createLoginRequest).toHaveBeenCalled(); }); describe('parseResponse', () => { @@ -113,7 +113,6 @@ describe('SamlService', () => { const result = await service.parseResponse(request); expect(result.extract).toEqual(mockExtract); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access expect(result.extract.attributes.role).toEqual(['role1']); }); @@ -133,7 +132,6 @@ describe('SamlService', () => { const result = await service.parseResponse(request); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access expect(result.extract.attributes.role).toEqual(['role1']); // Vérifie que le rôle est bien traité }); @@ -153,18 +151,14 @@ describe('SamlService', () => { const result = await service.parseResponse(request); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access expect(result.extract.attributes.role.length).toEqual(2); }); }); it('should create logout request URL', async () => { const mockUser = { nameID: 'test.user@label.fr', sessionIndex: undefined }; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const logoutUrl = await service.createLogoutRequestUrl(mockUser); expect(logoutUrl).toEqual('http://logout-url'); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - expect(samlify.ServiceProvider().createLogoutRequest).toHaveBeenCalledTimes(1); + expect(samlify.ServiceProvider({}).createLogoutRequest).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/generic/sso/src/api/saml/saml.service.ts b/packages/generic/sso/src/api/saml/saml.service.ts index d955c494b..15632639d 100644 --- a/packages/generic/sso/src/api/saml/saml.service.ts +++ b/packages/generic/sso/src/api/saml/saml.service.ts @@ -4,7 +4,10 @@ import * as fs from 'fs'; import * as validator from '@authenio/samlify-node-xmllint'; samlify.setSchemaValidator(validator); - +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ @Injectable() export class SamlService { private sp; @@ -12,8 +15,6 @@ export class SamlService { constructor() { // Initialiser le Service Provider (SP) - - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const spProps = { entityID: process.env.SSO_SP_ENTITY_ID, assertionConsumerService: [ @@ -42,7 +43,6 @@ export class SamlService { this.sp = samlify.ServiceProvider(spProps); // Initialiser l'Identity Provider (IdP) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const idpProps = { metadata: fs.readFileSync(String(process.env.SSO_IDP_METADATA), 'utf8'), encCert: fs.readFileSync(String(process.env.SSO_CERTIFICAT), 'utf8'), @@ -65,20 +65,18 @@ export class SamlService { this.idp = samlify.IdentityProvider(idpProps); } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - generateMetadata() { - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-member-access + generateMetadata(): string { return this.sp.getMetadata(); } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - createLoginRequestUrl() { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + createLoginRequestUrl(): { + context: string; + id: string; + } { return this.sp.createLoginRequest(this.idp, 'redirect'); } - async parseResponse(request: any) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access + async parseResponse(request?: { body?: { SAMLResponse: string } }) { const samlResponse = request?.body?.SAMLResponse; if (!samlResponse) throw new Error('Missing SAMLResponse in request body'); @@ -108,41 +106,40 @@ export class SamlService { }, ]; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const extract = samlify.Extractor.extract(samlContent, extractFields); - // eslint-disable-next-line no-console - //console.log('extract ', extract); + const extract: { + nameID?: string; + sessionIndex?: string; + attributes?: Record; + } = samlify.Extractor.extract(samlContent, extractFields) as { + nameID?: string; + sessionIndex?: string; + attributes?: Record; + }; + const roleKey = process.env.SSO_ATTRIBUTE_ROLE || 'role'; const appName = process.env.SSO_APP_NAME || ''; - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (typeof extract.attributes?.[roleKey] === 'string') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - extract.attributes[roleKey] = [extract.attributes[roleKey]]; - } + if (extract.attributes) { + const roleKeyValue = extract.attributes[roleKey]; + + const normalizedRoles = Array.isArray(roleKeyValue) ? roleKeyValue : roleKeyValue ? [roleKeyValue] : []; - // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - extract.attributes[roleKey] = extract.attributes[roleKey] - ?.filter((role: string) => role.includes(appName)) - .map((role: string) => role.replace(`${appName}:`, '')); + extract.attributes[roleKey] = normalizedRoles + .filter((role) => role.includes(appName)) + .map((role) => role.replace(`${appName}:`, '')); + } return { samlContent, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment extract, }; } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - async createLogoutRequestUrl(user: any) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + async createLogoutRequestUrl(user: { nameID: string; sessionIndex: string }) { return this.sp.createLogoutRequest(this.idp, 'redirect', { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access logoutNameID: user.nameID, - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment id: user.sessionIndex, nameIDFormat: process.env.SSO_NAME_ID_FORMAT, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access sessionIndex: user.sessionIndex, signRequest: true, signatureAlgorithm: process.env.SSO_SIGNATURE_ALGORITHM, From d5c593aeaa75f22787137732f2e8e6236b6c418a Mon Sep 17 00:00:00 2001 From: elhadj Date: Mon, 16 Dec 2024 14:57:42 +0100 Subject: [PATCH 06/20] fix: CASSLAB-125 => delete bearerTokenHandler --- .../localStorage/bearerTokenHandler.ts | 21 ------------------- .../src/services/localStorage/localStorage.ts | 2 -- 2 files changed, 23 deletions(-) delete mode 100644 packages/generic/client/src/services/localStorage/bearerTokenHandler.ts diff --git a/packages/generic/client/src/services/localStorage/bearerTokenHandler.ts b/packages/generic/client/src/services/localStorage/bearerTokenHandler.ts deleted file mode 100644 index 67390ef68..000000000 --- a/packages/generic/client/src/services/localStorage/bearerTokenHandler.ts +++ /dev/null @@ -1,21 +0,0 @@ -const BEARER_TOKEN_STORAGE_KEY = 'BEARER_TOKEN'; - -export { bearerTokenHandler }; - -const bearerTokenHandler = { - set, - remove, - get, -}; - -function set(bearerToken: string) { - localStorage.setItem(BEARER_TOKEN_STORAGE_KEY, bearerToken); -} - -function remove() { - localStorage.removeItem(BEARER_TOKEN_STORAGE_KEY); -} - -function get() { - return localStorage.getItem(BEARER_TOKEN_STORAGE_KEY); -} diff --git a/packages/generic/client/src/services/localStorage/localStorage.ts b/packages/generic/client/src/services/localStorage/localStorage.ts index 640e706c4..6774fb5a7 100644 --- a/packages/generic/client/src/services/localStorage/localStorage.ts +++ b/packages/generic/client/src/services/localStorage/localStorage.ts @@ -1,4 +1,3 @@ -import { bearerTokenHandler } from './bearerTokenHandler'; import { adminViewHandler, adminViews } from './adminViewHandler'; import { displayModeHandler } from './displayModeHandler'; import { @@ -34,7 +33,6 @@ export type { treatedDocumentFilterType, untreatedDocumentFilterType, toBeConfir const localStorage = { adminViewHandler, - bearerTokenHandler, displayModeHandler, publishableDocumentsStateHandler, treatedDocumentsStateHandler, From f9723aae78f6f502e00f8eaa099c275c245b732d Mon Sep 17 00:00:00 2001 From: DIALLO SAAD BOUH Date: Mon, 16 Dec 2024 15:48:01 +0100 Subject: [PATCH 07/20] doc: readme update --- packages/generic/sso/README.fr.md | 4 ---- packages/generic/sso/README.md | 2 -- 2 files changed, 6 deletions(-) diff --git a/packages/generic/sso/README.fr.md b/packages/generic/sso/README.fr.md index 447fa3ec0..e084573e3 100644 --- a/packages/generic/sso/README.fr.md +++ b/packages/generic/sso/README.fr.md @@ -8,8 +8,6 @@ Le service utilise la librairie **SAMLify** pour s'interfacer avec le SSO et fac
-LABEL s'interface avec le SSO Pages Blanches pour assurer l'authentification des utilisateurs via le protocole SAML 2. - Le diagramme ci-dessous illustre l'interaction entre l'application Label et le SSO Pages Blanches pour l'authentification. Label auth workflow @@ -26,8 +24,6 @@ Le diagramme ci-dessous illustre l'interaction entre l'application Label et le S 4. Après validation, l'accès aux ressources sécurisées est accordé, permettant à l'utilisateur de poursuivre sa session authentifiée.
-> L'application LABEL utilise le module SSO comme dépendance pour son intégration avec le système d'authentification unique (SSO). Les spécificités de cette intégration sont documentées dans le [readme](packages/generic/sso/README.md) du module SSO. - ## Prérequis diff --git a/packages/generic/sso/README.md b/packages/generic/sso/README.md index 65771e727..717d33fc7 100644 --- a/packages/generic/sso/README.md +++ b/packages/generic/sso/README.md @@ -9,8 +9,6 @@ The service utilizes the **SAMLify** library to interface with SSO, simplifying ## Workflow
-LABEL integrates with the SSO to facilitate user authentication using the SAML 2.0 protocol. - The diagram below illustrates the interaction between the Label application and the Pages Blanches SSO for authentication. Label auth workflow From 7e5505be15b47803689ec90af51ade81d4639e82 Mon Sep 17 00:00:00 2001 From: Elhadj NDiaye Date: Wed, 22 Jan 2025 18:06:30 +0100 Subject: [PATCH 08/20] Fix/update user db by user sso --- package.json | 2 +- .../backend/src/modules/sso/service/index.ts | 3 +- .../modules/sso/service/ssoService.spec.ts | 262 +++++++----------- .../src/modules/sso/service/ssoService.ts | 115 +++++--- .../repository/buildFakeUserRepository.ts | 12 + .../user/repository/buildUserRepository.ts | 9 + .../repository/customUserRepositoryType.ts | 5 + .../modules/user/service/userService/index.ts | 2 + .../user/service/userService/updateUser.ts | 17 ++ 9 files changed, 222 insertions(+), 205 deletions(-) create mode 100644 packages/generic/backend/src/modules/user/service/userService/updateUser.ts diff --git a/package.json b/package.json index d7d712d27..b7c3b6eb6 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "init:db": "scripts/initializeTestDb.sh", "lint": "lerna run lint", "start:backend": "lerna run --scope @label/cour-de-cassation start --stream", - "start:backend:dev": "nodemon", + "start:backend:dev": "RUN_MODE=LOCAL nodemon", "start:client:dev": "yarn compile:client && cd packages/generic/client && yarn start", "test": "lerna run test", "type": "lerna run type" diff --git a/packages/generic/backend/src/modules/sso/service/index.ts b/packages/generic/backend/src/modules/sso/service/index.ts index 41e055bcc..d95896ef3 100644 --- a/packages/generic/backend/src/modules/sso/service/index.ts +++ b/packages/generic/backend/src/modules/sso/service/index.ts @@ -1,5 +1,5 @@ import { buildCallAttemptsRegulator } from 'sder-core'; -import { acs, getMetadata, login, logout } from './ssoService'; +import { acs, getMetadata, getUserByEmail, login, logout } from './ssoService'; const DELAY_BETWEEN_LOGIN_ATTEMPTS_IN_SECONDS = 1 * 1000; @@ -16,6 +16,7 @@ function buildSsoService() { getMetadata, login, logout, + getUserByEmail, }; } diff --git a/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts b/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts index e1dfa89eb..36a4e9d34 100644 --- a/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts +++ b/packages/generic/backend/src/modules/sso/service/ssoService.spec.ts @@ -1,14 +1,12 @@ import { acs, + compareUser, getMetadata, + getUserFromSSO, login, logout, - samlService, setUserSessionAndReturnRedirectUrl, } from './ssoService'; -import { buildUserRepository } from '../../user'; -import { buildUserService } from '../../user/service/userService'; -import { Request } from 'express'; import { userType } from '@label/core'; jest.mock('@label/sso', () => ({ @@ -23,9 +21,12 @@ jest.mock('@label/sso', () => ({ parseResponse: jest.fn().mockResolvedValue({ extract: { nameID: 'test@example.com', - name: 'Test User', - role: 'annotator', - sessionIndex: 'session123', + sessionIndex: '63e7d8764f1a0a2f6c123456', + attributes: { + role: ['ANNOTATOR'], + email: 'test@example.com', + name: 'Test User', + }, }, }), })), @@ -36,28 +37,30 @@ jest.mock('../../../utils/logger', () => ({ error: jest.fn(), }, })); -jest.mock('../../user', () => ({ - userService: { - createUser: jest.fn(), - }, -})); jest.mock('../../user', () => ({ buildUserRepository: jest.fn().mockImplementation(() => ({ - findByEmail: jest.fn().mockResolvedValue({ - _id: '123', + findByEmail: jest.fn().mockResolvedValueOnce({ + _id: '63e7d8764f1a0a2f6c123457', email: 'test@example.com', name: 'Test User', role: 'annotator', }), })), + userService: { + createUser: jest.fn(), + updateUser: jest.fn().mockResolvedValue({ success: true }), + }, })); process.env.SSO_FRONT_SUCCESS_CONNEXION_ANNOTATOR_URL = 'http://localhost:55432/label/annotation'; -(process.env.SSO_FRONT_SUCCESS_CONNEXION_ADMIN_SCRUTATOR_URL = - 'http://localhost:55432/label/admin/main/summary'), - (process.env.SSO_FRONT_SUCCESS_CONNEXION_PUBLICATOR_URL = - 'http://localhost:55432/label/publishable-documents'); +process.env.SSO_FRONT_SUCCESS_CONNEXION_ADMIN_SCRUTATOR_URL = + 'http://localhost:55432/label/admin/main/summary'; +process.env.SSO_FRONT_SUCCESS_CONNEXION_PUBLICATOR_URL = + 'http://localhost:55432/label/publishable-documents'; +process.env.SSO_ATTRIBUTE_ROLE = 'role'; +process.env.SSO_APP_ROLES = 'admin,annotator,publicator,scrutator'; +process.env.SSO_APP_NAME = 'LABEL'; describe('SSO CNX functions', () => { describe('getMetadataSso', () => { @@ -67,6 +70,64 @@ describe('SSO CNX functions', () => { }); }); + describe('acsSso', () => { + it('should update an existing user if there are differences', async () => { + const mockReq = { + body: { SAMLResponse: 'mock-saml-response' }, + session: {}, + }; + const result = await acs(mockReq); + + expect(result).toContain( + process.env.SSO_FRONT_SUCCESS_CONNEXION_ANNOTATOR_URL, + ); + }); + }); + + describe('compareUser', () => { + it('should return true if name or role is different', () => { + const userSSO = ({ + name: 'New Name', + role: 'annotator', + email: 'test@example.com', + _id: '63e7d8764f1a0a2f6c123457', + } as unknown) as userType; + const userDB = ({ + name: 'Old Name', + role: 'admin', + email: 'test@example.com', + _id: '63e7d8764f1a0a2f6c123457', + } as unknown) as userType; + + const result = compareUser(userSSO, userDB); + expect(result).toBe(true); + }); + + it('should return false if name and role are the same', () => { + const userSSO = ({ + name: 'Test User', + role: 'annotator', + email: 'test@example.com', + _id: '63e7d8764f1a0a2f6c123457', + } as unknown) as userType; + const userDB = ({ + name: 'Test User', + role: 'annotator', + email: 'test@example.com', + _id: '63e7d8764f1a0a2f6c123457', + } as unknown) as userType; + + const result = compareUser(userSSO, userDB); + expect(result).toBe(false); + }); + + it('should throw error', () => { + expect(() => compareUser(undefined, undefined)).toThrowError( + `Both objects must be defined.`, + ); + }); + }); + describe('loginSso', () => { it('should return login URL', async () => { const loginUrl = await login(); @@ -78,72 +139,26 @@ describe('SSO CNX functions', () => { it('should return logout URL', async () => { const logoutUrl = await logout({ nameID: 'test-user-id', - sessionIndex: 'test-session-index', + sessionIndex: '63e7d8764f1a0a2f6c123431', }); expect(logoutUrl).toBe('logout-url'); }); }); - describe('acsSso', () => { - it('should handle ACS SSO and return a redirect URL', async () => { - const mockReq = { - body: { SAMLResponse: 'mock-saml-response' }, - session: { - user: { - _id: '123', - email: 'test@example.com', - name: 'Test User', - role: 'annotator', - }, - }, - }; - const redirectUrl = await acs(mockReq); - expect(redirectUrl).toContain( - process.env.SSO_FRONT_SUCCESS_CONNEXION_ANNOTATOR_URL, - ); - expect(mockReq.session.user).toBeDefined(); - expect(mockReq.session.user.email).toBe('test@example.com'); - }); - - const mockReq = { - body: { SAMLResponse: 'dummyResponse' }, - }; - - it('should handle the case where user does not exist and is auto-provisioned', async () => { - const mockNewUser = { email: 'newuser@example.com' }; - - (samlService.parseResponse as jest.Mock).mockResolvedValue({ - extract: { - nameID: 'newuser@example.com', - sessionIndex: 'session123', - attributes: { - role: ['ANNOTATEUR'], - email: 'newuser@example.com', - name: 'New User', - }, + describe('getUserFromSSO when throw error', () => { + expect(() => { + getUserFromSSO({ + nameID: 'fake@example.com', + sessionIndex: '63e7d8764f1a0a2f6c123478', + attributes: { + role: ['ANNOT'], + email: 'fake@example.com', + name: 'Fake User', }, }); - - const userRepository = buildUserRepository(); - jest - .spyOn(userRepository, 'findByEmail') - .mockRejectedValue( - new Error('No matching user for email newuser@example.com'), - ); - - const userService = buildUserService(); - jest - .spyOn(userService, 'createUser') - .mockResolvedValue('User created successfully'); - - jest - .spyOn(userRepository, 'findByEmail') - .mockReturnValue(mockNewUser as any); - - const result = await acs(mockReq); - - expect(result).toBeDefined(); - }); + }).toThrowError( + "User fake@example.com, role annot doesn't exist in application LABEL", + ); }); describe('setUserSessionAndReturnRedirectUrl', () => { @@ -157,98 +172,11 @@ describe('SSO CNX functions', () => { params: { id: '123456789', }, - } as unknown) as Request; - - it('should return the correct URL for annotator role', () => { - const user = ({ - _id: '1', - name: 'Annotator', - role: 'annotator', - email: 'annotator@test.com', - } as unknown) as userType; - const result = setUserSessionAndReturnRedirectUrl( - mockRequest, - user, - 'test-session-index', - ); - expect(mockRequest.session.user).toEqual({ - ...user, - sessionIndex: 'test-session-index', - }); - expect(result).toEqual( - process.env.SSO_FRONT_SUCCESS_CONNEXION_ANNOTATOR_URL, - ); - }); - - it('should return the correct URL for admin role', () => { - const user = ({ - _id: '2', - name: 'Admin', - role: 'admin', - email: 'admin@test.com', - } as unknown) as userType; - const result = setUserSessionAndReturnRedirectUrl( - mockRequest, - user, - 'test-session-index', - ); - - expect(mockRequest.session.user).toEqual({ - ...user, - sessionIndex: 'test-session-index', - }); - expect(result).toEqual( - process.env.SSO_FRONT_SUCCESS_CONNEXION_ADMIN_SCRUTATOR_URL, - ); - }); - - it('should return the correct URL for scrutator role', () => { - const user = ({ - _id: '3', - name: 'Scrutator', - role: 'scrutator', - email: 'scrutator@test.com', - } as unknown) as userType; - const result = setUserSessionAndReturnRedirectUrl( - mockRequest, - user, - 'test-session-index', - ); - - expect(mockRequest.session.user).toEqual({ - ...user, - sessionIndex: 'test-session-index', - }); - expect(result).toEqual( - process.env.SSO_FRONT_SUCCESS_CONNEXION_ADMIN_SCRUTATOR_URL, - ); - }); - - it('should return the correct URL for publicator role', () => { - const user = ({ - _id: '4', - name: 'Publicator', - role: 'publicator', - email: 'publicator@test.com', - } as unknown) as userType; - const result = setUserSessionAndReturnRedirectUrl( - mockRequest, - user, - 'test-session-index', - ); - - expect(mockRequest.session.user).toEqual({ - ...user, - sessionIndex: 'test-session-index', - }); - expect(result).toEqual( - process.env.SSO_FRONT_SUCCESS_CONNEXION_PUBLICATOR_URL, - ); - }); + } as unknown) as Partial; it('should throw an error for an invalid role', () => { const user = ({ - _id: '5', + _id: '63e7d8764f1a0a2f6c123489', name: 'InvalidRole', role: 'invalidRole', email: 'invalid@test.com', @@ -258,7 +186,7 @@ describe('SSO CNX functions', () => { setUserSessionAndReturnRedirectUrl( mockRequest, user, - 'test-session-index', + '63e7d8764f1a0a2f6c123456', ); }).toThrowError("Role doesn't exist in label"); }); diff --git a/packages/generic/backend/src/modules/sso/service/ssoService.ts b/packages/generic/backend/src/modules/sso/service/ssoService.ts index 16c712121..f200f95b2 100644 --- a/packages/generic/backend/src/modules/sso/service/ssoService.ts +++ b/packages/generic/backend/src/modules/sso/service/ssoService.ts @@ -24,6 +24,7 @@ export interface ParseResponseResult { /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable-next-line @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ function ssoSamlService() { return new SamlService(); @@ -55,15 +56,37 @@ export async function acs(req: any) { )) as ParseResponseResult; const { extract } = response; - try { - const user = (await getUserByEmail(extract?.nameID)) as userType; + const userSSO = getUserFromSSO(extract); + try { + const userDB = (await getUserByEmail(extract?.nameID)) as userType; + const hasDiff = compareUser(userSSO, userDB); + let currentUser = userDB; + if (hasDiff) { + currentUser = { ...userSSO, _id: userDB._id }; + + const resp = await userService.updateUser({ + userId: idModule.lib.buildId(userDB._id), + name: userSSO.name, + role: userSSO.role, + }); + await logger.log({ + operationName: 'updateUser', + msg: resp.success + ? `user ${userDB.email} name and role are updated successfully.` + : `The name and role of user ${userDB.email} could not be updated successfully.`, + }); + } await logger.log({ operationName: 'user connected', - msg: user.email, + msg: userDB.email, }); - return setUserSessionAndReturnRedirectUrl(req, user, extract?.sessionIndex); + return setUserSessionAndReturnRedirectUrl( + req, + currentUser, + extract?.sessionIndex, + ); } catch (err: unknown) { await logger.log({ operationName: `catch autoprovision user ${JSON.stringify(err)}`, @@ -74,37 +97,12 @@ export async function acs(req: any) { err instanceof Error && err.message.includes(`No matching user for email ${extract?.nameID}`) ) { - const { attributes } = extract; - const roles = (attributes[ - `${process.env.SSO_ATTRIBUTE_ROLE}` - ] as string[]).map((item: string) => item.toLowerCase()) as string[]; - - const appRoles = (process.env.SSO_APP_ROLES as string) - .toLowerCase() - .split(','); - const userRolesInAppRoles = every(roles, (element) => - includes(appRoles, element), - ); - - if (!roles.length || !userRolesInAppRoles) { - const errorMsg = `User ${extract.nameID}, role ${roles} doesn't exist in application ${process.env.SSO_APP_NAME}`; - logger.error({ operationName: 'ssoService', msg: errorMsg }); - throw new Error(errorMsg); - } - - const newUser = { - name: - (attributes[`${process.env.SSO_ATTRIBUTE_FULLNAME}`] as string) || - `${attributes[`${process.env.SSO_ATTRIBUTE_NAME}`] as string} ${ - attributes[`${process.env.SSO_ATTRIBUTE_FIRSTNAME}`] as string - }`, - email: attributes[`${process.env.SSO_ATTRIBUTE_MAIL}`] as string, - role: roles[0] as 'annotator' | 'scrutator' | 'admin' | 'publicator', - }; - - await userService.createUser(newUser); - - const createdUser = (await getUserByEmail(newUser.email)) as userType; + await userService.createUser({ + name: userSSO.name, + email: userSSO.email, + role: userSSO.role, + }); + const createdUser = (await getUserByEmail(userSSO.email)) as userType; await logger.log({ operationName: `Auto-provided user`, @@ -128,7 +126,7 @@ export async function getUserByEmail(email: string) { } export function setUserSessionAndReturnRedirectUrl( - req: Request, + req: Request | any, user: userType, sessionIndex: string, ) { @@ -158,3 +156,48 @@ export function setUserSessionAndReturnRedirectUrl( return roleToUrlMap[user.role]; } + +export function getUserFromSSO( + extract: ParseResponseResult['extract'], +): userType { + const { attributes } = extract; + const roles = (attributes[ + `${process.env.SSO_ATTRIBUTE_ROLE}` + ] as string[]).map((item: string) => item.toLowerCase()) as string[]; + + const appRoles = (process.env.SSO_APP_ROLES as string) + .toLowerCase() + .split(','); + const userRolesInAppRoles = every(roles, (element) => + includes(appRoles, element), + ); + + if (!roles.length || !userRolesInAppRoles) { + const errorMsg = `User ${extract.nameID}, role ${roles} doesn't exist in application ${process.env.SSO_APP_NAME}`; + logger.error({ operationName: 'getUserFromSSO', msg: errorMsg }); + throw new Error(errorMsg); + } + + return { + name: + (attributes[`${process.env.SSO_ATTRIBUTE_FULLNAME}`] as string) || + `${attributes[`${process.env.SSO_ATTRIBUTE_NAME}`] as string} ${ + attributes[`${process.env.SSO_ATTRIBUTE_FIRSTNAME}`] as string + }`, + email: attributes[`${process.env.SSO_ATTRIBUTE_MAIL}`] as string, + role: roles[0] as 'annotator' | 'scrutator' | 'admin' | 'publicator', + _id: idModule.lib.buildId(), + }; +} + +export function compareUser( + userSSO: userType | undefined, + userDB: userType | undefined, +): boolean { + if (!userSSO || !userDB) { + const errorMsg = `Both objects must be defined.`; + logger.error({ operationName: 'compareUser', msg: errorMsg }); + throw new Error(errorMsg); + } + return userSSO.name !== userDB.name || userSSO.role !== userDB.role; +} diff --git a/packages/generic/backend/src/modules/user/repository/buildFakeUserRepository.ts b/packages/generic/backend/src/modules/user/repository/buildFakeUserRepository.ts index d4fc55683..7ec34a108 100644 --- a/packages/generic/backend/src/modules/user/repository/buildFakeUserRepository.ts +++ b/packages/generic/backend/src/modules/user/repository/buildFakeUserRepository.ts @@ -18,5 +18,17 @@ const buildFakeUserRepository = buildFakeRepositoryBuilder< } return result; }, + async updateNameAndRoleById(userId, name, role) { + const storedUserIndex = collection.findIndex(({ _id }) => _id === userId); + if (storedUserIndex === -1) { + return { success: false }; + } + collection[storedUserIndex] = { + ...collection[storedUserIndex], + name, + role, + }; + return { success: true }; + }, }), }); diff --git a/packages/generic/backend/src/modules/user/repository/buildUserRepository.ts b/packages/generic/backend/src/modules/user/repository/buildUserRepository.ts index 60dedf140..997832927 100644 --- a/packages/generic/backend/src/modules/user/repository/buildUserRepository.ts +++ b/packages/generic/backend/src/modules/user/repository/buildUserRepository.ts @@ -26,5 +26,14 @@ const buildUserRepository = buildRepositoryBuilder< } return result; }, + async updateNameAndRoleById(userId, name, role) { + const { result } = await collection.updateOne( + { _id: userId }, + { $set: { name, role } }, + ); + return { + success: result.ok === 1, + }; + }, }), }); diff --git a/packages/generic/backend/src/modules/user/repository/customUserRepositoryType.ts b/packages/generic/backend/src/modules/user/repository/customUserRepositoryType.ts index 95c369bf1..01f0ae66b 100644 --- a/packages/generic/backend/src/modules/user/repository/customUserRepositoryType.ts +++ b/packages/generic/backend/src/modules/user/repository/customUserRepositoryType.ts @@ -4,4 +4,9 @@ export type { customUserRepositoryType }; type customUserRepositoryType = { findByEmail: (email: userType['email']) => Promise; + updateNameAndRoleById: ( + userId: userType['_id'], + name: userType['name'], + role: userType['role'], + ) => Promise<{ success: boolean }>; }; diff --git a/packages/generic/backend/src/modules/user/service/userService/index.ts b/packages/generic/backend/src/modules/user/service/userService/index.ts index 1942cfca0..f1a5bdfae 100644 --- a/packages/generic/backend/src/modules/user/service/userService/index.ts +++ b/packages/generic/backend/src/modules/user/service/userService/index.ts @@ -7,6 +7,7 @@ import { fetchUsersByAssignations } from './fetchUsersByAssignations'; import { fetchUsersByIds } from './fetchUsersByIds'; import { fetchWorkingUsers } from './fetchWorkingUsers'; import { signUpUser } from './signUpUser'; +import { updateUser } from './updateUser'; export { userService, buildUserService }; @@ -30,5 +31,6 @@ function buildUserService() { fetchWorkingUsers, fetchUserRole, signUpUser, + updateUser, }; } diff --git a/packages/generic/backend/src/modules/user/service/userService/updateUser.ts b/packages/generic/backend/src/modules/user/service/userService/updateUser.ts new file mode 100644 index 000000000..69377ef2d --- /dev/null +++ b/packages/generic/backend/src/modules/user/service/userService/updateUser.ts @@ -0,0 +1,17 @@ +import { userType } from '@label/core'; +import { buildUserRepository } from '../../repository'; + +export { updateUser }; + +async function updateUser({ + userId, + name, + role, +}: { + userId: userType['_id']; + name: string; + role: userType['role']; +}) { + const userRepository = buildUserRepository(); + return await userRepository.updateNameAndRoleById(userId, name, role); +} From f8bc57115d1b3e36c8463b75287aaaef4b9dead6 Mon Sep 17 00:00:00 2001 From: YATERA Boubacar Date: Tue, 11 Feb 2025 18:15:37 +0100 Subject: [PATCH 09/20] pushing sso on dev --- .gitlab-ci.yml | 12 ++- Dockerfile.label-backend | 3 + ansible/group_vars/dev/main.yml | 43 +++++++++- ansible/group_vars/dev/vault.yml | 129 ++++++++++++++++++++++++---- sso_files/certificate.pem | 21 +++++ sso_files/descriptor.txt | 1 + sso_files/keycloack-idp.xml | 89 +++++++++++++++++++ sso_files/privatekey.pem | 27 ++++++ sso_files/sp-keycloack-metadata.txt | 1 + 9 files changed, 304 insertions(+), 22 deletions(-) create mode 100644 sso_files/certificate.pem create mode 100644 sso_files/descriptor.txt create mode 100644 sso_files/keycloack-idp.xml create mode 100644 sso_files/privatekey.pem create mode 100644 sso_files/sp-keycloack-metadata.txt diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f989b05be..d2f62ca9e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,6 +56,7 @@ build_label_backend: - master - re7 - dev + - develop-open-sso tags: - docker @@ -79,6 +80,7 @@ build_label_client: - master - re7 - dev + - develop-open-sso tags: - docker @@ -86,7 +88,8 @@ deploy_label_backend: stage: deploy image: alpine/ansible:2.16.1 script: - - inventaire=$(eval "echo \$$CI_COMMIT_BRANCH") + #- inventaire=$(eval "echo \$$CI_COMMIT_BRANCH") + - inventaire=dev - mkdir /root/.ssh - cat $SSH_KEY > /root/.ssh/id_rsa - cat $KNOWN_HOSTS > /root/.ssh/known_hosts @@ -96,7 +99,7 @@ deploy_label_backend: rules: - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "re7" when: manual - - if: $CI_COMMIT_BRANCH == "dev" + - if: $CI_COMMIT_BRANCH == "dev" || $CI_COMMIT_BRANCH == "develop-open-sso" when: on_success tags: - docker @@ -107,7 +110,8 @@ deploy_label_client: stage: deploy image: alpine/ansible:2.16.1 script: - - inventaire=$(eval "echo \$$CI_COMMIT_BRANCH") + #- inventaire=$(eval "echo \$$CI_COMMIT_BRANCH") + - inventaire=dev - mkdir /root/.ssh - cat $SSH_KEY > /root/.ssh/id_rsa - cat $KNOWN_HOSTS > /root/.ssh/known_hosts @@ -117,7 +121,7 @@ deploy_label_client: rules: - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH == "re7" when: manual - - if: $CI_COMMIT_BRANCH == "dev" + - if: $CI_COMMIT_BRANCH == "dev" || $CI_COMMIT_BRANCH == "develop-open-sso" when: on_success tags: - docker diff --git a/Dockerfile.label-backend b/Dockerfile.label-backend index 27eb829e6..9e9405eb2 100644 --- a/Dockerfile.label-backend +++ b/Dockerfile.label-backend @@ -17,6 +17,8 @@ COPY ./package.json ./ COPY packages/generic/core/package.json ./packages/generic/core/ COPY packages/generic/backend/package.json ./packages/generic/backend/ COPY packages/courDeCassation/package.json ./packages/courDeCassation/ +COPY packages/generic/sso/package.json ./packages/generic/sso/ +COPY ./sso_files ./sso_files/ COPY . . @@ -36,6 +38,7 @@ RUN cd /home/node/packages/generic/core && yarn compile && \ ADD packages/generic/core packages/generic/core ADD packages/generic/backend packages/generic/backend ADD packages/courDeCassation packages/courDeCassation +ADD packages/generic/sso packages/generic/sso WORKDIR /home/node/packages/courDeCassation diff --git a/ansible/group_vars/dev/main.yml b/ansible/group_vars/dev/main.yml index bb3d30e60..8a73eb3b5 100644 --- a/ansible/group_vars/dev/main.yml +++ b/ansible/group_vars/dev/main.yml @@ -1,6 +1,47 @@ -git_branch: dev +#git_branch: dev +git_branch: develop-open-sso sder_mongodb_url: "{{ vault_sder_mongodb_url }}" label_mongodb_url: "{{ vault_label_mongodb_url }}" dbsder_api_key: "{{ vault_dbsder_api_key }}" jwt_private_key: "{{ vault_jwt_private_key }}" + +#SSO VARIABLES +COOKIE_PRIVATE_KEY: "{{ vault_cookie_private_key }}" + +#Service Provider (SP) +SSO_SP_ENTITY_ID: "{{ vault_sso_sp_entity_id }}" +SSO_SP_ASSERTION_CONSUMER_SERVICE_LOCATION: "{{ vault_sso_sp_assertion_consumer_service_location }}" + +#Identity Provider (IdP) +SSO_IDP_METADATA: "{{ vault_sso_idp_metadata }}" +SSO_IDP_SINGLE_SIGN_ON_SERVICE_LOCATION: "{{ vault_sso_idp_single_sign_on_service_location }}" +SSO_IDP_SINGLE_LOGOUT_SERVICE_LOCATION: "{{ vault_sso_idp_single_logout_service_location }}" + +SSO_CERTIFICAT: "{{ vault_sso_certificat }}" +SSO_SP_PRIVATE_KEY: "{{ vault_sso_sp_private_key }}" +# Les valeurs possibles du SSO_NAME_ID_FORMAT sont le IDP metadata.xml +SSO_NAME_ID_FORMAT: "{{ vault_sso_name_id_format }}" +SSO_SIGNATURE_ALGORITHM: "{{ vault_sso_signature_algorithm }}" + +# Authentication +SESSION_DURATION: "{{ vault_session_duration }}" +# FRONT END +SSO_FRONT_SUCCESS_CONNEXION_ANNOTATOR_URL: "{{ vault_sso_front_success_connexion_annotator_url }}" +SSO_FRONT_SUCCESS_CONNEXION_ADMIN_SCRUTATOR_URL: "{{ vault_sso_front_success_connexion_admin_scrutator_url }}" +SSO_FRONT_SUCCESS_CONNEXION_PUBLICATOR_URL: "{{ vault_sso_front_success_connexion_publicator_url }}" +# SSO URL du back à setter dans le serveur du client (front react) +REACT_APP_BACKEND_API_URL: "{{ vault_react_app_backend_api_url }}" + +#ATTRIBUTS KEYS +SSO_ATTRIBUTE_NAME: "{{ vault_sso_attribute_name }}" +SSO_ATTRIBUTE_FIRSTNAME: "{{ vault_sso_attribute_firstname }}" +SSO_ATTRIBUTE_FULLNAME: "{{ vault_sso_attribute_fullname }}" +SSO_ATTRIBUTE_MAIL: "{{ vault_sso_attribute_mail }}" +SSO_ATTRIBUTE_ROLE: "{{ vault_sso_attribute_role }}" + +#APPLICATION NAME +SSO_APP_NAME: "{{ vault_sso_app_name }}" + +#APPLICATION ROLES +SSO_APP_ROLES: "{{ vault_sso_app_roles }}" # pour la prod adminitrator pour harmoniser diff --git a/ansible/group_vars/dev/vault.yml b/ansible/group_vars/dev/vault.yml index e6542a8c3..dc869d1ad 100644 --- a/ansible/group_vars/dev/vault.yml +++ b/ansible/group_vars/dev/vault.yml @@ -1,18 +1,113 @@ $ANSIBLE_VAULT;1.1;AES256 -31343035396337353338646631636530663533343035363962353566613738656536346130346136 -3732653063633037306232346138333236636235373635320a333163343462363439333932303064 -62613833383533353761303766383866356661646166613130626562623430336433393666346361 -3030646438353033320a326630313466633836353335656635306535383838366535653939333132 -62356238656164616663363432313365613062333162343931363535346665663537656565626565 -30366264636230316632363935653765353165303530373264396461393065303031303265353262 -37333439666666336138643861636434366666666630666232376535306663383230613633623438 -36323533323538383430333264323534373036396438316666613738353337643230386666636639 -66663135393264646662663738316133653063383233323732373436316139316163313232313662 -31356161653534633838646266383331623663383533643761626564353931663461383939633132 -33316465626534313365343966353036623863333033643837353839313165656433653431376434 -36653437663630333434303833333661653265616432323939663263633432653664303936653161 -32326361633436376162643338666161396339366230663464323134643031393235633335363964 -66363562666465616338663365663034343339626564346435343539363632666363346535326562 -36653965643361363131353561303033643033396339303830343830663165346137346533313637 -32616165323236333636643036373130646465666630326364643764353465623139326164646533 -38653930393834313030343233323232356638653836326365363637343961663765 +38613035643133653830643033363663303463353166613063313362353839396162373234346334 +3164326239393439646437326338623632613133336230320a636165353837313039643136363066 +32653134373734613265356531353836306262643566333832646233373034346436346132646436 +6335366531376535630a343661363231356636353932313663306530373035316561663564383462 +62373866393035663862306261356331656566643764663038656165363335346331626130326433 +31396130663436363536343463626561353965383463353330633235383833343065633332626236 +30613732303036373531633737393562323166326264653430363738343732313766333733393032 +37316533333837643733313164626461626465353637343834336233353562666261663761646539 +38623936353661643663353363313031383634613935616335373430336631393537366232643335 +37373035376132636539366632646234383435373364386537306361333834326365636533313062 +34326466613262353764376664636162396634313438643463353864343538313163333236366261 +36356534363232373438633963303130616436623735343939656130306533393433336136363130 +36383837613739616266323432653233333635623863393065346633323663613862363763623535 +39633661613565363039623637636335316165316135353361656661313436383933333633613237 +30396534656561653832626266366364643762333665663866396261306437363865633734363161 +61336135393934303862636337376331363231613132313161303636343263393730333237353733 +36383161623462323839636535656661643963363536383234643061613265373164353861396131 +39613839646430376661636332396465363331323463643932646636613065316138386666353535 +38393634656530363764383736636432333633376665333434303332666237303963326631626336 +66363430353533396531363066303265616637626662393736373366313266303436326466623339 +66313639373138306361393566613063383638306535653663323939356230363433373837643937 +38646163643763363932333039326131666564613332636261623939666335316164393364363363 +36316332313062313262393162626461326433633632613666343936626232636162383734626232 +63346565653238633733623837613432623931303031643435326638666238653839666264303565 +63663766636237303066633838646534346662346665633338623636396433313865353861313365 +31306135333639656434656461623334383165393163653066386230643631323936393464376461 +65313938333763366233656531316532376431303763356337666436343866653262336635333834 +61623939343264386138613137643631646630306463633761316439383561396637303136613633 +32643533633164393061646566613735393936373538666134393333323063643262356533306335 +34376365306330366430646362616563303936653531383764616263323062666338336134336638 +35643562343331323861643236386639633932643161373330356565666661333234303932663636 +34336166376133323961366634376439623838653238333539373136336563326438666534303636 +65313530653261613034383034666162373133626131393364636363323635313636353364663836 +39343661343638373837353236623630306130323364313563376131353336303936306563396461 +62313830306534356163353531306635313235663730663730643332316232303833303732646366 +39336235643438383364663362643737636433653133373362366439343534333463323839653333 +62653538343739383366623135333731613930613966653038313837303038373764633432343739 +37613431666161346163376464653038633832633666303434373561333533323830396530636631 +31383638643033663861323539353662313430393334643862363730666364366263613031636435 +39356532373239353364333062316465363339306436393335656631326463376465343531663936 +65613865303763376532303032346561306636616166313036386331316232333264323631396264 +33356165646661343965366436353465303937303434636465326264363665636633666635336366 +61303333356339323138666634646462303237346535623034633039343938343965363232663637 +39396431653236636139343334656335306630373162353762383765393137643737656663383832 +39653664373836396238313332623565393730633930386465666362343565353736363938646530 +61633266303133363633363666396264346333326235316337346539313336303537363966636630 +65346365633238363163396331613965326465646231393264613537366363333731333133323232 +37326664333938306536613262383465643431643361643765373965633230633039343338346665 +64636263643331633065376361646566613064613766363266643730343937613239363164393137 +62623035343934663230633632313137303630326533373337636563646466313437353638323533 +61616239646531613539616630356439353434323838663431396663656362653139336634313562 +66346237653865343666666664666537613338323830346239643437633866343362613966623430 +35373439633433356430333431623734393364376232623933616266386431396165653839656430 +33663238313534663233653261383836376337613939653935616365613839633762626633363266 +37643661623837366264323265616662373332313133366661366530376534363333333839656265 +35383335343430323131663435623033663065353934306664653462616664616436373531373832 +65636662396163663734643066643132323336326137353632656430376362303134376636643562 +61323531623066633365633463353038323764616438323562313765376162313964636134386139 +39326562393866666365633565393965326263323662396236626165663239666238353736313437 +65376137653332303335646463353138646236343463303266323633306435336563386164353531 +38376361386530373531616234613961336531313562306532303630333964656465666530663565 +37316333386132376234323137623365363639336433336337363563383336396436376631336439 +39353061303465356139363534366564363838336635636566363736303637396535353135626135 +36363566646462383662663765323039333365393237333035613533313661663965613762373137 +32323632383339373830656338356237613534616436623836643736613066323164623930636538 +31366330653932326636353136333062373430326238316236633133346136336633316666393161 +36356565316663396161663666363232666532356166623366306139643538383961653664306231 +38396665383633636334653363643233653039623132376238633339346138623266393863613765 +39343364653438303762633163353965313830383366346366333835333332376131323535333434 +66343837656563646138616234653935346463643365343134633964366164326566333062393033 +64313437373761636337386136383664666662376261626334613432633433373835383132373537 +62316265393263613332363062353534616333646566386337356162323537386536666564653564 +30643530326432643263326232666330373738653834613964393532623764626138323038616662 +35316662303133343330376534613534363732333764303164383935666563633465383766303962 +61393463663562666639636433306639353130313336353735323932666234376534613033653934 +30363938656665356362343761386561346362333366646433353763613933633363616535333030 +65653834613665633135373064323138313139643065376534613262643763636237306536373839 +36613364316566323831396562383130333032646131623639643864306538376365633630646535 +39646336646464633263653036623865363035363436363432366261326364613532316262313763 +37306532363766663863363265653264396565343137333763663062386431393138636665353337 +30363863616536656635666264653665373466626238373732363239356562666530313032373433 +39336336343561653363613364313538616262623430613032386461653433356366653339643063 +35353738643862356631386631373338353334613337316436336239646135363437356432643734 +33353337633932623765363138323963633135663464643861323137386137346130376139653264 +65633934313232613830353030373661653439373834353262323162663465626266393837393663 +32656364353532666164373864613730656638643536336238356563346131363538316133346533 +37663135646561303063393061633332363364393939646631663432336561643131343938623161 +37613634373335666537396664643438646666346237336335363862323536613636396637373131 +32303761323665336261323866363064626436356235663637353433333965326238303030393331 +35656131333137643062656333613336353131393036343236383032663261653662393065643161 +63393866353962393734643932636432306365653037303865386531613630663337373536653335 +38366662636264353531633465326232316337376361663238646633616336666331663862363535 +33393862623232643561393566333161386566363064633264366562393936343364373736613661 +38616462306563663836363264306238313130316466663430363432653465663738653236373661 +64396535323839626538623763613031376361323764626161363965613931666461663762383064 +32653832633531373461313530396565373463356534373135663935323563303336623534393831 +66326564663862613836386439633534623764633465363832323266333630356536336562643339 +63313338336139376135333462663733376435353533396132303931613637323934343837623462 +30663531643737356435346638306636663137303265643734373962643439393337333238666463 +63333730626234363366376136383139666464623230323161613764306262346566626135366236 +39623863346231306535303766323433313137313635663864643864333338383961616339656639 +61666635353838396333363662626361343836303435313739306235366663306130313365363435 +63383664316430646263626433326535636134633837386439383465636338353733623839636465 +64306236613962373365373365353538626463316534636537663136353062323163356266663361 +39643235623431376637613364393935306538303264613736303932363666343937346365353536 +66343031643061333834343638356234653138393634616633306233356534656466336537313666 +37626438623637386365616135626265663765656435303834393437396533386263383436316232 +62626635323730656431393239346335303833383831303361613134343163363262353434376434 +39663831306136656539386137383431623262623930373233623535323264303134386233346639 +32643664336464376239613937353234663635623739386235346163316633626266353663636166 +39323030393862306539653538653735353738313835356532633939363839656563333163306336 +65643338386164366536 diff --git a/sso_files/certificate.pem b/sso_files/certificate.pem new file mode 100644 index 000000000..6bb6844e2 --- /dev/null +++ b/sso_files/certificate.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUGROx096mS1jemzv+M6W7N/KrhJowDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDEwMDcxMTE0NDFaFw0yNDEx +MDYxMTE0NDFaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQC9bs3r/g6KZ7PlsPYSEt4O84pFZpMHGGnGdPVULNgT +2mSacPYEYc3G+qfrGjZ673Yk9mfRe2CnvQaKD4SoDlXGEwrSus1j/fNqzdu6XsVv +hQSzjX8gwWb9DaRxpI+wHMqSqP4vcKP8u/R7Zgjb5NnHxZ5tE3GNH+MsuoKeo5ev +ck8eb9k0jbh4BNmwsRXu9rwB9at1N4AMCl1NTcBRGYs6HDeRJF9LsDMhOo3pDHR4 +Wi6i/DkAp6GqmMf0IFvWLpjQNZNdJBszP+S/cc3L8X3aZtx1sAmGDI75y5Y06wRV +29C5CJnGb3MtS8jZDqEof9m8vrtw11p2nn4IOzrF089HAgMBAAGjUzBRMB0GA1Ud +DgQWBBRj8buUgMEU/NS2o9e7gvn9H+Yb4zAfBgNVHSMEGDAWgBRj8buUgMEU/NS2 +o9e7gvn9H+Yb4zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBu +gXRYq77BrFe9Q0d4Jxwc/1Mrw4sa8O4Bk+tMaxkdO2/X7/PMXxJKqSeDqtLfnc4v +2BZWSuYpDP4zuUmXbLo9irME05RaObvPXsjjeUIwBpY0u10RlgufTxZlhpq4spIn +qtyOQw6WcgRjVLlfqzNUJE6ancUM326qnAnX344+iBUiD495vLQ41MKRJrmJV6G2 +7xZHsDbZCEQ/6V+otz1fh69FFcvXI37ufipb54PWO0oWSot5/b+iok22e7Qac/51 +ZDGUoisvww3FjPkt9325EzXlZbe4TC7ouZnkVyy7R7it8dSmH7or+UmdwRUkqrNx +lTghkmPqil3zLWUOV0at +-----END CERTIFICATE----- diff --git a/sso_files/descriptor.txt b/sso_files/descriptor.txt new file mode 100644 index 000000000..c1540fe17 --- /dev/null +++ b/sso_files/descriptor.txt @@ -0,0 +1 @@ +dXjGiBQM-RFDe5r-73YijXUff7SQPhMLMCuqeI-xKVwMIICnTCCAYUCBgGUvbGhrTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdtb2NrU1NPMB4XDTI1MDEzMTE4NDcwMloXDTM1MDEzMTE4NDg0MlowEjEQMA4GA1UEAwwHbW9ja1NTTzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPYd012W2ajvV+DjHGU9H5TWDHJJ5MiQDHud/uhVDPRlPGOXCFv4JMs55KXd4tjlFJHIpTrOSZrQ6KXTf5fHukP+jwFHNmWbNCWq/pZn/JwfxfSZK9BdYu6VQIaD9tWUpO7ClI8V7tgX6+QLfNkRBkxpCm2A6c/dwVMWmBORlUJFOos+1+wBAD6iCowHQYV7f4l/A1EeXqLEyvn/b+6+DgFIjhXIo+01Op4qRyl1v+J+L3AWUKhmWrzpz8o6uUZxCvfNYWQGrhocz47v24bNusYPQNyu+PsAcLj7QjG1egS8TJkSMgokIYNNkEEEQa9syvWDZJzy2/3F92TBnNHijf8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEA3pOUWm83H3Xy8h3/km6q94LGZ9dmJ6p3FT3rBzm9MIQN+q+eCkehPXdQuzMMlHgvlCX1jfax4MnhXGgFwdL9D0gFfT+KzgGS8Cv0Gzg88m7e5wD11wBhJ5j9gw4K/dtLfjgHZAdJPGX7kxaZHeIZMr1KyqeXjn+AqIhV8+Xk199ZOlNb454VSNBj+4uUxptm0z4p3WkJ33mehq+aRQe2P+XQ36MIJC+cIEyumqClz6UHR9miPKap3AXOS+5wkZeTI3WWqAxedmJ2Gp+QLtH++gxBh7/ENoY/y5eTKPiEEfJ6+ucIpqcaYjODLiMB9DpI9C+TzKZ7H0UGZD9KXvE0wg==urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress \ No newline at end of file diff --git a/sso_files/keycloack-idp.xml b/sso_files/keycloack-idp.xml new file mode 100644 index 000000000..e287813a3 --- /dev/null +++ b/sso_files/keycloack-idp.xml @@ -0,0 +1,89 @@ + + + + +nujwbKvmA_vsAlZfUh1FNTxFUzKiiUswe2c9Xrin314 + + MIICmzCCAYMCBgGUvMgDBzANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjUwMTMxMTQzMTUyWhcNMzUwMTMxMTQzMzMyWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMVXixE+OVEgPn2VmNpk/u1uQDVE25y66SpiIJp9UQwlg3zxQr0XdYZ0kF404KqiONhJuMuw6ixZg5ZCqWdNnT9Nn8X+Kpn4ICzE/kkNtpuuhdLcBCwu6YPJebAXMez+Hvucx7n4kauNpt5qHYGvmWK+Hvy2T+HBuXMWn0ez+u55jVx5m8suzp9i3afzG5U5Mn4kGR2QK0/p9+InGjJBAtWPFnib9LlHA2venV1r6N0kYEDibVKBkKYWSTmaYVAIHvsms9f5dBKWAPKCdaXq4+sUYJEym1/o82+640aiWYlZNTF2nSzE+0aSgyeK1ZU+6Yg2Doo31UFzUyqZ5C9kWhAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFSi9zKIw4oYd2B57G23nk1hyqUFmYqVSvcMggqYzs+GlafGWOaJebE+op+iwSAyvEhWXYVqnXRAelxZmH6CqBWJlvxWXlOkX0osc4muDbDsiE+KceDx9dDaQlpz1fSUa6lUTTgkksbqn7SrJ7+2VxH9eaY1fS2U6FNnfgttNb3zXBIJMTNaMJuFdkPDnR92/JZMdoppuOKZ7WahGjB/YMXPIQaCSLa+deMA/5iCDUbSbmAeHtG4bf2jljy0/gQT9AwcbrhMk9Sc/G9HEnE1DoFD/ezJIhN/T/YeTuY7HKo0Ow9eje2ytjANWbnO0cawiMUfywU6EaIBPCU53X3vNlE= + + + + + + + + MIICmzCCAYMCBgGUvMgDBzANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjUwMTMxMTQzMTUyWhcNMzUwMTMxMTQzMzMyWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMVXixE+OVEgPn2VmNpk/u1uQDVE25y66SpiIJp9UQwlg3zxQr0XdYZ0kF404KqiONhJuMuw6ixZg5ZCqWdNnT9Nn8X+Kpn4ICzE/kkNtpuuhdLcBCwu6YPJebAXMez+Hvucx7n4kauNpt5qHYGvmWK+Hvy2T+HBuXMWn0ez+u55jVx5m8suzp9i3afzG5U5Mn4kGR2QK0/p9+InGjJBAtWPFnib9LlHA2venV1r6N0kYEDibVKBkKYWSTmaYVAIHvsms9f5dBKWAPKCdaXq4+sUYJEym1/o82+640aiWYlZNTF2nSzE+0aSgyeK1ZU+6Yg2Doo31UFzUyqZ5C9kWhAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFSi9zKIw4oYd2B57G23nk1hyqUFmYqVSvcMggqYzs+GlafGWOaJebE+op+iwSAyvEhWXYVqnXRAelxZmH6CqBWJlvxWXlOkX0osc4muDbDsiE+KceDx9dDaQlpz1fSUa6lUTTgkksbqn7SrJ7+2VxH9eaY1fS2U6FNnfgttNb3zXBIJMTNaMJuFdkPDnR92/JZMdoppuOKZ7WahGjB/YMXPIQaCSLa+deMA/5iCDUbSbmAeHtG4bf2jljy0/gQT9AwcbrhMk9Sc/G9HEnE1DoFD/ezJIhN/T/YeTuY7HKo0Ow9eje2ytjANWbnO0cawiMUfywU6EaIBPCU53X3vNlE= + + + + + + + + + + + + + +urn:oasis:names:tc:SAML:2.0:nameid-format:persistent +urn:oasis:names:tc:SAML:2.0:nameid-format:transient +urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified +urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress + + + + + + MIICmzCCAYMCBgGUvMgDBzANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjUwMTMxMTQzMTUyWhcNMzUwMTMxMTQzMzMyWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMVXixE+OVEgPn2VmNpk/u1uQDVE25y66SpiIJp9UQwlg3zxQr0XdYZ0kF404KqiONhJuMuw6ixZg5ZCqWdNnT9Nn8X+Kpn4ICzE/kkNtpuuhdLcBCwu6YPJebAXMez+Hvucx7n4kauNpt5qHYGvmWK+Hvy2T+HBuXMWn0ez+u55jVx5m8suzp9i3afzG5U5Mn4kGR2QK0/p9+InGjJBAtWPFnib9LlHA2venV1r6N0kYEDibVKBkKYWSTmaYVAIHvsms9f5dBKWAPKCdaXq4+sUYJEym1/o82+640aiWYlZNTF2nSzE+0aSgyeK1ZU+6Yg2Doo31UFzUyqZ5C9kWhAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFSi9zKIw4oYd2B57G23nk1hyqUFmYqVSvcMggqYzs+GlafGWOaJebE+op+iwSAyvEhWXYVqnXRAelxZmH6CqBWJlvxWXlOkX0osc4muDbDsiE+KceDx9dDaQlpz1fSUa6lUTTgkksbqn7SrJ7+2VxH9eaY1fS2U6FNnfgttNb3zXBIJMTNaMJuFdkPDnR92/JZMdoppuOKZ7WahGjB/YMXPIQaCSLa+deMA/5iCDUbSbmAeHtG4bf2jljy0/gQT9AwcbrhMk9Sc/G9HEnE1DoFD/ezJIhN/T/YeTuY7HKo0Ow9eje2ytjANWbnO0cawiMUfywU6EaIBPCU53X3vNlE= + + + + + + + + MIICmzCCAYMCBgGUvMgDBzANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjUwMTMxMTQzMTUyWhcNMzUwMTMxMTQzMzMyWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMVXixE+OVEgPn2VmNpk/u1uQDVE25y66SpiIJp9UQwlg3zxQr0XdYZ0kF404KqiONhJuMuw6ixZg5ZCqWdNnT9Nn8X+Kpn4ICzE/kkNtpuuhdLcBCwu6YPJebAXMez+Hvucx7n4kauNpt5qHYGvmWK+Hvy2T+HBuXMWn0ez+u55jVx5m8suzp9i3afzG5U5Mn4kGR2QK0/p9+InGjJBAtWPFnib9LlHA2venV1r6N0kYEDibVKBkKYWSTmaYVAIHvsms9f5dBKWAPKCdaXq4+sUYJEym1/o82+640aiWYlZNTF2nSzE+0aSgyeK1ZU+6Yg2Doo31UFzUyqZ5C9kWhAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFSi9zKIw4oYd2B57G23nk1hyqUFmYqVSvcMggqYzs+GlafGWOaJebE+op+iwSAyvEhWXYVqnXRAelxZmH6CqBWJlvxWXlOkX0osc4muDbDsiE+KceDx9dDaQlpz1fSUa6lUTTgkksbqn7SrJ7+2VxH9eaY1fS2U6FNnfgttNb3zXBIJMTNaMJuFdkPDnR92/JZMdoppuOKZ7WahGjB/YMXPIQaCSLa+deMA/5iCDUbSbmAeHtG4bf2jljy0/gQT9AwcbrhMk9Sc/G9HEnE1DoFD/ezJIhN/T/YeTuY7HKo0Ow9eje2ytjANWbnO0cawiMUfywU6EaIBPCU53X3vNlE= + + + + + + + + + + +urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress +urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName +urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName +urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos +urn:oasis:names:tc:SAML:2.0:nameid-format:entity +urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + MIICmzCCAYMCBgGUvMgDBzANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjUwMTMxMTQzMTUyWhcNMzUwMTMxMTQzMzMyWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMVXixE+OVEgPn2VmNpk/u1uQDVE25y66SpiIJp9UQwlg3zxQr0XdYZ0kF404KqiONhJuMuw6ixZg5ZCqWdNnT9Nn8X+Kpn4ICzE/kkNtpuuhdLcBCwu6YPJebAXMez+Hvucx7n4kauNpt5qHYGvmWK+Hvy2T+HBuXMWn0ez+u55jVx5m8suzp9i3afzG5U5Mn4kGR2QK0/p9+InGjJBAtWPFnib9LlHA2venV1r6N0kYEDibVKBkKYWSTmaYVAIHvsms9f5dBKWAPKCdaXq4+sUYJEym1/o82+640aiWYlZNTF2nSzE+0aSgyeK1ZU+6Yg2Doo31UFzUyqZ5C9kWhAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFSi9zKIw4oYd2B57G23nk1hyqUFmYqVSvcMggqYzs+GlafGWOaJebE+op+iwSAyvEhWXYVqnXRAelxZmH6CqBWJlvxWXlOkX0osc4muDbDsiE+KceDx9dDaQlpz1fSUa6lUTTgkksbqn7SrJ7+2VxH9eaY1fS2U6FNnfgttNb3zXBIJMTNaMJuFdkPDnR92/JZMdoppuOKZ7WahGjB/YMXPIQaCSLa+deMA/5iCDUbSbmAeHtG4bf2jljy0/gQT9AwcbrhMk9Sc/G9HEnE1DoFD/ezJIhN/T/YeTuY7HKo0Ow9eje2ytjANWbnO0cawiMUfywU6EaIBPCU53X3vNlE= + + + + + + + + MIICmzCCAYMCBgGUvMgDBzANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjUwMTMxMTQzMTUyWhcNMzUwMTMxMTQzMzMyWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMVXixE+OVEgPn2VmNpk/u1uQDVE25y66SpiIJp9UQwlg3zxQr0XdYZ0kF404KqiONhJuMuw6ixZg5ZCqWdNnT9Nn8X+Kpn4ICzE/kkNtpuuhdLcBCwu6YPJebAXMez+Hvucx7n4kauNpt5qHYGvmWK+Hvy2T+HBuXMWn0ez+u55jVx5m8suzp9i3afzG5U5Mn4kGR2QK0/p9+InGjJBAtWPFnib9LlHA2venV1r6N0kYEDibVKBkKYWSTmaYVAIHvsms9f5dBKWAPKCdaXq4+sUYJEym1/o82+640aiWYlZNTF2nSzE+0aSgyeK1ZU+6Yg2Doo31UFzUyqZ5C9kWhAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFSi9zKIw4oYd2B57G23nk1hyqUFmYqVSvcMggqYzs+GlafGWOaJebE+op+iwSAyvEhWXYVqnXRAelxZmH6CqBWJlvxWXlOkX0osc4muDbDsiE+KceDx9dDaQlpz1fSUa6lUTTgkksbqn7SrJ7+2VxH9eaY1fS2U6FNnfgttNb3zXBIJMTNaMJuFdkPDnR92/JZMdoppuOKZ7WahGjB/YMXPIQaCSLa+deMA/5iCDUbSbmAeHtG4bf2jljy0/gQT9AwcbrhMk9Sc/G9HEnE1DoFD/ezJIhN/T/YeTuY7HKo0Ow9eje2ytjANWbnO0cawiMUfywU6EaIBPCU53X3vNlE= + + + + + +urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress +urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName +urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName +urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos +urn:oasis:names:tc:SAML:2.0:nameid-format:entity +urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + diff --git a/sso_files/privatekey.pem b/sso_files/privatekey.pem new file mode 100644 index 000000000..1ab4e2df9 --- /dev/null +++ b/sso_files/privatekey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAvW7N6/4Oimez5bD2EhLeDvOKRWaTBxhpxnT1VCzYE9pkmnD2 +BGHNxvqn6xo2eu92JPZn0Xtgp70Gig+EqA5VxhMK0rrNY/3zas3bul7Fb4UEs41/ +IMFm/Q2kcaSPsBzKkqj+L3Cj/Lv0e2YI2+TZx8WebRNxjR/jLLqCnqOXr3JPHm/Z +NI24eATZsLEV7va8AfWrdTeADApdTU3AURmLOhw3kSRfS7AzITqN6Qx0eFouovw5 +AKehqpjH9CBb1i6Y0DWTXSQbMz/kv3HNy/F92mbcdbAJhgyO+cuWNOsEVdvQuQiZ +xm9zLUvI2Q6hKH/ZvL67cNdadp5+CDs6xdPPRwIDAQABAoIBAAQvdo1IR7n3IJpq +loU/tXhPGTb/VTBK8ctYujLp6rxFjwN6i3T9VDaZQyyGn72HnOykJRcTysbp/kL+ +pMexyWNe+FY/mlojOkWZ1sj/Xw1fuwLclXp7y3K74m5AXIxflno3EaaqrnTfEj/H +uVpibA1l6GIwk8mycqqCVHB83NUbf7g7I1fUfhfY24/LxaxkTZIFPBKiLV/CLTL8 +2czfpd1NwOLRMLgpLdMxx03DP4uaZfq7UNCBgDXnghWbGzZuzpZrKYNOpPYeNASh +HMg4g1mvJCbcYpF5Q+u+4h/cgTDYNmSyX39ctkYxeOJotmH8M5eogBr1oCoBYroG +TDMu9cECgYEA4Ror48HEbV2onQ44mnioZkraYKwhBsxuM8jyQJPShxuGK6PCgXua +sQYooIVIR0RCwWZQ4hw2dwATVXRhB7nA3Eh2XwWCh5FedWDHIe5DvzYC0+aJJKTE +AyrD2WsJMux6unZMo8qMkQSe7zkCFftWpRzvVKWzrKzyQ3zo/vnqdW0CgYEA129A +f4gH5IBhSqmpBKBjNBEhSWT6L1v6DFET7ytXgR9cYLAykTLBAKAXJKGME/Kv5fci +7vfVdbuLG1GR7JcfDw3NXDcSranOq6/K88NIsYY/pH4Vq0UGiBgU1+IrLdBhOMB6 +KJgi8p4cv3r+QpEfu7iLjci2OHXzhlEms8FzywMCgYEAxAKREzsH+x+iElhuy3uj +T6eAbsuT2qKql2c0Iy1VFhbOhzOKzEtAUUOGnvhQDtaOtm+MoMdmWcr0CuZTE3IZ +UPe8M1PN7JSVxunlnFMoJNk4LyJAa2sZz5QuhCTjFre6yqD2bW2TZze52Z8vhMqe +ERqYAIJlaUgTkNa04EnociUCgYAQr5k7R3n1BMyET+e7aADHA5ykZqHKEUGouo7k +s2KvqZIqGvuPq3KvbbbdK3YCBYYCNcYK5D8wQzpe/05iGMJbFCKXxdw4fzJ2scLy +Zmm29kLvpqRfA5Wh7NuQbQATKFSfZKkRg9cRG5X2brxKw5rFm2GTtbwHW3tlJ9vr +iExDqQKBgHnZUVLIU8u53L+wGe0hCf+RxS/kMXpe6gymvHPms2D2L9NYlWBee+7k +QjPaBRMTxJivRfARDEgbYSatBVvXXq0njvdNpIIFjDGwr1ve18aD876yB6FIWY/V +TeEKzppbu6I4Eyzyhg312AABxqEoiT8CI3R7e8KsM4D0vGaCdXRg +-----END RSA PRIVATE KEY----- diff --git a/sso_files/sp-keycloack-metadata.txt b/sso_files/sp-keycloack-metadata.txt new file mode 100644 index 000000000..cc239d0be --- /dev/null +++ b/sso_files/sp-keycloack-metadata.txt @@ -0,0 +1 @@ +nujwbKvmA_vsAlZfUh1FNTxFUzKiiUswe2c9Xrin314MIICmzCCAYMCBgGUvMgDBzANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMjUwMTMxMTQzMTUyWhcNMzUwMTMxMTQzMzMyWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMVXixE+OVEgPn2VmNpk/u1uQDVE25y66SpiIJp9UQwlg3zxQr0XdYZ0kF404KqiONhJuMuw6ixZg5ZCqWdNnT9Nn8X+Kpn4ICzE/kkNtpuuhdLcBCwu6YPJebAXMez+Hvucx7n4kauNpt5qHYGvmWK+Hvy2T+HBuXMWn0ez+u55jVx5m8suzp9i3afzG5U5Mn4kGR2QK0/p9+InGjJBAtWPFnib9LlHA2venV1r6N0kYEDibVKBkKYWSTmaYVAIHvsms9f5dBKWAPKCdaXq4+sUYJEym1/o82+640aiWYlZNTF2nSzE+0aSgyeK1ZU+6Yg2Doo31UFzUyqZ5C9kWhAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFSi9zKIw4oYd2B57G23nk1hyqUFmYqVSvcMggqYzs+GlafGWOaJebE+op+iwSAyvEhWXYVqnXRAelxZmH6CqBWJlvxWXlOkX0osc4muDbDsiE+KceDx9dDaQlpz1fSUa6lUTTgkksbqn7SrJ7+2VxH9eaY1fS2U6FNnfgttNb3zXBIJMTNaMJuFdkPDnR92/JZMdoppuOKZ7WahGjB/YMXPIQaCSLa+deMA/5iCDUbSbmAeHtG4bf2jljy0/gQT9AwcbrhMk9Sc/G9HEnE1DoFD/ezJIhN/T/YeTuY7HKo0Ow9eje2ytjANWbnO0cawiMUfywU6EaIBPCU53X3vNlE=urn:oasis:names:tc:SAML:2.0:nameid-format:persistent \ No newline at end of file From 205f749945020e7fff5e850a81629dec1bfd8d7d Mon Sep 17 00:00:00 2001 From: YATERA Boubacar Date: Tue, 11 Feb 2025 18:23:48 +0100 Subject: [PATCH 10/20] pushing sso on dev --- .github/workflows/test-lint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-lint.yml b/.github/workflows/test-lint.yml index 39f52a758..a10614e13 100644 --- a/.github/workflows/test-lint.yml +++ b/.github/workflows/test-lint.yml @@ -6,6 +6,7 @@ on: - dev - re7 - master + - develop-open-sso push: branches: - dev From 3948b381fff40623ad12f65e83a69e0f69565c11 Mon Sep 17 00:00:00 2001 From: YATERA Boubacar Date: Tue, 11 Feb 2025 18:24:48 +0100 Subject: [PATCH 11/20] pushing sso on dev --- .github/workflows/test-lint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-lint.yml b/.github/workflows/test-lint.yml index a10614e13..b8b5d7454 100644 --- a/.github/workflows/test-lint.yml +++ b/.github/workflows/test-lint.yml @@ -12,6 +12,7 @@ on: - dev - re7 - master + - develop-open-sso jobs: build: From 9242838a07377cdfc5149f71606c1640e9840672 Mon Sep 17 00:00:00 2001 From: YATERA Boubacar Date: Tue, 11 Feb 2025 18:36:27 +0100 Subject: [PATCH 12/20] pushing sso on dev --- Dockerfile.label-backend | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile.label-backend b/Dockerfile.label-backend index 9e9405eb2..b6eec1661 100644 --- a/Dockerfile.label-backend +++ b/Dockerfile.label-backend @@ -31,16 +31,16 @@ RUN cat package.json | sed 's|"packages/generic/\*"|"packages/generic/backend", RUN yarn install --production # Compile project without lerna -RUN cd /home/node/packages/generic/core && yarn compile && \ - cd /home/node/packages/generic/backend && yarn compile && \ - cd /home/node/packages/courDeCassation && yarn compile +RUN cd packages/generic/core && yarn compile && \ + cd packages/generic/backend && yarn compile && \ + cd packages/courDeCassation && yarn compile ADD packages/generic/core packages/generic/core ADD packages/generic/backend packages/generic/backend ADD packages/courDeCassation packages/courDeCassation ADD packages/generic/sso packages/generic/sso -WORKDIR /home/node/packages/courDeCassation +WORKDIR /packages/courDeCassation RUN chown node . From 8a81d99081d1ccf30598c4c2b23d985591c221b8 Mon Sep 17 00:00:00 2001 From: YATERA Boubacar Date: Tue, 11 Feb 2025 18:43:24 +0100 Subject: [PATCH 13/20] pushing sso on dev skip compile --- .github/workflows/test-lint.yml | 2 -- Dockerfile.label-backend | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-lint.yml b/.github/workflows/test-lint.yml index b8b5d7454..39f52a758 100644 --- a/.github/workflows/test-lint.yml +++ b/.github/workflows/test-lint.yml @@ -6,13 +6,11 @@ on: - dev - re7 - master - - develop-open-sso push: branches: - dev - re7 - master - - develop-open-sso jobs: build: diff --git a/Dockerfile.label-backend b/Dockerfile.label-backend index b6eec1661..d9b515787 100644 --- a/Dockerfile.label-backend +++ b/Dockerfile.label-backend @@ -31,9 +31,9 @@ RUN cat package.json | sed 's|"packages/generic/\*"|"packages/generic/backend", RUN yarn install --production # Compile project without lerna -RUN cd packages/generic/core && yarn compile && \ - cd packages/generic/backend && yarn compile && \ - cd packages/courDeCassation && yarn compile +# RUN cd packages/generic/core && yarn compile && \ +# cd packages/generic/backend && yarn compile && \ +# cd packages/courDeCassation && yarn compile ADD packages/generic/core packages/generic/core ADD packages/generic/backend packages/generic/backend From 13aeacd0015189f0db26a2a96355fed04733d0df Mon Sep 17 00:00:00 2001 From: YATERA Boubacar Date: Tue, 11 Feb 2025 19:07:15 +0100 Subject: [PATCH 14/20] pushing with good ip --- ansible/group_vars/dev/vault.yml | 223 +++++++++++++++---------------- ansible/inventories/dev.yml | 26 ++-- 2 files changed, 124 insertions(+), 125 deletions(-) diff --git a/ansible/group_vars/dev/vault.yml b/ansible/group_vars/dev/vault.yml index dc869d1ad..e84b96b2d 100644 --- a/ansible/group_vars/dev/vault.yml +++ b/ansible/group_vars/dev/vault.yml @@ -1,113 +1,112 @@ $ANSIBLE_VAULT;1.1;AES256 -38613035643133653830643033363663303463353166613063313362353839396162373234346334 -3164326239393439646437326338623632613133336230320a636165353837313039643136363066 -32653134373734613265356531353836306262643566333832646233373034346436346132646436 -6335366531376535630a343661363231356636353932313663306530373035316561663564383462 -62373866393035663862306261356331656566643764663038656165363335346331626130326433 -31396130663436363536343463626561353965383463353330633235383833343065633332626236 -30613732303036373531633737393562323166326264653430363738343732313766333733393032 -37316533333837643733313164626461626465353637343834336233353562666261663761646539 -38623936353661643663353363313031383634613935616335373430336631393537366232643335 -37373035376132636539366632646234383435373364386537306361333834326365636533313062 -34326466613262353764376664636162396634313438643463353864343538313163333236366261 -36356534363232373438633963303130616436623735343939656130306533393433336136363130 -36383837613739616266323432653233333635623863393065346633323663613862363763623535 -39633661613565363039623637636335316165316135353361656661313436383933333633613237 -30396534656561653832626266366364643762333665663866396261306437363865633734363161 -61336135393934303862636337376331363231613132313161303636343263393730333237353733 -36383161623462323839636535656661643963363536383234643061613265373164353861396131 -39613839646430376661636332396465363331323463643932646636613065316138386666353535 -38393634656530363764383736636432333633376665333434303332666237303963326631626336 -66363430353533396531363066303265616637626662393736373366313266303436326466623339 -66313639373138306361393566613063383638306535653663323939356230363433373837643937 -38646163643763363932333039326131666564613332636261623939666335316164393364363363 -36316332313062313262393162626461326433633632613666343936626232636162383734626232 -63346565653238633733623837613432623931303031643435326638666238653839666264303565 -63663766636237303066633838646534346662346665633338623636396433313865353861313365 -31306135333639656434656461623334383165393163653066386230643631323936393464376461 -65313938333763366233656531316532376431303763356337666436343866653262336635333834 -61623939343264386138613137643631646630306463633761316439383561396637303136613633 -32643533633164393061646566613735393936373538666134393333323063643262356533306335 -34376365306330366430646362616563303936653531383764616263323062666338336134336638 -35643562343331323861643236386639633932643161373330356565666661333234303932663636 -34336166376133323961366634376439623838653238333539373136336563326438666534303636 -65313530653261613034383034666162373133626131393364636363323635313636353364663836 -39343661343638373837353236623630306130323364313563376131353336303936306563396461 -62313830306534356163353531306635313235663730663730643332316232303833303732646366 -39336235643438383364663362643737636433653133373362366439343534333463323839653333 -62653538343739383366623135333731613930613966653038313837303038373764633432343739 -37613431666161346163376464653038633832633666303434373561333533323830396530636631 -31383638643033663861323539353662313430393334643862363730666364366263613031636435 -39356532373239353364333062316465363339306436393335656631326463376465343531663936 -65613865303763376532303032346561306636616166313036386331316232333264323631396264 -33356165646661343965366436353465303937303434636465326264363665636633666635336366 -61303333356339323138666634646462303237346535623034633039343938343965363232663637 -39396431653236636139343334656335306630373162353762383765393137643737656663383832 -39653664373836396238313332623565393730633930386465666362343565353736363938646530 -61633266303133363633363666396264346333326235316337346539313336303537363966636630 -65346365633238363163396331613965326465646231393264613537366363333731333133323232 -37326664333938306536613262383465643431643361643765373965633230633039343338346665 -64636263643331633065376361646566613064613766363266643730343937613239363164393137 -62623035343934663230633632313137303630326533373337636563646466313437353638323533 -61616239646531613539616630356439353434323838663431396663656362653139336634313562 -66346237653865343666666664666537613338323830346239643437633866343362613966623430 -35373439633433356430333431623734393364376232623933616266386431396165653839656430 -33663238313534663233653261383836376337613939653935616365613839633762626633363266 -37643661623837366264323265616662373332313133366661366530376534363333333839656265 -35383335343430323131663435623033663065353934306664653462616664616436373531373832 -65636662396163663734643066643132323336326137353632656430376362303134376636643562 -61323531623066633365633463353038323764616438323562313765376162313964636134386139 -39326562393866666365633565393965326263323662396236626165663239666238353736313437 -65376137653332303335646463353138646236343463303266323633306435336563386164353531 -38376361386530373531616234613961336531313562306532303630333964656465666530663565 -37316333386132376234323137623365363639336433336337363563383336396436376631336439 -39353061303465356139363534366564363838336635636566363736303637396535353135626135 -36363566646462383662663765323039333365393237333035613533313661663965613762373137 -32323632383339373830656338356237613534616436623836643736613066323164623930636538 -31366330653932326636353136333062373430326238316236633133346136336633316666393161 -36356565316663396161663666363232666532356166623366306139643538383961653664306231 -38396665383633636334653363643233653039623132376238633339346138623266393863613765 -39343364653438303762633163353965313830383366346366333835333332376131323535333434 -66343837656563646138616234653935346463643365343134633964366164326566333062393033 -64313437373761636337386136383664666662376261626334613432633433373835383132373537 -62316265393263613332363062353534616333646566386337356162323537386536666564653564 -30643530326432643263326232666330373738653834613964393532623764626138323038616662 -35316662303133343330376534613534363732333764303164383935666563633465383766303962 -61393463663562666639636433306639353130313336353735323932666234376534613033653934 -30363938656665356362343761386561346362333366646433353763613933633363616535333030 -65653834613665633135373064323138313139643065376534613262643763636237306536373839 -36613364316566323831396562383130333032646131623639643864306538376365633630646535 -39646336646464633263653036623865363035363436363432366261326364613532316262313763 -37306532363766663863363265653264396565343137333763663062386431393138636665353337 -30363863616536656635666264653665373466626238373732363239356562666530313032373433 -39336336343561653363613364313538616262623430613032386461653433356366653339643063 -35353738643862356631386631373338353334613337316436336239646135363437356432643734 -33353337633932623765363138323963633135663464643861323137386137346130376139653264 -65633934313232613830353030373661653439373834353262323162663465626266393837393663 -32656364353532666164373864613730656638643536336238356563346131363538316133346533 -37663135646561303063393061633332363364393939646631663432336561643131343938623161 -37613634373335666537396664643438646666346237336335363862323536613636396637373131 -32303761323665336261323866363064626436356235663637353433333965326238303030393331 -35656131333137643062656333613336353131393036343236383032663261653662393065643161 -63393866353962393734643932636432306365653037303865386531613630663337373536653335 -38366662636264353531633465326232316337376361663238646633616336666331663862363535 -33393862623232643561393566333161386566363064633264366562393936343364373736613661 -38616462306563663836363264306238313130316466663430363432653465663738653236373661 -64396535323839626538623763613031376361323764626161363965613931666461663762383064 -32653832633531373461313530396565373463356534373135663935323563303336623534393831 -66326564663862613836386439633534623764633465363832323266333630356536336562643339 -63313338336139376135333462663733376435353533396132303931613637323934343837623462 -30663531643737356435346638306636663137303265643734373962643439393337333238666463 -63333730626234363366376136383139666464623230323161613764306262346566626135366236 -39623863346231306535303766323433313137313635663864643864333338383961616339656639 -61666635353838396333363662626361343836303435313739306235366663306130313365363435 -63383664316430646263626433326535636134633837386439383465636338353733623839636465 -64306236613962373365373365353538626463316534636537663136353062323163356266663361 -39643235623431376637613364393935306538303264613736303932363666343937346365353536 -66343031643061333834343638356234653138393634616633306233356534656466336537313666 -37626438623637386365616135626265663765656435303834393437396533386263383436316232 -62626635323730656431393239346335303833383831303361613134343163363262353434376434 -39663831306136656539386137383431623262623930373233623535323264303134386233346639 -32643664336464376239613937353234663635623739386235346163316633626266353663636166 -39323030393862306539653538653735353738313835356532633939363839656563333163306336 -65643338386164366536 +63356263333336306163393631623932613836343339323333636630633538653262353238383339 +3530333933633961303539616638636335633036653864640a376332626466373763626438363139 +63316534306165653830303863303438636437313234623434363362393264333633333363323632 +3233366335663766300a306330643563623936353338646239373139616637396162623930616663 +36366431666161363638343631353363366265363339666262613464383933363762323666363938 +32383466333436313463643035626162396465346466636330366661363731333362646534626630 +30653631336330303031346235306665393832323065306466663333326535626234623065303535 +33623035646533613239643032353235393337633739303662363133343130373837353235383961 +30316530393162363061633161316339336164386163333234623632366332323638666537323737 +61613538363561663832643730663536333032303537616235373131303662333838343933393662 +34303431663062393166666636633763333638643731363764323665306364306636666133373239 +66323066623763396231643164633366393262663563663538336666353462393961306264663833 +62616633323632363861386137393231393936363535343233313962633233613363356331663830 +37373134313932613334643766643263323937343036323133383361306662373333623939303637 +65346462393435323061356237373462376431323537613561393930663164656136356233653766 +61346662633661616563653130303337333334373164663937646165313365396361333036336638 +34333138343735646237373231386162633431653938383732653337366630643636386232333163 +64653462636130383936383566363732616232346130343830343030306230656333346631313762 +39366335623036383638623264316338303236336236616433326335316364633632323037393333 +38613237313835336136306663303061343635396538346666666234333132353638626563646165 +39623065373631636633663837333931663761316262323234336165623030323965313932616166 +37363261353566643534393137306533333561376439633638633736343036303434323136616564 +39613663623466663564356362613661636139633061356166623433306434663463653830363166 +32653661376463653631356234656161643532326161376131623836633263373938383663356135 +62306635306463343630313839653839313333393762313937376337326262653932316362633265 +39626461653239643162363563613837393238323030343661633830306135343933313065636137 +39306638666633306166663835636361633663363039623838633963623838646166373962636638 +38323138363630386165663238336335353934343234666561636565313865386363353933386362 +65636166303230633235633939363363386666303661326164346133613864616366613566623165 +36633066393130663365653461633664333835326537316232336431386633343234313034303033 +65333864343830636339376533613236623836313363376232663433326238393230373039356431 +64663335326139616335616231336231393066336163373938663431353234333266653262663133 +39333766366436316234393261623263646431626463653138336566656364343938346339646336 +35323162626166356535333732323935323566643739383562303066303432343636393935313433 +63633263386336643331356562353537313435316363623730646135646130656562386138316539 +33386333636265323432363836626237366565316530646539386338363663303036613135313930 +31353430636438393662373563326635343363316335306637626537353534373462313139656163 +66336131643834623563386261393365646661353766616363376438386262643766333363643035 +36303339653163383063313361383938316563326632396136646431626661376230383361636232 +32343834666338663938303665383264353862653830366638643831313033663633396337333462 +66643838306334343830326537626133363866386131666663633366623361373462323264346236 +35373734306366346339663665306263376433663664383062386463313066333436663630653564 +34613766633034346231316539376335643637303832643330363739363966333236336437653062 +66613730323465326434623066376633323930336436323764326233333730323630333763663566 +65323262626138336336316466623538616261613130316635376466666661363763306338613038 +61373761383362633535303962356163653966313530616365396535396533336463653639373534 +66333633383336663636326661316632613563376561653635353532353735646633396435326638 +37303536656631616163633230363737313661623764633438363333333761353938383838313733 +64623734643335303462646133373164383835363765323361323935616338613338383161393132 +34643836326532616536383030663835623131343437383061626666393163386264366430346437 +64376531316362336439336362316230636461633136643161396536383861616362343133366638 +66653332303961353034323131353238316663666565313863356336376235383132373030343966 +36323265623833306634663639383466333236306636343662373262303266343761663836626432 +66656136663837393839333735623963306536626537313332383733376665396465376665373835 +32386337373965646665303937656634323332396665326563366135346330353266303065623863 +30303666356530663837313764303862373737616363336632376331323234303634383238663931 +38663936353339346636613737616230656631393262383832313031303264396238663237643261 +65313238373733336362653636623665333765313932623939313964303237393566613062356564 +64643461376130336435626436623536363861333134656661636261356461623530336330323239 +35646339643938313261353431343535663034646462353130643763623565343731323338373635 +65616430383036623338373162643364323464613235313234653332336332633161353266626164 +64363235643564333637316663353632343539316539646263306637343839636438636133663532 +66646465616432323031323866393139353236313735383964636563626431343264363538383131 +65643535633934323835393434366635636664636239326465386233343431356233666661643631 +39363036333234316138666339633035323731343766643266653236333531393366363866326436 +62666433393439363733376435393635666366333138346562623138663533363633613566616561 +65306164353435306364666433376633323539323666643765666533333532623538663237393363 +32623130356130326231643635363433613736323163316163373064333939653865643738346339 +39313938306131326132613363303939613762636239623031623139333836666132313263386635 +30643332623461363033303963313237326563653234393664363566636237613336346137356665 +63393638346137303230353237666663326361303530336636316165346562333834393961353032 +33643234353837353234663136336261303434643432623638386434383032373932636563343734 +31323739313566623936366565323333396335373734303839393866396531666533633235396135 +30303436636133653561316230316361313164376235316339633562346630363039313031306639 +63376534633463373032613139643330346463323932643031343064623761386365656335653261 +35326237656430613537313634353835373734356162346434343536363335316434366236383861 +65353362386630636465623964363464383764386232653034316161303038666566326439303832 +34653639633634313862656163613739636661646464326331303436346335613961623038366266 +66373139366561336235663065373063613863386161303865376633333730303561336239376239 +66303338333338396565653263373235636666663564633266633239613037333265323364363763 +38333430633336343939333737653431326232653637313061303234376233346166356534616665 +34613634643934343335353033623538326539376532323433393363386437616465636365393938 +63643634313664323639666334643639386639313561613062323765323631343336623934396135 +64333764653832356430373139643535373264663264353934333030653766303563336536323930 +63343766333562333533393964373534356330626462623639643462353062343739343962343632 +62636561616131633935613439333434363863373338646466333732323733653366626536393434 +65626364366139613361613139643634613161613936313365363630663638373635373966623633 +36306466393338373339356233653464306336323161356261306464396661646132343633613236 +61663239306237626361393761386561373232396433346633623563313765313332326139316636 +37393135333838643435653761373937366361326136306333303132663532643839323837353335 +61326562363534386233643636343933396565343835333730396334666531613065666632356436 +38343031323563653963636666653632623832626436336537373738323330613662663832303964 +33623662613139613133663565333330623666353430616134396164636238383732613461326265 +30353562343932353565653839626666623038323065343232336238333061616638386363616236 +63313331396136393861353862316565643138313433326664656137353761623133336139616264 +64323636383430663231653063343431356431623638306263376638333730633437646639323633 +37373331313437353731643637303532323739663232336665666433663466663132343636383736 +31656330623930373337623030643764336331373037626532346337383061643466386538356138 +39386364633333343838303837613335376232646238643739316266623837376230363234323338 +62646139633864623134663333623332393530633533306461643136643164336533363261346563 +64313435636439323763336638316534356631623164336239366137626230623239653936383134 +34653738386563356164653864666264323139666231623635373437313332303434376130323862 +37366666663762663665323363646230636438623635643362626434383362643362326461313861 +31653633643561643336613737346563313134333434663436653136343638323264623666633664 +30313436333962333233623064363938613137393165646237353366356661336237633834306237 +62343931343532376664373161653938303265353433643161643530653762323634633632623032 +31363363326261643865353436623337343561323530363334353032376465633537396432653462 +66313464353065333632323336303539656366623438363462636165656365353839343632666138 +65663531653932653963653736346231663665326365336164396537343236373464353765316136 +39353232363131373263346335393932353365613935646338393765663730376364656539623961 +643661623832373163623365363536393063 diff --git a/ansible/inventories/dev.yml b/ansible/inventories/dev.yml index 5290ae7ce..db8cc17d7 100644 --- a/ansible/inventories/dev.yml +++ b/ansible/inventories/dev.yml @@ -1,14 +1,14 @@ $ANSIBLE_VAULT;1.1;AES256 -63313766356339323166616532636432303333333336636361653630613337613862353830313034 -6537356335653436613436613135343732613739373135360a653235376538303761396364383735 -61393261343239396263336331643266343462303761333139626135313539326363393864346533 -3334633138343130300a326165393735393564616464336334636431636435333035376335393836 -31316138386533353465386264353939356134346638373266396163333562616436346334326366 -37323231613638383135663839643436373130386235326430616663343461396630633434613238 -66393136336234303533663339323062663263373631396632633838663065323731653338643430 -66323930653631636235666363656330653439343364653037656433666334313432363134646162 -32363736663263393064656362376231383166393663376635323561616634613666393836626462 -35303333666465373435373466643732306630373330633334323235343066306335313037333366 -33316666326332643264326531663466636337303830386437626165636134633332333965326634 -61643365663232633239643732656564346636303662636333623533663231383333313162643964 -3432 +65363935346232303932393362666636643337633164646138626538323835333234663634643966 +6465666438393733653132313462303864383135343162300a633263653464363163373963333966 +32626330396433383831346164343165656162396338323763666265383361333665386134373536 +3131353238366262350a666135346466343235353262353262356338613431336531343962373661 +65373661633638386238393034393537366264313637623465323762646561306362393733333465 +32333865363830356661323937393837303239326336356664313631616430643064303638353834 +62333663633035396365376537316563666366363637633433643436363361343034326636323834 +32373339356137383937386130313561303461326236343635323839646662393466626361623632 +30396263366438643262623136323935646437323766356638303938616662353739616162313236 +37656236616462663239363333623534613436646435323332373262643935343764326161396662 +39336239666362346239306262343963326663316632363062616663643962366330386162393666 +30613631376261643632333766366262623736386532343064336131333138363361636139373662 +3531 From 431ccd37d646717d0fbcdbb19a4a2c4189242418 Mon Sep 17 00:00:00 2001 From: YATERA Boubacar Date: Wed, 12 Feb 2025 09:51:18 +0100 Subject: [PATCH 15/20] reconfiguring dockerfile backend prod --- Dockerfile.label-backend | 39 ++++++++------ Dockerfile.label-backend.copy | 52 +++++++++++++++++++ docker-compose-dev.yml | 3 +- .../{keycloack-idp.xml => metadata_idp.xml} | 0 4 files changed, 76 insertions(+), 18 deletions(-) create mode 100644 Dockerfile.label-backend.copy rename sso_files/{keycloack-idp.xml => metadata_idp.xml} (100%) diff --git a/Dockerfile.label-backend b/Dockerfile.label-backend index d9b515787..483ff8537 100644 --- a/Dockerfile.label-backend +++ b/Dockerfile.label-backend @@ -1,11 +1,11 @@ FROM node:16-alpine as label-backend -ARG http_proxy -ARG https_proxy +# ARG http_proxy +# ARG https_proxy ENV API_PORT=55430 # Use proxy -RUN yarn config set proxy $http_proxy; \ - yarn config set https-proxy $https_proxy; +# RUN yarn config set proxy $http_proxy; \ +# yarn config set https-proxy $https_proxy; WORKDIR /home/node/ @@ -22,25 +22,29 @@ COPY ./sso_files ./sso_files/ COPY . . -# Do not bring client dependencies to backend prod -# Workaround to rewrite 'workspaces' in packages.json file to not run 'yarn install' in all packages +# Exclure les dépendances inutiles pour le backend prod RUN cat package.json | sed 's|"packages/generic/\*"|"packages/generic/backend", "packages/generic/core", "packages/generic/sso"|' > package.json.new && \ mv package.json.new package.json # Install dependencies -RUN yarn install --production +RUN yarn install --pure-lockfile +RUN ls -la -# Compile project without lerna -# RUN cd packages/generic/core && yarn compile && \ -# cd packages/generic/backend && yarn compile && \ -# cd packages/courDeCassation && yarn compile +# Compilation explicite des packages nécessaires +# RUN cd packages/generic/core && yarn compile +# RUN cd packages/generic/backend && yarn compile +# RUN cd packages/generic/sso && yarn compile +# RUN cd packages/courDeCassation && yarn compile -ADD packages/generic/core packages/generic/core -ADD packages/generic/backend packages/generic/backend -ADD packages/courDeCassation packages/courDeCassation -ADD packages/generic/sso packages/generic/sso +# ADD packages/generic/core packages/generic/core +# ADD packages/generic/backend packages/generic/backend +# ADD packages/courDeCassation packages/courDeCassation +# ADD packages/generic/sso packages/generic/sso -WORKDIR /packages/courDeCassation +RUN yarn build + +# Vérification que le fichier compilé existe +RUN ls -la packages/courDeCassation/dist/ || (echo "Erreur: dist/ est manquant" && exit 1) RUN chown node . @@ -49,4 +53,5 @@ USER node # Expose the listening port of your app EXPOSE ${API_PORT} -CMD ["sh", "-c", "RUN_MODE=PROD node dist/labelServer.js -s settings/settings.json"] +# Lancement avec vérification +CMD ["sh", "-c", "RUN_MODE=PROD node packages/courDeCassation/dist/labelServer.js -s packages/courDeCassation/settings/settings.json"] diff --git a/Dockerfile.label-backend.copy b/Dockerfile.label-backend.copy new file mode 100644 index 000000000..d9b515787 --- /dev/null +++ b/Dockerfile.label-backend.copy @@ -0,0 +1,52 @@ +FROM node:16-alpine as label-backend +ARG http_proxy +ARG https_proxy +ENV API_PORT=55430 + +# Use proxy +RUN yarn config set proxy $http_proxy; \ + yarn config set https-proxy $https_proxy; + +WORKDIR /home/node/ + +# Install git for sder and sder-core package +RUN apk add git + +# Copy context files +COPY ./package.json ./ +COPY packages/generic/core/package.json ./packages/generic/core/ +COPY packages/generic/backend/package.json ./packages/generic/backend/ +COPY packages/courDeCassation/package.json ./packages/courDeCassation/ +COPY packages/generic/sso/package.json ./packages/generic/sso/ +COPY ./sso_files ./sso_files/ + +COPY . . + +# Do not bring client dependencies to backend prod +# Workaround to rewrite 'workspaces' in packages.json file to not run 'yarn install' in all packages +RUN cat package.json | sed 's|"packages/generic/\*"|"packages/generic/backend", "packages/generic/core", "packages/generic/sso"|' > package.json.new && \ + mv package.json.new package.json + +# Install dependencies +RUN yarn install --production + +# Compile project without lerna +# RUN cd packages/generic/core && yarn compile && \ +# cd packages/generic/backend && yarn compile && \ +# cd packages/courDeCassation && yarn compile + +ADD packages/generic/core packages/generic/core +ADD packages/generic/backend packages/generic/backend +ADD packages/courDeCassation packages/courDeCassation +ADD packages/generic/sso packages/generic/sso + +WORKDIR /packages/courDeCassation + +RUN chown node . + +USER node + +# Expose the listening port of your app +EXPOSE ${API_PORT} + +CMD ["sh", "-c", "RUN_MODE=PROD node dist/labelServer.js -s settings/settings.json"] diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index c3a7fa14f..35b898a42 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -13,7 +13,8 @@ services: backend: build: context: ./ - dockerfile: DockerfileLocalDev + # dockerfile: DockerfileLocalDev + dockerfile: Dockerfile.label-backend environment: - RUN_MODE=LOCAL ports: diff --git a/sso_files/keycloack-idp.xml b/sso_files/metadata_idp.xml similarity index 100% rename from sso_files/keycloack-idp.xml rename to sso_files/metadata_idp.xml From cde6208b47553bcc9cb3f0279fd135158c4ea378 Mon Sep 17 00:00:00 2001 From: YATERA Boubacar Date: Wed, 12 Feb 2025 09:51:22 +0100 Subject: [PATCH 16/20] reconfiguring dockerfile backend prod --- Dockerfile.label-backend | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile.label-backend b/Dockerfile.label-backend index 483ff8537..d9fba04b3 100644 --- a/Dockerfile.label-backend +++ b/Dockerfile.label-backend @@ -27,8 +27,7 @@ RUN cat package.json | sed 's|"packages/generic/\*"|"packages/generic/backend", mv package.json.new package.json # Install dependencies -RUN yarn install --pure-lockfile -RUN ls -la +RUN yarn install --production # Compilation explicite des packages nécessaires # RUN cd packages/generic/core && yarn compile From dc477764321b7fb5c59f1cb5c1dc338024f00ca2 Mon Sep 17 00:00:00 2001 From: YATERA Boubacar Date: Wed, 12 Feb 2025 10:00:15 +0100 Subject: [PATCH 17/20] reconfiguring dockerfile backend prod --- Dockerfile.label-backend | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Dockerfile.label-backend b/Dockerfile.label-backend index d9fba04b3..230a66a25 100644 --- a/Dockerfile.label-backend +++ b/Dockerfile.label-backend @@ -1,11 +1,11 @@ FROM node:16-alpine as label-backend -# ARG http_proxy -# ARG https_proxy +ARG http_proxy +ARG https_proxy ENV API_PORT=55430 # Use proxy -# RUN yarn config set proxy $http_proxy; \ -# yarn config set https-proxy $https_proxy; +RUN yarn config set proxy $http_proxy; \ + yarn config set https-proxy $https_proxy; WORKDIR /home/node/ @@ -28,19 +28,20 @@ RUN cat package.json | sed 's|"packages/generic/\*"|"packages/generic/backend", # Install dependencies RUN yarn install --production +# RUN yarn install --pure-lockfile # Compilation explicite des packages nécessaires -# RUN cd packages/generic/core && yarn compile -# RUN cd packages/generic/backend && yarn compile -# RUN cd packages/generic/sso && yarn compile -# RUN cd packages/courDeCassation && yarn compile +RUN cd packages/generic/core && yarn compile +RUN cd packages/generic/backend && yarn compile +RUN cd packages/generic/sso && yarn compile +RUN cd packages/courDeCassation && yarn compile # ADD packages/generic/core packages/generic/core # ADD packages/generic/backend packages/generic/backend # ADD packages/courDeCassation packages/courDeCassation # ADD packages/generic/sso packages/generic/sso -RUN yarn build +# RUN yarn build # Vérification que le fichier compilé existe RUN ls -la packages/courDeCassation/dist/ || (echo "Erreur: dist/ est manquant" && exit 1) From 5f434f516f5bb4683fbfdde92876c038705b83d3 Mon Sep 17 00:00:00 2001 From: YATERA Boubacar Date: Wed, 12 Feb 2025 10:15:11 +0100 Subject: [PATCH 18/20] resolving bug --- Dockerfile.label-backend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.label-backend b/Dockerfile.label-backend index 230a66a25..748e6abb3 100644 --- a/Dockerfile.label-backend +++ b/Dockerfile.label-backend @@ -31,9 +31,9 @@ RUN yarn install --production # RUN yarn install --pure-lockfile # Compilation explicite des packages nécessaires +RUN cd packages/generic/sso && yarn compile RUN cd packages/generic/core && yarn compile RUN cd packages/generic/backend && yarn compile -RUN cd packages/generic/sso && yarn compile RUN cd packages/courDeCassation && yarn compile # ADD packages/generic/core packages/generic/core From 6292a8b234431073dd699513c2b7a8f4f0e44cb8 Mon Sep 17 00:00:00 2001 From: YATERA Boubacar Date: Wed, 12 Feb 2025 10:23:48 +0100 Subject: [PATCH 19/20] resolving bug --- Dockerfile.label-backend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.label-backend b/Dockerfile.label-backend index 748e6abb3..cdef5b56d 100644 --- a/Dockerfile.label-backend +++ b/Dockerfile.label-backend @@ -31,7 +31,7 @@ RUN yarn install --production # RUN yarn install --pure-lockfile # Compilation explicite des packages nécessaires -RUN cd packages/generic/sso && yarn compile +# RUN cd packages/generic/sso && yarn compile RUN cd packages/generic/core && yarn compile RUN cd packages/generic/backend && yarn compile RUN cd packages/courDeCassation && yarn compile From 86267eb0bc1848b7bc6e96c2589a57374ac9bf66 Mon Sep 17 00:00:00 2001 From: YATERA Boubacar Date: Wed, 12 Feb 2025 10:40:52 +0100 Subject: [PATCH 20/20] resolving bug --- Dockerfile.label-backend | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Dockerfile.label-backend b/Dockerfile.label-backend index cdef5b56d..34ea61a78 100644 --- a/Dockerfile.label-backend +++ b/Dockerfile.label-backend @@ -1,11 +1,11 @@ FROM node:16-alpine as label-backend -ARG http_proxy -ARG https_proxy +# ARG http_proxy +# ARG https_proxy ENV API_PORT=55430 # Use proxy -RUN yarn config set proxy $http_proxy; \ - yarn config set https-proxy $https_proxy; +# RUN yarn config set proxy $http_proxy; \ +# yarn config set https-proxy $https_proxy; WORKDIR /home/node/ @@ -32,9 +32,9 @@ RUN yarn install --production # Compilation explicite des packages nécessaires # RUN cd packages/generic/sso && yarn compile -RUN cd packages/generic/core && yarn compile -RUN cd packages/generic/backend && yarn compile -RUN cd packages/courDeCassation && yarn compile +# RUN cd packages/generic/core && yarn compile +# RUN cd packages/generic/backend && yarn compile +# RUN cd packages/courDeCassation && yarn compile # ADD packages/generic/core packages/generic/core # ADD packages/generic/backend packages/generic/backend