Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic redirect support #560

Merged
merged 4 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ There's also an auto-builder utility that watches the filesystem and continuousl

To serve the website locally with a web browser specify the base URL with `KC_URL`:

export KC_URL=http://localhost:8000
export KC_URL=http://localhost:5000
mvn install
npm run serve

Finally, when building the website to be published you need to include `-Dpublish`. This should usually not be done manually though as there is a GitHub Action that takes care of building and publishing to `https://github.com/keycloak/keycloak.github.io`.

Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,11 @@
"bootstrap": "5.1.x",
"@fortawesome/fontawesome-free": "5.15.4",
"tocbot": "4.18.0"
},
"scripts": {
"serve": "http-server target/web -c-1 -p 5000 -a 127.0.0.1 --cors"
},
"devDependencies": {
"http-server": "^14.1.1"
}
}
30 changes: 20 additions & 10 deletions pages/404.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,26 @@
<@tmpl.page current="keys" title="Page not found" noindex=true>

<div class="container mt-5">
<h1>Page not found</h1>
<h1 id="heading">Page not found</h1>

<p>
The page you’re looking for does not exist. It may have been moved. You can return to <a href="${links.home}">the start page</a>, or follow one of the links in the navigation on the top.
</p>
<p>
If you arrived at this page by clicking on a link, please notify the owner of the site that the link is broken. If you typed the URL of this page manually, please double-check that you entered the address correctly.
</p>
<div id="notfound">
<p>
The page you’re looking for does not exist. It may have been moved. You can return to <a href="${links.home}">the start page</a>, or follow one of the links in the navigation on the top.
</p>
<p>
If you arrived at this page by clicking on a link, please notify the owner of the site that the link is broken. If you typed the URL of this page manually, please double-check that you entered the address correctly.
</p>

<p>
If you think this is a bug that the Keycloak team should fix, create <a id="buglink" href="https://github.com/keycloak/keycloak-web/issues/new?template=bug.yml&title=Broken%20link%20on%20the%20website%20{url}&description=%0A%0AURL:%20{url}">a bug issue on the GitHub issue tracker</a>.
</p>
<p>
If you think this is a bug that the Keycloak team should fix, create <a id="buglink" href="https://github.com/keycloak/keycloak-web/issues/new?template=bug.yml&title=Broken%20link%20on%20the%20website%20{url}&description=%0A%0AURL:%20{url}">a bug issue on the GitHub issue tracker</a>.
</p>
</div>

<div id="redirecting" style="display: none">
<p>
You are being redirected to <a id="redirectlink" href="...">...</a>.
</p>
</div>

<script>
document.addEventListener("DOMContentLoaded", function() {
Expand All @@ -27,4 +35,6 @@

</div>

<script src="${links.getResource('js/redirect.js')}" type="text/javascript"></script>

</@tmpl.page>
46 changes: 46 additions & 0 deletions resources/js/redirect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
var pageWithHash = window.location.pathname + window.location.hash;
var page = window.location.pathname;
var openRedirects = new XMLHttpRequest();
openRedirects.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
var redirects = this.responseText.split(/\r?\n/);
for (var i = 0; i < redirects.length; i++) {
var redirect = redirects[i].split("=");
if (redirects[i].trim().startsWith("#")) {
// allow for line comments
continue;
}
var pattern = new RegExp(redirect[0])
if (pattern.test(page) || pattern.test(pageWithHash)) {
document.title = "Redirecting..."
document.getElementById("heading").innerText = "Redirecting...";
document.getElementById("redirecting").style.display = "block";
document.getElementById("notfound").style.display = "none";
document.getElementById("redirectlink").href = redirect[1];
document.getElementById("redirectlink").innerText = redirect[1];
var anchor = "";
if (redirect[1].indexOf("#") !== -1) {
anchor = "#" + redirect[1].split("#")[1];
}
var checkRedirect = new XMLHttpRequest();
checkRedirect.onreadystatechange = function() {
if (this.readyState === 4) {
if (this.status === 200) {
// The response URL will no longer contain the anchor, therefore, add it again
window.location = checkRedirect.responseURL + anchor;
} else {
document.getElementById("heading").innerText = "Redirect failed";
document.getElementById("notfound").style.display = "block";
document.getElementById("redirecting").style.display = "none";
}
}
}
checkRedirect.open("GET", redirect[1], true);
checkRedirect.send();
break;
}
}
}
};
openRedirects.open("GET", "/resources/redirects", true);
openRedirects.send();
9 changes: 9 additions & 0 deletions resources/redirects
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Regex with with or without a anchor -> target path
^/docs/(latest|[0-9.]+)/securing_apps=/guides.html#securing-apps
^/docs/(latest|[0-9.]+)/server_installation=/guides.html#server
^/docs/(latest|[0-9.]+)/getting_started=/guides.html#getting-started
^/docs/([0-9.]+)/server-admin=/docs-api/latest/server-admin/index.html
^/docs/([0-9.]+)/upgrading=/docs-api/latest/upgrading/index.html
^/docs-api/[0-9.]+/rest-api=/docs-api/latest/rest-api/index.html
^/[0-9]+/[0-9]+/keycloak-[0-9]+-released=/blog.html
^/404=/
48 changes: 25 additions & 23 deletions src/main/java/org/keycloak/webbuilder/builders/AppBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
Expand All @@ -36,30 +37,31 @@ private void installPackage(String name, String version) throws Exception {
// Get package contents as tarball stream.
Package packageInfo = Registry.getPackage(name);
Version latestVersion = packageInfo.getVersionByTag(version);
ArchiveInputStream<TarArchiveEntry> tarball = latestVersion.getDist().getTarballStream();

// Copy package contents to installation path.
ArchiveEntry entry;
while ((entry = tarball.getNextEntry()) != null) {
// Skip any files not part of the package contents.
String packagePrefix = "package";
if (!entry.getName().startsWith(packagePrefix)) {
continue;
try (ArchiveInputStream<TarArchiveEntry> tarball = latestVersion.getDist().getTarballStream()) {

// Copy package contents to installation path.
ArchiveEntry entry;
while ((entry = tarball.getNextEntry()) != null) {
// Skip any files not part of the package contents.
String packagePrefix = "package";
if (!entry.getName().startsWith(packagePrefix)) {
continue;
}

// Resolve path without 'package' prefix.
Path entryPath = Path.of(packagePrefix).relativize(Path.of(entry.getName()));

// Skip file if it's extension is not permitted.
String extension = getFileExtension(entryPath.getFileName().toString());
if (!ALLOWED_EXTENSIONS.contains(extension)) {
continue;
}

// Resolve target path and copy file.
Path targetPath = installationPath.resolve(entryPath);
Files.createDirectories(targetPath.getParent());
Files.copy(tarball, targetPath, StandardCopyOption.REPLACE_EXISTING);
}

// Resolve path without 'package' prefix.
Path entryPath = Path.of(packagePrefix).relativize(Path.of(entry.getName()));

// Skip file if it's extension is not permitted.
String extension = getFileExtension(entryPath.getFileName().toString());
if(!ALLOWED_EXTENSIONS.contains(extension)) {
continue;
}

// Resolve target path and copy file.
Path targetPath = installationPath.resolve(entryPath);
Files.createDirectories(targetPath.getParent());
Files.copy(tarball, targetPath);
}

// Add package to the imports so it can be written to the import map later.
Expand Down