refactor: reorganize to standard PHP structure

- Moved /lib → /src (PHP source code)
- Moved /includes → /public/includes (main site templates)
- Admin section remains self-contained in /public/admin with its own /inc
- Updated all require/include paths across codebase
- Updated config/bootstrap.php, justfile, tests, docs
- All tests passing 

Structure now follows PHP best practices:
  /config      - Configuration files
  /database    - SQLite database + schema
  /docs        - Documentation (intact)
  /nginx       - Server config (intact)
  /public      - Web-accessible files (entry point)
    /admin     - Self-contained admin interface
    /assets    - CSS, fonts, icons
    /includes  - Main site templates (header/footer)
  /scripts     - Deployment scripts (intact)
  /src         - PHP source classes (Database, AdminAuth, RateLimit)
  /tests       - Test suites
This commit is contained in:
Théophile Gervreau-Mercier
2026-02-12 12:11:16 +01:00
parent 0b650cd3e7
commit 0e4921583e
26 changed files with 40 additions and 42 deletions

View File

@@ -25,7 +25,7 @@ This deploys all files to `/var/www/posterg/`:
- `includes/``/var/www/posterg/includes/`
- `config/``/var/www/posterg/config/`
- `database/``/var/www/posterg/database/`
- `lib/``/var/www/posterg/lib/`
- `src/``/var/www/posterg/lib/`
### 3. Update Nginx Configuration

View File

@@ -130,7 +130,7 @@ just fixtures
### Create a New Page
1. Create `newpage.php` in root
2. Add `require_once __DIR__ . '/lib/Database.php';`
2. Add `require_once __DIR__ . '/src/Database.php';`
3. Include header: `include 'inc/header.php';`
4. Add your content
5. Include footer: `include 'inc/footer.php';`
@@ -138,7 +138,7 @@ just fixtures
Example:
```php
<?php
require_once __DIR__ . '/lib/Database.php';
require_once __DIR__ . '/src/Database.php';
include 'inc/header.php';
?>
@@ -154,7 +154,7 @@ include 'inc/header.php';
### Add a Database Function
1. Edit `lib/Database.php`
1. Edit `src/Database.php`
2. Add your method to the `Database` class
3. Write a test in `tests/Unit/DatabaseTest.php`
4. Run tests: `just test-unit`
@@ -215,7 +215,7 @@ See `tests/README.md` for complete testing guide.
**Quick example:**
```php
<?php
require_once __DIR__ . '/../../lib/Database.php';
require_once __DIR__ . '/../../src/Database.php';
echo "My Test\n";
echo "=======\n\n";
@@ -243,7 +243,7 @@ just deploy
This deploys:
- Public site (root PHP files)
- Admin panel (`admin/`)
- Shared libraries (`lib/`)
- Shared libraries (`src/`)
**Note:** `vendor/` is automatically excluded from deployment.

View File

@@ -66,7 +66,7 @@ git commit -m "Pre-migration checkpoint"
This will:
- Move `apps/public/*` to root
- Move `apps/admin/` to `admin/`
- Rename `shared/` to `lib/`
- Rename `shared/` to `src/`
- Update all `require` paths automatically
- Remove `apps/` directory
- Update `.gitignore`
@@ -149,9 +149,9 @@ just deploy-admin
| `apps/public/assets/` | `assets/` |
| `apps/public/inc/` | `inc/` |
| `apps/admin/` | `admin/` |
| `shared/Database.php` | `lib/Database.php` |
| `shared/config.php` | `lib/config.php` |
| `shared/cache/` | `lib/cache/` |
| `shared/Database.php` | `src/Database.php` |
| `shared/config.php` | `src/config.php` |
| `shared/cache/` | `src/cache/` |
### Path Updates

View File

@@ -96,9 +96,9 @@ echo "vendor/" >> .gitignore
| `apps/public/inc/header.php` | `inc/header.php` |
| `apps/public/assets/posterg.css` | `assets/posterg.css` |
| `apps/admin/index.php` | `admin/index.php` |
| `shared/Database.php` | `lib/Database.php` |
| `shared/config.php` | `lib/config.php` |
| `shared/cache/` | `lib/cache/` |
| `shared/Database.php` | `src/Database.php` |
| `shared/config.php` | `src/config.php` |
| `shared/cache/` | `src/cache/` |
## Deployment Changes

View File

@@ -138,7 +138,7 @@ and serve files through a dedicated PHP endpoint that validates access rights.
### 5. Rate Limiter Vulnerable to IP Spoofing
**File:** `lib/RateLimit.php` — method `getClientIdentifier()`
**File:** `src/RateLimit.php` — method `getClientIdentifier()`
```php
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
@@ -373,7 +373,7 @@ define('DATABASE_PATH', APP_ROOT . '/database/test.db');
```
This constant is never used anywhere. `Database.php` uses `getDatabasePath()` from
`lib/config.php`. The duplicate creates confusion about which configuration is
`src/config.php`. The duplicate creates confusion about which configuration is
authoritative and could lead to future bugs if someone uses the wrong one.
**Fix:** Remove the `DATABASE_PATH` define from `bootstrap.php`.

View File

@@ -111,7 +111,7 @@ include APP_ROOT . '/includes/header.php';
|---------|-------------|----------------------------|
| Purpose | Reusable library | Single website |
| Structure | Complex (PSR-4, namespaces) | Simple (includes, classes) |
| Directories | `src/`, `resources/`, `var/`, `bin/` | `public/`, `includes/`, `lib/` |
| Directories | `src/`, `resources/`, `var/`, `bin/` | `public/`, `includes/`, `src/` |
| Autoloading | PSR-4 namespaces | Simple require statements |
| Config | Complex bootstrap with many constants | Minimal bootstrap |
| Caching | `var/cache/` with framework | Simple file-based if needed |

View File

@@ -14,7 +14,7 @@
| 1 | No HTTPS — admin credentials exposed in transit | 🔴 CRITICAL | **TLS is terminated upstream by the reverse proxy** (outside nginx). nginx.conf does not need to handle TLS directly. |
| 3 | Uploaded files stored inside the webroot | 🟠 HIGH | Storage moved to `STORAGE_ROOT` (`/var/www/posterg/storage/`), defined in `config/bootstrap.php`. `formulaire.php` updated. |
| 4 | File path mismatch — media files broken and insecure | 🟠 HIGH | DB paths are now storage-relative (`theses/YEAR/ID/file`, `covers/file`). New controller `public/media.php` serves files safely. `memoire.php` and `search.php` updated to use `/media.php?path=…`. Cover recording in `formulaire.php` fixed (was never inserted into DB). |
| 5 | Rate limiter bypassed by IP spoofing (`X-Forwarded-For`) | 🟠 HIGH | `lib/RateLimit.php` `getClientIdentifier()` now uses `REMOTE_ADDR` only. |
| 5 | Rate limiter bypassed by IP spoofing (`X-Forwarded-For`) | 🟠 HIGH | `src/RateLimit.php` `getClientIdentifier()` now uses `REMOTE_ADDR` only. |
| 6 | `.htaccess` security rules silently ignored by nginx | 🟠 HIGH | All rules ported to `nginx/posterg.conf` (CSP to `/admin/` block, `.log` denial, `autoindex off`). See `nginx/HTACCESS_TO_NGINX.md`. |
| 13 | Deprecated `X-XSS-Protection` header | 🔵 LOW | **Removed** from `nginx/posterg.conf`; see `nginx/SECURITY_HEADERS.md` for rationale. |
@@ -49,7 +49,7 @@
| # | Issue | Severity | Resolution |
|---|-------|----------|------------|
| 2 | No PHP-level authentication in admin panel | 🔴 CRITICAL | `lib/AdminAuth.php` implements a session guard with `password_verify` + `session_regenerate_id`. All admin PHP files now call `AdminAuth::requireLogin()` instead of bare `session_start()`. Credentials stored in gitignored `config/admin_credentials.php` (define `ADMIN_PASSWORD_HASH`). No-op when constant is absent (dev / cli-server). Also resolves item #8 (session cookie hardening via `session_set_cookie_params` before `session_start`). See `nginx/PHP_AUTH_LAYER.md`. |
| 2 | No PHP-level authentication in admin panel | 🔴 CRITICAL | `src/AdminAuth.php` implements a session guard with `password_verify` + `session_regenerate_id`. All admin PHP files now call `AdminAuth::requireLogin()` instead of bare `session_start()`. Credentials stored in gitignored `config/admin_credentials.php` (define `ADMIN_PASSWORD_HASH`). No-op when constant is absent (dev / cli-server). Also resolves item #8 (session cookie hardening via `session_set_cookie_params` before `session_start`). See `nginx/PHP_AUTH_LAYER.md`. |
| 8 | Session cookies not hardened (`Secure`, `HttpOnly`, `SameSite` missing) | 🟡 MEDIUM | **Resolved as part of item #2.** `AdminAuth::startSession()` sets `HttpOnly=true`, `SameSite=Strict`, `Secure=true` (off on cli-server), `Path=/admin`, `Lifetime=0` before every `session_start()`. |
---