The 2018 stack (Angular 5.2 / CLI 1.7, PHP, MySQL) had not been touched since
July 2018. Rebuilt rather than upgraded in place: the frontend was 17 major
versions behind, and of ~16,700 lines of PHP only ~150 were application logic —
the rest was four near-identical vendored copies of php-crud-api plus
class.upload.php.
Backend — ASP.NET Core 10, EF Core, SQLite
* ASP.NET Core Identity (PBKDF2) + JWT bearer auth
* Clean REST API replacing php-crud-api's filter[]/transform query syntax
* Box art uploads re-encoded to WebP via SkiaSharp
* Imports the 105 games recovered from the 2018 dump on first run
Frontend — Angular 22, zoneless, signals, Material 22
* Standalone components, lazy routes, functional guards and interceptor
* Vitest replaces Karma/Jasmine; fonts and icons bundled, no CDN calls
* No provideAnimations: @angular/animations is deprecated in v22 and
Material no longer depends on it (pinned by a test)
Docker
* Multi-stage builds for both services, non-root at runtime
* nginx serves the SPA and reverse-proxies the API, so everything is
same-origin; one volume holds the database, uploads and DP keys
Security issues in the old code, not carried across:
* Two endpoints exposed unauthenticated CRUD over every table
* The client chose whose rows to read (filter[]=userId,eq,N); ownership now
comes from the JWT subject server-side
* Login was hardcoded to a single username
* crypt() with one global salt, silently truncating passwords to 8 chars
* JWT secret was the literal string "testing", tokens never expired
* Token travelled in the query string rather than a header
* Uploads were anonymous with the path built from the client filename
* Access-Control-Allow-Origin: *
The live MySQL password committed in 2018 remains in git history and must be
rotated independently of this change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
34 lines
1.3 KiB
Bash
34 lines
1.3 KiB
Bash
# Copy to .env and fill in. .env is gitignored — never commit real secrets.
|
|
#
|
|
# The 2018 version of this project committed its live database password to the
|
|
# repository, which is why it now has to be treated as compromised. Keep secrets
|
|
# in .env, and keep .env out of git.
|
|
|
|
# --- Required --------------------------------------------------------------
|
|
# JWT signing key. Minimum 32 characters; the API refuses to start without it.
|
|
# Generate one with: openssl rand -base64 48
|
|
JWT_KEY=
|
|
|
|
# --- First-run seeding ------------------------------------------------------
|
|
# On a database with no users, the API creates this account and imports the 105
|
|
# games recovered from the 2018 MySQL dump. Once a user exists, this is ignored.
|
|
# Password rules: 12+ chars, upper, lower and a digit.
|
|
SEED_USERNAME=ckoch
|
|
SEED_EMAIL=you@example.com
|
|
SEED_PASSWORD=
|
|
|
|
# Set to false once you are past first run, or to start with an empty library.
|
|
SEED_ENABLED=true
|
|
|
|
# --- Optional ---------------------------------------------------------------
|
|
# Host port the web UI is published on.
|
|
WEB_PORT=8080
|
|
|
|
# Token lifetime in minutes. Default is 12 hours; there is no refresh flow, so
|
|
# expiry returns you to the login form.
|
|
JWT_LIFETIME_MINUTES=720
|
|
|
|
JWT_ISSUER=LudosData
|
|
JWT_AUDIENCE=LudosData
|
|
CORS_ORIGIN=http://localhost:8080
|