sign in and register fixes for looped and forwarded pages
This commit is contained in:
parent
5257a0d2fe
commit
ffccff85d4
6 changed files with 68 additions and 20 deletions
|
|
@ -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} />;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
29
app/page.tsx
29
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 (
|
||||
<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 projects—all in one place.
|
||||
Free to use. Manage laser rigs, settings, and projects—all 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
15
lib/jwt.ts
Normal 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();
|
||||
}
|
||||
|
|
@ -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 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`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue