Browse Source

unifying htaccess.
moved bin and other admin logic in admin
renamed all files to be lowercase.

Medowar 1 month ago
parent
commit
5e5a308fa5

+ 4 - 1
.htaccess

@@ -7,7 +7,10 @@ RewriteEngine On
 RewriteRule "(^|/)\." - [F,L]
 
 # Block internal directories from public access
-RewriteRule "^(config|src|storage|bin|docs|lib)(/|$)" - [F,L,NC]
+RewriteRule "^(config|src|storage|docs|lib)(/|$)" - [F,L,NC]
+
+# Block admin-internal scripts (CLI tools + auth class file)
+RewriteRule "^admin/(auth|cleanup|test-mail)\.php$" - [F,L,NC]
 
 # Keep direct file-based endpoints, fallback unknown routes to index
 RewriteCond %{REQUEST_FILENAME} !-f

+ 1 - 1
admin/application.php

@@ -50,7 +50,7 @@ $csrf = Csrf::token();
 <header class="site-header">
     <div class="container header-inner">
         <a class="brand" href="<?= htmlspecialchars(Bootstrap::url('admin/index.php')) ?>">
-            <img class="brand-logo" src="<?= htmlspecialchars(Bootstrap::url('assets/images/feuerwehr-Logo-invers.webp')) ?>" alt="Feuerwehr Logo">
+            <img class="brand-logo" src="<?= htmlspecialchars(Bootstrap::url('assets/images/feuerwehr-logo-invers.webp')) ?>" alt="Feuerwehr Logo">
             <div class="brand-title"><?= htmlspecialchars((string) ($app['project_name'] ?? 'Admin')) ?></div>
         </a>
     </div>

+ 5 - 0
src/Admin/Auth.php → admin/auth.php

@@ -6,6 +6,11 @@ namespace App\Admin;
 
 use App\App\Bootstrap;
 
+if (realpath((string) ($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
+    http_response_code(404);
+    exit;
+}
+
 final class Auth
 {
     /** @var array<string, mixed> */

+ 5 - 0
bin/cleanup.php → admin/cleanup.php

@@ -6,6 +6,11 @@ declare(strict_types=1);
 use App\App\Bootstrap;
 use App\Storage\FileSystem;
 
+if (PHP_SAPI !== 'cli') {
+    http_response_code(403);
+    exit('Forbidden');
+}
+
 require dirname(__DIR__) . '/src/autoload.php';
 Bootstrap::init(false);
 

+ 1 - 1
admin/index.php

@@ -48,7 +48,7 @@ if ($query !== '') {
 <header class="site-header">
     <div class="container header-inner">
         <a class="brand" href="<?= htmlspecialchars(Bootstrap::url('admin/index.php')) ?>">
-            <img class="brand-logo" src="<?= htmlspecialchars(Bootstrap::url('assets/images/feuerwehr-Logo-invers.webp')) ?>" alt="Feuerwehr Logo">
+            <img class="brand-logo" src="<?= htmlspecialchars(Bootstrap::url('assets/images/feuerwehr-logo-invers.webp')) ?>" alt="Feuerwehr Logo">
             <div class="brand-title"><?= htmlspecialchars((string) ($app['project_name'] ?? 'Admin')) ?></div>
         </a>
     </div>

+ 1 - 1
admin/login.php

@@ -53,7 +53,7 @@ $csrf = Csrf::token();
 <header class="site-header">
     <div class="container header-inner">
         <a class="brand" href="<?= htmlspecialchars(Bootstrap::url('admin/login.php')) ?>">
-            <img class="brand-logo" src="<?= htmlspecialchars(Bootstrap::url('assets/images/feuerwehr-Logo-invers.webp')) ?>" alt="Feuerwehr Logo">
+            <img class="brand-logo" src="<?= htmlspecialchars(Bootstrap::url('assets/images/feuerwehr-logo-invers.webp')) ?>" alt="Feuerwehr Logo">
             <div class="brand-title"><?= htmlspecialchars((string) ($app['project_name'] ?? 'Admin')) ?></div>
         </a>
     </div>

+ 6 - 1
bin/test-mail.php → admin/test-mail.php

@@ -2,7 +2,7 @@
 
 /**
  * CLI test script for mail rendering and PDF generation.
- * Run: php bin/test-mail.php
+ * Run: php admin/test-mail.php
  *
  * Writes output files to storage/logs/ for inspection.
  * Does NOT send actual emails.
@@ -10,6 +10,11 @@
 
 declare(strict_types=1);
 
+if (PHP_SAPI !== 'cli') {
+    http_response_code(403);
+    exit('Forbidden');
+}
+
 require_once __DIR__ . '/../src/autoload.php';
 
 use App\App\Bootstrap;

+ 0 - 0
assets/images/feuerwehr-Logo-invers.webp → assets/images/feuerwehr-logo-invers.webp


+ 0 - 7
bin/.htaccess

@@ -1,7 +0,0 @@
-<IfModule mod_authz_core.c>
-  Require all denied
-</IfModule>
-<IfModule !mod_authz_core.c>
-  Order allow,deny
-  Deny from all
-</IfModule>

+ 0 - 7
config/.htaccess

@@ -1,7 +0,0 @@
-<IfModule mod_authz_core.c>
-  Require all denied
-</IfModule>
-<IfModule !mod_authz_core.c>
-  Order allow,deny
-  Deny from all
-</IfModule>

+ 0 - 7
docs/.htaccess

@@ -1,7 +0,0 @@
-<IfModule mod_authz_core.c>
-  Require all denied
-</IfModule>
-<IfModule !mod_authz_core.c>
-  Order allow,deny
-  Deny from all
-</IfModule>

+ 13 - 13
docs/AI_OVERVIEW.md → docs/ai_overview.md

@@ -26,15 +26,15 @@ Digitaler Mitgliedsantrag für Feuerwehrverein mit Flatfile-Speicherung und Admi
   - `admin/download-zip.php`
   - `admin/delete.php`
 - Kernlogik:
-  - `src/Storage/JsonStore.php`
-  - `src/Storage/FileUploadStore.php`
-  - `src/Form/Validator.php`
-  - `src/Security/Csrf.php`
-  - `src/Security/RateLimiter.php`
-  - `src/Mail/Mailer.php` (HTML-Mails + PDF-Anhänge)
-  - `src/Mail/MimeMailBuilder.php` (MIME-Mails via nativer mail()-Funktion)
-  - `src/Mail/PdfGenerator.php` (FPDF, Antrags- und Anlagen-PDFs)
-  - `src/Mail/SubmissionFormatter.php` (Formulardaten für Mail/PDF aufbereiten)
+  - `src/storage/jsonstore.php`
+  - `src/storage/fileuploadstore.php`
+  - `src/form/validator.php`
+  - `src/security/csrf.php`
+  - `src/security/ratelimiter.php`
+  - `src/mail/mailer.php` (HTML-Mails + PDF-Anhänge)
+  - `src/mail/mimemailbuilder.php` (MIME-Mails via nativer mail()-Funktion)
+  - `src/mail/pdfgenerator.php` (FPDF, Antrags- und Anlagen-PDFs)
+  - `src/mail/submissionformatter.php` (Formulardaten für Mail/PDF aufbereiten)
 - Bibliotheken:
   - `lib/fpdf/` — FPDF 1.86, einzelne Klasse + Schrift-Metriken
 
@@ -58,10 +58,10 @@ Digitaler Mitgliedsantrag für Feuerwehrverein mit Flatfile-Speicherung und Admi
 - Neue Formularfelder: `config/form_schema.php`
 - Pflichtregeln ändern: `config/form_schema.php` (`required` / `required_if`)
 - Upload-Typen/Limits: `config/app.local.php` + optional pro Feld im Schema
-- Admin-Session/Login: `config/app.local.php` + `src/Admin/Auth.php`
-- Mailtexte/Empfänger: `config/mail.local.php` + `src/Mail/Mailer.php`
-- Retention-Tage: `config/app.local.php` + Cron `bin/cleanup.php`
-- Rate-Limit-Parameter: `config/app.local.php -> rate_limit` (Details: `docs/RATE_LIMITING.md`)
+- Admin-Session/Login: `config/app.local.php` + `admin/auth.php`
+- Mailtexte/Empfänger: `config/mail.local.php` + `src/mail/mailer.php`
+- Retention-Tage: `config/app.local.php` + Cron `admin/cleanup.php`
+- Rate-Limit-Parameter: `config/app.local.php -> rate_limit` (Details: `docs/rate_limiting.md`)
 - Disclaimer-Startseite: `config/app.local.php -> disclaimer` + `index.php`
 - Versionskontrollierte Config-Vorlagen: `config/app.sample.php`, `config/mail.sample.php`
 - Lokale Runtime-Configs (nicht versioniert): `config/app.local.php`, `config/mail.local.php`

+ 0 - 0
docs/AUTH_INTEGRATION.md → docs/auth_integration.md


+ 0 - 0
docs/FORM_SCHEMA.md → docs/form_schema.md


+ 99 - 0
docs/initial_setup.md

@@ -0,0 +1,99 @@
+# Initial Setup
+
+## Voraussetzungen
+
+- Apache HTTPD mit aktivem `mod_rewrite`
+- `AllowOverride All` für den Projektpfad
+- PHP `>= 8.0`
+- Schreibrechte für `storage/` (Webserver-User)
+
+## Erforderliche PHP-Erweiterungen
+
+- `fileinfo` (MIME-Prüfung bei Uploads)
+- `iconv` (Zeichensatzkonvertierung für PDF-Ausgabe)
+- `zip` (Admin-Funktion: ZIP-Download aller Uploads)
+
+Hinweis:
+- `json`, `session`, `filter`, `hash`, `openssl` sind in Standard-PHP-Builds typischerweise vorhanden und werden verwendet.
+- `gd` wird empfohlen: WebP-Bilder können sonst nicht in PDF-Dateien eingebettet werden.
+
+Schnellcheck:
+
+```bash
+php -m | grep -Ei 'fileinfo|iconv|zip|gd'
+php -r "echo class_exists('ZipArchive') ? 'ZipArchive ok' : 'ZipArchive fehlt', PHP_EOL;"
+```
+
+## 1) Dateien bereitstellen
+
+```bash
+cp config/app.sample.php config/app.local.php
+cp config/mail.sample.php config/mail.local.php
+```
+
+## 2) Pflichtwerte konfigurieren
+
+### `config/app.local.php`
+
+Diese Werte müssen gesetzt/validiert werden:
+
+- `base_url` (z. B. `'/antrag'` bei Subfolder-Hosting, sonst `'/'`)
+- `contact_email`
+- `admin.credentials`:
+  - `username`
+  - `password_hash`
+- optional, aber meist anzupassen:
+  - `project_name`
+  - `submission_success_message`
+  - `disclaimer.*`
+  - `retention.*`
+  - `rate_limit.*`
+  - `uploads.*`
+  - `storage.*` (falls Speicherorte abweichend sein sollen)
+
+Passwort-Hash erzeugen:
+
+```bash
+php -r "echo password_hash('DEIN-PASSWORT', PASSWORD_DEFAULT), PHP_EOL;"
+```
+
+### `config/mail.local.php`
+
+Diese Werte müssen gesetzt werden:
+
+- `from`
+- `from_name`
+- `recipients` (mindestens ein Admin-Empfänger)
+- `subjects.admin`
+- `subjects.applicant`
+
+Zusätzlich muss der Mailversand auf dem Host funktionieren (`mail()`/MTA-Konfiguration).
+
+## 3) Verzeichnisrechte
+
+Mindestens diese Verzeichnisse müssen für PHP schreibbar sein:
+
+- `storage/drafts`
+- `storage/submissions`
+- `storage/uploads`
+- `storage/rate_limit`
+- `storage/logs`
+- `storage/locks`
+
+## 4) Cronjob einrichten
+
+Täglich ausführen:
+
+```bash
+php /pfad/zum/projekt/admin/cleanup.php
+```
+
+## 5) .htaccess-Setup
+
+Aktuell wird ein zentrales Root-`.htaccess` verwendet.
+
+- Es blockiert direkte Zugriffe auf interne Ordner (`config`, `src`, `storage`, `docs`, `lib`).
+- Es blockiert interne Admin-Skripte (`admin/auth.php`, `admin/cleanup.php`, `admin/test-mail.php`).
+- Zusätzliche `.htaccess` pro Unterordner sind dafür nicht erforderlich.
+
+Wichtig: Wenn `AllowOverride` deaktiviert ist, greifen diese Regeln nicht.

+ 3 - 3
docs/OPERATIONS.md → docs/operations.md

@@ -12,7 +12,7 @@
 Täglich ausführen:
 
 ```bash
-php /pfad/zum/projekt/bin/cleanup.php
+php /pfad/zum/projekt/admin/cleanup.php
 ```
 
 ## Retention
@@ -42,7 +42,7 @@ php /pfad/zum/projekt/bin/cleanup.php
 
 - Konfiguration: `config/app.local.php -> rate_limit`
 - Persistenz: `storage/rate_limit/`
-- Detaillierte Doku: `docs/RATE_LIMITING.md`
+- Detaillierte Doku: `docs/rate_limiting.md`
 - Bei erhöhten `429`-Antworten zuerst `requests/window_seconds` prüfen und gegen reale Nutzerlast kalibrieren.
 - Für Tests kann das Limiting global deaktiviert werden: `rate_limit.enabled = false`.
 
@@ -69,5 +69,5 @@ Regelmäßig sichern:
 - Upload Fehler: `upload_max_filesize` / `post_max_size` und Schema-Limits prüfen.
 - Login geht nicht: `config/app.local.php -> admin.credentials` prüfen (username + password_hash).
 - ZIP Download fehlgeschlagen: `ZipArchive` Erweiterung auf Hosting prüfen.
-- Viele `429` Antworten: `docs/RATE_LIMITING.md` prüfen, Limits anpassen oder `storage/rate_limit/` kontrollieren.
+- Viele `429` Antworten: `docs/rate_limiting.md` prüfen, Limits anpassen oder `storage/rate_limit/` kontrollieren.
 - 500 ohne Apache/PHP-Fehlerausgabe: `storage/logs/php_fatal.log` und `storage/logs/php_runtime.log` prüfen.

+ 1 - 1
docs/RATE_LIMITING.md → docs/rate_limiting.md

@@ -6,7 +6,7 @@ Schützt die API gegen Spam, Bot-Traffic und Missbrauch durch zu viele Anfragen
 
 ## Implementierung
 
-- Klasse: `src/Security/RateLimiter.php`
+- Klasse: `src/security/ratelimiter.php`
 - Strategie: Sliding-Window auf Basis von Zeitstempeln
 - Persistenz: Flat Files in `storage/rate_limit/`
 - Schlüsselablage: pro Key wird `sha256(key).json` verwendet

+ 1 - 1
docs/STYLE_SYSTEM.md → docs/style_system.md

@@ -314,7 +314,7 @@ Email styles are inline HTML/CSS and must keep the same dark-theme palette.
 
 ## Asset and Branding Contract
 - Required organization logo:
-  - `assets/feuerwehr-Logo-invers.webp`
+  - `assets/images/feuerwehr-logo-invers.webp`
   - If rebranding for another intranet, replace asset file while preserving placement/sizing behavior.
 - Media/image behavior:
   - source path pattern in current system: `assets/images/<filename>`

+ 1 - 1
index.php

@@ -204,7 +204,7 @@ function renderField(array $field, string $addressDisclaimerText): void
 <header class="site-header">
     <div class="container header-inner">
         <a class="brand" href="<?= htmlspecialchars(Bootstrap::url('index.php')) ?>">
-            <img class="brand-logo" src="<?= htmlspecialchars(Bootstrap::url('assets/images/feuerwehr-Logo-invers.webp')) ?>" alt="Feuerwehr Logo">
+            <img class="brand-logo" src="<?= htmlspecialchars(Bootstrap::url('assets/images/feuerwehr-logo-invers.webp')) ?>" alt="Feuerwehr Logo">
             <div>
                 <div class="brand-title"><?= htmlspecialchars((string) $app['project_name']) ?></div>
                 <div class="brand-subtitle">Feuerwehr Freising</div>

+ 11 - 8
README.md → readme.md

@@ -30,12 +30,14 @@ Schlankes PHP-Flatfile-Projekt für einen digitalen Mitgliedsantrag (deutsches F
 - `src/` PHP-Logik
 - `config/` Konfiguration
 - `storage/` Datenablage (JSON, Uploads, Logs)
-- `bin/cleanup.php` tägliche Bereinigung
+- `admin/cleanup.php` tägliche Bereinigung
 - `docs/` AI-first Dokumentation
 - `.htaccess` Apache-Schutz und Routing
 
 ## Setup (Shared Hosting)
 
+Detaillierte Schritt-für-Schritt-Anleitung: `docs/initial_setup.md`
+
 1. Projekt hochladen.
 2. Apache verwenden (mit aktiviertem `mod_rewrite`) und `AllowOverride All` für das Projekt sicherstellen.
 3. Document Root auf das Projekt-Root setzen.
@@ -51,7 +53,7 @@ Schlankes PHP-Flatfile-Projekt für einen digitalen Mitgliedsantrag (deutsches F
    - Ergebnis in `config/app.local.php -> admin.credentials[*].password_hash`
    - Benutzername in `config/app.local.php -> admin.credentials[*].username`
 8. Cronjob einrichten (täglich):
-   - `php /pfad/zum/projekt/bin/cleanup.php`
+   - `php /pfad/zum/projekt/admin/cleanup.php`
 
 Hinweis:
 - `config/app.php` und `config/mail.php` sind stabile Loader-Dateien im Repo.
@@ -64,7 +66,7 @@ Hinweis:
 - Honeypot + Rate Limit aktiv.
 - Rate Limit fuer Tests deaktivierbar ueber `config/app.local.php -> rate_limit.enabled = false`.
 - Uploads werden auf Typ, MIME und Größe geprüft.
-- Interne Ordner (`config`, `src`, `storage`, `bin`, `docs`) werden per `.htaccess` blockiert.
+- Interne Ordner (`config`, `src`, `storage`, `docs`, `lib`) werden per `.htaccess` blockiert.
 
 ## Wichtige URLs
 
@@ -78,8 +80,9 @@ Lokale PHP-Laufzeit wird benötigt (CLI + Webserver), um Syntaxchecks/Tests ausz
 
 ## Weiterführende Doku
 
-- `docs/AI_OVERVIEW.md`
-- `docs/FORM_SCHEMA.md`
-- `docs/OPERATIONS.md`
-- `docs/RATE_LIMITING.md`
-- `docs/AUTH_INTEGRATION.md`
+- `docs/ai_overview.md`
+- `docs/initial_setup.md`
+- `docs/form_schema.md`
+- `docs/operations.md`
+- `docs/rate_limiting.md`
+- `docs/auth_integration.md`

+ 0 - 7
src/.htaccess

@@ -1,7 +0,0 @@
-<IfModule mod_authz_core.c>
-  Require all denied
-</IfModule>
-<IfModule !mod_authz_core.c>
-  Order allow,deny
-  Deny from all
-</IfModule>

+ 0 - 0
src/App/Bootstrap.php → src/app/bootstrap.php


+ 9 - 1
src/autoload.php

@@ -5,6 +5,14 @@ declare(strict_types=1);
 require_once dirname(__DIR__) . '/lib/fpdf/fpdf.php';
 
 spl_autoload_register(static function (string $class): void {
+    if ($class === 'App\\Admin\\Auth') {
+        $adminAuthFile = dirname(__DIR__) . '/admin/auth.php';
+        if (is_file($adminAuthFile)) {
+            require $adminAuthFile;
+        }
+        return;
+    }
+
     $prefix = 'App\\';
     $baseDir = __DIR__ . '/';
 
@@ -13,7 +21,7 @@ spl_autoload_register(static function (string $class): void {
     }
 
     $relativeClass = substr($class, strlen($prefix));
-    $file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
+    $file = $baseDir . strtolower(str_replace('\\', '/', $relativeClass)) . '.php';
 
     if (is_file($file)) {
         require $file;

+ 0 - 0
src/Form/FormSchema.php → src/form/formschema.php


+ 0 - 0
src/Form/Validator.php → src/form/validator.php


+ 0 - 0
src/Mail/Mailer.php → src/mail/mailer.php


+ 0 - 0
src/Mail/MimeMailBuilder.php → src/mail/mimemailbuilder.php


+ 0 - 0
src/Mail/PdfGenerator.php → src/mail/pdfgenerator.php


+ 0 - 0
src/Mail/SubmissionFormatter.php → src/mail/submissionformatter.php


+ 0 - 0
src/Security/Csrf.php → src/security/csrf.php


+ 0 - 0
src/Security/RateLimiter.php → src/security/ratelimiter.php


+ 0 - 0
src/Storage/FileSystem.php → src/storage/filesystem.php


+ 0 - 0
src/Storage/FileUploadStore.php → src/storage/fileuploadstore.php


+ 0 - 0
src/Storage/JsonStore.php → src/storage/jsonstore.php


+ 0 - 7
storage/.htaccess

@@ -1,7 +0,0 @@
-<IfModule mod_authz_core.c>
-  Require all denied
-</IfModule>
-<IfModule !mod_authz_core.c>
-  Order allow,deny
-  Deny from all
-</IfModule>

+ 0 - 0
TODO.md → todo.md