Files
authentik/website/docusaurus-theme/redirects/index.mjs
Teffen Ellis 6d81aea5aa website: Unify Netlify redirects with Docusaurus's client-side router. (#16430)
* 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>
2025-08-29 15:11:18 +02:00

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);
}
}