From 80324afea25bb4281db950a86417252e0291cfac Mon Sep 17 00:00:00 2001 From: tonym Date: Tue, 16 Jun 2026 01:26:00 -0500 Subject: [PATCH] =?UTF-8?q?v0.16.1=20=E2=80=94=20Fix=20forced-password-cha?= =?UTF-8?q?nge=20re-firing=20every=20visit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard gate trusted session.user.mustChangePassword from the JWT, which is only refreshed on re-login/update() — so after a user set a new password the stale cookie kept bouncing them back to /change-password. Read the flag from the DB (the source of truth) so it clears immediately and never re-fires. Cheap for a 2-user app; also re-checks the account is still active. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app/(dashboard)/layout.tsx | 15 ++++++++++++--- src/lib/changelog.ts | 7 +++++++ src/lib/version.ts | 2 +- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx index dc20652..46aafe6 100644 --- a/src/app/(dashboard)/layout.tsx +++ b/src/app/(dashboard)/layout.tsx @@ -1,5 +1,6 @@ import { redirect } from "next/navigation"; import { getSession } from "@/lib/auth"; +import { db } from "@/lib/db"; import { Sidebar } from "@/components/layout/sidebar"; import { TopNav } from "@/components/layout/topnav"; import { Footer } from "@/components/layout/footer"; @@ -7,10 +8,18 @@ import { Footer } from "@/components/layout/footer"; export default async function DashboardLayout({ children }: { children: React.ReactNode }) { const session = await getSession(); if (!session) redirect("/login"); + // Force a password change before anything else (provisioned accounts ship with - // a known default + mustChangePassword=true). The target page is outside this - // layout group, so there's no redirect loop. - if (session.user.mustChangePassword) redirect("/change-password"); + // a known default + mustChangePassword=true). Read the flag from the DB, not the + // JWT: the cookie is only refreshed on re-login/update(), so trusting it would + // keep bouncing the user back here even after they've set a new password. The + // target page is outside this layout group, so there's no redirect loop. + const account = await db.user.findUnique({ + where: { id: session.user.id }, + select: { active: true, mustChangePassword: true }, + }); + if (!account || !account.active) redirect("/login"); + if (account.mustChangePassword) redirect("/change-password"); return (
diff --git a/src/lib/changelog.ts b/src/lib/changelog.ts index 7ca3cad..4ac2d14 100644 --- a/src/lib/changelog.ts +++ b/src/lib/changelog.ts @@ -8,6 +8,13 @@ export type ChangelogEntry = { }; export const CHANGELOG: ChangelogEntry[] = [ + { + version: "0.16.1", + date: "2026-06-16", + changes: [ + "Fixed: after you set a new password, the app could keep asking you to change it again on every visit. It now clears the moment you save your new password.", + ], + }, { version: "0.16.0", date: "2026-06-16", diff --git a/src/lib/version.ts b/src/lib/version.ts index 3a3431a..df9620a 100644 --- a/src/lib/version.ts +++ b/src/lib/version.ts @@ -1,2 +1,2 @@ // Bump on every user-visible release. Changelog entries live in `src/lib/changelog.ts`. -export const APP_VERSION = "0.16.0"; +export const APP_VERSION = "0.16.1";