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
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:
co-authored by
Claude Sonnet 5
parent
dca0349e8c
commit
09dce2ab98
@@ -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'),
|
||||||
|
|||||||
+2
-2
@@ -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
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user