import { filter, map, Observable, take } from 'rxjs'; import { UsersActions } from '../../../../store/users/users.actions'; import type { User } from '../../../../shared-kernel'; export const DEFAULT_POST_AUTH_URL = '/dashboard'; const AUTH_ROUTE_PATHS = new Set(['/login', '/register']); const MAX_RETURN_URL_DEPTH = 10; export type AuthenticationOutcome = | { kind: 'success'; user: User } | { kind: 'failure'; error: string }; export function isAuthRoutePath(path: string): boolean { return AUTH_ROUTE_PATHS.has(path); } export function getRoutePathFromUrl(url: string): string { if (!url) { return '/'; } const [path] = url.split(/[?#]/, 1); return path || '/'; } export function extractReturnUrlParam(url: string): string | null { const queryStart = url.indexOf('?'); if (queryStart === -1) { return null; } const hashStart = url.indexOf('#', queryStart + 1); const query = hashStart === -1 ? url.slice(queryStart + 1) : url.slice(queryStart + 1, hashStart); return new URLSearchParams(query).get('returnUrl'); } export function resolveSafeReturnUrl( url: string | null | undefined, fallback = DEFAULT_POST_AUTH_URL ): string { let candidate = url?.trim() ?? ''; let depth = 0; while (candidate && depth < MAX_RETURN_URL_DEPTH) { if (!candidate.startsWith('/') || candidate.startsWith('//')) { return fallback; } const path = getRoutePathFromUrl(candidate); if (!isAuthRoutePath(path)) { return candidate; } const nestedReturnUrl = extractReturnUrlParam(candidate)?.trim(); if (!nestedReturnUrl) { return fallback; } candidate = nestedReturnUrl; depth += 1; } return fallback; } export function buildLoginReturnQueryParams( currentUrl: string, fallback = DEFAULT_POST_AUTH_URL ): { returnUrl?: string } { const safeReturnUrl = resolveSafeReturnUrl(currentUrl, fallback); if (safeReturnUrl === fallback) { return {}; } return { returnUrl: safeReturnUrl }; } export function waitForAuthenticationOutcome( actions$: Observable<{ type: string; user?: User; error?: string }> ): Observable { return actions$.pipe( filter((action) => action.type === UsersActions.setCurrentUser.type || action.type === UsersActions.loadCurrentUserFailure.type ), take(1), map((action) => { if (action.type === UsersActions.loadCurrentUserFailure.type) { return { kind: 'failure' as const, error: action.error || 'Authentication failed' }; } if (!action.user) { return { kind: 'failure' as const, error: 'Authentication failed' }; } return { kind: 'success' as const, user: action.user }; }) ); }