feat(email): improve password reset emails, onboarding templates, and handle server action errors without production obfuscation
All checks were successful
Staging Build / build (push) Successful in 3m28s
All checks were successful
Staging Build / build (push) Successful in 3m28s
This commit is contained in:
@@ -4,6 +4,7 @@ import { createClient } from '@/lib/supabase/server'
|
||||
import { createAdminClient } from '@/lib/supabase/admin'
|
||||
import { headers } from 'next/headers'
|
||||
import { sendLockoutEmail } from '@/lib/utils/email'
|
||||
import { sendMail } from '@/utils/mail'
|
||||
|
||||
export async function signIn(email: string, password: string) {
|
||||
try {
|
||||
@@ -108,25 +109,92 @@ export async function signOut() {
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
function getBeautifulEmailHtml(title: string, messageHtml: string, buttonText?: string, buttonUrl?: string) {
|
||||
return `
|
||||
<div style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; background-color: #f8fafc; padding: 40px 10px; margin: 0; width: 100%;">
|
||||
<div style="max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 16px; overflow: hidden; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -2px rgba(0, 0, 0, 0.05); border: 1px solid #e2e8f0;">
|
||||
<!-- Header banner with gradient -->
|
||||
<div style="background: linear-gradient(135deg, #1e3a8a 0%, #312e81 100%); padding: 32px; text-align: center;">
|
||||
<h1 style="color: #ffffff; margin: 0; font-size: 24px; font-weight: 800; letter-spacing: -0.5px;">${title}</h1>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div style="padding: 40px 32px; background-color: #ffffff;">
|
||||
${messageHtml}
|
||||
|
||||
${buttonText && buttonUrl ? `
|
||||
<div style="text-align: center; margin: 32px 0;">
|
||||
<a href="${buttonUrl}" style="background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); color: #ffffff; padding: 14px 28px; text-decoration: none; border-radius: 8px; font-weight: bold; font-size: 15px; display: inline-block; box-shadow: 0 4px 10px rgba(37, 99, 235, 0.2); transition: all 0.2s ease;">
|
||||
${buttonText}
|
||||
</a>
|
||||
</div>
|
||||
<p style="color: #64748b; font-size: 13px; line-height: 1.6; margin-top: 32px; border-top: 1px solid #f1f5f9; padding-top: 20px;">
|
||||
Falls der Button nicht funktioniert, kopieren Sie bitte diesen Link in Ihren Browser:<br>
|
||||
<a href="${buttonUrl}" style="color: #2563eb; text-decoration: none; word-break: break-all;">${buttonUrl}</a>
|
||||
</p>
|
||||
` : ''}
|
||||
|
||||
<hr style="border: 0; border-top: 1px solid #f1f5f9; margin: 32px 0;">
|
||||
<p style="color: #94a3b8; font-size: 11px; text-align: center; margin: 0; line-height: 1.6;">
|
||||
Dies ist eine automatisch generierte E-Mail.<br>
|
||||
© ${new Date().getFullYear()} CASPOS Shop. Alle Rechte vorbehalten.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
export async function resetPassword(email: string) {
|
||||
const supabase = await createClient()
|
||||
const origin = (await headers()).get('origin') || ''
|
||||
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
||||
redirectTo: `${origin}/auth/update-password`,
|
||||
})
|
||||
if (error) {
|
||||
throw new Error(error.message)
|
||||
try {
|
||||
const admin = createAdminClient()
|
||||
const { data, error } = await admin.auth.admin.generateLink({
|
||||
type: 'recovery',
|
||||
email,
|
||||
})
|
||||
if (error) {
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
const tokenHash = (data as any)?.properties?.hashed_token;
|
||||
if (!tokenHash) {
|
||||
return { success: false, error: 'Wiederherstellungs-Token konnte nicht generiert werden.' }
|
||||
}
|
||||
|
||||
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://staging.hephex.de'
|
||||
const resetLink = `${siteUrl}/auth/verify-link?token_hash=${tokenHash}&type=recovery&next=/auth/update-password`
|
||||
|
||||
const messageHtml = `
|
||||
<p style="color: #475569; font-size: 16px; line-height: 1.6; margin-top: 0;">Hallo,</p>
|
||||
<p style="color: #475569; font-size: 16px; line-height: 1.6;">Sie haben das Zurücksetzen Ihres Passworts angefordert.</p>
|
||||
<p style="color: #475569; font-size: 16px; line-height: 1.6;">Bitte klicken Sie auf den untenstehenden Button, um ein neues Passwort festzulegen:</p>
|
||||
`;
|
||||
|
||||
await sendMail({
|
||||
to: email,
|
||||
subject: 'Passwort zurücksetzen – CASPOS Shop',
|
||||
text: `Hallo,\n\nSie haben das Zurücksetzen Ihres Passworts angefordert.\n\nBitte klicken Sie auf den folgenden Link, um ein neues Passwort festzulegen:\n${resetLink}\n\nViele Grüße,\nIhr CASPOS Shop-Team`,
|
||||
html: getBeautifulEmailHtml('Passwort zurücksetzen', messageHtml, 'Passwort zurücksetzen', resetLink)
|
||||
})
|
||||
|
||||
return { success: true }
|
||||
} catch (err: any) {
|
||||
console.error('Password reset error:', err)
|
||||
return { success: false, error: err.message || 'Fehler beim Senden der Passwort-Zurücksetzen-E-Mail.' }
|
||||
}
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export async function updatePassword(password: string) {
|
||||
const supabase = await createClient()
|
||||
const { error } = await supabase.auth.updateUser({ password })
|
||||
if (error) {
|
||||
throw new Error(error.message)
|
||||
try {
|
||||
const supabase = await createClient()
|
||||
const { error } = await supabase.auth.updateUser({ password })
|
||||
if (error) {
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
return { success: true }
|
||||
} catch (err: any) {
|
||||
console.error('Update password error:', err)
|
||||
return { success: false, error: err.message || 'Fehler beim Aktualisieren des Passworts.' }
|
||||
}
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
export async function verifyAdmin() {
|
||||
|
||||
Reference in New Issue
Block a user