Fix broken login on omsorgWeb: restore refreshToken in auth responses
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

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>
This commit is contained in:
Felix Kemmler
2026-08-10 19:02:16 +02:00
co-authored by Claude Sonnet 5
parent dca0349e8c
commit 09dce2ab98
14 changed files with 226 additions and 24 deletions
@@ -1,3 +1,8 @@
namespace OmsorgCore.Api.Contracts; 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);
@@ -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);
@@ -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);
@@ -82,13 +82,26 @@ public class AuthController : ControllerBase
await _dispatcher.DispatchAsync(new AuditEvent(result.UserId, result.Username, ipAddress, "Login"), cancellationToken); await _dispatcher.DispatchAsync(new AuditEvent(result.UserId, result.Username, ipAddress, "Login"), cancellationToken);
SetRefreshTokenCookie(result.RefreshToken); 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")] [HttpPost("refresh")]
public async Task<ActionResult<LoginResponse>> Refresh(CancellationToken cancellationToken) public async Task<ActionResult<LoginResponse>> 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(); return Unauthorized();
} }
@@ -99,19 +112,28 @@ public class AuthController : ControllerBase
return Unauthorized(); return Unauthorized();
} }
SetRefreshTokenCookie(result.RefreshToken); // Cookie nur erneuern, wenn der Aufrufer auch eine Cookie vorgelegt hat - ein reiner
return Ok(new LoginResponse(result.Token, result.ExpiresAt.Value, result.MustChangePassword)); // 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")] [HttpPost("logout")]
public async Task<IActionResult> Logout(CancellationToken cancellationToken) public async Task<IActionResult> 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); 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( await _dispatcher.DispatchAsync(
new AuditEvent(_currentUserService.UserId, _currentUserService.Username, _currentUserService.IpAddress, "Logout"), new AuditEvent(_currentUserService.UserId, _currentUserService.Username, _currentUserService.IpAddress, "Logout"),
cancellationToken); cancellationToken);
@@ -31,7 +31,7 @@ return array(
'React\\ChildProcess\\' => array($vendorDir . '/react/child-process/src'), 'React\\ChildProcess\\' => array($vendorDir . '/react/child-process/src'),
'React\\Cache\\' => array($vendorDir . '/react/cache/src'), 'React\\Cache\\' => array($vendorDir . '/react/cache/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/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\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'), 'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
@@ -201,8 +201,8 @@ class ComposerStaticInit3d156d10b574f52a2dfd9a5ced1e76a6
), ),
'Psr\\Http\\Message\\' => 'Psr\\Http\\Message\\' =>
array ( array (
0 => __DIR__ . '/..' . '/psr/http-message/src', 0 => __DIR__ . '/..' . '/psr/http-factory/src',
1 => __DIR__ . '/..' . '/psr/http-factory/src', 1 => __DIR__ . '/..' . '/psr/http-message/src',
), ),
'Psr\\Http\\Client\\' => 'Psr\\Http\\Client\\' =>
array ( array (
@@ -3,7 +3,7 @@
'name' => '__root__', 'name' => '__root__',
'pretty_version' => 'dev-main', 'pretty_version' => 'dev-main',
'version' => 'dev-main', 'version' => 'dev-main',
'reference' => 'ee74ed65f5b0816bf56ecd8e4a346fe039c64dc6', 'reference' => 'dca0349e8c889d0d7ff4442450c6f80fabac8468',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@@ -13,7 +13,7 @@
'__root__' => array( '__root__' => array(
'pretty_version' => 'dev-main', 'pretty_version' => 'dev-main',
'version' => 'dev-main', 'version' => 'dev-main',
'reference' => 'ee74ed65f5b0816bf56ecd8e4a346fe039c64dc6', 'reference' => 'dca0349e8c889d0d7ff4442450c6f80fabac8468',
'type' => 'library', 'type' => 'library',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@@ -57,6 +57,7 @@ src/models/ForgotPasswordVerifyRequest.ts
src/models/ForgotPasswordVerifyResponse.ts src/models/ForgotPasswordVerifyResponse.ts
src/models/LoginRequest.ts src/models/LoginRequest.ts
src/models/LoginResponse.ts src/models/LoginResponse.ts
src/models/LogoutRequest.ts
src/models/MeResponse.ts src/models/MeResponse.ts
src/models/ModuleType.ts src/models/ModuleType.ts
src/models/OrderResponse.ts src/models/OrderResponse.ts
@@ -67,6 +68,7 @@ src/models/PermissionAction.ts
src/models/PermissionDto.ts src/models/PermissionDto.ts
src/models/PermissionEffect.ts src/models/PermissionEffect.ts
src/models/PermissionScope.ts src/models/PermissionScope.ts
src/models/RefreshRequest.ts
src/models/ResetUserPasswordRequest.ts src/models/ResetUserPasswordRequest.ts
src/models/RolePermissionsResponse.ts src/models/RolePermissionsResponse.ts
src/models/RoleResponse.ts src/models/RoleResponse.ts
+26 -6
View File
@@ -23,8 +23,10 @@ import type {
ForgotPasswordVerifyResponse, ForgotPasswordVerifyResponse,
LoginRequest, LoginRequest,
LoginResponse, LoginResponse,
LogoutRequest,
MeResponse, MeResponse,
PasswordPolicyResponse, PasswordPolicyResponse,
RefreshRequest,
} from '../models/index'; } from '../models/index';
import { import {
ChangePasswordRequestFromJSON, ChangePasswordRequestFromJSON,
@@ -43,10 +45,14 @@ import {
LoginRequestToJSON, LoginRequestToJSON,
LoginResponseFromJSON, LoginResponseFromJSON,
LoginResponseToJSON, LoginResponseToJSON,
LogoutRequestFromJSON,
LogoutRequestToJSON,
MeResponseFromJSON, MeResponseFromJSON,
MeResponseToJSON, MeResponseToJSON,
PasswordPolicyResponseFromJSON, PasswordPolicyResponseFromJSON,
PasswordPolicyResponseToJSON, PasswordPolicyResponseToJSON,
RefreshRequestFromJSON,
RefreshRequestToJSON,
} from '../models/index'; } from '../models/index';
export interface ApiAuthChangePasswordPostRequest { export interface ApiAuthChangePasswordPostRequest {
@@ -69,6 +75,14 @@ export interface ApiAuthLoginPostRequest {
loginRequest?: LoginRequest; 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<runtime.ApiResponse<void>> { async apiAuthLogoutPostRaw(requestParameters: ApiAuthLogoutPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<void>> {
const queryParameters: any = {}; const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {}; const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.accessToken) { if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken; const token = this.configuration.accessToken;
const tokenString = await token("Bearer", []); const tokenString = await token("Bearer", []);
@@ -285,6 +301,7 @@ export class AuthApi extends runtime.BaseAPI {
method: 'POST', method: 'POST',
headers: headerParameters, headers: headerParameters,
query: queryParameters, query: queryParameters,
body: LogoutRequestToJSON(requestParameters['logoutRequest']),
}, initOverrides); }, initOverrides);
return new runtime.VoidApiResponse(response); return new runtime.VoidApiResponse(response);
@@ -292,8 +309,8 @@ export class AuthApi extends runtime.BaseAPI {
/** /**
*/ */
async apiAuthLogoutPost(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> { async apiAuthLogoutPost(requestParameters: ApiAuthLogoutPostRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<void> {
await this.apiAuthLogoutPostRaw(initOverrides); await this.apiAuthLogoutPostRaw(requestParameters, initOverrides);
} }
/** /**
@@ -368,11 +385,13 @@ export class AuthApi extends runtime.BaseAPI {
/** /**
*/ */
async apiAuthRefreshPostRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<LoginResponse>> { async apiAuthRefreshPostRaw(requestParameters: ApiAuthRefreshPostRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<LoginResponse>> {
const queryParameters: any = {}; const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {}; const headerParameters: runtime.HTTPHeaders = {};
headerParameters['Content-Type'] = 'application/json';
if (this.configuration && this.configuration.accessToken) { if (this.configuration && this.configuration.accessToken) {
const token = this.configuration.accessToken; const token = this.configuration.accessToken;
const tokenString = await token("Bearer", []); const tokenString = await token("Bearer", []);
@@ -389,6 +408,7 @@ export class AuthApi extends runtime.BaseAPI {
method: 'POST', method: 'POST',
headers: headerParameters, headers: headerParameters,
query: queryParameters, query: queryParameters,
body: RefreshRequestToJSON(requestParameters['refreshRequest']),
}, initOverrides); }, initOverrides);
return new runtime.JSONApiResponse(response, (jsonValue) => LoginResponseFromJSON(jsonValue)); 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<LoginResponse> { async apiAuthRefreshPost(requestParameters: ApiAuthRefreshPostRequest = {}, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<LoginResponse> {
const response = await this.apiAuthRefreshPostRaw(initOverrides); const response = await this.apiAuthRefreshPostRaw(requestParameters, initOverrides);
return await response.value(); return await response.value();
} }
@@ -25,6 +25,12 @@ export interface LoginResponse {
* @memberof LoginResponse * @memberof LoginResponse
*/ */
accessToken?: string | null; accessToken?: string | null;
/**
*
* @type {string}
* @memberof LoginResponse
*/
refreshToken?: string | null;
/** /**
* *
* @type {Date} * @type {Date}
@@ -57,6 +63,7 @@ export function LoginResponseFromJSONTyped(json: any, ignoreDiscriminator: boole
return { return {
'accessToken': json['accessToken'] == null ? undefined : json['accessToken'], 'accessToken': json['accessToken'] == null ? undefined : json['accessToken'],
'refreshToken': json['refreshToken'] == null ? undefined : json['refreshToken'],
'expiresAt': json['expiresAt'] == null ? undefined : (new Date(json['expiresAt'])), 'expiresAt': json['expiresAt'] == null ? undefined : (new Date(json['expiresAt'])),
'mustChangePassword': json['mustChangePassword'] == null ? undefined : json['mustChangePassword'], 'mustChangePassword': json['mustChangePassword'] == null ? undefined : json['mustChangePassword'],
}; };
@@ -74,6 +81,7 @@ export function LoginResponseToJSONTyped(value?: LoginResponse | null, ignoreDis
return { return {
'accessToken': value['accessToken'], 'accessToken': value['accessToken'],
'refreshToken': value['refreshToken'],
'expiresAt': value['expiresAt'] == null ? undefined : ((value['expiresAt']).toISOString()), 'expiresAt': value['expiresAt'] == null ? undefined : ((value['expiresAt']).toISOString()),
'mustChangePassword': value['mustChangePassword'], 'mustChangePassword': value['mustChangePassword'],
}; };
@@ -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'],
};
}
@@ -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'],
};
}
@@ -35,6 +35,7 @@ export * from './ForgotPasswordVerifyRequest';
export * from './ForgotPasswordVerifyResponse'; export * from './ForgotPasswordVerifyResponse';
export * from './LoginRequest'; export * from './LoginRequest';
export * from './LoginResponse'; export * from './LoginResponse';
export * from './LogoutRequest';
export * from './MeResponse'; export * from './MeResponse';
export * from './ModuleType'; export * from './ModuleType';
export * from './OrderResponse'; export * from './OrderResponse';
@@ -45,6 +46,7 @@ export * from './PermissionAction';
export * from './PermissionDto'; export * from './PermissionDto';
export * from './PermissionEffect'; export * from './PermissionEffect';
export * from './PermissionScope'; export * from './PermissionScope';
export * from './RefreshRequest';
export * from './ResetUserPasswordRequest'; export * from './ResetUserPasswordRequest';
export * from './RolePermissionsResponse'; export * from './RolePermissionsResponse';
export * from './RoleResponse'; export * from './RoleResponse';
+7 -4
View File
@@ -2,8 +2,11 @@ import { AuthApi } from "omsorgcore-client-ts";
import { configFor, callApi } from "./apiClientHelpers.js"; import { configFor, callApi } from "./apiClientHelpers.js";
// Kapselt /api/auth/* von omsorgCore über den generierten Client (omsorgcore-client-ts). // Kapselt /api/auth/* von omsorgCore über den generierten Client (omsorgcore-client-ts).
// refresh/logout brauchen keinen Refresh-Token-Parameter mehr - er steckt in der // refresh/logout brauchen im Browser keinen Refresh-Token-Parameter - er steckt in der
// HttpOnly-Cookie, die der Browser dank credentials:"include" automatisch mitschickt. // 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) { function toTokenPair(data) {
return { return {
accessToken: data.accessToken, accessToken: data.accessToken,
@@ -23,14 +26,14 @@ export async function login(username, password) {
export async function refresh() { export async function refresh() {
const api = new AuthApi(configFor(undefined)); 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 }; if (!result.ok) return { ok: false, status: result.status };
return { ok: true, ...toTokenPair(result.data) }; return { ok: true, ...toTokenPair(result.data) };
} }
export async function logout() { export async function logout() {
const api = new AuthApi(configFor(undefined)); 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 }; return { ok: result.ok, status: result.status };
} }