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';
Drei String-Konstanten, die für die Verwendung im Reducer exportiert werden (reducers/authentication.js). Der switch(type)-Fall im Reducer matcht exakt auf diese Strings.
Warum Strings, keine Symbole? Redux-Konvention – Strings sind serialisierbar, in Redux DevTools debugbar und funktionieren modulübergreifend ohne geteilte Referenzen.
Hinweis: USER_LOGOUT war früher hier – entfernt in Commit 7c60c2fbb (Jul 2019), als das Logout auf das reine Dispatch von USER_LOGIN_BEGIN (Reset des States) vereinfacht wurde.
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 Creator – reine Funktionen, die Plain-Objects zurückgeben. Die synchronen „Nachrichten“, die an den Reducer gesendet werden.
| Creator | Signatur | Was der Reducer damit macht |
|---|---|---|
userLoginBegin() |
() → { type: BEGIN } |
Setzt zurück auf { loading: true, error: null, user: null } – Spinner aktiv, alte Daten gelöscht |
userLoginSuccess(user, version, buildTimestamp, alertMessage) |
(obj, str, str, str?) → { type, payload } |
Schreibt alle vier Felder in den State – user, version, buildTimestamp, alertMessage. loading → false |
userLoginFailure(error) |
(str) → { type, payload: { error } } |
Speichert Fehlermeldung, löscht user, loading → false |
Diese werden von den Thunks unten aufgerufen – niemals direkt von Komponenten dispatcht.
const catchError = (dispatch) => (error) => dispatch(userLoginFailure(error.message));
Gecurriede Funktion: catchError(dispatch)(error).
dispatch – die Dispatch-Funktion des Redux-Stores, injiziert durch die Thunk-Middleware.error-Objekt – dessen .message-Eigenschaft wird zum Payload.Wird in .catch()-Ketten sowohl von login() als auch loadUserStatus() verwendet:
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 });
});
};Zweck: „Wer bin ich?“ – wird beim App-Start und nach dem Login aufgerufen, um zu prüfen, ob der Benutzer eine gültige Sitzung hat.
(a) Dispatcht USER_LOGIN_BEGIN – Spinner aktiv, alter State gelöscht.
(b) GET /rs/userStatus – sendet das Session-Cookie (credentials: 'include'). Trifft auf UserStatusRest.kt:111.
(c) handleHTTPErrors – wenn Status ≠ 2xx, wird Error('Fetch failed: Error {status}') ausgelöst. Landet im .catch()-Block.
(d) JSON-Antwort parsen. Erwartete Struktur: { userData: {...}, systemData: {version, buildTimestamp}, alertMessage?: string }.
(e) Destructuring und Dispatch von USER_LOGIN_SUCCESS mit allen Feldern. Der Reducer schreibt sie in den State.
(f) Sitzung ungültig oder abgelaufen. Zwei Fälle:
handleHTTPErrors löst Ausnahme aus → wir leiten zur Login-Seite weiter und erhalten die aktuelle URL als ?url=-Parameter, damit der Benutzer dorthin zurückkehrt.Dispatch: catchError(dispatch)({ message: undefined }) – error.message ist undefined. Der Reducer speichert { error: undefined } – löscht effektiv vorherige Fehler.
Wird aufgerufen:
useEffect(() => dispatch(loadUserStatus()), [])login() ruft loadUserStatus()(dispatch) in Zeile 83 aufexport 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)
};Zweck: Sendet Anmeldeinformationen an den Server. Bei Erfolg wird die Sitzung abgefragt, um Benutzerdaten zu erhalten.
(a) Dispatcht USER_LOGIN_BEGIN – Spinner aktiv.
(b) POST /rsPublic/login mit JSON-Body. Trifft auf LoginPageRest.kt:93 → LoginService.authenticate(). Der Server setzt beim Erfolg das JSESSIONID-Cookie.
(c) stayLoggedIn: keepSignedIn – das „Angemeldet bleiben“-Flag. Wenn true, setzt der Server zusätzlich ein stayLoggedIn-Cookie (CookieService.kt:201) gültig für 30 Tage. Beim nächsten Besuch erneuert dieses Cookie automatisch die Sitzung ohne Passwort.
(d) handleHTTPErrors – wenn der Login fehlschlägt (401, 403 usw.), wird eine Ausnahme ausgelöst und landet im .catch().
(e) Bei Erfolg (200): Weiterleitung in loadUserStatus(). Login authentifiziert nur – wir müssen immer noch wissen, wer sich angemeldet hat. loadUserStatus()(dispatch) ruft den Thunk manuell auf (er gibt (dispatch) => {...} zurück, also wird er mit dispatch aufgerufen). Daher erwartet der Test 3 Actions: BEGIN (Login) + BEGIN (loadUserStatus) + SUCCESS (loadUserStatus).
(f) Bei Fehler: catchError(dispatch) dispatcht USER_LOGIN_FAILURE mit der Fehlermeldung – z. B. 'Fetch failed: Error 401' bei falschem Passwort.
APP-START:
ProjectForge.jsx → dispatch(loadUserStatus())
└─ GET /rs/userStatus
├─ 200 → SUCCESS(userData, systemData, alertMessage)
└─ 401 → Weiterleitung zu /react/public/login
LOGIN:
login(username, password, keepSignedIn)
├─ dispatch(BEGIN)
├─ POST /rsPublic/login
├─ Server setzt JSESSIONID (+ stayLoggedIn-Cookie wenn keepSignedIn)
├─ 200 → loadUserStatus()(dispatch)
│ └─ GET /rs/userStatus → BEGIN → SUCCESS
└─ 401/Fehler → catchError → FAILURE
NACH DEM LOGIN:
Benutzer kehrt zur ursprünglichen Seite zurück. ProjectForge.jsx wird neu gemountet.
loadUserStatus() läuft erneut – dieses Mal ist JSESSIONID gültig → SUCCESS
Zwei Hilfsfunktionen aus rest.js:
getServiceURL(path)– erstellt die vollständige URL: fügthttp://localhost:8080(Dev) oder""(Prod) voran.'/rsPublic/login'→ Devhttp://localhost:8080/rsPublic/login.handleHTTPErrors(response)– löstError('Fetch failed: Error {status}')aus, wenn!response.ok.Keine weiteren Abhängigkeiten. Die Datei ist mit diesen beiden Hilfsfunktionen komplett eigenständig.