From 8297cf69d17a3f220103816990a694ea1d2218d4 Mon Sep 17 00:00:00 2001 From: Thomas Bernard Date: Thu, 27 Jun 2024 14:29:01 +0200 Subject: [PATCH] Add OIDC with Passage2 --- backend/requirements.txt | 1 + docker-compose.yml | 12 +++- frontend/.env.development | 3 +- frontend/.env.production | 2 + frontend/.eslintrc-auto-import.json | 3 +- frontend/package-lock.json | 64 +++++++++++++++++-- frontend/package.json | 1 + frontend/src/components.d.ts | 16 ++--- .../src/components/authentication/User.vue | 11 ++++ frontend/src/router/index.ts | 28 ++++++++ frontend/src/utils/authentication.ts | 12 ++++ 11 files changed, 134 insertions(+), 19 deletions(-) create mode 100644 frontend/.env.production create mode 100644 frontend/src/components/authentication/User.vue create mode 100644 frontend/src/utils/authentication.ts diff --git a/backend/requirements.txt b/backend/requirements.txt index ad35831a..1a5393ca 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -9,6 +9,7 @@ pyyaml>=5.4.1 user-agents==2.2.0 boto3==1.28.39 autodynatrace==2.0.0 +PyJWT==2.8.0 # ML https://github.com/dnum-mi/basegun-ml/raw/MLPackages/MLpackages/basegun_ml/dist/basegunml-0.1.tar.gz # Dev diff --git a/docker-compose.yml b/docker-compose.yml index fb245b81..c74aab11 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,15 +37,25 @@ services: target: ${BUILD_TARGET:-dev} container_name: basegun-frontend ports: - - 8080:80 # if BUILD_TARGET = prod - 3000:5173 volumes: - ./frontend/src:/app/src - /app/node_modules + # Mock Cloud Pi S3 minio: image: minio/minio command: server /data --console-address ":9001" ports: - 9000:9000 - 9001:9001 + + # Mock Passage2 OIDC + keycloak: + image: quay.io/keycloak/keycloak:25.0.0 + command: start-dev + environment: + - KEYCLOAK_ADMIN=admin + - KEYCLOAK_ADMIN_PASSWORD=password + ports: + - 8080:8080 \ No newline at end of file diff --git a/frontend/.env.development b/frontend/.env.development index 531964bd..bf796d25 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -1 +1,2 @@ -VITE_API_HOST=http://localhost:5000 \ No newline at end of file +VITE_OIDC_AUTHORITY=http://localhost:8080/realms/master/ +VITE_OIDC_CLIENT_ID=basegun \ No newline at end of file diff --git a/frontend/.env.production b/frontend/.env.production new file mode 100644 index 00000000..0af402a0 --- /dev/null +++ b/frontend/.env.production @@ -0,0 +1,2 @@ +VITE_OIDC_AUTHORITY=https://auth.sso.interieur.rie.gouv.fr/.well-known/openid-configuration +VITE_OIDC_CLIENT_ID=BaseGun-Production-69i \ No newline at end of file diff --git a/frontend/.eslintrc-auto-import.json b/frontend/.eslintrc-auto-import.json index 85f45d94..c1da55d3 100644 --- a/frontend/.eslintrc-auto-import.json +++ b/frontend/.eslintrc-auto-import.json @@ -296,6 +296,7 @@ "provideLocal": true, "useClipboardItems": true, "useScheme": true, - "useTabs": true + "useTabs": true, + "useStore": true } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7b2bb793..3e27000d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,9 +10,9 @@ "dependencies": { "@gouvfr/dsfr": "~1.11.0", "@gouvminint/vue-dsfr": "^5.8.0", - "@vueuse/core": "^10.7.2", "axios": "^1.6.7", "luxon": "^3.4.4", + "oidc-client-ts": "^3.0.1", "pinia": "^2.1.7", "pinia-plugin-persistedstate": "^3.2.1", "swiper": "^11.0.6", @@ -3144,7 +3144,10 @@ "node_modules/@types/web-bluetooth": { "version": "0.0.20", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/@types/yauzl": { "version": "2.10.0", @@ -4407,6 +4410,9 @@ "version": "10.7.2", "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.2.tgz", "integrity": "sha512-AOyAL2rK0By62Hm+iqQn6Rbu8bfmbgaIMXcE3TSr7BdQ42wnSFlwIdPjInO62onYsEMK/yDMU8C6oGfDAtZ2qQ==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "10.7.2", @@ -4421,6 +4427,9 @@ "version": "10.7.2", "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.2.tgz", "integrity": "sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==", + "dev": true, + "optional": true, + "peer": true, "funding": { "url": "https://github.com/sponsors/antfu" } @@ -4429,6 +4438,9 @@ "version": "10.7.2", "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.2.tgz", "integrity": "sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==", + "dev": true, + "optional": true, + "peer": true, "dependencies": { "vue-demi": ">=0.14.6" }, @@ -7831,6 +7843,14 @@ "verror": "1.10.0" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -8385,6 +8405,17 @@ } } }, + "node_modules/oidc-client-ts": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.0.1.tgz", + "integrity": "sha512-xX8unZNtmtw3sOz4FPSqDhkLFnxCDsdo2qhFEH2opgWnF/iXMFoYdBQzkwCxAZVgt3FT3DnuBY3k80EZHT0RYg==", + "dependencies": { + "jwt-decode": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -13485,7 +13516,10 @@ "@types/web-bluetooth": { "version": "0.0.20", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "dev": true, + "optional": true, + "peer": true }, "@types/yauzl": { "version": "2.10.0", @@ -14390,6 +14424,9 @@ "version": "10.7.2", "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.2.tgz", "integrity": "sha512-AOyAL2rK0By62Hm+iqQn6Rbu8bfmbgaIMXcE3TSr7BdQ42wnSFlwIdPjInO62onYsEMK/yDMU8C6oGfDAtZ2qQ==", + "dev": true, + "optional": true, + "peer": true, "requires": { "@types/web-bluetooth": "^0.0.20", "@vueuse/metadata": "10.7.2", @@ -14400,12 +14437,18 @@ "@vueuse/metadata": { "version": "10.7.2", "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.2.tgz", - "integrity": "sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==" + "integrity": "sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==", + "dev": true, + "optional": true, + "peer": true }, "@vueuse/shared": { "version": "10.7.2", "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.2.tgz", "integrity": "sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==", + "dev": true, + "optional": true, + "peer": true, "requires": { "vue-demi": ">=0.14.6" } @@ -16862,6 +16905,11 @@ "verror": "1.10.0" } }, + "jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==" + }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -17261,6 +17309,14 @@ } } }, + "oidc-client-ts": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.0.1.tgz", + "integrity": "sha512-xX8unZNtmtw3sOz4FPSqDhkLFnxCDsdo2qhFEH2opgWnF/iXMFoYdBQzkwCxAZVgt3FT3DnuBY3k80EZHT0RYg==", + "requires": { + "jwt-decode": "^4.0.0" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 21df5a3e..9802d323 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "@gouvminint/vue-dsfr": "^5.8.0", "axios": "^1.6.7", "luxon": "^3.4.4", + "oidc-client-ts": "^3.0.1", "pinia": "^2.1.7", "pinia-plugin-persistedstate": "^3.2.1", "swiper": "^11.0.6", diff --git a/frontend/src/components.d.ts b/frontend/src/components.d.ts index c71cccf3..4ed047d3 100644 --- a/frontend/src/components.d.ts +++ b/frontend/src/components.d.ts @@ -7,26 +7,18 @@ export {} declare module "vue" { export interface GlobalComponents { - AccessibilityPage: (typeof import("./components/AccessibilityPage.vue"))["default"]; AskingExpert: (typeof import("./components/AskingExpert.vue"))["default"]; + AuthCallback: (typeof import("./components/authentication/AuthCallback.vue"))["default"]; + AuthRedirect: (typeof import("./components/authentication/AuthRedirect.vue"))["default"]; ContactExpert: (typeof import("./components/ContactExpert.vue"))["default"]; - DsfrAccordion: (typeof import("@gouvminint/vue-dsfr"))["DsfrAccordion"]; - DsfrAccordionsGroup: (typeof import("@gouvminint/vue-dsfr"))["DsfrAccordionsGroup"]; + copy: (typeof import("./components/authentification/AuthRedirect copy.vue"))["default"]; DsfrAlert: (typeof import("@gouvminint/vue-dsfr"))["DsfrAlert"]; DsfrButton: (typeof import("@gouvminint/vue-dsfr"))["DsfrButton"]; - DsfrCheckbox: (typeof import("@gouvminint/vue-dsfr"))["DsfrCheckbox"]; - DsfrFileUpload: (typeof import("@gouvminint/vue-dsfr"))["DsfrFileUpload"]; DsfrHeader: (typeof import("@gouvminint/vue-dsfr"))["DsfrHeader"]; DsfrInput: (typeof import("@gouvminint/vue-dsfr"))["DsfrInput"]; - DsfrInputGroup: (typeof import("@gouvminint/vue-dsfr"))["DsfrInputGroup"]; DsfrModal: (typeof import("@gouvminint/vue-dsfr"))["DsfrModal"]; DsfrPicture: (typeof import("@gouvminint/vue-dsfr"))["DsfrPicture"]; DsfrRadioButton: (typeof import("@gouvminint/vue-dsfr"))["DsfrRadioButton"]; - DsfrRadioButtonSet: (typeof import("@gouvminint/vue-dsfr"))["DsfrRadioButtonSet"]; - DsfrSelect: (typeof import("@gouvminint/vue-dsfr"))["DsfrSelect"]; - DsfrTable: (typeof import("@gouvminint/vue-dsfr"))["DsfrTable"]; - DsfrTag: (typeof import("@gouvminint/vue-dsfr"))["DsfrTag"]; - FooterMES: (typeof import("./components/FooterMES.vue"))["default"]; HeaderMain: (typeof import("./components/HeaderMain.vue"))["default"]; MissingCardAlert: (typeof import("./components/MissingCardAlert.vue"))["default"]; OnboardingSwiper: (typeof import("./components/OnboardingSwiper.vue"))["default"]; @@ -37,7 +29,7 @@ declare module "vue" { RouterView: (typeof import("vue-router"))["RouterView"]; SnackbarAlert: (typeof import("./components/SnackbarAlert.vue"))["default"]; StepsGuide: (typeof import("./components/StepsGuide.vue"))["default"]; - UploadButton: (typeof import("./components/UploadButton.vue"))["default"]; + User: (typeof import("./components/authentication/User.vue"))["default"]; VIcon: (typeof import("oh-vue-icons"))["OhVueIcon"]; } } diff --git a/frontend/src/components/authentication/User.vue b/frontend/src/components/authentication/User.vue new file mode 100644 index 00000000..a223e141 --- /dev/null +++ b/frontend/src/components/authentication/User.vue @@ -0,0 +1,11 @@ + + diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 3c9ec45e..afe6ee60 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -6,6 +6,7 @@ import { } from "vue-router"; import { clearLocalStorage } from "@/utils/storage-utils.js"; +import { mgr } from "@/utils/authentication"; import MissingCardPage from "@/views/MissingCardPage.vue"; @@ -44,6 +45,8 @@ const IdentificationBlankGun = () => const ExpertSituation = () => import("@/views/GuideContactExpert/ExpertSituation.vue"); +const User = () => import("@/components/authentication/User.vue"); + const routes: RouteRecordRaw[] = [ { path: "/", @@ -205,6 +208,31 @@ const routes: RouteRecordRaw[] = [ name: "ExpertSituationGN", component: ExpertSituation, }, + { + path: "/auth", + children: [ + { + path: "redirect", + name: "AuthRedirect", + beforeEnter: (to, from) => { + mgr.signinRedirect(); + }, + }, + { + path: "callback", + name: "AuthCallback", + beforeEnter: (to, from) => { + mgr.signinCallback(); + return { name: "User" }; + }, + }, + { + path: "user", + name: "User", + component: User, + }, + ], + }, ]; const router = createRouter({ diff --git a/frontend/src/utils/authentication.ts b/frontend/src/utils/authentication.ts new file mode 100644 index 00000000..e754bb78 --- /dev/null +++ b/frontend/src/utils/authentication.ts @@ -0,0 +1,12 @@ +import { UserManager } from "oidc-client-ts"; + +const FRONTEND_URL = window.location.origin; + +export const mgr = new UserManager({ + authority: import.meta.env.VITE_OIDC_AUTHORITY, + client_id: import.meta.env.VITE_OIDC_CLIENT_ID, + redirect_uri: `${FRONTEND_URL}/auth/callback`, + silent_redirect_uri: `${FRONTEND_URL}`, + post_logout_redirect_uri: `${FRONTEND_URL}`, + response_type: "code", +});