src/actions/authentication.jsimport { getServiceURL, handleHTTPErrors } from '../utilities/rest';export const USER_LOGIN_BEGIN = 'USER_LOGIN_BEGIN'; export const USER_LOGIN_SUCCESS = 'USER_LOGIN_SUCCESS'; export const USER_LOGIN_FAILURE = 'USER_LOGIN_FAILURE';
Trois constantes de chaîne exportées pour être utilisées par le reducer (reducers/authentication.js). Le switch(type) du reducer correspond à ces chaînes exactes.
Pourquoi des chaînes et non des symboles ? Convention Redux — les chaînes sont sérialisables, débogables dans Redux DevTools, et fonctionnent entre les modules sans nécessiter de références partagées.
Note : USER_LOGOUT se trouvait ici auparavant — supprimé dans le commit 7c60c2fbb (juil. 2019) lorsque la déconnexion a été simplifiée pour ne plus faire que dispatch USER_LOGIN_BEGIN (réinitialise l'état).
export const userLoginBegin = () => ({
type: USER_LOGIN_BEGIN,
});
export const userLoginSuccess = (user, version, buildTimestamp, alertMessage) => ({
type: USER_LOGIN_SUCCESS,
payload: { user, version, buildTimestamp, alertMessage },
});
export const userLoginFailure = (error) => ({
type: USER_LOGIN_FAILURE,
payload: { error },
});Action creators — des fonctions pures retournant des objets simples. Ce sont les « messages » synchrones envoyés au reducer.
| Créateur | Signature | Action du reducer |
|---|---|---|
userLoginBegin() |
() → { type: BEGIN } |
Réinitialise à { loading: true, error: null, user: null } — spinner lancé, anciennes données effacées |
userLoginSuccess(user, version, buildTimestamp, alertMessage) |
(obj, str, str, str?) → { type, payload } |
Écrit les quatre champs dans l'état — user, version, buildTimestamp, alertMessage. loading → false |
userLoginFailure(error) |
(str) → { type, payload: { error } } |
Stocke le message d'erreur, efface user, loading → false |
Ces fonctions sont appelées depuis les thunks ci-dessous — jamais dispatchées directement par les composants.
const catchError = (dispatch) => (error) => dispatch(userLoginFailure(error.message));
Fonction curryfiée : catchError(dispatch)(error).
dispatch — la fonction dispatch du store Redux, injectée par le middleware thunk.error — sa propriété .message devient le payload.Utilisé dans les chaînes .catch() de login() et loadUserStatus() :
login() à la ligne 84 : .catch(catchError(dispatch))loadUserStatus() à la ligne 61 : catchError(dispatch)({ message: undefined }) — transmet { message: undefined } car loadUserStatus gère l'erreur différemment (redirige vers la page de connexion, n'a pas besoin de message d'erreur).export const loadUserStatus = () => (dispatch) => {
dispatch(userLoginBegin()); // (a)
return fetch( // (b)
getServiceURL('userStatus'),
{ method: 'GET', credentials: 'include' },
)
.then(handleHTTPErrors) // (c)
.then((response) => response.json()) // (d)
.then(({ userData, systemData, alertMessage }) => { // (e)
dispatch(userLoginSuccess(
userData,
systemData.version,
systemData.buildTimestamp,
alertMessage,
));
})
.catch(() => { // (f)
const { pathname, search } = window.location;
const href = pathname + search;
if (!pathname.startsWith('/react/public/login')
&& !pathname.startsWith('/react/public/datatransfer/')) {
window.location.href =
`/react/public/login?url=${encodeURIComponent(href)}`;
}
catchError(dispatch)({ message: undefined });
});
};Rôle : « Qui suis-je ? » — appelé au démarrage de l'application et après la connexion pour vérifier si l'utilisateur a une session valide.
(a) Dispatch USER_LOGIN_BEGIN — spinner lancé, ancien état effacé.
(b) GET /rs/userStatus — envoie le cookie de session (credentials: 'include'). Appelle UserStatusRest.kt:111.
(c) handleHTTPErrors — si status ≠ 2xx, lève Error('Fetch failed: Error {status}'). Passe au .catch().
(d) Analyse la réponse JSON. Structure attendue : { userData: {...}, systemData: {version, buildTimestamp}, alertMessage?: string }.
(e) Déstructure et dispatch USER_LOGIN_SUCCESS avec tous les champs. Le reducer les écrit dans l'état.
(f) Session invalide ou expirée. Deux cas :
handleHTTPErrors lève une erreur → on redirige vers la page de connexion, en préservant l'URL actuelle via le paramètre ?url= pour que l'utilisateur revienne là où il était.Dispatch : catchError(dispatch)({ message: undefined }) — error.message sera undefined. Le reducer stocke { error: undefined } — efface efficacement toute erreur précédente.
Cet appel est effectué :
useEffect(() => dispatch(loadUserStatus()), [])login() appelle loadUserStatus()(dispatch) à la ligne 83export const login = (username, password, keepSignedIn) => (dispatch) => {
dispatch(userLoginBegin()); // (a)
return fetch( // (b)
getServiceURL('/rsPublic/login'),
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username,
password,
stayLoggedIn: keepSignedIn, // (c)
}),
credentials: 'include',
},
)
.then(handleHTTPErrors) // (d)
.then(() => loadUserStatus()(dispatch)) // (e)
.catch(catchError(dispatch)); // (f)
};Rôle : Envoyer les identifiants au serveur. En cas de succès, interroger la session pour récupérer les données utilisateur.
(a) Dispatch USER_LOGIN_BEGIN — spinner lancé.
(b) POST /rsPublic/login avec un corps JSON. Appelle LoginPageRest.kt:93 → LoginService.authenticate(). Le serveur définit le cookie JSESSIONID en cas de succès.
(c) stayLoggedIn: keepSignedIn — le flag « se souvenir de moi ». Quand true, le serveur définit en plus un cookie stayLoggedIn (CookieService.kt:201) valable 30 jours. À la prochaine visite, ce cookie renouvelle automatiquement la session sans mot de passe.
(d) handleHTTPErrors — si la connexion échoue (401, 403, etc.), lève une erreur et passe au .catch().
(e) En cas de succès (200) : enchaîne avec loadUserStatus(). La connexion n'authentifie que — il faut quand même savoir qui s'est connecté. loadUserStatus()(dispatch) invoque le thunk manuellement (il retourne (dispatch) => {...}, donc on l'appelle avec dispatch). C'est pourquoi le test attend 3 actions : BEGIN (login) + BEGIN (loadUserStatus) + SUCCESS (loadUserStatus).
(f) En cas d'échec : catchError(dispatch) dispatch USER_LOGIN_FAILURE avec le message d'erreur — ex. 'Fetch failed: Error 401' pour un mot de passe incorrect.
DÉMARRAGE DE L'APP :
ProjectForge.jsx → dispatch(loadUserStatus())
└─ GET /rs/userStatus
├─ 200 → SUCCESS(userData, systemData, alertMessage)
└─ 401 → redirige vers /react/public/login
CONNEXION :
login(username, password, keepSignedIn)
├─ dispatch(BEGIN)
├─ POST /rsPublic/login
├─ le serveur définit JSESSIONID (+ cookie stayLoggedIn si keepSignedIn)
├─ 200 → loadUserStatus()(dispatch)
│ └─ GET /rs/userStatus → BEGIN → SUCCESS
└─ 401/erreur → catchError → FAILURE
APRÈS CONNEXION :
L'utilisateur revient à la page initiale. ProjectForge.jsx est remonté.
loadUserStatus() s'exécute à nouveau — cette fois JSESSIONID est valide → SUCCESS
Documentation associée : commentaire sur le diff de authentication.test.js | commentaire sur le diff de reducers/authentication.js
Deux utilitaires provenant de rest.js :
getServiceURL(path)— construit l'URL complète : préfixehttp://localhost:8080(dev) ou""(prod).'/rsPublic/login'→ devhttp://localhost:8080/rsPublic/login.handleHTTPErrors(response)— lèveError('Fetch failed: Error {status}')si!response.ok.Aucune autre dépendance. Le fichier est autonome hormis ces deux helpers.