neuer demo tenant switcher + demo tenants mit eigenem artisan command. Event Admin überarbeitet, aber das ist nur ein Zwischenstand.
This commit is contained in:
@@ -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');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user