Skip to content
Merged
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
102 changes: 102 additions & 0 deletions packages/db/scripts/diagnose-auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { createHash } from "crypto";
import * as dotenv from "dotenv";
import path from "path";
import { eq, and } from "drizzle-orm";

dotenv.config({ path: path.resolve(__dirname, "../../../.env") });

import { verificationTokens } from "../src/schemas";

const DATABASE_URL = process.env.DATABASE_URL;
const AUTH_SECRET = process.env.AUTH_SECRET || process.env.NEXTAUTH_SECRET;

if (!DATABASE_URL) throw new Error("DATABASE_URL is not defined");
if (!AUTH_SECRET) throw new Error("AUTH_SECRET/NEXTAUTH_SECRET is not defined");

const pool = new Pool({ connectionString: DATABASE_URL });
const db = drizzle(pool);

async function diagnose() {
console.log("=== NextAuth Email Verification Diagnostic ===\n");
console.log(`AUTH_SECRET present: ${!!AUTH_SECRET}`);
console.log(`AUTH_SECRET (first 10 chars): ${AUTH_SECRET!.substring(0, 10)}...`);
console.log(`DATABASE_URL present: ${!!DATABASE_URL}\n`);

// Step 1: Check existing tokens
console.log("--- Step 1: Checking existing verification tokens ---");
const existingTokens = await db.select().from(verificationTokens);
console.log(`Found ${existingTokens.length} existing token(s).`);
for (const t of existingTokens) {
const isExpired = new Date(t.expires) < new Date();
console.log(` identifier: ${t.identifier}, expired: ${isExpired}, token_hash_start: ${t.token.substring(0, 20)}...`);
}

// Step 2: Simulate creating a verification token
console.log("\n--- Step 2: Simulating token creation ---");
const rawToken = "test_diagnostic_token_12345";
const testEmail = "diagnostic@test.com";
const hashedToken = createHash("sha256")
.update(`${rawToken}${AUTH_SECRET}`)
.digest("hex");
const expires = new Date(Date.now() + 86400 * 1000); // 24 hours

console.log(`Raw token: ${rawToken}`);
console.log(`Hashed token: ${hashedToken}`);
console.log(`Identifier: ${testEmail}`);
console.log(`Expires: ${expires.toISOString()}`);

try {
await db.insert(verificationTokens).values({
identifier: testEmail,
token: hashedToken,
expires,
});
console.log("✅ Token inserted successfully");
} catch (err: any) {
console.log(`❌ Failed to insert token: ${err.message}`);
await pool.end();
return;
}

// Step 3: Simulate verification (lookup + delete)
console.log("\n--- Step 3: Simulating token verification ---");
const reHashedToken = createHash("sha256")
.update(`${rawToken}${AUTH_SECRET}`)
.digest("hex");

console.log(`Re-hashed token: ${reHashedToken}`);
console.log(`Hashes match: ${hashedToken === reHashedToken}`);

try {
const result = await db
.delete(verificationTokens)
.where(
and(
eq(verificationTokens.identifier, testEmail),
eq(verificationTokens.token, reHashedToken)
)
)
.returning();

if (result.length > 0) {
console.log("✅ Token found and deleted successfully — verification WOULD work!");
} else {
console.log("❌ Token NOT found — verification WOULD fail!");
console.log(" This means the hash comparison is failing in the DB");
}
} catch (err: any) {
console.log(`❌ Error during verification: ${err.message}`);
}

// Step 4: Re-check if the token was cleaned up
console.log("\n--- Step 4: Post-cleanup check ---");
const remaining = await db.select().from(verificationTokens);
console.log(`Remaining tokens: ${remaining.length}`);

console.log("\n=== Diagnostic complete ===");
await pool.end();
}

diagnose().catch(console.error);
46 changes: 46 additions & 0 deletions sites/mainweb/app/(portal)/api/debug-auth/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { NextResponse } from "next/server";
import { createHash } from "crypto";

// Temporary debug endpoint to check auth configuration
// DELETE THIS AFTER DEBUGGING
export async function GET() {
const authSecret = process.env.AUTH_SECRET;
const nextAuthSecret = process.env.NEXTAUTH_SECRET;
const nextAuthUrl = process.env.NEXTAUTH_URL;
const authUrl = process.env.AUTH_URL;
const dbUrl = process.env.DATABASE_URL;
const emailHost = process.env.EMAIL_SERVER_HOST;
const emailUser = process.env.EMAIL_SERVER_USER;
const emailPass = process.env.EMAIL_SERVER_PASSWORD;

// Test hash with each secret
const testToken = "test123";
const hashWithAuth = authSecret
? createHash("sha256").update(`${testToken}${authSecret}`).digest("hex").substring(0, 16)
: "N/A";
const hashWithNextAuth = nextAuthSecret
? createHash("sha256").update(`${testToken}${nextAuthSecret}`).digest("hex").substring(0, 16)
: "N/A";

return NextResponse.json({
env: {
AUTH_SECRET_set: !!authSecret,
AUTH_SECRET_first5: authSecret ? authSecret.substring(0, 5) : null,
NEXTAUTH_SECRET_set: !!nextAuthSecret,
NEXTAUTH_SECRET_first5: nextAuthSecret ? nextAuthSecret.substring(0, 5) : null,
NEXTAUTH_URL: nextAuthUrl,
AUTH_URL: authUrl,
DATABASE_URL_set: !!dbUrl,
EMAIL_HOST: emailHost,
EMAIL_USER: emailUser,
EMAIL_PASS_set: !!emailPass,
secrets_match: authSecret === nextAuthSecret,
},
hash_test: {
with_AUTH_SECRET: hashWithAuth,
with_NEXTAUTH_SECRET: hashWithNextAuth,
hashes_match: hashWithAuth === hashWithNextAuth,
},
node_env: process.env.NODE_ENV,
});
}
Loading