Merge pull request #136 from Hestia-Homes/new-reporting

enhanced logging and added verification error catching
This commit is contained in:
KhalimCK 2025-11-14 10:33:42 +00:00 committed by GitHub
commit e9bd3f90d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 135 additions and 6 deletions

View file

@ -82,7 +82,16 @@ export const AuthOptions: NextAuthOptions = {
},
from: EMAIL_FROM, // noreply email
maxAge: 60 * 60, // magic link valid for 1 hour
sendVerificationRequest: MagicLinksEmail,
// Slightly extended magic link email sender, to log sends
sendVerificationRequest: async ({ identifier, url, provider }) => {
console.log("EMAIL MAGIC LINK SENT:", {
email: identifier,
url,
timestamp: new Date().toISOString(),
});
await MagicLinksEmail({ identifier, url, provider });
},
}),
],
@ -92,7 +101,7 @@ export const AuthOptions: NextAuthOptions = {
pages: {
signIn: "/", // your landing/login page
verifyRequest: "/auth/verify-request",
error: "/auth/error",
error: "/api/auth/error",
},
// ------------------------------------------------------------------
@ -107,6 +116,13 @@ export const AuthOptions: NextAuthOptions = {
if (!user?.email) return false;
const normalisedEmail = user.email.toLowerCase();
console.log("SignIn attempt:", {
email: normalisedEmail,
provider: account?.provider,
type: account?.type,
timestamp: new Date().toISOString(),
});
// Fetch the user (NextAuth will have created them already if new)
const [dbUser] = await db
.select()
@ -115,7 +131,10 @@ export const AuthOptions: NextAuthOptions = {
// New user - next auth will handle
if (!dbUser) {
console.log("New user sign up for email:", normalisedEmail);
console.log("New user created through sign-in:", {
email: normalisedEmail,
provider: account?.provider,
});
return true;
}
@ -159,7 +178,10 @@ export const AuthOptions: NextAuthOptions = {
// Link OAuth ID if missing (helps for older accounts)
if (account && !dbUser.oauthId) {
console.log("Linking OAuth ID for user:", normalisedEmail);
console.log("Backfilling OAuth ID:", {
email: normalisedEmail,
provider: account.provider,
});
const provider = account.provider as OauthProvider;
await db
.update(users)
@ -189,6 +211,10 @@ export const AuthOptions: NextAuthOptions = {
async jwt({ token, user: userFromLogin }) {
// Initial sign-in: attach DB fields
if (userFromLogin) {
console.log("JWT initial login:", {
email: userFromLogin.email,
timestamp: new Date().toISOString(),
});
const existing = await db.query.user.findFirst({
where: eq(users.email, userFromLogin.email!),
});
@ -224,8 +250,13 @@ export const AuthOptions: NextAuthOptions = {
/**
* Redirect users after login
*/
async redirect({ baseUrl }) {
async redirect({ url, baseUrl }) {
// If the user has not onboarded, send them to onboarding
console.log("Redirect triggered:", {
from: url,
to: `${baseUrl}/home`,
timestamp: new Date().toISOString(),
});
return `${baseUrl}/home`;
},
},

View file

@ -0,0 +1,50 @@
import { NextResponse } from "next/server";
import { db } from "@/app/db/db";
import { verificationTokens } from "@/app/db/schema/users";
import { eq } from "drizzle-orm";
export async function GET(request: Request) {
const url = new URL(request.url);
const error = url.searchParams.get("error");
const token = url.searchParams.get("token");
// Capture where the user came from
const referer = request.headers.get("referer");
const forwardedFor = request.headers.get("x-forwarded-for");
const host = request.headers.get("host");
let tokenRecord = null;
if (token) {
try {
tokenRecord = await db
.select()
.from(verificationTokens)
.where(eq(verificationTokens.token, token))
.limit(1);
} catch (err) {
console.error("Error querying verificationTokens", err);
}
}
console.error("Auth error callback triggered", {
error,
timestamp: new Date().toISOString(),
path: url.pathname,
query: Object.fromEntries(url.searchParams),
userAgent: request.headers.get("user-agent"),
referer,
forwardedFor,
host,
tokenPresentInQuery: Boolean(token),
tokenMatchedDB: Boolean(tokenRecord?.length),
tokenDBRecord: tokenRecord?.[0] ?? null,
});
const redirectUrl = new URL("/auth/error", request.url);
if (error) redirectUrl.searchParams.set("error", error);
return NextResponse.redirect(redirectUrl);
}

View file

@ -0,0 +1,48 @@
"use client";
import { useSearchParams } from "next/navigation";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
export default function AuthErrorPage() {
const params = useSearchParams();
const errorType = params.get("error");
const message =
errorType === "Verification"
? "Your login link is no longer valid."
: "We couldnt complete your login.";
const subMessage =
errorType === "Verification"
? "This usually happens when the link was already used, expired, or opened automatically by your email app."
: "Please request a new login link below.";
return (
<div className="flex flex-col items-center justify-center min-h-screen px-6 pb-12 bg-gray-50">
<div className="bg-white shadow-md rounded-xl p-8 max-w-md w-full text-center border border-gray-200">
<div className="flex justify-center mb-4">
<ExclamationTriangleIcon className="h-10 w-10 text-red-500" />
</div>
<h1 className="text-2xl font-semibold text-gray-900 mb-2">{message}</h1>
<p className="text-gray-600 leading-relaxed mb-6">{subMessage}</p>
<a
href="/"
className="
inline-flex items-center px-5 py-3 rounded-lg
bg-brandblue text-white font-medium
hover:bg-brandblue/90 transition
"
>
Request a new login link
</a>
</div>
<p className="mt-6 text-sm text-gray-500">
Need help? Contact your Domna representative.
</p>
</div>
);
}

View file

@ -25,7 +25,7 @@ const getSession = cache(async () => {
return session;
});
export function Footer() {
function Footer() {
return (
<footer className="bg-brandblue text-white p-4 text-center border-t border-gray-300">
<p>