From 09dce2ab98512e60166c382ccb4486d9d307a747 Mon Sep 17 00:00:00 2001 From: Felix Kemmler Date: Mon, 10 Aug 2026 19:02:16 +0200 Subject: [PATCH] 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 --- .../OmsorgCore.Api/Contracts/LoginResponse.cs | 7 +- .../OmsorgCore.Api/Contracts/LogoutRequest.cs | 5 ++ .../Contracts/RefreshRequest.cs | 5 ++ .../Controllers/AuthController.cs | 38 ++++++++--- .../vendor/composer/autoload_psr4.php | 2 +- .../vendor/composer/autoload_static.php | 4 +- .../vendor/composer/installed.php | 4 +- .../api-client-ts/.openapi-generator/FILES | 2 + omsorgapp/api-client-ts/src/apis/AuthApi.ts | 32 +++++++-- .../api-client-ts/src/models/LoginResponse.ts | 8 +++ .../api-client-ts/src/models/LogoutRequest.ts | 65 +++++++++++++++++++ .../src/models/RefreshRequest.ts | 65 +++++++++++++++++++ omsorgapp/api-client-ts/src/models/index.ts | 2 + omsorgapp/src/api/authApi.js | 11 ++-- 14 files changed, 226 insertions(+), 24 deletions(-) create mode 100644 omsorgCore/src/OmsorgCore.Api/Contracts/LogoutRequest.cs create mode 100644 omsorgCore/src/OmsorgCore.Api/Contracts/RefreshRequest.cs create mode 100644 omsorgapp/api-client-ts/src/models/LogoutRequest.ts create mode 100644 omsorgapp/api-client-ts/src/models/RefreshRequest.ts diff --git a/omsorgCore/src/OmsorgCore.Api/Contracts/LoginResponse.cs b/omsorgCore/src/OmsorgCore.Api/Contracts/LoginResponse.cs index 0916845..c84afb1 100644 --- a/omsorgCore/src/OmsorgCore.Api/Contracts/LoginResponse.cs +++ b/omsorgCore/src/OmsorgCore.Api/Contracts/LoginResponse.cs @@ -1,3 +1,8 @@ namespace OmsorgCore.Api.Contracts; -public record LoginResponse(string AccessToken, DateTime ExpiresAt, bool MustChangePassword); +// RefreshToken steht hier UND als HttpOnly-Cookie (siehe AuthController.SetRefreshTokenCookie) - +// zwei Konsumenten mit unterschiedlichem Transport: omsorgapp (Browser) ignoriert dieses Feld +// bewusst und verlässt sich nur auf die Cookie (XSS-sicher, siehe omsorgapp/src/api/authApi.js). +// omsorgWeb (PHP, server-seitiger Aufrufer ohne Browser-Cookie-Jar) MUSS den Wert hier lesen und +// selbst in der PHP-Session verwalten, siehe omsorgWeb/mitarbeiter-app/lib/auth.php. +public record LoginResponse(string AccessToken, string RefreshToken, DateTime ExpiresAt, bool MustChangePassword); diff --git a/omsorgCore/src/OmsorgCore.Api/Contracts/LogoutRequest.cs b/omsorgCore/src/OmsorgCore.Api/Contracts/LogoutRequest.cs new file mode 100644 index 0000000..d0d1412 --- /dev/null +++ b/omsorgCore/src/OmsorgCore.Api/Contracts/LogoutRequest.cs @@ -0,0 +1,5 @@ +namespace OmsorgCore.Api.Contracts; + +// Optional - nur für Aufrufer ohne Browser-Cookie-Jar (server-seitige API-Clients wie omsorgWeb), +// siehe AuthController.Logout. Ein Browser schickt keinen Body, die HttpOnly-Cookie reicht. +public record LogoutRequest(string? RefreshToken); diff --git a/omsorgCore/src/OmsorgCore.Api/Contracts/RefreshRequest.cs b/omsorgCore/src/OmsorgCore.Api/Contracts/RefreshRequest.cs new file mode 100644 index 0000000..4b29bec --- /dev/null +++ b/omsorgCore/src/OmsorgCore.Api/Contracts/RefreshRequest.cs @@ -0,0 +1,5 @@ +namespace OmsorgCore.Api.Contracts; + +// Optional - nur für Aufrufer ohne Browser-Cookie-Jar (server-seitige API-Clients wie omsorgWeb), +// siehe AuthController.Refresh. Ein Browser schickt keinen Body, die HttpOnly-Cookie reicht. +public record RefreshRequest(string? RefreshToken); diff --git a/omsorgCore/src/OmsorgCore.Api/Controllers/AuthController.cs b/omsorgCore/src/OmsorgCore.Api/Controllers/AuthController.cs index ff595e0..7069ff0 100644 --- a/omsorgCore/src/OmsorgCore.Api/Controllers/AuthController.cs +++ b/omsorgCore/src/OmsorgCore.Api/Controllers/AuthController.cs @@ -82,13 +82,26 @@ public class AuthController : ControllerBase await _dispatcher.DispatchAsync(new AuditEvent(result.UserId, result.Username, ipAddress, "Login"), cancellationToken); SetRefreshTokenCookie(result.RefreshToken); - return Ok(new LoginResponse(result.Token, result.ExpiresAt.Value, result.MustChangePassword)); + return Ok(new LoginResponse(result.Token, result.RefreshToken, result.ExpiresAt.Value, result.MustChangePassword)); + } + + // Zwei Transportwege für den vorgelegten Refresh-Token, in dieser Reihenfolge geprüft: + // 1. HttpOnly-Cookie (Browser, omsorgapp) - schickt bewusst keinen Body. + // 2. Body (server-seitige API-Clients ohne Cookie-Jar, z. B. omsorgWeb per PHP-cURL/Guzzle). + private string? ResolveRefreshToken(string? bodyRefreshToken) + { + if (Request.Cookies.TryGetValue(RefreshTokenCookieName, out var cookieToken) && !string.IsNullOrEmpty(cookieToken)) + { + return cookieToken; + } + return string.IsNullOrEmpty(bodyRefreshToken) ? null : bodyRefreshToken; } [HttpPost("refresh")] - public async Task> Refresh(CancellationToken cancellationToken) + public async Task> Refresh(RefreshRequest? request, CancellationToken cancellationToken) { - if (!Request.Cookies.TryGetValue(RefreshTokenCookieName, out var refreshToken) || string.IsNullOrEmpty(refreshToken)) + var refreshToken = ResolveRefreshToken(request?.RefreshToken); + if (refreshToken is null) { return Unauthorized(); } @@ -99,19 +112,28 @@ public class AuthController : ControllerBase return Unauthorized(); } - SetRefreshTokenCookie(result.RefreshToken); - return Ok(new LoginResponse(result.Token, result.ExpiresAt.Value, result.MustChangePassword)); + // Cookie nur erneuern, wenn der Aufrufer auch eine Cookie vorgelegt hat - ein reiner + // API-Client (Body-basiert) soll keine Cookie untergeschoben bekommen, die er nie abfragt. + if (Request.Cookies.ContainsKey(RefreshTokenCookieName)) + { + SetRefreshTokenCookie(result.RefreshToken); + } + return Ok(new LoginResponse(result.Token, result.RefreshToken, result.ExpiresAt.Value, result.MustChangePassword)); } [HttpPost("logout")] - public async Task Logout(CancellationToken cancellationToken) + public async Task Logout(LogoutRequest? request, CancellationToken cancellationToken) { - if (Request.Cookies.TryGetValue(RefreshTokenCookieName, out var refreshToken) && !string.IsNullOrEmpty(refreshToken)) + var refreshToken = ResolveRefreshToken(request?.RefreshToken); + if (refreshToken is not null) { await _authService.RevokeAsync(refreshToken, cancellationToken); } - Response.Cookies.Delete(RefreshTokenCookieName, new CookieOptions { Path = "/api/auth" }); + if (Request.Cookies.ContainsKey(RefreshTokenCookieName)) + { + Response.Cookies.Delete(RefreshTokenCookieName, new CookieOptions { Path = "/api/auth" }); + } await _dispatcher.DispatchAsync( new AuditEvent(_currentUserService.UserId, _currentUserService.Username, _currentUserService.IpAddress, "Logout"), cancellationToken); diff --git a/omsorgWeb/mitarbeiter-app/api-client-php/vendor/composer/autoload_psr4.php b/omsorgWeb/mitarbeiter-app/api-client-php/vendor/composer/autoload_psr4.php index e3b0225..d758694 100644 --- a/omsorgWeb/mitarbeiter-app/api-client-php/vendor/composer/autoload_psr4.php +++ b/omsorgWeb/mitarbeiter-app/api-client-php/vendor/composer/autoload_psr4.php @@ -31,7 +31,7 @@ return array( 'React\\ChildProcess\\' => array($vendorDir . '/react/child-process/src'), 'React\\Cache\\' => array($vendorDir . '/react/cache/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), - 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), 'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), diff --git a/omsorgWeb/mitarbeiter-app/api-client-php/vendor/composer/autoload_static.php b/omsorgWeb/mitarbeiter-app/api-client-php/vendor/composer/autoload_static.php index 720df9f..eb21c60 100644 --- a/omsorgWeb/mitarbeiter-app/api-client-php/vendor/composer/autoload_static.php +++ b/omsorgWeb/mitarbeiter-app/api-client-php/vendor/composer/autoload_static.php @@ -201,8 +201,8 @@ class ComposerStaticInit3d156d10b574f52a2dfd9a5ced1e76a6 ), 'Psr\\Http\\Message\\' => array ( - 0 => __DIR__ . '/..' . '/psr/http-message/src', - 1 => __DIR__ . '/..' . '/psr/http-factory/src', + 0 => __DIR__ . '/..' . '/psr/http-factory/src', + 1 => __DIR__ . '/..' . '/psr/http-message/src', ), 'Psr\\Http\\Client\\' => array ( diff --git a/omsorgWeb/mitarbeiter-app/api-client-php/vendor/composer/installed.php b/omsorgWeb/mitarbeiter-app/api-client-php/vendor/composer/installed.php index 585fda0..97fd85c 100644 --- a/omsorgWeb/mitarbeiter-app/api-client-php/vendor/composer/installed.php +++ b/omsorgWeb/mitarbeiter-app/api-client-php/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => '__root__', 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => 'ee74ed65f5b0816bf56ecd8e4a346fe039c64dc6', + 'reference' => 'dca0349e8c889d0d7ff4442450c6f80fabac8468', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -13,7 +13,7 @@ '__root__' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => 'ee74ed65f5b0816bf56ecd8e4a346fe039c64dc6', + 'reference' => 'dca0349e8c889d0d7ff4442450c6f80fabac8468', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), diff --git a/omsorgapp/api-client-ts/.openapi-generator/FILES b/omsorgapp/api-client-ts/.openapi-generator/FILES index f7d2a90..3b6d4e1 100644 --- a/omsorgapp/api-client-ts/.openapi-generator/FILES +++ b/omsorgapp/api-client-ts/.openapi-generator/FILES @@ -57,6 +57,7 @@ src/models/ForgotPasswordVerifyRequest.ts src/models/ForgotPasswordVerifyResponse.ts src/models/LoginRequest.ts src/models/LoginResponse.ts +src/models/LogoutRequest.ts src/models/MeResponse.ts src/models/ModuleType.ts src/models/OrderResponse.ts @@ -67,6 +68,7 @@ src/models/PermissionAction.ts src/models/PermissionDto.ts src/models/PermissionEffect.ts src/models/PermissionScope.ts +src/models/RefreshRequest.ts src/models/ResetUserPasswordRequest.ts src/models/RolePermissionsResponse.ts src/models/RoleResponse.ts diff --git a/omsorgapp/api-client-ts/src/apis/AuthApi.ts b/omsorgapp/api-client-ts/src/apis/AuthApi.ts index 854b564..83cb5ba 100644 --- a/omsorgapp/api-client-ts/src/apis/AuthApi.ts +++ b/omsorgapp/api-client-ts/src/apis/AuthApi.ts @@ -23,8 +23,10 @@ import type { ForgotPasswordVerifyResponse, LoginRequest, LoginResponse, + LogoutRequest, MeResponse, PasswordPolicyResponse, + RefreshRequest, } from '../models/index'; import { ChangePasswordRequestFromJSON, @@ -43,10 +45,14 @@ import { LoginRequestToJSON, LoginResponseFromJSON, LoginResponseToJSON, + LogoutRequestFromJSON, + LogoutRequestToJSON, MeResponseFromJSON, MeResponseToJSON, PasswordPolicyResponseFromJSON, PasswordPolicyResponseToJSON, + RefreshRequestFromJSON, + RefreshRequestToJSON, } from '../models/index'; export interface ApiAuthChangePasswordPostRequest { @@ -69,6 +75,14 @@ export interface ApiAuthLoginPostRequest { loginRequest?: LoginRequest; } +export interface ApiAuthLogoutPostRequest { + logoutRequest?: LogoutRequest; +} + +export interface ApiAuthRefreshPostRequest { + refreshRequest?: RefreshRequest; +} + /** * */ @@ -264,11 +278,13 @@ export class AuthApi extends runtime.BaseAPI { /** */ - async apiAuthLogoutPostRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async apiAuthLogoutPostRaw(requestParameters: ApiAuthLogoutPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; + headerParameters['Content-Type'] = 'application/json'; + if (this.configuration && this.configuration.accessToken) { const token = this.configuration.accessToken; const tokenString = await token("Bearer", []); @@ -285,6 +301,7 @@ export class AuthApi extends runtime.BaseAPI { method: 'POST', headers: headerParameters, query: queryParameters, + body: LogoutRequestToJSON(requestParameters['logoutRequest']), }, initOverrides); return new runtime.VoidApiResponse(response); @@ -292,8 +309,8 @@ export class AuthApi extends runtime.BaseAPI { /** */ - async apiAuthLogoutPost(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - await this.apiAuthLogoutPostRaw(initOverrides); + async apiAuthLogoutPost(requestParameters: ApiAuthLogoutPostRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + await this.apiAuthLogoutPostRaw(requestParameters, initOverrides); } /** @@ -368,11 +385,13 @@ export class AuthApi extends runtime.BaseAPI { /** */ - async apiAuthRefreshPostRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { + async apiAuthRefreshPostRaw(requestParameters: ApiAuthRefreshPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> { const queryParameters: any = {}; const headerParameters: runtime.HTTPHeaders = {}; + headerParameters['Content-Type'] = 'application/json'; + if (this.configuration && this.configuration.accessToken) { const token = this.configuration.accessToken; const tokenString = await token("Bearer", []); @@ -389,6 +408,7 @@ export class AuthApi extends runtime.BaseAPI { method: 'POST', headers: headerParameters, query: queryParameters, + body: RefreshRequestToJSON(requestParameters['refreshRequest']), }, initOverrides); return new runtime.JSONApiResponse(response, (jsonValue) => LoginResponseFromJSON(jsonValue)); @@ -396,8 +416,8 @@ export class AuthApi extends runtime.BaseAPI { /** */ - async apiAuthRefreshPost(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { - const response = await this.apiAuthRefreshPostRaw(initOverrides); + async apiAuthRefreshPost(requestParameters: ApiAuthRefreshPostRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise { + const response = await this.apiAuthRefreshPostRaw(requestParameters, initOverrides); return await response.value(); } diff --git a/omsorgapp/api-client-ts/src/models/LoginResponse.ts b/omsorgapp/api-client-ts/src/models/LoginResponse.ts index cd03b22..b0049d7 100644 --- a/omsorgapp/api-client-ts/src/models/LoginResponse.ts +++ b/omsorgapp/api-client-ts/src/models/LoginResponse.ts @@ -25,6 +25,12 @@ export interface LoginResponse { * @memberof LoginResponse */ accessToken?: string | null; + /** + * + * @type {string} + * @memberof LoginResponse + */ + refreshToken?: string | null; /** * * @type {Date} @@ -57,6 +63,7 @@ export function LoginResponseFromJSONTyped(json: any, ignoreDiscriminator: boole return { 'accessToken': json['accessToken'] == null ? undefined : json['accessToken'], + 'refreshToken': json['refreshToken'] == null ? undefined : json['refreshToken'], 'expiresAt': json['expiresAt'] == null ? undefined : (new Date(json['expiresAt'])), 'mustChangePassword': json['mustChangePassword'] == null ? undefined : json['mustChangePassword'], }; @@ -74,6 +81,7 @@ export function LoginResponseToJSONTyped(value?: LoginResponse | null, ignoreDis return { 'accessToken': value['accessToken'], + 'refreshToken': value['refreshToken'], 'expiresAt': value['expiresAt'] == null ? undefined : ((value['expiresAt']).toISOString()), 'mustChangePassword': value['mustChangePassword'], }; diff --git a/omsorgapp/api-client-ts/src/models/LogoutRequest.ts b/omsorgapp/api-client-ts/src/models/LogoutRequest.ts new file mode 100644 index 0000000..5aabcdf --- /dev/null +++ b/omsorgapp/api-client-ts/src/models/LogoutRequest.ts @@ -0,0 +1,65 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * OmsorgCore.Api + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface LogoutRequest + */ +export interface LogoutRequest { + /** + * + * @type {string} + * @memberof LogoutRequest + */ + refreshToken?: string | null; +} + +/** + * Check if a given object implements the LogoutRequest interface. + */ +export function instanceOfLogoutRequest(value: object): value is LogoutRequest { + return true; +} + +export function LogoutRequestFromJSON(json: any): LogoutRequest { + return LogoutRequestFromJSONTyped(json, false); +} + +export function LogoutRequestFromJSONTyped(json: any, ignoreDiscriminator: boolean): LogoutRequest { + if (json == null) { + return json; + } + return { + + 'refreshToken': json['refreshToken'] == null ? undefined : json['refreshToken'], + }; +} + +export function LogoutRequestToJSON(json: any): LogoutRequest { + return LogoutRequestToJSONTyped(json, false); +} + +export function LogoutRequestToJSONTyped(value?: LogoutRequest | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'refreshToken': value['refreshToken'], + }; +} + diff --git a/omsorgapp/api-client-ts/src/models/RefreshRequest.ts b/omsorgapp/api-client-ts/src/models/RefreshRequest.ts new file mode 100644 index 0000000..4a2f8b8 --- /dev/null +++ b/omsorgapp/api-client-ts/src/models/RefreshRequest.ts @@ -0,0 +1,65 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * OmsorgCore.Api + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { mapValues } from '../runtime'; +/** + * + * @export + * @interface RefreshRequest + */ +export interface RefreshRequest { + /** + * + * @type {string} + * @memberof RefreshRequest + */ + refreshToken?: string | null; +} + +/** + * Check if a given object implements the RefreshRequest interface. + */ +export function instanceOfRefreshRequest(value: object): value is RefreshRequest { + return true; +} + +export function RefreshRequestFromJSON(json: any): RefreshRequest { + return RefreshRequestFromJSONTyped(json, false); +} + +export function RefreshRequestFromJSONTyped(json: any, ignoreDiscriminator: boolean): RefreshRequest { + if (json == null) { + return json; + } + return { + + 'refreshToken': json['refreshToken'] == null ? undefined : json['refreshToken'], + }; +} + +export function RefreshRequestToJSON(json: any): RefreshRequest { + return RefreshRequestToJSONTyped(json, false); +} + +export function RefreshRequestToJSONTyped(value?: RefreshRequest | null, ignoreDiscriminator: boolean = false): any { + if (value == null) { + return value; + } + + return { + + 'refreshToken': value['refreshToken'], + }; +} + diff --git a/omsorgapp/api-client-ts/src/models/index.ts b/omsorgapp/api-client-ts/src/models/index.ts index 5f89593..da36632 100644 --- a/omsorgapp/api-client-ts/src/models/index.ts +++ b/omsorgapp/api-client-ts/src/models/index.ts @@ -35,6 +35,7 @@ export * from './ForgotPasswordVerifyRequest'; export * from './ForgotPasswordVerifyResponse'; export * from './LoginRequest'; export * from './LoginResponse'; +export * from './LogoutRequest'; export * from './MeResponse'; export * from './ModuleType'; export * from './OrderResponse'; @@ -45,6 +46,7 @@ export * from './PermissionAction'; export * from './PermissionDto'; export * from './PermissionEffect'; export * from './PermissionScope'; +export * from './RefreshRequest'; export * from './ResetUserPasswordRequest'; export * from './RolePermissionsResponse'; export * from './RoleResponse'; diff --git a/omsorgapp/src/api/authApi.js b/omsorgapp/src/api/authApi.js index 110069f..9e7a152 100644 --- a/omsorgapp/src/api/authApi.js +++ b/omsorgapp/src/api/authApi.js @@ -2,8 +2,11 @@ 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 keinen Refresh-Token-Parameter mehr - er steckt in der -// HttpOnly-Cookie, die der Browser dank credentials:"include" automatisch mitschickt. +// 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, @@ -23,14 +26,14 @@ export async function login(username, password) { export async function refresh() { const api = new AuthApi(configFor(undefined)); - const result = await callApi(api.apiAuthRefreshPostRaw()); + 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()); + const result = await callApi(api.apiAuthLogoutPostRaw({})); return { ok: result.ok, status: result.status }; }