mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 17:46:06 +00:00
refactor: eslint and prettier conflict will be resolved and formatted
This commit is contained in:
+74
-75
@@ -2,12 +2,12 @@ import { createHash, randomBytes } from 'crypto';
|
||||
import { db } from '@/lib/db';
|
||||
import nodemailer from 'nodemailer';
|
||||
import {
|
||||
brandedEmailTemplate,
|
||||
emailButton,
|
||||
emailHeading,
|
||||
emailRow,
|
||||
escapeHtml,
|
||||
EMAIL_COLORS,
|
||||
brandedEmailTemplate,
|
||||
emailButton,
|
||||
emailHeading,
|
||||
emailRow,
|
||||
escapeHtml,
|
||||
EMAIL_COLORS,
|
||||
} from '@/lib/email-brand';
|
||||
import { logError } from '@/lib/logger';
|
||||
|
||||
@@ -16,7 +16,7 @@ const TOKEN_EXPIRY_HOURS = 2;
|
||||
|
||||
/** Hash a raw token before persisting so the DB stores only the digest. */
|
||||
function hashToken(token: string): string {
|
||||
return createHash('sha256').update(token).digest('hex');
|
||||
return createHash('sha256').update(token).digest('hex');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,7 +25,7 @@ function hashToken(token: string): string {
|
||||
* without a mail server continue to function.
|
||||
*/
|
||||
export function isEmailVerificationEnabled(): boolean {
|
||||
return !!(process.env.SMTP_HOST && process.env.SMTP_USER && process.env.SMTP_PASSWORD);
|
||||
return !!(process.env.SMTP_HOST && process.env.SMTP_USER && process.env.SMTP_PASSWORD);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,19 +34,19 @@ export function isEmailVerificationEnabled(): boolean {
|
||||
* Any existing tokens for this email are deleted first (at most one live token).
|
||||
*/
|
||||
export async function createVerificationToken(email: string): Promise<string> {
|
||||
const token = randomBytes(32).toString('hex');
|
||||
const tokenHash = hashToken(token);
|
||||
const expires = new Date(Date.now() + TOKEN_EXPIRY_HOURS * 60 * 60 * 1000);
|
||||
const token = randomBytes(32).toString('hex');
|
||||
const tokenHash = hashToken(token);
|
||||
const expires = new Date(Date.now() + TOKEN_EXPIRY_HOURS * 60 * 60 * 1000);
|
||||
|
||||
// Delete existing tokens for this identifier before creating a new one
|
||||
await db.verificationToken.deleteMany({ where: { identifier: email } });
|
||||
// Delete existing tokens for this identifier before creating a new one
|
||||
await db.verificationToken.deleteMany({ where: { identifier: email } });
|
||||
|
||||
await db.verificationToken.create({
|
||||
data: { identifier: email, token: tokenHash, expires },
|
||||
});
|
||||
await db.verificationToken.create({
|
||||
data: { identifier: email, token: tokenHash, expires },
|
||||
});
|
||||
|
||||
// Return the raw (unhashed) token — only ever sent to the user, never stored.
|
||||
return token;
|
||||
// Return the raw (unhashed) token — only ever sent to the user, never stored.
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,29 +56,29 @@ export async function createVerificationToken(email: string): Promise<string> {
|
||||
* already verified, or deleted account).
|
||||
*/
|
||||
export async function consumeVerificationToken(token: string): Promise<string | null> {
|
||||
const tokenHash = hashToken(token);
|
||||
const record = await db.verificationToken.findUnique({ where: { token: tokenHash } });
|
||||
const tokenHash = hashToken(token);
|
||||
const record = await db.verificationToken.findUnique({ where: { token: tokenHash } });
|
||||
|
||||
if (!record) return null;
|
||||
if (record.expires < new Date()) {
|
||||
await db.verificationToken.delete({ where: { token: tokenHash } }).catch(() => null);
|
||||
return null;
|
||||
}
|
||||
if (!record) return null;
|
||||
if (record.expires < new Date()) {
|
||||
await db.verificationToken.delete({ where: { token: tokenHash } }).catch(() => null);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Atomically mark email as verified and delete the token
|
||||
const [user] = await db.$transaction([
|
||||
db.user.updateMany({
|
||||
where: { email: record.identifier, emailVerified: null },
|
||||
data: { emailVerified: new Date() },
|
||||
}),
|
||||
db.verificationToken.delete({ where: { token: tokenHash } }),
|
||||
]);
|
||||
// Atomically mark email as verified and delete the token
|
||||
const [user] = await db.$transaction([
|
||||
db.user.updateMany({
|
||||
where: { email: record.identifier, emailVerified: null },
|
||||
data: { emailVerified: new Date() },
|
||||
}),
|
||||
db.verificationToken.delete({ where: { token: tokenHash } }),
|
||||
]);
|
||||
|
||||
// count === 0 means the user was already verified or has been deleted.
|
||||
// Return null so a replayed/stale token never produces a misleading success redirect.
|
||||
if (user.count === 0) return null;
|
||||
// count === 0 means the user was already verified or has been deleted.
|
||||
// Return null so a replayed/stale token never produces a misleading success redirect.
|
||||
if (user.count === 0) return null;
|
||||
|
||||
return record.identifier;
|
||||
return record.identifier;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -86,36 +86,35 @@ export async function consumeVerificationToken(token: string): Promise<string |
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function createTransport() {
|
||||
const host = process.env.SMTP_HOST;
|
||||
const port = Number(process.env.SMTP_PORT || '587');
|
||||
const user = process.env.SMTP_USER;
|
||||
const pass = process.env.SMTP_PASSWORD;
|
||||
if (!host || !user || !pass) return null;
|
||||
return nodemailer.createTransport({ host, port, secure: port === 465, auth: { user, pass } });
|
||||
const host = process.env.SMTP_HOST;
|
||||
const port = Number(process.env.SMTP_PORT || '587');
|
||||
const user = process.env.SMTP_USER;
|
||||
const pass = process.env.SMTP_PASSWORD;
|
||||
if (!host || !user || !pass) return null;
|
||||
return nodemailer.createTransport({ host, port, secure: port === 465, auth: { user, pass } });
|
||||
}
|
||||
|
||||
export async function sendVerificationEmail(email: string, token: string): Promise<void> {
|
||||
const transporter = createTransport();
|
||||
if (!transporter) return;
|
||||
const transporter = createTransport();
|
||||
if (!transporter) return;
|
||||
|
||||
const baseUrl = process.env.NEXTAUTH_URL;
|
||||
if (!baseUrl) {
|
||||
// A missing NEXTAUTH_URL means the verification link will be malformed and the
|
||||
// user will be permanently locked out with no visible failure. Treat as fatal.
|
||||
logError(
|
||||
'NEXTAUTH_URL is not set — cannot build a valid verification link.',
|
||||
new Error(
|
||||
'Set NEXTAUTH_URL to your deployment origin (e.g. https://app.example.com).'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
const baseUrl = process.env.NEXTAUTH_URL;
|
||||
if (!baseUrl) {
|
||||
// A missing NEXTAUTH_URL means the verification link will be malformed and the
|
||||
// user will be permanently locked out with no visible failure. Treat as fatal.
|
||||
logError(
|
||||
'NEXTAUTH_URL is not set — cannot build a valid verification link.',
|
||||
new Error('Set NEXTAUTH_URL to your deployment origin (e.g. https://app.example.com).')
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const verifyUrl = `${baseUrl}/api/auth/verify-email?token=${encodeURIComponent(token)}`;
|
||||
const from = process.env.SMTP_FROM || process.env.EMAIL_FROM || 'OpenFrame <[email protected]>';
|
||||
const verifyUrl = `${baseUrl}/api/auth/verify-email?token=${encodeURIComponent(token)}`;
|
||||
const from =
|
||||
process.env.SMTP_FROM || process.env.EMAIL_FROM || 'OpenFrame <[email protected]>';
|
||||
|
||||
const html = brandedEmailTemplate(
|
||||
`
|
||||
const html = brandedEmailTemplate(
|
||||
`
|
||||
<tr>${emailHeading('✉', 'Verify your email address')}</tr>
|
||||
<tr><td style="padding:20px;">
|
||||
<table cellpadding="0" cellspacing="0" style="width:100%;margin-bottom:20px;">
|
||||
@@ -129,19 +128,19 @@ export async function sendVerificationEmail(email: string, token: string): Promi
|
||||
${emailButton('Verify Email Address →', verifyUrl)}
|
||||
</td></tr>
|
||||
`,
|
||||
{
|
||||
footerText: `This link expires in ${TOKEN_EXPIRY_HOURS} hours.`,
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
await transporter.sendMail({
|
||||
from,
|
||||
to: email,
|
||||
subject: 'Verify your OpenFrame email address',
|
||||
html,
|
||||
});
|
||||
} catch (err) {
|
||||
logError('Failed to send verification email:', err);
|
||||
{
|
||||
footerText: `This link expires in ${TOKEN_EXPIRY_HOURS} hours.`,
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
await transporter.sendMail({
|
||||
from,
|
||||
to: email,
|
||||
subject: 'Verify your OpenFrame email address',
|
||||
html,
|
||||
});
|
||||
} catch (err) {
|
||||
logError('Failed to send verification email:', err);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user