fix: Login-Rate-Limit, Sicherheitsheader und Deploy-Skript-PATH nachziehen
Some checks are pending
Playwright Tests / test (push) Waiting to run
Test and SonarQube / quality (push) Waiting to run

Sicherheitsdurchgang vor dem geplanten Beta-Test mit Security-Scannern:

- Login (authorize() in src/lib/auth.ts) hatte als einziger Auth-Endpunkt kein
  Rate-Limit, obwohl signup/reset-password/verify-email/resend-verification es
  bereits nutzen. Jetzt max. 8 Versuche pro E-Mail in 15 Minuten.
- Fehlende Standardheader ergaenzt (X-Content-Type-Options, X-Frame-Options,
  Referrer-Policy, Permissions-Policy), X-Powered-By abgeschaltet. Bewusst
  ohne CSP -- braucht eine eigene, getestete Whitelist fuer OAuth/Fonts/CDN.
- npm audit fix (ohne --force): 24 -> 10 gemeldete Schwachstellen, keine
  Breaking Changes. Die verbleibenden 10 haengen an next@14 und next-auth@4
  (siehe CHANGELOG "Known issues") und brauchen ein eigenes Major-Upgrade.

Ausserdem zwei Betriebsfixes, die beim heutigen Deploy live auffielen:

- scripts/update-app.sh setzt jetzt selbst PATH=/usr/local/bin:$PATH. Ohne
  Login-Shell fand es pm2 nicht und brach nach erfolgreichem Build beim
  Neustart ab -- die Instanz blieb dabei auf dem alten Stand, kein Datenverlust,
  aber ein unnoetiger manueller Eingriff.
- tsconfig.json nimmt .next-build/types/**/*.ts mit auf; Next.js traegt das
  beim Bauen mit NEXT_DIST_DIR=.next-build sonst als nie eingecheckte
  Abweichung auf dem Produktionshost ein.

v1.122.0

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
mARTin-B78 2026-09-12 10:30:12 +02:00
parent bc41ef340d
commit 3742ae52c6
8 changed files with 230 additions and 4686 deletions

View File

@ -2,6 +2,40 @@
All notable changes to this project are documented in this file.
## [1.122.0] - 2026-09-12
### Security
- **Login-Endpunkt hatte kein Rate-Limit.** signup/reset-password/verify-email/
resend-verification waren bereits gedrosselt, der eigentliche Login
(`authorize()` in `src/lib/auth.ts`) nicht -- ein Bruteforce gegen ein
bekanntes Konto lief unbegrenzt durch. Jetzt maximal 8 Versuche pro E-Mail
in 15 Minuten, mit eigener Fehlermeldung auf der Login-Seite.
- Fehlende Standard-Sicherheitsheader ergaenzt (`X-Content-Type-Options`,
`X-Frame-Options`, `Referrer-Policy`, `Permissions-Policy`) und
`X-Powered-By: Next.js` abgeschaltet. Bewusst ohne Content-Security-Policy --
die braucht eine sorgfaeltige Whitelist fuer OAuth, Google Fonts und die
FontAwesome-CDN und ist ein eigener Schritt.
- `npm audit fix` (ohne `--force`) eingespielt: 24 auf 10 gemeldete
Schwachstellen reduziert, alles ohne Breaking Changes.
### Fixed
- `scripts/update-app.sh` setzt jetzt selbst `PATH=/usr/local/bin:$PATH`.
Ein Lauf ohne Login-Shell (z. B. `ssh host 'scripts/update-app.sh'`) fand
`pm2` sonst nicht und brach direkt nach einem erfolgreichen Build beim
Neustart ab -- am 12.09. live aufgetreten, siehe Betriebsnotiz.
- `tsconfig.json` nimmt `.next-build/types/**/*.ts` mit auf. Next.js traegt
diesen Pfad beim Bauen mit `NEXT_DIST_DIR=.next-build` automatisch ein;
ohne den committeten Eintrag stand das auf dem Produktionshost als
staendige, nie eingecheckte Abweichung in `git status`.
### Known issues
- **`next` (14.2.35) und `next-auth`/`@auth/core` (4.24.x) bleiben mit
bekannten kritischen CVEs** (u. a. ein E-Mail-Normalisierungs-Bypass in
Auth.js und mehrere Next.js-Advisories bis hin zu RCE-Klassen). Der Fix
ist ein Major-Upgrade auf next@16 und next-auth v5 -- ein eigenes,
sorgfaeltig zu testendes Vorhaben, kein Patch. `npm audit` zeigt den
aktuellen Stand.
## [1.121.2] - 2026-09-12
### Changed

View File

@ -3,6 +3,8 @@ const { version } = require('./package.json');
const nextConfig = {
output: 'standalone',
// Kein "X-Powered-By: Next.js" -- unnoetige Versions-/Framework-Preisgabe an Scanner.
poweredByHeader: false,
// Ziel des Builds. update-app.sh baut nach .next-build, damit das laufende .next
// waehrend des Builds unberuehrt bleibt, und schaltet erst nach erfolgreichem
// Smoke-Test um. Ohne das zerstoert ein fehlgeschlagener Build die laufende App.
@ -30,6 +32,23 @@ const nextConfig = {
experimental: {
serverComponentsExternalPackages: ['ical.js', 'tsdav'],
},
// Standard-Sicherheitsheader, die von Scannern (securityheaders.com, Mozilla Observatory,
// OWASP ZAP Baseline) sonst als fehlend markiert werden. Bewusst ohne Content-Security-Policy:
// die brauchte eine sorgfaeltige Whitelist (Google/Microsoft-OAuth, Google Fonts, FontAwesome-
// CDN, inline Styles in WeeklyView) und eigene Tests, um nichts zu zerbrechen -- eigener Schritt.
async headers() {
return [
{
source: '/:path*',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
],
},
];
},
webpack: (config, { isServer }) => {
// Ignore .md files in node_modules (fixes apple-icloud README.md parse error)
config.module.rules.push({

4838
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "my-weekly-todo-list",
"version": "1.121.3",
"version": "1.122.0",
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js",
"scripts": {

View File

@ -23,6 +23,12 @@ main() {
# it the shell sees tee's exit status (always 0) and a failed build looks like success.
set -euo pipefail
# pm2 lives in /usr/local/bin. The systemd unit sets this explicitly, but a plain
# `ssh host 'scripts/update-app.sh'` (no login shell, .bashrc never sourced) does not —
# it leaves PATH at /usr/bin:/bin and the restart step below dies with "pm2: not found",
# after the build has already succeeded and switched over. Hit in production 2026-09-12.
export PATH="/usr/local/bin:$PATH"
local APP_DIR="/root/My-Weekly-ToDo-List"
local PM2_APP="my-weekly-todo"
local LOG_FILE="/var/log/my-weekly-todo-update.log"

View File

@ -29,6 +29,10 @@ function LoginContent() {
if (result?.error) {
if (result.error === 'CredentialsSignin') {
setCredError('Invalid email or password');
} else if (result.error === 'too_many_attempts') {
setCredError('Too many login attempts. Please wait a few minutes and try again.');
} else if (result.error === 'email_not_verified') {
setCredError('Please verify your email before signing in');
} else {
setCredError(result.error);
}

View File

@ -6,6 +6,7 @@ import { PrismaAdapter } from "@next-auth/prisma-adapter";
import CredentialsProvider from "next-auth/providers/credentials";
import { compare } from "bcryptjs";
import { prisma } from "@/lib/prisma";
import { rateLimit } from "@/lib/rate-limit";
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
@ -52,6 +53,15 @@ export const authOptions: NextAuthOptions = {
throw new Error("Invalid credentials");
}
// Passwort-Bruteforce gegen ein bekanntes Konto drosseln: dieser Endpunkt hatte als
// einziger Auth-Pfad kein Limit, waehrend signup/reset-password/verify-email bereits
// ueber rate-limit.ts geschuetzt sind. Pro E-Mail statt IP, weil NextAuth v4 hier keine
// vollwertige Request mit Headern reicht.
const limit = rateLimit(`login:${credentials.email.toLowerCase()}`, 8, 15 * 60 * 1000);
if (!limit.ok) {
throw new Error("too_many_attempts");
}
const user = await prisma.user.findUnique({
where: {
email: credentials.email

View File

@ -33,7 +33,8 @@
"next-env.d.ts",
"src/**/*.ts",
"src/**/*.tsx",
".next/types/**/*.ts"
".next/types/**/*.ts",
".next-build/types/**/*.ts"
],
"exclude": [
"node_modules",