fix: Login-Rate-Limit, Sicherheitsheader und Deploy-Skript-PATH nachziehen
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:
parent
bc41ef340d
commit
3742ae52c6
34
CHANGELOG.md
34
CHANGELOG.md
@ -2,6 +2,40 @@
|
|||||||
|
|
||||||
All notable changes to this project are documented in this file.
|
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
|
## [1.121.2] - 2026-09-12
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@ -3,6 +3,8 @@ const { version } = require('./package.json');
|
|||||||
|
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: 'standalone',
|
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
|
// Ziel des Builds. update-app.sh baut nach .next-build, damit das laufende .next
|
||||||
// waehrend des Builds unberuehrt bleibt, und schaltet erst nach erfolgreichem
|
// waehrend des Builds unberuehrt bleibt, und schaltet erst nach erfolgreichem
|
||||||
// Smoke-Test um. Ohne das zerstoert ein fehlgeschlagener Build die laufende App.
|
// Smoke-Test um. Ohne das zerstoert ein fehlgeschlagener Build die laufende App.
|
||||||
@ -30,6 +32,23 @@ const nextConfig = {
|
|||||||
experimental: {
|
experimental: {
|
||||||
serverComponentsExternalPackages: ['ical.js', 'tsdav'],
|
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 }) => {
|
webpack: (config, { isServer }) => {
|
||||||
// Ignore .md files in node_modules (fixes apple-icloud README.md parse error)
|
// Ignore .md files in node_modules (fixes apple-icloud README.md parse error)
|
||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
|
|||||||
4838
package-lock.json
generated
4838
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"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",
|
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -23,6 +23,12 @@ main() {
|
|||||||
# it the shell sees tee's exit status (always 0) and a failed build looks like success.
|
# it the shell sees tee's exit status (always 0) and a failed build looks like success.
|
||||||
set -euo pipefail
|
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 APP_DIR="/root/My-Weekly-ToDo-List"
|
||||||
local PM2_APP="my-weekly-todo"
|
local PM2_APP="my-weekly-todo"
|
||||||
local LOG_FILE="/var/log/my-weekly-todo-update.log"
|
local LOG_FILE="/var/log/my-weekly-todo-update.log"
|
||||||
|
|||||||
@ -29,6 +29,10 @@ function LoginContent() {
|
|||||||
if (result?.error) {
|
if (result?.error) {
|
||||||
if (result.error === 'CredentialsSignin') {
|
if (result.error === 'CredentialsSignin') {
|
||||||
setCredError('Invalid email or password');
|
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 {
|
} else {
|
||||||
setCredError(result.error);
|
setCredError(result.error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { PrismaAdapter } from "@next-auth/prisma-adapter";
|
|||||||
import CredentialsProvider from "next-auth/providers/credentials";
|
import CredentialsProvider from "next-auth/providers/credentials";
|
||||||
import { compare } from "bcryptjs";
|
import { compare } from "bcryptjs";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { rateLimit } from "@/lib/rate-limit";
|
||||||
|
|
||||||
export const authOptions: NextAuthOptions = {
|
export const authOptions: NextAuthOptions = {
|
||||||
adapter: PrismaAdapter(prisma),
|
adapter: PrismaAdapter(prisma),
|
||||||
@ -52,6 +53,15 @@ export const authOptions: NextAuthOptions = {
|
|||||||
throw new Error("Invalid credentials");
|
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({
|
const user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
email: credentials.email
|
email: credentials.email
|
||||||
|
|||||||
@ -33,7 +33,8 @@
|
|||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
"src/**/*.tsx",
|
"src/**/*.tsx",
|
||||||
".next/types/**/*.ts"
|
".next/types/**/*.ts",
|
||||||
|
".next-build/types/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user