Merge pull request #398 from ArnasDon/fix/365-login-ngrok-redirect

fix: full-page navigation after login so the session is applied (#365)
This commit is contained in:
Arnas Donauskas 2026-07-17 12:41:47 +03:00 committed by GitHub
commit f69c82aea0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 41 additions and 7 deletions

View File

@ -64,6 +64,34 @@ const SECURITY_HEADERS = [
] as const; ] as const;
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/**
* Cross-origin dev access (Next.js 16).
*
* Next 16 blocks requests to dev-only resources (`/_next/*` internals,
* the HMR websocket, the dev overlay) unless the browser's Origin is
* the host the dev server booted on — `localhost` by default. Tunnels
* like ngrok serve the app from a public HTTPS host, so without
* allow-listing that host those dev requests come back 403: HMR stops
* working and the dev session degrades over the tunnel (issue #365).
*
* Wildcards match subdomains only (Next's CSRF matcher), so the
* randomised tunnel subdomain is covered. Add any other host via
* `ALLOWED_DEV_ORIGINS` (comma-separated). This key is dev-only and
* has no effect on a production build.
*/
allowedDevOrigins: [
"*.ngrok-free.app",
"*.ngrok.app",
"*.ngrok.io",
"*.trycloudflare.com",
"*.loca.lt",
...(process.env.ALLOWED_DEV_ORIGINS
? process.env.ALLOWED_DEV_ORIGINS.split(",")
.map((origin) => origin.trim())
.filter(Boolean)
: []),
],
/** /**
* Cache-Control policy. * Cache-Control policy.
* *

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import { Suspense, useState } from "react"; import { Suspense, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import Link from "next/link"; import Link from "next/link";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { createClient } from "@/lib/supabase/client"; import { createClient } from "@/lib/supabase/client";
@ -42,7 +42,6 @@ function LoginPageInner() {
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const router = useRouter();
const supabase = createClient(); const supabase = createClient();
const handleLogin = async (e: React.FormEvent) => { const handleLogin = async (e: React.FormEvent) => {
@ -61,11 +60,18 @@ function LoginPageInner() {
return; return;
} }
if (inviteToken) { // Full-page navigation (not router.push) so the browser issues a
router.push(`/join/${encodeURIComponent(inviteToken)}`); // fresh top-level request that carries the just-written Supabase
} else { // auth cookies to the middleware gating /dashboard. A soft
router.push("/dashboard"); // client-side navigation can reach the protected route before the
} // server observes the new session, so the middleware bounces it
// back to /login — which looks like the page "just refreshing"
// instead of signing in (issue #365). Mirrors the deliberate full
// reload the invite-accept flow already uses in join/[token].
const destination = inviteToken
? `/join/${encodeURIComponent(inviteToken)}`
: "/dashboard";
window.location.href = destination;
}; };
return ( return (