mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-08 11:37:25 +00:00
Merge pull request #136 from Hestia-Homes/new-reporting
enhanced logging and added verification error catching
This commit is contained in:
commit
e9bd3f90d5
4 changed files with 135 additions and 6 deletions
|
|
@ -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`;
|
||||
},
|
||||
},
|
||||
|
|
|
|||
50
src/app/api/auth/error/route.ts
Normal file
50
src/app/api/auth/error/route.ts
Normal 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);
|
||||
}
|
||||
48
src/app/auth/error/page.tsx
Normal file
48
src/app/auth/error/page.tsx
Normal 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 couldn’t 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue