sign in and register fixes for looped and forwarded pages

This commit is contained in:
makearmy 2025-10-02 14:21:12 -04:00
parent 5257a0d2fe
commit ffccff85d4
6 changed files with 68 additions and 20 deletions

View file

@ -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 <SignIn nextPath={nextPath} reauth={reauth} />;

View file

@ -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 (
<div className="mx-auto max-w-md rounded-lg border p-6 space-y-4">
<div>
@ -116,14 +126,13 @@ export default function SignIn({ nextPath = "/portal", reauth = false }: Props)
</button>
</form>
{!reauth && (
<div className="mt-2 text-center text-sm">
<span className="opacity-70">New here?</span>{" "}
<a className="underline" href="/auth/sign-up">
Create an account
</a>
</div>
)}
{/* Always show a sign-up path, even on reauth, to avoid dead-ends for first-time visitors */}
<div className="mt-2 text-center text-sm">
<span className="opacity-70">{reauth ? "Don't have an account?" : "New here?"}</span>{" "}
<a className="underline" href={createHref}>
Create an account
</a>
</div>
</div>
);
}

View file

@ -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;

View file

@ -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 (
<main className="mx-auto max-w-5xl px-4 py-12">
{reauth && (
<p className="mb-6 rounded-md border bg-yellow-50 p-3 text-sm text-yellow-900">
Your session expired. Please sign in again.
</p>
)}
<section className="mb-10 text-center">
<h1 className="text-3xl font-bold tracking-tight">MakeArmy</h1>
<p className="mt-2 text-base text-muted-foreground">
Free to use. Manage laser rigs, settings, and projectsall in one place.
Free to use. Manage laser rigs, settings, and projectsall in one
place.
</p>
</section>
@ -29,12 +45,13 @@ export default async function HomePage() {
<div className="rounded-lg border p-6">
<h2 className="mb-3 text-lg font-semibold">Sign in</h2>
{/* Uses your existing sign-in component */}
<SignIn nextPath="/portal" reauth={false} />
<SignIn nextPath="/portal" reauth={reauth} />
</div>
</section>
<section className="mt-8 text-center text-xs text-muted-foreground">
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).
</section>
</main>
);

15
lib/jwt.ts Normal file
View file

@ -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();
}

View file

@ -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<string, string> };
function legacyMap(pathname: string): MapResult | null {
// If were already inside the portal, dont try to remap again.
if (pathname.startsWith("/portal")) return null;
// Never map the homepage, and if were already inside the portal, dont 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`