217 lines
8.2 KiB
TypeScript
217 lines
8.2 KiB
TypeScript
'use server'
|
||
|
||
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 {
|
||
const supabase = await createClient()
|
||
const { data, error } = await supabase.auth.signInWithPassword({
|
||
email,
|
||
password,
|
||
})
|
||
if (error) {
|
||
// Track failed attempts
|
||
const { data: usr, error: usrError } = await supabase
|
||
.from('users')
|
||
.select('failed_attempts, email')
|
||
.eq('email', email)
|
||
.single()
|
||
|
||
const attempts = (usr?.failed_attempts ?? 0) + 1
|
||
|
||
// Update attempts count
|
||
await supabase
|
||
.from('users')
|
||
.update({ failed_attempts: attempts })
|
||
.eq('email', email)
|
||
|
||
// Lock account on 5th failure
|
||
if (attempts >= 5) {
|
||
await supabase
|
||
.from('users')
|
||
.update({ role: 'gesperrt' })
|
||
.eq('email', email)
|
||
// Notify admin
|
||
await sendLockoutEmail('info@hephex.de')
|
||
return { success: false, error: 'Konto gesperrt nach 5 Fehlversuchen.' }
|
||
}
|
||
|
||
return { success: false, error: error.message }
|
||
}
|
||
|
||
const userId = data.user?.id
|
||
if (userId) {
|
||
const adminClient = createAdminClient()
|
||
let { data: userData, error: userError } = await adminClient
|
||
.from('users')
|
||
.select('role')
|
||
.eq('id', userId)
|
||
.single()
|
||
|
||
if (userError || !userData) {
|
||
// Fallback: create database records if missing for this authenticated user
|
||
try {
|
||
const adminSupabase = createAdminClient()
|
||
|
||
// Ensure profile exists
|
||
await adminSupabase.from('profiles').insert([{ id: userId }]).select()
|
||
|
||
// Ensure user exists
|
||
const { data: insertedUser, error: insertError } = await adminSupabase
|
||
.from('users')
|
||
.insert([{ id: userId, role: 'partner' }])
|
||
.select('role')
|
||
.single()
|
||
|
||
if (!insertError && insertedUser) {
|
||
userData = insertedUser
|
||
userError = null
|
||
}
|
||
} catch (adminErr) {
|
||
console.error("Failed to backfill user records:", adminErr)
|
||
}
|
||
}
|
||
|
||
if (userError || !userData || (userData.role !== 'partner' && userData.role !== 'admin')) {
|
||
await supabase.auth.signOut()
|
||
if (userData?.role === 'gesperrt') {
|
||
return { success: false, error: "Ihr Konto wurde gesperrt. Bitte wenden Sie sich an den Administrator." }
|
||
}
|
||
return { success: false, error: "Zugriff verweigert. Nur registrierte Partner dürfen sich anmelden." }
|
||
}
|
||
|
||
// Andere aktive Sitzungen beenden
|
||
await supabase.auth.signOut({ scope: 'others' })
|
||
|
||
return { success: true, role: userData.role }
|
||
}
|
||
|
||
return { success: true, role: 'partner' }
|
||
} catch (err: any) {
|
||
console.error("SignIn error:", err)
|
||
return { success: false, error: err?.message || "Ein unerwarteter Fehler ist aufgetreten." }
|
||
}
|
||
}
|
||
|
||
export async function signUp(email: string, password: string) {
|
||
throw new Error("Registrierung ist deaktiviert.")
|
||
}
|
||
|
||
export async function signOut() {
|
||
const supabase = await createClient()
|
||
const { error } = await supabase.auth.signOut()
|
||
if (error) {
|
||
throw new Error(error.message)
|
||
}
|
||
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) {
|
||
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.' }
|
||
}
|
||
}
|
||
|
||
export async function updatePassword(password: string) {
|
||
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.' }
|
||
}
|
||
}
|
||
|
||
export async function verifyAdmin() {
|
||
const supabase = await createClient()
|
||
const { data: { user } } = await supabase.auth.getUser()
|
||
if (!user) throw new Error("Nicht authentifiziert.")
|
||
|
||
const { data: userData, error } = await supabase
|
||
.from('users')
|
||
.select('role')
|
||
.eq('id', user.id)
|
||
.single()
|
||
|
||
if (error || !userData || userData.role !== 'admin') {
|
||
throw new Error("Zugriff verweigert. Nur Administratoren erlaubt.")
|
||
}
|
||
return user
|
||
}
|