Skip to content
Merged

yeah #82

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions packages/auth/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,16 @@ export const authConfig: NextAuthConfig = {
const { createTransport } = await import("nodemailer");
const transport = createTransport(provider.server);

// Parse the NextAuth callback URL to extract token and callbackUrl
const parsedUrl = new URL(url);
const host = parsedUrl.host;
const token = parsedUrl.searchParams.get("token") || "";
const callbackUrl = parsedUrl.searchParams.get("callbackUrl") || "/dashboard";

// Build an intermediate /verify URL that prevents email scanners
// from consuming the one-time token via pre-fetch GET requests
// from consuming the one-time token via pre-fetch GET requests.
// We pass the ENTIRE original callback URL encoded to avoid
// dropping any parameters NextAuth needs internally.
const verifyUrl = new URL("/verify", parsedUrl.origin);
verifyUrl.searchParams.set("token", token);
verifyUrl.searchParams.set("callback", Buffer.from(url).toString("base64"));
verifyUrl.searchParams.set("email", identifier);
verifyUrl.searchParams.set("callbackUrl", callbackUrl);
const safeUrl = verifyUrl.toString();

const result = await transport.sendMail({
Expand Down
28 changes: 15 additions & 13 deletions sites/mainweb/app/(portal)/verify/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,22 @@ function VerifyContent() {
const searchParams = useSearchParams();
const [verifying, setVerifying] = useState(false);

// Build the actual NextAuth callback URL from the search params
const callbackUrl = searchParams?.get('callbackUrl') || '/dashboard';
const token = searchParams?.get('token') || '';
// The full NextAuth callback URL is base64-encoded in the 'callback' param
const encodedCallback = searchParams?.get('callback') || '';
const email = searchParams?.get('email') || '';

let callbackUrl = '';
try {
callbackUrl = atob(encodedCallback);
} catch {
// invalid base64
}

const handleVerify = () => {
if (!callbackUrl) return;
setVerifying(true);
// Redirect to the actual NextAuth email callback
const params = new URLSearchParams({
callbackUrl,
token,
email,
});
window.location.href = `/api/auth/callback/nodemailer?${params.toString()}`;
// Redirect to the exact original NextAuth callback URL
window.location.href = callbackUrl;
};

return (
Expand Down Expand Up @@ -55,16 +57,16 @@ function VerifyContent() {
<div className="relative z-10">
<button
onClick={handleVerify}
disabled={verifying || !token}
disabled={verifying || !callbackUrl}
className="px-12 py-5 bg-gradient-to-r from-emerald-600 to-emerald-500 text-white font-black text-xs uppercase tracking-[0.3em] hover:from-emerald-500 hover:to-emerald-400 transition-all rounded-lg shadow-[0_0_30px_rgba(16,185,129,0.2)] disabled:opacity-30 active:scale-95"
>
{verifying ? 'Verifying...' : 'Complete Sign In'}
</button>
</div>

{!token && (
{!callbackUrl && (
<p className="relative z-10 mt-8 text-red-500/70 font-mono text-xs">
Error: No verification token found. Please request a new sign-in link.
Error: Invalid or missing verification link. Please request a new sign-in link.
</p>
)}

Expand Down
Loading