124 lines
2.8 KiB
TypeScript
124 lines
2.8 KiB
TypeScript
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<AuthenticationOutcome> {
|
|
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
|
|
};
|
|
})
|
|
);
|
|
}
|