mirror of
https://github.com/goauthentik/authentik
synced 2026-04-27 09:57:31 +02:00
* website: Unify Netlify redirects with Docusaurus's client-side router. * website: Flesh out client-redirects. * Potential fix for code scanning alert no. 256: Incomplete string escaping or encoding Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> * website: Use package. * website: use permanent redirect. * Apply suggestions from code review Co-authored-by: Dewi Roberts <dewi@goauthentik.io> Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Dewi Roberts <dewi@goauthentik.io> Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> * website: Spelling. * website: Add link. * website: Clarify. * website: Remove doc. * website: Add redirects for API and integrations. --------- Signed-off-by: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Dewi Roberts <dewi@goauthentik.io>
168 lines
3.9 KiB
JavaScript
168 lines
3.9 KiB
JavaScript
/**
|
|
* @file Docusaurus redirects utils.
|
|
*/
|
|
|
|
import escapeStringRegexp from "escape-string-regexp";
|
|
|
|
/**
|
|
* @typedef {Object} RedirectEntry
|
|
*
|
|
* @property {string} from
|
|
* @property {string} to
|
|
* @property {boolean} force
|
|
*/
|
|
|
|
/**
|
|
* Given a pathname, return a RegExp that matches the pathname.
|
|
*
|
|
* @param {string} pathname
|
|
* @returns {RegExp}
|
|
*/
|
|
export function pathnameToMatcher(pathname) {
|
|
return new RegExp(
|
|
"^" + escapeStringRegexp(pathname).replace(/\\\*/g, "(?<splat>.*)").replace(/\//g, "\\/"),
|
|
"i",
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Given a destination, return a RegExp that matches the destination.
|
|
*
|
|
* This is used to match the inverse of a pathname matcher.
|
|
* @param {string} destination
|
|
* @returns {RegExp}
|
|
*/
|
|
export function destinationToMatcher(destination) {
|
|
const safeDestination = escapeStringRegexp(destination)
|
|
.replace(/:splat/g, "(?<splat>.*)")
|
|
.replace(/\//g, "\\/");
|
|
return new RegExp("^" + safeDestination + "$", "i");
|
|
}
|
|
|
|
/**
|
|
* A two-way map of route rewrites.
|
|
*/
|
|
export class RewriteIndex {
|
|
/**
|
|
* @type {Map<RegExp, string>}
|
|
*/
|
|
#fromMap = new Map();
|
|
|
|
/**
|
|
* @type {Map<RegExp, string>}
|
|
*/
|
|
#destinationMap = new Map();
|
|
|
|
/**
|
|
* @type {Map<string, string>}
|
|
*/
|
|
#aliasMap = new Map();
|
|
|
|
/**
|
|
*
|
|
* @param {Iterable<RedirectEntry>} redirectEntries
|
|
*/
|
|
constructor(redirectEntries) {
|
|
for (const entry of redirectEntries) {
|
|
const fromMatcher = pathnameToMatcher(entry.from);
|
|
this.#fromMap.set(fromMatcher, entry.to);
|
|
|
|
const destinationMatcher = destinationToMatcher(entry.to);
|
|
this.#destinationMap.set(destinationMatcher, entry.from.replaceAll("*", ":splat"));
|
|
|
|
if (!entry.from.includes("*")) {
|
|
this.#aliasMap.set(entry.to, entry.from);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find the final destination for the given pathname, following all redirects.
|
|
*
|
|
* @param {string} pathname
|
|
* @returns {string}
|
|
*/
|
|
finalDestination(pathname) {
|
|
if (!pathname) return pathname;
|
|
|
|
let destination = this.findNextDestination(pathname);
|
|
|
|
while (true) {
|
|
const next = this.findNextDestination(destination);
|
|
|
|
if (next === destination) {
|
|
break;
|
|
}
|
|
|
|
destination = next;
|
|
}
|
|
|
|
return destination ?? pathname;
|
|
}
|
|
|
|
/**
|
|
* Find the next destination for the given pathname.
|
|
*
|
|
* @param {string} pathname
|
|
* @returns {string}
|
|
*/
|
|
findNextDestination(pathname) {
|
|
for (const [from, to] of this.#fromMap) {
|
|
const match = from.exec(pathname);
|
|
|
|
if (!match) continue;
|
|
|
|
let result = to;
|
|
|
|
if (!match.groups) return result;
|
|
|
|
for (const [group, value] of Object.entries(match.groups)) {
|
|
result = result.replace(`:${group}`, value);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
return pathname;
|
|
}
|
|
|
|
/**
|
|
* @param {string} pathname
|
|
* @returns {string[]}
|
|
*/
|
|
findAliases(pathname) {
|
|
const aliases = new Set();
|
|
|
|
for (const [destinationMatcher, destination] of this.#destinationMap) {
|
|
const alias = this.#aliasMap.get(pathname);
|
|
|
|
if (alias) {
|
|
aliases.add(alias);
|
|
}
|
|
|
|
const match = destinationMatcher.exec(pathname);
|
|
|
|
if (!match) continue;
|
|
|
|
let result = destination;
|
|
|
|
if (!match.groups) {
|
|
if (pathname === result) continue;
|
|
|
|
aliases.add(result);
|
|
continue;
|
|
}
|
|
|
|
for (const [group, value] of Object.entries(match.groups)) {
|
|
result = result.replace(`:${group}`, value);
|
|
}
|
|
|
|
if (result === pathname) continue;
|
|
|
|
aliases.add(result);
|
|
}
|
|
|
|
return Array.from(aliases);
|
|
}
|
|
}
|