gendesign/tradein-mvp/frontend/src/lib/isPathAllowed.ts
Light1YT 7a2b055b35
All checks were successful
Deploy / changes (push) Successful in 5s
Deploy Trade-In / changes (push) Successful in 5s
Deploy Trade-In / build-backend (push) Has been skipped
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 1m42s
Deploy Trade-In / deploy (push) Successful in 39s
Deploy / build-frontend (push) Successful in 2m55s
Deploy / deploy (push) Successful in 1m1s
feat(rbac): frontend route-guard + nav filter (#586)
PR B (frontend) to backend RBAC #585. RouteGuard wraps app, NoAccessScreen on 403 from /me, TopNav filtered by role.
2026-05-26 06:51:45 +00:00

57 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* MIRROR of main frontend `frontend/src/lib/isPathAllowed.ts` — keep in sync
* manually. Rationale: tradein-mvp — отдельный Next.js bundle с basePath=/trade-in,
* нет общего workspace package, поэтому дублируем (как делают backend mirrors
* `backend/app/core/auth.py` ↔ `tradein-mvp/backend/app/core/auth.py`).
*
* JS-порт `backend/app/core/auth.py::is_path_allowed`. Семантика glob:
* `/**` → matches everything (admin scope)
* `/foo/**` → matches `/foo`, `/foo/`, `/foo/bar`, …
* `/foo/*` → один сегмент после `/foo/`
* `/foo` → exact match
*/
const REGEX_META = /[.+^${}()|[\]\\]/g;
function escapeRegex(s: string): string {
return s.replace(REGEX_META, "\\$&");
}
function globToRegex(pattern: string): RegExp {
const dstar = "\x00DSTAR\x00";
const sstar = "\x00SSTAR\x00";
const work = pattern.replace(/\*\*/g, dstar).replace(/\*/g, sstar);
let regex = escapeRegex(work);
regex = regex.replace(new RegExp(escapeRegex(dstar), "g"), ".*");
regex = regex.replace(new RegExp(escapeRegex(sstar), "g"), "[^/]*");
if (regex.endsWith("/.*")) {
regex = regex.slice(0, -"/.*".length) + "(?:/.*)?";
}
return new RegExp(`^${regex}$`);
}
const REGEX_CACHE = new Map<string, RegExp>();
function compileGlob(pattern: string): RegExp {
const cached = REGEX_CACHE.get(pattern);
if (cached) return cached;
const compiled = globToRegex(pattern);
REGEX_CACHE.set(pattern, compiled);
return compiled;
}
export function isPathAllowed(
allowedPaths: readonly string[],
denyPaths: readonly string[],
path: string,
): boolean {
for (const p of denyPaths) {
if (compileGlob(p).test(path)) return false;
}
for (const p of allowedPaths) {
if (compileGlob(p).test(path)) return true;
}
return false;
}