die tenant admin oauth authentifizierung wurde implementiert und funktioniert jetzt. Zudem wurde das marketing frontend dashboard implementiert.

This commit is contained in:
Codex Agent
2025-11-04 16:14:17 +01:00
parent 92e64c361a
commit fe380689fb
63 changed files with 4239 additions and 1142 deletions

View File

@@ -0,0 +1,172 @@
import { ADMIN_LOGIN_PATH, ADMIN_LOGIN_START_PATH } from '../constants';
const LAST_DESTINATION_KEY = 'tenant.oauth.lastDestination';
function ensureLeadingSlash(target: string): string {
if (!target) {
return '/';
}
if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(target)) {
return target;
}
return target.startsWith('/') ? target : `/${target}`;
}
function base64UrlEncode(value: string): string {
const encoder = new TextEncoder();
const bytes = encoder.encode(value);
let binary = '';
bytes.forEach((byte) => {
binary += String.fromCharCode(byte);
});
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/u, '');
}
function base64UrlDecode(value: string): string | null {
try {
const padded = value.padEnd(value.length + ((4 - (value.length % 4)) % 4), '=');
const normalized = padded.replace(/-/g, '+').replace(/_/g, '/');
const binary = atob(normalized);
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
const decoder = new TextDecoder();
return decoder.decode(bytes);
} catch (error) {
console.warn('[Auth] Failed to decode return_to parameter', error);
return null;
}
}
export function encodeReturnTo(value: string): string {
const trimmed = value.trim();
if (trimmed === '') {
return '';
}
return base64UrlEncode(trimmed);
}
export function decodeReturnTo(value: string | null): string | null {
if (!value) {
return null;
}
return base64UrlDecode(value);
}
export interface ReturnTargetResolution {
finalTarget: string;
encodedFinal: string;
}
export function resolveReturnTarget(raw: string | null, fallback: string): ReturnTargetResolution {
const normalizedFallback = ensureLeadingSlash(fallback);
if (!raw) {
const encoded = encodeReturnTo(normalizedFallback);
return { finalTarget: normalizedFallback, encodedFinal: encoded };
}
const decodedPrimary = decodeReturnTo(raw);
if (!decodedPrimary) {
const encoded = encodeReturnTo(normalizedFallback);
return { finalTarget: normalizedFallback, encodedFinal: encoded };
}
const normalizedPrimary = decodedPrimary.trim();
const wrapperPaths = [ADMIN_LOGIN_START_PATH, ADMIN_LOGIN_PATH];
for (const wrapper of wrapperPaths) {
if (normalizedPrimary.startsWith(wrapper)) {
try {
const url = new URL(normalizedPrimary, window.location.origin);
const innerRaw = url.searchParams.get('return_to');
if (!innerRaw) {
const encoded = encodeReturnTo(normalizedFallback);
return { finalTarget: normalizedFallback, encodedFinal: encoded };
}
return resolveReturnTarget(innerRaw, normalizedFallback);
} catch (error) {
console.warn('[Auth] Failed to parse return_to chain', error);
const encoded = encodeReturnTo(normalizedFallback);
return { finalTarget: normalizedFallback, encodedFinal: encoded };
}
}
}
const finalTarget = ensureLeadingSlash(normalizedPrimary);
const encodedFinal = encodeReturnTo(finalTarget);
return { finalTarget, encodedFinal };
}
export function buildAdminOAuthStartPath(targetPath: string, encodedTarget?: string): string {
const sanitizedTarget = ensureLeadingSlash(targetPath);
const encoded = encodedTarget ?? encodeReturnTo(sanitizedTarget);
const url = new URL(ADMIN_LOGIN_START_PATH, window.location.origin);
url.searchParams.set('return_to', encoded);
return `${url.pathname}${url.search}`;
}
function resolveLocale(): string {
const raw = document.documentElement.lang || 'de';
const normalized = raw.toLowerCase();
if (normalized.includes('-')) {
return normalized.split('-')[0] || 'de';
}
return normalized || 'de';
}
export function buildMarketingLoginUrl(returnPath: string): string {
const sanitizedPath = ensureLeadingSlash(returnPath);
const encoded = encodeReturnTo(sanitizedPath);
const locale = resolveLocale();
const loginPath = `/${locale}/login`;
const url = new URL(loginPath, window.location.origin);
url.searchParams.set('return_to', encoded);
return url.toString();
}
export function storeLastDestination(path: string): void {
if (typeof window === 'undefined') {
return;
}
const sanitized = ensureLeadingSlash(path.trim());
try {
window.sessionStorage.setItem(LAST_DESTINATION_KEY, sanitized);
} catch (error) {
if (import.meta.env.DEV) {
console.warn('[Auth] Failed to store last destination', error);
}
}
}
export function consumeLastDestination(): string | null {
if (typeof window === 'undefined') {
return null;
}
try {
const value = window.sessionStorage.getItem(LAST_DESTINATION_KEY);
if (value) {
window.sessionStorage.removeItem(LAST_DESTINATION_KEY);
return ensureLeadingSlash(value);
}
} catch (error) {
if (import.meta.env.DEV) {
console.warn('[Auth] Failed to read last destination', error);
}
}
return null;
}