neuer demo tenant switcher + demo tenants mit eigenem artisan command. Event Admin überarbeitet, aber das ist nur ein Zwischenstand.

This commit is contained in:
Codex Agent
2025-11-25 09:47:39 +01:00
parent 8947a37261
commit fd788ef770
22 changed files with 1096 additions and 299 deletions

View File

@@ -16,6 +16,13 @@ export class AuthError extends Error {
let cachedToken: StoredToken | null = null;
function getCsrfToken(): string | undefined {
const meta = typeof document !== 'undefined'
? (document.querySelector('meta[name=\"csrf-token\"]') as HTMLMetaElement | null)
: null;
return meta?.content;
}
function decodeStoredToken(raw: string | null): StoredToken | null {
if (!raw) {
return null;
@@ -106,6 +113,38 @@ function persistToken(token: StoredToken): void {
cachedToken = token;
}
async function exchangeSessionForToken(): Promise<StoredToken | null> {
const csrf = getCsrfToken();
try {
const response = await fetch('/api/v1/tenant-auth/exchange', {
method: 'POST',
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
...(csrf ? { 'X-CSRF-TOKEN': csrf } : {}),
},
credentials: 'same-origin',
});
if (!response.ok) {
return null;
}
const data = (await response.json()) as { token?: string; abilities?: string[] } | null;
if (!data?.token) {
return null;
}
return storePersonalAccessToken(data.token, Array.isArray(data.abilities) ? data.abilities : []);
} catch (error) {
if (import.meta.env.DEV) {
console.warn('[Auth] Session token exchange failed', error);
}
return null;
}
}
export function storePersonalAccessToken(accessToken: string, abilities: string[]): StoredToken {
const stored: StoredToken = {
accessToken,
@@ -167,22 +206,29 @@ export function isAuthError(value: unknown): value is AuthError {
}
export async function authorizedFetch(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {
const stored = loadToken();
if (!stored) {
notifyAuthFailure();
throw new AuthError('unauthenticated', 'No active tenant admin token');
}
const headers = new Headers(init.headers);
headers.set('Authorization', `Bearer ${stored.accessToken}`);
if (!headers.has('Accept')) {
headers.set('Accept', 'application/json');
}
const response = await fetch(input, { ...init, headers });
let stored = loadToken();
if (!stored) {
stored = await exchangeSessionForToken();
}
if (stored?.accessToken) {
headers.set('Authorization', `Bearer ${stored.accessToken}`);
}
const response = await fetch(input, {
...init,
headers,
credentials: init.credentials ?? 'same-origin',
});
if (response.status === 401) {
clearTokens();
if (stored) {
clearTokens();
}
notifyAuthFailure();
throw new AuthError('unauthorized', 'Token rejected by API');
}