From ffccff85d439de71c9c47fc5ce8670313345c964 Mon Sep 17 00:00:00 2001 From: makearmy Date: Thu, 2 Oct 2025 14:21:12 -0400 Subject: [PATCH] sign in and register fixes for looped and forwarded pages --- app/auth/sign-in/page.tsx | 3 ++- app/auth/sign-in/sign-in.tsx | 29 +++++++++++++++++++---------- app/auth/sign-up/page.tsx | 3 ++- app/page.tsx | 29 +++++++++++++++++++++++------ lib/jwt.ts | 15 +++++++++++++++ middleware.ts | 9 +++++++-- 6 files changed, 68 insertions(+), 20 deletions(-) create mode 100644 lib/jwt.ts diff --git a/app/auth/sign-in/page.tsx b/app/auth/sign-in/page.tsx index 59709b12..2938212c 100644 --- a/app/auth/sign-in/page.tsx +++ b/app/auth/sign-in/page.tsx @@ -2,6 +2,7 @@ import { cookies } from "next/headers"; import { redirect } from "next/navigation"; import SignIn from "./sign-in"; +import { isJwtValid } from "@/lib/jwt"; export default async function SignInPage({ searchParams, @@ -22,7 +23,7 @@ export default async function SignInPage({ if (!reauth) { const ck = await cookies(); const at = ck.get("ma_at")?.value; - if (at) redirect("/portal"); + if (isJwtValid(at)) redirect("/portal"); } return ; diff --git a/app/auth/sign-in/sign-in.tsx b/app/auth/sign-in/sign-in.tsx index 55b24cdc..ce4c6868 100644 --- a/app/auth/sign-in/sign-in.tsx +++ b/app/auth/sign-in/sign-in.tsx @@ -20,11 +20,16 @@ export default function SignIn({ nextPath = "/portal", reauth = false }: Props) setErr(null); setLoading(true); try { + const body = { + identifier: identifier.trim(), + password, + }; + const res = await fetch("/api/auth/login", { method: "POST", credentials: "include", headers: { "Content-Type": "application/json", Accept: "application/json" }, - body: JSON.stringify({ identifier, password }), + body: JSON.stringify(body), }); const txt = await res.text(); @@ -34,7 +39,10 @@ export default function SignIn({ nextPath = "/portal", reauth = false }: Props) } catch {} if (!res.ok) { - throw new Error(j?.error || j?.message || `Sign-in failed (${res.status})`); + // surface server-provided message when available + const msg = + j?.error || j?.message || (res.status === 401 ? "Invalid credentials." : `Sign-in failed (${res.status})`); + throw new Error(msg); } // If this sign-in is being used as a re-auth step, set a short-lived 'recent auth' marker. @@ -56,6 +64,8 @@ export default function SignIn({ nextPath = "/portal", reauth = false }: Props) [identifier, password, nextPath, router, reauth] ); + const createHref = `/auth/sign-up?next=${encodeURIComponent(nextPath)}`; + return (
@@ -116,14 +126,13 @@ export default function SignIn({ nextPath = "/portal", reauth = false }: Props) - {!reauth && ( -
- New here?{" "} - - Create an account - -
- )} + {/* Always show a sign-up path, even on reauth, to avoid dead-ends for first-time visitors */} +
+ {reauth ? "Don't have an account?" : "New here?"}{" "} + + Create an account + +
); } diff --git a/app/auth/sign-up/page.tsx b/app/auth/sign-up/page.tsx index 569ae57c..96ce741d 100644 --- a/app/auth/sign-up/page.tsx +++ b/app/auth/sign-up/page.tsx @@ -2,6 +2,7 @@ import { cookies } from "next/headers"; import { redirect } from "next/navigation"; import SignUp from "./sign-up"; +import { isJwtValid } from "@/lib/jwt"; export default async function SignUpPage({ searchParams, @@ -10,7 +11,7 @@ export default async function SignUpPage({ }) { const ck = await cookies(); const at = ck.get("ma_at")?.value; - if (at) redirect("/portal"); + if (isJwtValid(at)) redirect("/portal"); const sp = searchParams ?? {}; const nextParam = Array.isArray(sp.next) ? sp.next[0] : sp.next; diff --git a/app/page.tsx b/app/page.tsx index 245481bc..29fd3114 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -3,19 +3,35 @@ import { cookies } from "next/headers"; import { redirect } from "next/navigation"; import SignIn from "@/app/auth/sign-in/sign-in"; import SignUp from "@/app/auth/sign-up/sign-up"; +import { isJwtValid } from "@/lib/jwt"; -export default async function HomePage() { - // If already signed in, go straight to the app +type SearchParams = { [key: string]: string | string[] | undefined }; + +export default async function HomePage({ + searchParams, +}: { + searchParams?: SearchParams; +}) { + // If already signed in with a VALID token, go straight to the app const ck = await cookies(); const at = ck.get("ma_at")?.value; - if (at) redirect("/portal"); + if (isJwtValid(at)) redirect("/portal"); + + const reauth = searchParams?.reauth === "1"; return (
+ {reauth && ( +

+ Your session expired. Please sign in again. +

+ )} +

MakeArmy

- Free to use. Manage laser rigs, settings, and projects—all in one place. + Free to use. Manage laser rigs, settings, and projects—all in one + place.

@@ -29,12 +45,13 @@ export default async function HomePage() {

Sign in

{/* Uses your existing sign-in component */} - +
- We only use cookies strictly necessary to operate the site (e.g., your sign-in session). + We only use cookies strictly necessary to operate the site (e.g., your + sign-in session).
); diff --git a/lib/jwt.ts b/lib/jwt.ts new file mode 100644 index 00000000..287a9ff2 --- /dev/null +++ b/lib/jwt.ts @@ -0,0 +1,15 @@ +// lib/jwt.ts +export function jwtExp(token?: string | null): number | null { + if (!token) return null; + try { + const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString("utf8")); + return typeof payload?.exp === "number" ? payload.exp : null; + } catch { + return null; + } +} + +export function isJwtValid(token?: string | null): boolean { + const exp = jwtExp(token); + return !!exp && exp * 1000 > Date.now(); +} diff --git a/middleware.ts b/middleware.ts index ac77008f..a535742e 100644 --- a/middleware.ts +++ b/middleware.ts @@ -83,6 +83,11 @@ import { NextResponse, NextRequest } from "next/server"; const url = req.nextUrl.clone(); const { pathname } = url; + // ── 0) Absolute rule: the homepage must never redirect (no mapping, no gating). + if (pathname === "/") { + return NextResponse.next(); + } + // ── 1) Legacy → Portal / Canonical mapping (runs before auth gating) const mapped = legacyMap(pathname); if (mapped && !isSameUrl(req, mapped)) { @@ -174,8 +179,8 @@ import { NextResponse, NextRequest } from "next/server"; type MapResult = { pathname: string; query?: Record }; function legacyMap(pathname: string): MapResult | null { - // If we’re already inside the portal, don’t try to remap again. - if (pathname.startsWith("/portal")) return null; + // Never map the homepage, and if we’re already inside the portal, don’t remap again. + if (pathname === "/" || pathname.startsWith("/portal")) return null; // 1) DETAIL PAGES: map legacy detail URLs straight into the portal with ?id= // NOTE: We intentionally DO NOT remap `/lasers/:id` and `/projects/:id`