From 0334252fed384f5a3ba3285ca0cbc8ab9a87637e Mon Sep 17 00:00:00 2001 From: DoctoDre Date: Tue, 28 Jul 2026 21:13:21 +0200 Subject: [PATCH] fix(andreknie.de): close package two review gaps --- privat/CV/andreknie.de/server/index.test.js | 17 ++++++++++ .../server/routes/resource-download.js | 2 +- .../CV/andreknie.de/server/services/mailer.js | 17 +++++++--- .../server/services/mailer.test.js | 28 ++++++++++++++- privat/CV/andreknie.de/src/pages/Home.css | 34 +++++++++++++++++++ 5 files changed, 91 insertions(+), 7 deletions(-) diff --git a/privat/CV/andreknie.de/server/index.test.js b/privat/CV/andreknie.de/server/index.test.js index 487b784..a27be55 100644 --- a/privat/CV/andreknie.de/server/index.test.js +++ b/privat/CV/andreknie.de/server/index.test.js @@ -112,4 +112,21 @@ describe('backend mail safety', () => { expect(response.status).toBe(400) expect((await response.json()).errors.resource_id).toBeTruthy() }) + + it('silently accepts resource honeypot submissions', async () => { + const response = await fetch(`${baseUrl}/api/resource-download`, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + name: 'Test', + email: 'test@example.invalid', + company: 'Synthetic Test', + resource_id: 'does-not-exist', + company_website: 'https://bot.example.invalid', + }), + }) + + expect(response.status).toBe(201) + expect((await response.json()).ok).toBe(true) + }) }) diff --git a/privat/CV/andreknie.de/server/routes/resource-download.js b/privat/CV/andreknie.de/server/routes/resource-download.js index 70c641a..c953018 100644 --- a/privat/CV/andreknie.de/server/routes/resource-download.js +++ b/privat/CV/andreknie.de/server/routes/resource-download.js @@ -23,7 +23,7 @@ router.post('/', resourceLimiter, antiSpam, async (req, res) => { const errors = {} if (!name || name.trim().length === 0) errors.name = 'Name ist erforderlich.' if (name && name.length > 100) errors.name = 'Max. 100 Zeichen.' - if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errors.email = 'Gueltige E-Mail erforderlich.' + if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errors.email = 'Gültige E-Mail erforderlich.' if (!company || company.trim().length === 0) errors.company = 'Unternehmen ist erforderlich.' if (company && company.length > 150) errors.company = 'Max. 150 Zeichen.' if (!resource_id || !getAvailableResourceIds().includes(resource_id)) { diff --git a/privat/CV/andreknie.de/server/services/mailer.js b/privat/CV/andreknie.de/server/services/mailer.js index 300c6a5..6003e18 100644 --- a/privat/CV/andreknie.de/server/services/mailer.js +++ b/privat/CV/andreknie.de/server/services/mailer.js @@ -62,11 +62,7 @@ export async function sendStakeholderNotification(type, data) { 'resource-download': `Ressourcenanfrage von ${data.name}`, } - const body = [ - 'Bitte die angefragten Inhalte prüfen und bei positiver Prüfung ein aktuelles Profil von André verschicken.', - '', - ...Object.entries(data).map(([key, val]) => `${key}: ${val}`), - ].join('\n') + const body = buildStakeholderMessage(type, data) await t.sendMail({ from: SMTP_USER, @@ -76,6 +72,17 @@ export async function sendStakeholderNotification(type, data) { }) } +export function buildStakeholderMessage(type, data) { + const fields = Object.entries(data).map(([key, val]) => `${key}: ${val}`) + if (type !== 'resource-download') return fields.join('\n') + + return [ + 'Bitte die angefragten Inhalte prüfen und bei positiver Prüfung ein aktuelles Profil von André verschicken.', + '', + ...fields, + ].join('\n') +} + export { STAKEHOLDER_EMAIL, BASE_URL } /** diff --git a/privat/CV/andreknie.de/server/services/mailer.test.js b/privat/CV/andreknie.de/server/services/mailer.test.js index 1e8abf1..798e5bf 100644 --- a/privat/CV/andreknie.de/server/services/mailer.test.js +++ b/privat/CV/andreknie.de/server/services/mailer.test.js @@ -1,5 +1,10 @@ import { describe, expect, it } from 'vitest' -import { isMailerReady, MailerNotReadyError, sendConfirmationEmail } from './mailer.js' +import { + buildStakeholderMessage, + isMailerReady, + MailerNotReadyError, + sendConfirmationEmail, +} from './mailer.js' describe('mailer readiness', () => { it('reports missing SMTP configuration without exposing credentials', async () => { @@ -10,3 +15,24 @@ describe('mailer readiness', () => { .rejects.toMatchObject({ code: 'MAILER_NOT_READY' }) }) }) + +describe('stakeholder notification content', () => { + const data = { + name: 'Synthetic Test', + email: 'test@example.invalid', + } + + it('adds the manual profile instruction to resource requests', () => { + const message = buildStakeholderMessage('resource-download', data) + + expect(message).toContain('ein aktuelles Profil von André verschicken') + expect(message).toContain('name: Synthetic Test') + }) + + it('does not add the resource instruction to contact notifications', () => { + const message = buildStakeholderMessage('contact', data) + + expect(message).not.toContain('ein aktuelles Profil von André verschicken') + expect(message).toBe('name: Synthetic Test\nemail: test@example.invalid') + }) +}) diff --git a/privat/CV/andreknie.de/src/pages/Home.css b/privat/CV/andreknie.de/src/pages/Home.css index f0c556e..2054f66 100644 --- a/privat/CV/andreknie.de/src/pages/Home.css +++ b/privat/CV/andreknie.de/src/pages/Home.css @@ -14,8 +14,42 @@ @media (max-width: 600px) { .hero { + padding-top: 100px; padding-bottom: calc(60px + var(--dock-content-space)); } + + .hero-photo { + width: 140px; + height: 140px; + margin-bottom: 16px; + } + + .hero h1 { + font-size: 2.25rem; + margin-bottom: 8px; + } + + .hero-roles { + font-size: 1rem; + margin-bottom: 12px; + } + + .hero-subtext { + font-size: 1rem; + line-height: 1.45; + margin-bottom: 24px; + } + + .hero-actions { + flex-wrap: nowrap; + gap: 10px; + } + + .hero-actions .primary-button, + .hero-actions .secondary-button { + padding: 12px 18px; + font-size: 0.95rem; + } } .hero-content {