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 @@
+
+
+ {{ user }}
+
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",
+});