fix(auth): redirect active sessions from /login (#9491)

Validated in local merge-train (diegosouzapw batch)
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-06 10:39:05 -03:00
committed by GitHub
parent 53c8016d53
commit 8fdb67f1d3
4 changed files with 66 additions and 1 deletions

View File

@@ -0,0 +1 @@
- **fix(auth):** redirect active sessions from /login by checking the session cookie before showing the login form. (thanks @DaDecky)

View File

@@ -1,4 +1,6 @@
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
import { jwtVerify } from "jose";
import { getSettings, updateSettings } from "@/lib/localDb";
import {
hasManagementPasswordConfigured,
@@ -9,6 +11,24 @@ import { getNodeRuntimeSupport } from "@/shared/utils/nodeRuntimeSupport.ts";
import { updateRequireLoginSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
function getJwtSecret(): Uint8Array | null {
const secret = process.env.JWT_SECRET?.trim();
return secret ? new TextEncoder().encode(secret) : null;
}
async function checkSessionAuthenticated(): Promise<boolean> {
try {
const cookieStore = await cookies();
const token = cookieStore.get("auth_token")?.value;
const secret = getJwtSecret();
if (!token || !secret) return false;
await jwtVerify(token, secret);
return true;
} catch {
return false;
}
}
// Node.js compatibility check — reflect the supported secure runtime floors used by CLI/CI.
function getNodeCompatibility() {
const { nodeVersion, nodeCompatible } = getNodeRuntimeSupport();
@@ -28,10 +48,12 @@ export async function GET() {
try {
const settings = await getSettings();
const requireLogin = settings.requireLogin !== false;
const authenticated = await checkSessionAuthenticated();
const hasPassword = hasManagementPasswordConfigured(settings);
const setupComplete = !!settings.setupComplete;
const oidcEnabled = !!settings.oidcEnabled;
return NextResponse.json({
authenticated,
requireLogin,
hasPassword,
setupComplete,
@@ -42,6 +64,7 @@ export async function GET() {
console.error("[API] Error fetching require-login settings:", error);
return NextResponse.json(
{
authenticated: false,
requireLogin: true,
hasPassword: true,
setupComplete: true,

View File

@@ -36,7 +36,7 @@ export default function LoginPage() {
const data = await res.json();
if (data.nodeVersion) setNodeVersion(data.nodeVersion);
if (data.nodeCompatible === false) setNodeCompatible(false);
if (data.requireLogin === false) {
if (data.authenticated === true || data.requireLogin === false) {
router.push("/dashboard");
router.refresh();
return;

View File

@@ -0,0 +1,41 @@
/**
* Auth redirect: active sessions are redirected from /login to /dashboard.
*
* Upstream: decolua/9router#3005 — fix(auth): redirect active sessions from /login
* When a user navigates to /login while already authenticated, the login page
* fetches /api/settings/require-login and redirects to /dashboard.
*/
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
describe("auth redirect login (port from 9router#3005)", () => {
it("login page checks data.authenticated === true before showing the form", () => {
const source = fs.readFileSync(path.resolve("src/app/login/page.tsx"), "utf-8");
// The redirect guard must check both:
// - authenticated=true (active session → redirect to dashboard)
// - requireLogin=false (no auth configured → allow access)
const redirectCheck = source.match(/if\s*\(.*authenticated.*requireLogin.*\)/);
assert.ok(redirectCheck, "login page must check both authenticated and requireLogin");
assert.ok(
source.includes("data.authenticated === true"),
"login page must check data.authenticated === true for redirect",
);
});
it("require-login API route returns authenticated field", () => {
const source = fs.readFileSync(
path.resolve("src/app/api/settings/require-login/route.ts"),
"utf-8",
);
assert.ok(
source.includes("authenticated:"),
"require-login route must include authenticated in the response",
);
assert.ok(
source.includes("authenticated,"),
"authenticated must be part of the JSON response object (spread or key)",
);
});
});