fix(auth): redirect unauthorized page requests (#628)

This commit is contained in:
Justin Shetty
2026-09-11 14:30:26 -07:00
committed by GitHub
parent b9ee1f664e
commit 9d1c093780
4 changed files with 157 additions and 50 deletions
+4
View File
@@ -0,0 +1,4 @@
export function isHtmlPageRequest(request: Request): boolean {
const acceptsHtml = request.headers.get("accept")?.toLowerCase().includes("text/html");
return (request.method === "GET" || request.method === "HEAD") && Boolean(acceptsHtml);
}
+3 -50
View File
@@ -1,5 +1,3 @@
import { randomUUID } from "node:crypto";
import { jwt } from "@elysiajs/jwt";
import { Elysia, t } from "elysia";
import { BaseHtml } from "../components/base";
import { Header } from "../components/header";
@@ -12,57 +10,12 @@ import {
HTTP_ALLOWED,
WEBROOT,
} from "../helpers/env";
import { userService } from "../services/user";
export { userService } from "../services/user";
export let FIRST_RUN = db.query("SELECT * FROM users").get() === null || false;
export const userService = new Elysia({ name: "user/service" })
.use(
jwt({
name: "jwt",
schema: t.Object({
id: t.String(),
}),
secret: process.env.JWT_SECRET ?? randomUUID(),
exp: "7d",
}),
)
.model({
signIn: t.Object({
email: t.String(),
password: t.String(),
}),
session: t.Cookie({
auth: t.String(),
jobId: t.Optional(t.String()),
}),
optionalSession: t.Cookie({
auth: t.Optional(t.String()),
jobId: t.Optional(t.String()),
}),
})
.macro("auth", {
cookie: "session",
async resolve({ status, jwt, cookie: { auth } }) {
if (!auth.value) {
return status(401, {
success: false,
message: "Unauthorized",
});
}
const user = await jwt.verify(auth.value);
if (!user) {
return status(401, {
success: false,
message: "Unauthorized",
});
}
return {
success: true,
user,
};
},
});
export const user = new Elysia()
.use(userService)
.get("/setup", ({ redirect }) => {
+63
View File
@@ -0,0 +1,63 @@
import { randomUUID } from "node:crypto";
import { jwt } from "@elysiajs/jwt";
import { Elysia, t } from "elysia";
import { WEBROOT } from "../helpers/env";
import { isHtmlPageRequest } from "../helpers/isHtmlPageRequest";
export const userService = new Elysia({ name: "user/service" })
.use(
jwt({
name: "jwt",
schema: t.Object({
id: t.String(),
}),
secret: process.env.JWT_SECRET ?? randomUUID(),
exp: "7d",
}),
)
.model({
signIn: t.Object({
email: t.String(),
password: t.String(),
}),
session: t.Cookie({
auth: t.String(),
jobId: t.Optional(t.String()),
}),
optionalSession: t.Cookie({
auth: t.Optional(t.String()),
jobId: t.Optional(t.String()),
}),
})
.macro("auth", {
cookie: "optionalSession",
async resolve({ request, set, status, jwt, cookie: { auth } }) {
const unauthorized = () => {
if (isHtmlPageRequest(request)) {
set.headers.location = `${WEBROOT}/login`;
return status(302, {
success: false,
message: "Redirecting to login",
});
}
return status(401, {
success: false,
message: "Unauthorized",
});
};
if (!auth.value) {
return unauthorized();
}
const user = await jwt.verify(auth.value);
if (!user) {
auth.remove();
return unauthorized();
}
return {
success: true,
user,
};
},
});
+87
View File
@@ -0,0 +1,87 @@
import { expect, test } from "bun:test";
import { Elysia } from "elysia";
import { isHtmlPageRequest } from "../../src/helpers/isHtmlPageRequest";
import { userService } from "../../src/services/user";
const app = new Elysia()
.use(userService)
.get("/protected", () => "protected", { auth: true })
.post("/protected", () => "protected", { auth: true });
test("identifies HTML page navigation requests", () => {
expect(
isHtmlPageRequest(
new Request("http://localhost/protected", {
headers: { accept: "text/html" },
}),
),
).toBe(true);
expect(
isHtmlPageRequest(
new Request("http://localhost/protected", {
headers: { accept: "TEXT/HTML" },
}),
),
).toBe(true);
expect(
isHtmlPageRequest(
new Request("http://localhost/protected", {
method: "HEAD",
headers: { accept: "text/html,application/xhtml+xml" },
}),
),
).toBe(true);
});
test("does not identify API requests as HTML page navigation", () => {
expect(
isHtmlPageRequest(
new Request("http://localhost/protected", {
method: "POST",
headers: { accept: "text/html" },
}),
),
).toBe(false);
expect(
isHtmlPageRequest(
new Request("http://localhost/protected", {
headers: { accept: "application/json" },
}),
),
).toBe(false);
expect(
isHtmlPageRequest(
new Request("http://localhost/protected", {
headers: { accept: "*/*" },
}),
),
).toBe(false);
});
test("redirects unauthorized HTML requests to login", async () => {
const response = await app.handle(
new Request("http://localhost/protected", {
headers: { accept: "text/html" },
redirect: "manual",
}),
);
expect(response.status).toBe(302);
expect(response.headers.get("location")).toBe("/login");
});
test("returns JSON 401 for unauthorized API requests", async () => {
const response = await app.handle(
new Request("http://localhost/protected", {
method: "POST",
headers: { accept: "application/json" },
}),
);
expect(response.status).toBe(401);
expect(await response.json()).toEqual({ success: false, message: "Unauthorized" });
});