Files
omsorg/omsorgapp/src/api/authApi.js
T
Felix KemmlerandClaude Sonnet 5 09dce2ab98
Docker-Images bauen und veröffentlichen / build (, omsorgCore/Dockerfile, omsorgcore) (push) Successful in 14s
Docker-Images bauen und veröffentlichen / build (, omsorgWeb/Dockerfile, omsorgweb) (push) Successful in 5s
Docker-Images bauen und veröffentlichen / build (, omsorgapp/Dockerfile, omsorgapp) (push) Successful in 17s
Fix broken login on omsorgWeb: restore refreshToken in auth responses
The cookie-only refresh-token migration earlier this session broke both
mitarbeiter-app and mitarbeiter-app-legacy: they're server-to-server PHP
clients (cURL/Guzzle calling omsorgCore directly) with no browser cookie
jar, so dropping refreshToken from the login/refresh response body left
them with nothing to store - login appeared to succeed, redirected to
the dashboard, but the very next page's session check failed silently
(mitarbeiter-app's _ensure_fresh_token() bails out whenever
$_SESSION['omsorgcore_refresh_token'] is empty), bouncing the user back
to the login form every time.

Fix: dual-mode refresh token transport instead of cookie-only.
- LoginResponse includes refreshToken again (restores the pre-migration
  contract PHP already expected) alongside the HttpOnly cookie.
- AuthController.Refresh/Logout accept an optional body-carried
  RefreshRequest/LogoutRequest as a fallback: cookie is checked first
  (browser/omsorgapp), body second (server-to-server clients).
- omsorgapp keeps ignoring the body's refreshToken and relies solely on
  the cookie (XSS-safe) - only its authApi.js needed a small update since
  the regenerated client now requires an explicit (empty) parameter
  object for refresh/logout.
- Regenerated omsorgcore-client-ts; api-client-php's lib/ was already
  consistent (never regenerated during the original migration, so it
  still expected refreshToken all along - only the backend had stopped
  providing it).

Verified end-to-end against a live instance: PHP login+refresh via
omsorgcore_login()/omsorgcore_refresh(), and the browser cookie-only
flow via curl with Origin/credentials headers - both work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 19:02:16 +02:00

79 lines
3.5 KiB
JavaScript

import { AuthApi } from "omsorgcore-client-ts";
import { configFor, callApi } from "./apiClientHelpers.js";
// Kapselt /api/auth/* von omsorgCore über den generierten Client (omsorgcore-client-ts).
// refresh/logout brauchen im Browser keinen Refresh-Token-Parameter - er steckt in der
// HttpOnly-Cookie, die dank credentials:"include" automatisch mitgeschickt wird. Das Backend
// liefert refreshToken trotzdem im Response-Body mit (server-seitige API-Clients wie omsorgWeb
// haben keinen Browser-Cookie-Jar und brauchen ihn dort, siehe omsorgCore/CLAUDE.md) - hier
// bewusst ignoriert, nie in JS-Variablen/localStorage abgelegt (XSS-Schutz).
function toTokenPair(data) {
return {
accessToken: data.accessToken,
expiresAt: data.expiresAt,
mustChangePassword: !!data.mustChangePassword
};
}
export async function login(username, password) {
const api = new AuthApi(configFor(undefined));
const result = await callApi(api.apiAuthLoginPostRaw({ loginRequest: { username, password } }));
if (!result.ok) {
return { ok: false, status: result.status, error: result.error || result.data?.error };
}
return { ok: true, ...toTokenPair(result.data) };
}
export async function refresh() {
const api = new AuthApi(configFor(undefined));
const result = await callApi(api.apiAuthRefreshPostRaw({}));
if (!result.ok) return { ok: false, status: result.status };
return { ok: true, ...toTokenPair(result.data) };
}
export async function logout() {
const api = new AuthApi(configFor(undefined));
const result = await callApi(api.apiAuthLogoutPostRaw({}));
return { ok: result.ok, status: result.status };
}
export async function me(accessToken) {
const api = new AuthApi(configFor(accessToken));
const result = await callApi(api.apiAuthMeGetRaw());
if (!result.ok) return { ok: false, status: result.status };
return { ok: true, username: result.data.username, role: result.data.role, permissions: result.data.permissions };
}
export async function requestPasswordReset(username) {
const api = new AuthApi(configFor(undefined));
const result = await callApi(api.apiAuthForgotPasswordRequestPostRaw({ forgotPasswordRequestRequest: { username } }));
if (!result.ok) return { ok: false, status: result.status };
return { ok: true, status: result.data.status };
}
export async function verifyPasswordResetCode(username, pin) {
const api = new AuthApi(configFor(undefined));
const result = await callApi(api.apiAuthForgotPasswordVerifyPostRaw({ forgotPasswordVerifyRequest: { username, pin } }));
if (!result.ok) return { ok: false, status: result.status, error: result.data?.error };
return { ok: true, resetToken: result.data.resetToken };
}
export async function resetPassword(resetToken, newPassword) {
const api = new AuthApi(configFor(undefined));
const result = await callApi(api.apiAuthForgotPasswordResetPostRaw({ forgotPasswordResetRequest: { resetToken, newPassword } }));
return { ok: result.ok, status: result.status, data: result.data };
}
export async function changePassword(accessToken, currentPassword, newPassword) {
const api = new AuthApi(configFor(accessToken));
const result = await callApi(api.apiAuthChangePasswordPostRaw({ changePasswordRequest: { currentPassword, newPassword } }));
return { ok: result.ok, status: result.status, data: result.data };
}
export async function getPasswordPolicy() {
const api = new AuthApi(configFor(undefined));
const result = await callApi(api.apiAuthPasswordPolicyGetRaw());
if (!result.ok) return { ok: false, status: result.status };
return { ok: true, minLength: result.data.minLength };
}