Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 47s
This commit deletes several files related to Passkey functionality, including CORS_TEST_ANLEITUNG.md, CROSS_DEVICE_DEBUG.md, CROSS_DEVICE_PROBLEM_ZUSAMMENFASSUNG.md, SMARTPHONE_TEST_ANLEITUNG.md, test-cors.html, test-smartphone.html, and Vue components for Passkey registration and recovery. These removals are part of a broader effort to streamline the codebase and focus on core authentication methods while Passkey support is under review.
103 lines
3.9 KiB
JavaScript
103 lines
3.9 KiB
JavaScript
import nodemailer from 'nodemailer'
|
|
import { readUsers, writeUsers } from '../../../../utils/auth.js'
|
|
import { assertRateLimit, getClientIp, registerRateLimitFailure, registerRateLimitSuccess } from '../../../../utils/rate-limit.js'
|
|
import { generateRecoveryToken, hashRecoveryToken, pruneRecoveryTokens } from '../../../../utils/passkey-recovery.js'
|
|
import { writeAuditLog } from '../../../../utils/audit-log.js'
|
|
|
|
function isValidEmail(email) {
|
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(email || ''))
|
|
}
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
const body = await readBody(event)
|
|
const email = String(body?.email || '').trim().toLowerCase()
|
|
|
|
if (!email || !isValidEmail(email)) {
|
|
// No enumeration; still 200
|
|
return { success: true, message: 'Falls ein Konto mit dieser E-Mail existiert, wurde eine E-Mail gesendet.' }
|
|
}
|
|
|
|
const ip = getClientIp(event)
|
|
assertRateLimit(event, {
|
|
name: 'auth:passkey-recovery:request:ip',
|
|
keyParts: [ip],
|
|
windowMs: 60 * 60 * 1000,
|
|
maxAttempts: 30,
|
|
lockoutMs: 30 * 60 * 1000
|
|
})
|
|
assertRateLimit(event, {
|
|
name: 'auth:passkey-recovery:request:email',
|
|
keyParts: [email],
|
|
windowMs: 60 * 60 * 1000,
|
|
maxAttempts: 5,
|
|
lockoutMs: 60 * 60 * 1000
|
|
})
|
|
|
|
const users = await readUsers()
|
|
const user = users.find(u => String(u.email || '').toLowerCase() === email)
|
|
|
|
// Always respond success
|
|
if (!user) {
|
|
await registerRateLimitFailure(event, { name: 'auth:passkey-recovery:request:ip', keyParts: [ip] })
|
|
await registerRateLimitFailure(event, { name: 'auth:passkey-recovery:request:email', keyParts: [email] })
|
|
await writeAuditLog('auth.passkey.recovery.request', { ip, email, userFound: false })
|
|
return { success: true, message: 'Falls ein Konto mit dieser E-Mail existiert, wurde eine E-Mail gesendet.' }
|
|
}
|
|
|
|
// Token erzeugen und (gehasht) am User speichern
|
|
const token = generateRecoveryToken()
|
|
const tokenHash = hashRecoveryToken(token)
|
|
const ttlMin = Number(process.env.PASSKEY_RECOVERY_TTL_MIN || 30)
|
|
const expiresAt = new Date(Date.now() + ttlMin * 60 * 1000).toISOString()
|
|
|
|
if (!Array.isArray(user.passkeyRecoveryTokens)) user.passkeyRecoveryTokens = []
|
|
user.passkeyRecoveryTokens.push({
|
|
tokenHash,
|
|
createdAt: new Date().toISOString(),
|
|
expiresAt,
|
|
usedAt: null
|
|
})
|
|
pruneRecoveryTokens(user)
|
|
|
|
const updated = users.map(u => (u.id === user.id ? user : u))
|
|
await writeUsers(updated)
|
|
|
|
registerRateLimitSuccess(event, { name: 'auth:passkey-recovery:request:email', keyParts: [email] })
|
|
await writeAuditLog('auth.passkey.recovery.request', { ip, email, userFound: true, userId: user.id })
|
|
|
|
// Mail senden (wenn SMTP konfiguriert)
|
|
const smtpUser = process.env.SMTP_USER
|
|
const smtpPass = process.env.SMTP_PASS
|
|
|
|
if (smtpUser && smtpPass) {
|
|
const transporter = nodemailer.createTransport({
|
|
host: process.env.SMTP_HOST || 'smtp.gmail.com',
|
|
port: process.env.SMTP_PORT || 587,
|
|
secure: false,
|
|
auth: { user: smtpUser, pass: smtpPass }
|
|
})
|
|
|
|
const baseUrl = process.env.NUXT_PUBLIC_BASE_URL || 'http://localhost:3100'
|
|
// Passkey-Wiederherstellungsseite vorläufig deaktiviert
|
|
// const link = `${baseUrl}/passkey-wiederherstellen?token=${token}`
|
|
const link = `${baseUrl}/login` // Fallback auf Login-Seite
|
|
|
|
await transporter.sendMail({
|
|
from: process.env.SMTP_FROM || 'noreply@harheimertc.de',
|
|
to: user.email,
|
|
subject: 'Passkey wiederherstellen - Harheimer TC',
|
|
html: `
|
|
<h2>Passkey wiederherstellen</h2>
|
|
<p>Hallo ${user.name || ''},</p>
|
|
<p>Sie haben eine Anfrage gestellt, um einen neuen Passkey hinzuzufügen.</p>
|
|
<p>Bitte klicken Sie auf den folgenden Link (gültig für ${ttlMin} Minuten):</p>
|
|
<p><a href="${link}">Neuen Passkey hinzufügen</a></p>
|
|
<p>Wenn Sie das nicht waren, ignorieren Sie diese E-Mail.</p>
|
|
`
|
|
})
|
|
}
|
|
|
|
return { success: true, message: 'Falls ein Konto mit dieser E-Mail existiert, wurde eine E-Mail gesendet.' }
|
|
})
|
|
|