Redirect to initial url after authentication (#893)

pull/897/head
MrMugame 2024-06-22 12:45:23 +02:00 committed by GitHub
parent bd3e91ba65
commit b6d95ec632
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 168 additions and 130 deletions

View File

@ -10,5 +10,5 @@ export async function cleanCommand() {
} }
await editor.flashNotification("Now wiping all state and logging out..."); await editor.flashNotification("Now wiping all state and logging out...");
await debug.cleanup(); await debug.cleanup();
await editor.openUrl("/.auth?logout", true); await editor.openUrl("/.logout", true);
} }

View File

@ -1,6 +1,6 @@
import { deleteCookie, getCookie, setCookie } from "hono/helper.ts"; import { deleteCookie, getCookie, setCookie } from "hono/helper.ts";
import { cors } from "hono/middleware.ts"; import { cors } from "hono/middleware.ts";
import { type Context, Hono, type HonoRequest } from "hono/mod.ts"; import { type Context, Hono, type HonoRequest, validator } from "hono/mod.ts";
import { AssetBundle } from "$lib/asset_bundle/bundle.ts"; import { AssetBundle } from "$lib/asset_bundle/bundle.ts";
import { FileMeta } from "$sb/types.ts"; import { FileMeta } from "$sb/types.ts";
import { ShellRequest } from "$type/rpc.ts"; import { ShellRequest } from "$type/rpc.ts";
@ -286,25 +286,44 @@ export class HttpServer {
"/.auth", "/.auth",
]; ];
// Middleware handling the /.auth page and flow // TODO: This should probably be a POST request
this.app.all("/.auth", async (c) => { this.app.get("/.logout", async (c) => {
const url = new URL(c.req.url); const url = new URL(c.req.url);
const req = c.req; deleteCookie(c, authCookieName(url.host));
const host = url.host; // e.g. localhost:3000
if (url.search === "?logout") { return c.redirect("/.auth");
deleteCookie(c, authCookieName(host)); });
this.app.get("/.auth", async (c) => {
const html = this.clientAssetBundle.readTextFileSync(".client/auth.html");
return c.html(html);
}).post(
validator("form", (value, c) => {
const username = value["username"];
const password = value["password"];
if (
!username || typeof username !== "string" ||
!password || typeof password !== "string"
) {
return c.redirect("/.auth?error=0");
} }
if (req.method === "GET") {
return c.html( return { username, password };
this.clientAssetBundle.readTextFileSync(".client/auth.html"), }),
); async (c) => {
} else if (req.method === "POST") { const req = c.req;
const values = await c.req.parseBody(); const url = new URL(c.req.url);
const username = values["username"]; const { username, password } = req.valid("form");
const password = values["password"];
const spaceServer = await this.ensureSpaceServer(req); const spaceServer = await this.ensureSpaceServer(req);
const { user: expectedUser, pass: expectedPassword } = spaceServer
.auth!; const {
user: expectedUser,
pass: expectedPassword,
} = spaceServer.auth!;
if (username === expectedUser && password === expectedPassword) { if (username === expectedUser && password === expectedPassword) {
// Generate a JWT and set it as a cookie // Generate a JWT and set it as a cookie
const jwt = await spaceServer.jwtIssuer.createJWT( const jwt = await spaceServer.jwtIssuer.createJWT(
@ -312,21 +331,23 @@ export class HttpServer {
authenticationExpirySeconds, authenticationExpirySeconds,
); );
console.log("Successful auth"); console.log("Successful auth");
setCookie(c, authCookieName(host), jwt, { setCookie(c, authCookieName(url.host), jwt, {
expires: new Date( expires: new Date(
Date.now() + authenticationExpirySeconds * 1000, Date.now() + authenticationExpirySeconds * 1000,
), // in a week ), // in a week
// sameSite: "Strict", // sameSite: "Strict",
// httpOnly: true, // httpOnly: true,
}); });
return c.redirect("/"); const values = await c.req.parseBody();
const from = values["from"];
return c.redirect(typeof from === "string" ? from : "/");
} else { } else {
console.error("Authentication failed, redirecting to auth page."); console.error("Authentication failed, redirecting to auth page.");
return c.redirect("/.auth?error=1"); return c.redirect("/.auth?error=1");
} }
} else { },
).all(async (c) => {
return c.redirect("/.auth"); return c.redirect("/.auth");
}
}); });
// Check auth // Check auth
@ -339,6 +360,14 @@ export class HttpServer {
} }
const url = new URL(req.url); const url = new URL(req.url);
const host = url.host; const host = url.host;
const redirectToAuth = () => {
// Try filtering api paths
if (req.path.startsWith("/.") || req.path.endsWith(".md")) {
return c.redirect("/.auth");
} else {
return c.redirect(`/.auth?from=${req.path}`);
}
};
if (!excludedPaths.includes(url.pathname)) { if (!excludedPaths.includes(url.pathname)) {
const authCookie = getCookie(c, authCookieName(host)); const authCookie = getCookie(c, authCookieName(host));
@ -360,7 +389,7 @@ export class HttpServer {
} }
if (!authCookie) { if (!authCookie) {
console.log("Unauthorized access, redirecting to auth page"); console.log("Unauthorized access, redirecting to auth page");
return c.redirect("/.auth"); return redirectToAuth();
} }
const { user: expectedUser } = spaceServer.auth!; const { user: expectedUser } = spaceServer.auth!;
@ -376,7 +405,7 @@ export class HttpServer {
"Error verifying JWT, redirecting to auth page", "Error verifying JWT, redirecting to auth page",
e.message, e.message,
); );
return c.redirect("/.auth"); return redirectToAuth();
} }
} }
return next(); return next();
@ -395,9 +424,7 @@ export class HttpServer {
); );
// File list // File list
this.app.get( this.app.get("/index.json", async (c) => {
"/index.json",
async (c) => {
const req = c.req; const req = c.req;
const spaceServer = await this.ensureSpaceServer(req); const spaceServer = await this.ensureSpaceServer(req);
if (req.header("X-Sync-Mode")) { if (req.header("X-Sync-Mode")) {
@ -411,8 +438,7 @@ export class HttpServer {
// The reason to do this is to handle authentication systems like Authelia nicely // The reason to do this is to handle authentication systems like Authelia nicely
return c.redirect("/"); return c.redirect("/");
} }
}, });
);
// RPC shell // RPC shell
this.app.post("/.rpc/shell", async (c) => { this.app.post("/.rpc/shell", async (c) => {
@ -466,16 +492,12 @@ export class HttpServer {
const filePathRegex = "/:path{[^!].*\\.[a-zA-Z]+}"; const filePathRegex = "/:path{[^!].*\\.[a-zA-Z]+}";
const mdExt = ".md"; const mdExt = ".md";
this.app.get( this.app.get(filePathRegex, async (c) => {
filePathRegex,
async (c) => {
const req = c.req; const req = c.req;
const name = req.param("path")!; const name = req.param("path")!;
const spaceServer = await this.ensureSpaceServer(req); const spaceServer = await this.ensureSpaceServer(req);
console.log( console.log("Requested file", name);
"Requested file",
name,
);
if ( if (
name.endsWith(mdExt) && name.endsWith(mdExt) &&
// This header signififies the requests comes directly from the http_space_primitives client (not the browser) // This header signififies the requests comes directly from the http_space_primitives client (not the browser)
@ -555,8 +577,7 @@ export class HttpServer {
console.error("Error GETting file", name, e.message); console.error("Error GETting file", name, e.message);
return c.notFound(); return c.notFound();
} }
}, }).put(
).put(
async (c) => { async (c) => {
const req = c.req; const req = c.req;
const name = req.param("path")!; const name = req.param("path")!;

View File

@ -76,10 +76,23 @@
<script> <script>
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const error = params.get('error'); const error = params.get('error');
if (error === "1") { if (error === "0") {
document.querySelector('.error-message').innerText = "The sent data was invalid";
} else if (error === "1") {
document.querySelector('.error-message').innerText = "Invalid username or password"; document.querySelector('.error-message').innerText = "Invalid username or password";
} }
const from = params.get("from");
if (from) {
var input = document.createElement("input");
input.setAttribute("type", "hidden");
input.setAttribute("name", "from");
input.setAttribute("value", from);
document.getElementById("login").appendChild(input);
}
</script> </script>
</body> </body>

View File

@ -97,7 +97,11 @@ self.addEventListener("fetch", (event: any) => {
const pathname = requestUrl.pathname; const pathname = requestUrl.pathname;
if (pathname === "/.auth" || pathname === "/index.json") { if (
pathname === "/.auth" ||
pathname === "/.logout" ||
pathname === "/index.json"
) {
return fetch(request); return fetch(request);
} else if (/\/.+\.[a-zA-Z]+$/.test(pathname)) { } else if (/\/.+\.[a-zA-Z]+$/.test(pathname)) {
// If this is a /*.* request, this can either be a plug worker load or an attachment load // If this is a /*.* request, this can either be a plug worker load or an attachment load