diff --git a/src/assets/icones/keycloak.svg b/src/assets/icones/keycloak.svg
new file mode 100644
index 000000000..62de2e59f
--- /dev/null
+++ b/src/assets/icones/keycloak.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/Routes/AutoLogoutContainer.jsx b/src/components/Routes/AutoLogoutContainer.jsx
index 695aa98ab..5b9661bb7 100644
--- a/src/components/Routes/AutoLogoutContainer.jsx
+++ b/src/components/Routes/AutoLogoutContainer.jsx
@@ -10,7 +10,7 @@ import { close as closeAction, open as openAction } from 'state/autoLogout'
import { useAppDispatch, useAppSelector } from 'state'
import { logout as logoutAction } from 'state/me'
-import { ACCES_TOKEN, BACK_API_URL, REFRESH_TOKEN, REFRESH_TOKEN_INTERVAL, SESSION_TIMEOUT } from '../../constants'
+import { ACCESS_TOKEN, BACK_API_URL, REFRESH_TOKEN, REFRESH_TOKEN_INTERVAL, SESSION_TIMEOUT } from '../../constants'
import useStyles from './styles'
@@ -78,7 +78,7 @@ const AutoLogoutContainer = () => {
const res = await axios.post(`${BACK_API_URL}/accounts/refresh/`)
if (res.status === 200) {
- localStorage.setItem(ACCES_TOKEN, res.data.access)
+ localStorage.setItem(ACCESS_TOKEN, res.data.access)
localStorage.setItem(REFRESH_TOKEN, res.data.refresh)
} else {
logout()
diff --git a/src/components/Routes/LeftSideBar/LeftSideBar.tsx b/src/components/Routes/LeftSideBar/LeftSideBar.tsx
index 705e7a448..1aeaa4c45 100644
--- a/src/components/Routes/LeftSideBar/LeftSideBar.tsx
+++ b/src/components/Routes/LeftSideBar/LeftSideBar.tsx
@@ -151,7 +151,6 @@ const LeftSideBar: React.FC<{ open?: boolean }> = (props) => {
>
{
- localStorage.clear()
dispatch(logoutAction())
navigate('/')
}}
@@ -170,7 +169,6 @@ const LeftSideBar: React.FC<{ open?: boolean }> = (props) => {
{
- localStorage.clear()
dispatch(logoutAction())
navigate('/')
}}
diff --git a/src/components/Routes/PrivateRoute.tsx b/src/components/Routes/PrivateRoute.tsx
index 7204633e5..c1fbeea3f 100644
--- a/src/components/Routes/PrivateRoute.tsx
+++ b/src/components/Routes/PrivateRoute.tsx
@@ -5,7 +5,7 @@ import { gql } from 'apollo-boost'
import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Button } from '@mui/material'
-import { ACCES_TOKEN } from '../../constants'
+import { ACCESS_TOKEN } from '../../constants'
import { useAppSelector, useAppDispatch } from '../../state'
import { login } from '../../state/me'
@@ -24,7 +24,7 @@ const PrivateRoute: React.FC = () => {
const me = useAppSelector((state) => state.me)
const dispatch = useAppDispatch()
const location = useLocation()
- const authToken = localStorage.getItem(ACCES_TOKEN)
+ const authToken = localStorage.getItem(ACCESS_TOKEN)
const [allowRedirect, setRedirection] = useState(false)
diff --git a/src/constants.js b/src/constants.js
index 707af65a7..4992e84c1 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -1,8 +1,21 @@
export let BOOLEANTRUE = 'true'
-export const ACCES_TOKEN = 'access'
+export const ACCESS_TOKEN = 'access'
export const REFRESH_TOKEN = 'refresh'
+export const OIDC_PROVIDER_URL = import.meta.env.DEV
+ ? import.meta.env.VITE_OIDC_PROVIDER_URL
+ : '{VITE_OIDC_PROVIDER_URL}'
+export const OIDC_REDIRECT_URI = import.meta.env.DEV
+ ? import.meta.env.VITE_OIDC_REDIRECT_URI
+ : '{VITE_OIDC_REDIRECT_URI}'
+export const OIDC_RESPONSE_TYPE = import.meta.env.DEV
+ ? import.meta.env.VITE_OIDC_RESPONSE_TYPE
+ : '{VITE_OIDC_RESPONSE_TYPE}'
+export const OIDC_CLIENT_ID = import.meta.env.DEV ? import.meta.env.VITE_OIDC_CLIENT_ID : '{VITE_OIDC_CLIENT_ID}'
+export const OIDC_SCOPE = import.meta.env.DEV ? import.meta.env.VITE_OIDC_SCOPE : '{VITE_OIDC_SCOPE}'
+export const OIDC_STATE = import.meta.env.DEV ? import.meta.env.VITE_OIDC_STATE : '{VITE_OIDC_STATE}'
+
export const BACK_API_URL = import.meta.env.DEV ? import.meta.env.VITE_BACK_API_URL : '{VITE_BACK_API_URL}'
export const REQUEST_API_URL = import.meta.env.DEV ? import.meta.env.VITE_REQUEST_API_URL : '{VITE_REQUEST_API_URL}'
export const FHIR_API_URL = import.meta.env.DEV ? import.meta.env.VITE_FHIR_API_URL : '{VITE_FHIR_API_URL}'
diff --git a/src/services/aphp/servicePractitioner.ts b/src/services/aphp/servicePractitioner.ts
index 8641ec4d6..9c1096825 100644
--- a/src/services/aphp/servicePractitioner.ts
+++ b/src/services/aphp/servicePractitioner.ts
@@ -1,5 +1,6 @@
import { AxiosResponse } from 'axios'
import apiBackend from 'services/apiBackend'
+import { ACCESS_TOKEN, REFRESH_TOKEN } from '../../constants'
export interface IServicePractitioner {
/**
@@ -11,7 +12,8 @@ export interface IServicePractitioner {
*
* Retourne la reponse de Axios
*/
- authenticate: (username: string, password: string) => Promise
+ authenticateWithCredentials: (username: string, password: string) => Promise
+ authenticateWithCode: (code: string) => Promise
/**
* Cette fonction permet d'appeler la route de logout
@@ -46,7 +48,7 @@ export interface IServicePractitioner {
}
const servicePractitioner: IServicePractitioner = {
- authenticate: async (username, password) => {
+ authenticateWithCredentials: async (username, password) => {
try {
const formData = new FormData()
formData.append('username', username.toString())
@@ -54,12 +56,27 @@ const servicePractitioner: IServicePractitioner = {
return await apiBackend.post(`/accounts/login/`, formData)
} catch (error) {
- console.error("erreur lors de l'exécution de la fonction authenticate", error)
+ console.error('Error authenticating with credentials', error)
+ return error
+ }
+ },
+
+ authenticateWithCode: async (authCode: string) => {
+ try {
+ const res = await apiBackend.post(`/auth/oidc/login`, { auth_code: authCode })
+ if (res.status === 200) {
+ localStorage.setItem(ACCESS_TOKEN, res.data.jwt.access)
+ localStorage.setItem(REFRESH_TOKEN, res.data.jwt.refresh)
+ }
+ return res
+ } catch (error) {
+ console.error('Error authenticating with an authorization code', error)
return error
}
},
logout: async () => {
+ localStorage.clear()
await apiBackend.post(`/accounts/logout/`)
},
diff --git a/src/services/apiBackend.js b/src/services/apiBackend.js
index cf85c51a7..4539f803e 100644
--- a/src/services/apiBackend.js
+++ b/src/services/apiBackend.js
@@ -1,5 +1,5 @@
import axios from 'axios'
-import { ACCES_TOKEN, BACK_API_URL } from '../constants'
+import { ACCESS_TOKEN, BACK_API_URL } from '../constants'
const apiBackend = axios.create({
baseURL: BACK_API_URL,
@@ -9,7 +9,7 @@ const apiBackend = axios.create({
})
apiBackend.interceptors.request.use((config) => {
- const token = localStorage.getItem(ACCES_TOKEN)
+ const token = localStorage.getItem(ACCESS_TOKEN)
config.headers.Authorization = `Bearer ${token}`
return config
})
diff --git a/src/services/apiFhir.js b/src/services/apiFhir.js
index 7df62f725..c273f4857 100644
--- a/src/services/apiFhir.js
+++ b/src/services/apiFhir.js
@@ -1,5 +1,5 @@
import axios from 'axios'
-import { ACCES_TOKEN, FHIR_API_URL } from '../constants'
+import { ACCESS_TOKEN, FHIR_API_URL } from '../constants'
const apiFhir = axios.create({
baseURL: FHIR_API_URL,
@@ -10,7 +10,7 @@ const apiFhir = axios.create({
})
apiFhir.interceptors.request.use((config) => {
- const token = localStorage.getItem(ACCES_TOKEN)
+ const token = localStorage.getItem(ACCESS_TOKEN)
config.headers.Authorization = `Bearer ${token}`
return config
})
diff --git a/src/views/Login/Login.jsx b/src/views/Login/Login.jsx
index cd9068e70..d9cbff64f 100644
--- a/src/views/Login/Login.jsx
+++ b/src/views/Login/Login.jsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react'
+import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import localforage from 'localforage'
import {
@@ -21,15 +21,27 @@ import NoRights from 'components/ErrorView/NoRights'
import logo from 'assets/images/logo-login.png'
import logoAPHP from 'assets/images/logo-aphp.png'
+import { ReactComponent as Keycloak } from 'assets/icones/keycloak.svg'
import { useAppDispatch } from 'state'
import { login as loginAction } from 'state/me'
-import { ACCES_TOKEN, REFRESH_TOKEN } from '../../constants'
+import {
+ ACCESS_TOKEN,
+ REFRESH_TOKEN,
+ OIDC_CLIENT_ID,
+ OIDC_PROVIDER_URL,
+ OIDC_REDIRECT_URI,
+ OIDC_RESPONSE_TYPE,
+ OIDC_SCOPE,
+ OIDC_STATE
+} from '../../constants'
import services from 'services/aphp'
import useStyles from './styles'
import { getDaysLeft } from '../../utils/formatDate'
+import Welcome from '../Welcome/Welcome'
+import clsx from 'clsx'
const ErrorSnackBarAlert = ({ open, setError, errorMessage }) => {
const _setError = () => {
@@ -97,11 +109,19 @@ const Login = () => {
const [error, setError] = useState(false)
const [errorMessage, setErrorMessage] = useState('')
const [open, setOpen] = useState(false)
+ const [authCode, setAuthCode] = useState(undefined)
+ const urlParams = new URLSearchParams(window.location.search)
+ const code = urlParams.get('code')
- React.useEffect(() => {
+ useEffect(() => {
localforage.setItem('persist:root', '')
+ if (code) setAuthCode(code)
}, [])
+ useEffect(() => {
+ if (authCode) login()
+ }, [authCode])
+
const getPractitionerData = async (practitioner, lastConnection, maintenance, accessExpirations = []) => {
if (practitioner) {
const practitionerPerimeters = await services.perimeters.getPerimeters()
@@ -169,12 +189,19 @@ const Login = () => {
if (loading) return
setLoading(true)
- if (!username || !password) {
- setLoading(false)
- return setError(true), setErrorMessage("L'un des champs nom d'utilisateur ou mot de passe est vide.")
- }
+ let response = null
- const response = await services.practitioner.authenticate(username, password)
+ if (authCode) {
+ response = await services.practitioner.authenticateWithCode(authCode)
+ } else {
+ if (!username || !password) {
+ setLoading(false)
+ return setError(true), setErrorMessage("L'un des champs nom d'utilisateur ou mot de passe est vide.")
+ }
+ if (username && password) {
+ response = await services.practitioner.authenticateWithCredentials(username, password)
+ }
+ }
if (!response) {
setLoading(false)
@@ -214,10 +241,10 @@ const Login = () => {
const { status, data = {} } = response
if (status === 200) {
- localStorage.setItem(ACCES_TOKEN, data.jwt.access)
+ localStorage.setItem(ACCESS_TOKEN, data.jwt.access)
localStorage.setItem(REFRESH_TOKEN, data.jwt.refresh)
- const practitioner = await services.practitioner.fetchPractitioner(username)
+ const practitioner = await services.practitioner.fetchPractitioner(data.user.provider_username)
if (!practitioner || practitioner.error || !practitioner.response || practitioner.response.status !== 200) {
setLoading(false)
@@ -266,9 +293,28 @@ const Login = () => {
login()
}
+ const oidcLogin = (e) => {
+ e.preventDefault()
+ window.location =
+ `${OIDC_PROVIDER_URL}?state=${OIDC_STATE}&` + // eslint-disable-line
+ `client_id=${OIDC_CLIENT_ID}&` + // eslint-disable-line
+ `redirect_uri=${OIDC_REDIRECT_URI}&` + // eslint-disable-line
+ `response_type=${OIDC_RESPONSE_TYPE}&` + // eslint-disable-line
+ `scope=${OIDC_SCOPE}` // eslint-disable-line
+ }
+
if (noRights === true) return
- return (
+ return code ? (
+
+
+ Connexion...
+
+
+
+ ) : authCode ? (
+
+ ) : (
<>
@@ -332,6 +378,17 @@ const Login = () => {
>
{loading ? : 'Connexion'}
+ }
+ >
+ Connexion via Keycloak
+
{/* */}
diff --git a/src/views/Login/styles.ts b/src/views/Login/styles.ts
index cbd0b6597..9cb126f91 100644
--- a/src/views/Login/styles.ts
+++ b/src/views/Login/styles.ts
@@ -13,7 +13,7 @@ const useStyles = makeStyles()((theme: Theme) => ({
marginBottom: theme.spacing(2)
},
bienvenue: {
- fontSize: '15px'
+ fontSize: 15
},
image: {
backgroundImage: `url(${BackgroundLogin})`,
@@ -22,15 +22,26 @@ const useStyles = makeStyles()((theme: Theme) => ({
backgroundPosition: 'center'
},
submit: {
- margin: theme.spacing(2, 0, 5),
+ margin: theme.spacing(2, 0, 0),
backgroundColor: '#5BC5F2',
color: 'white',
- height: '50px',
- width: '185px',
- borderRadius: '25px'
+ height: 50,
+ width: 185,
+ borderRadius: 25
+ },
+ oidcButton: {
+ backgroundColor: '#153D8A',
+ width: 250
},
mention: {
- marginTop: '8px'
+ marginTop: 8
+ },
+ oidcConnexionProgress: {
+ margin: '10%',
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ rowGap: 30
}
}))