Commit Graph

82 Commits

Author SHA1 Message Date
julien 6641da9a3d feat(validation): replace fieldValidation with express-validator on products and tags
Add products.validators.js and tags.validators.js following the same
pattern as articles/users. Wire createProductRules, updateProductRules,
and createTagRules into their respective routes via the validate middleware.
Remove the local fieldValidation helper from both controllers and fix
missing return statements on next() calls in products.controller.js.
2026-04-27 00:04:50 +02:00
julien 80cd96e006 chore(deps): add helmet, express-rate-limit, express-validator
Add production dependencies introduced in recent security commits.
Add docs/ENVIRONMENT_VARIABLES.md documenting TRUST_PROXY, SECRET,
SESSION_SECRET, and ALLOWED_ORIGINS with configuration guidance.
2026-04-26 23:52:09 +02:00
julien 54b343b2fb chore: remove legacy root-level documentation files 2026-04-26 23:51:58 +02:00
julien 7c16e5d8cb docs(adr): add ADR 0012 — express-validator for input validation 2026-04-26 22:07:55 +02:00
julien 61cdec00e7 feat(validation): introduce express-validator on CMS user and article endpoints
Replaces the broken fieldValidation helper (called next() without return,
so execution continued on invalid input) with express-validator chains
applied at the route level. Validation covers: email format, username
constraints, password min length, field lengths, URL and phone formats.
products and tags controllers retain their local fieldValidation pending
a separate ecommerce validation pass.
2026-04-26 22:06:28 +02:00
julien f1f913f63f fix(proxy): configure trust proxy via TRUST_PROXY env var
Without trust proxy, the rate limiter uses the reverse proxy IP instead
of the real client IP, making all requests appear to come from one IP.
Set TRUST_PROXY=1 in production when behind a single nginx/Apache hop.
2026-04-26 21:58:36 +02:00
julien c2e65adbc6 fix(security): require auth on clan creation, remove debug console.log
POST /herowars/clans was accessible without authentication.
console.log(req.profile) in profiles controller leaked user data to logs.
2026-04-26 21:57:31 +02:00
julien ed0cd81bde chore(db): remove unused mysql models barrel
models/mysql/index.js was never imported by the application — mysql.js
is the actual model registry. The barrel created a false registration
point that caused the Application migration bug.
2026-04-26 21:55:22 +02:00
julien 3d31456500 fix(db): register Application model in mysql.js DB initializer
Application was added to models/mysql/index.js but not to mysql.js,
which is the actual file that instantiates all Sequelize models.
DB.Application was undefined, causing the relationships to fail at startup.
2026-04-26 21:52:05 +02:00
julien f03b672465 docs(env): add SESSION_SECRET and ALLOWED_ORIGINS to .env.example 2026-04-26 20:58:02 +02:00
julien cc155214e0 docs(adr): add ADR 0010 (CORS restriction) and 0011 (auth rate limiting) 2026-04-26 20:54:26 +02:00
julien 21f3560e08 fix(config): fail fast at startup if SECRET env var is missing
A hardcoded fallback 'acapisecret' was silently used when SECRET was
not set, allowing JWT tokens to be forged by anyone knowing the default.
The app now throws at module load time if SECRET is absent.
2026-04-26 20:53:34 +02:00
julien ebc7c7751a fix(security): add Helmet headers and rate limiting on auth endpoints
Helmet adds HTTP security headers (HSTS, X-Frame-Options,
X-Content-Type-Options, etc.). CSP is disabled pending deployment-
specific tuning. Rate limiting (10 req / 15 min per IP) is applied
to POST /login and POST / (registration) to mitigate brute force
and credential stuffing attacks.
2026-04-26 20:45:46 +02:00
julien beb8abb2bc fix(session): use dedicated SESSION_SECRET instead of JWT secret
Sharing the JWT secret with express-session means a single leaked
secret compromises both auth mechanisms. SESSION_SECRET is now read
from its own env var, falling back to SECRET only if not set.
2026-04-26 20:43:13 +02:00
julien f293d35455 fix(herowars): replace Object.assign with explicit field whitelists
Mass assignment via Object.assign(entity, req.body) allowed callers
to overwrite protected fields (id, author). Updates are now restricted
to explicitly listed data fields on both Clan and Member.
2026-04-26 20:42:29 +02:00
julien 98847b31e7 fix(cors): restrict allowed origins via ALLOWED_ORIGINS env var
Wildcard CORS allowed any website to make authenticated cross-origin
requests on behalf of a victim. Origins are now read from the
ALLOWED_ORIGINS env var (comma-separated). No origins configured
means all cross-origin requests are rejected.
2026-04-26 20:41:36 +02:00
julien 946436fdaf docs(adr): add ADR 0008 (HMAC-SHA256 API keys) and 0009 (Application to MySQL) 2026-04-26 20:37:33 +02:00
julien a27b8018b1 fix(users): restrict role updates to Admin users only
Any authenticated user could previously call PUT /update/role and
escalate their own role to Admin. The endpoint now returns 403 if
the requesting user is not already an Admin.
2026-04-26 20:32:37 +02:00
julien ece5506904 fix(comments): use authenticated user id as authorId instead of req.body
Accepting authorId from the request body allowed any authenticated user
to create comments impersonating another user. The author is now always
the authenticated user from req.payload.
2026-04-26 20:31:37 +02:00
julien 6245d1eabd fix(auth): replace fixed-salt bcrypt with HMAC-SHA256 for API key hashing
bcrypt with a fixed salt is deterministic but defeats the purpose of
bcrypt salting. API keys are high-entropy UUIDs (128 bits), making
brute-force resistance unnecessary — HMAC-SHA256 with the app secret
is the correct primitive for deterministic, secure key hashing.

Also fixes application.author.id → application.authorId in the API key
auth path, which was silently setting req.payload.id to undefined.
2026-04-26 20:31:06 +02:00
julien b3a31e4725 feat(auth): migrate Application model from MongoDB to MySQL
- Add Sequelize migration creating the 'application' table
- Add Application Sequelize model with slugify, toJSONFor, toAuthJSON
- Rewrite passport-headerapikey strategy to use DB.Application
- Rewrite applications.routes.js to use Sequelize (findAll, count,
  build/save, destroy) instead of Mongoose
- Fix pre-existing bug: passport was querying { encryptedKey } but the
  stored field is named 'apikey'
- Add Application to MySQL models index and relationships
- Remove Application from Mongoose models index and delete the schema
2026-04-26 18:26:58 +02:00
julien 097e11be1b docs: add backend documentation files
Reference guides (Sequelize model methods, Swagger) and migration reports
from the MongoDB-to-MySQL and domain-based route structure migrations.
2026-04-26 16:54:56 +02:00
julien 83216e96be chore: remove .gitkeep from docs/decisions now that ADRs exist 2026-04-26 16:52:41 +02:00
julien a9ef4cf629 docs(adr): convert all ADRs to MADR 2.1.2 format
Rewrites all 7 backend ADRs from a custom structure to the MADR 2.1.2
template required by the VS Code ADR Manager extension: bullet metadata
(Status/Date), standardised section headings, "Chosen option: X, because Y"
wording, and explicit Pros/Cons blocks per option.
2026-04-26 16:51:22 +02:00
julien d52795fde3 Add 7 ADRs documenting backend architecture decisions 2026-04-26 16:35:08 +02:00
julien 66821427be fix(skydive): migrate author field from ObjectId to UUID string
- Register SkydiverProfile model in mysql.js (fixes startup crash on association)
- Replace $toObjectId with direct string match in all 12 aggregation queries
- Guard toJSONFor on all Mongo models to fall back to MySQL user.toProfileJSONFor()
  when author is a UUID string (restores username/image in API responses)
- Pass user to toJSONFor() everywhere instead of null
- Remove dead .populate('author') calls (author: String has no ref)
- Fix ownership check in /last route: compare author string to req.payload.id
  instead of the now-absent author.username property
2026-04-26 04:27:47 +02:00
julien da1f9437ab feat(tests): add Jest+Supertest infrastructure with jump route suite (14 tests)
- Extract Express app creation into src/createApp.js to enable testing without starting the server
- Add Jest, Supertest, test helpers (createTestApp, auth token generator)
- Write 14 integration tests for skydive/jumps: auth protection, CRUD, ownership checks

Bugs caught and fixed by the test suite:
- MongoDB models: author field typed as Schema.Types.UUID (Mongoose 6 rejects string UUIDs) → changed to String
- Jump.toJSONFor: called toProfileJSONFor on a UUID string → guarded with typeof check
- exceptions.handler: used err.statusCode but express-jwt throws err.status → returns 500 on 401 errors
- Jump.count(query): deprecated Mongoose method was ignoring the filter → replaced with countDocuments
- jumps.routes GET /: $or query included non-schema field "title" stripped by strictQuery, leaving {} (match-all) → replaced with slug/lieu search
- $regex: was passing a JS RegExp object which serializes to {} in MongoDB → now passes raw string
2026-04-26 01:23:01 +02:00
julien 4c765945b3 refactor(naming): harmonize controller and route file names
- Delete product.controller.js, merge unique functions into products.controller.js
- Rename user.controller.js → users.controller.js, user.routes.js → users.routes.js
- Remove dead herowars/herowars.routes.js reference from herowars index
- Update all import paths accordingly
2026-04-26 00:39:29 +02:00
julien 30104cda05 chore(skydive): remove jumps /feed route
Following is a CMS concept (article feed by followed authors).
Applying it to jumps was a design error — no component ever called
this endpoint. Removed route and associated UserService.getUserFollowed
dependency from skydive.
2026-04-26 00:16:16 +02:00
julien c6d327fb4f chore(models): remove mongo/User from index
MongoDB User model deleted — all user lookups now go through MySQL.
2026-04-26 00:06:06 +02:00
julien 5bcecd01f9 refactor(skydive): replace MongoDB User with MySQL UserService
All skydive routes now fetch users via UserService (MySQL) instead of
mongoose.model('User'). Key changes:
- user._id.toString() → user.id (same UUID, different source)
- User.findOne({ username }) → UserService.getUserByUsername()
- /feed route migrated to async/await using UserService.getUserFollowed()
  to resolve the following list from the MySQL Follower table
- jump/file/application author set as user.id (UUID string) instead of
  the full Mongoose document
- jump.toJSONFor(null) — following field in responses is now always false
  until Jump model is migrated away from MongoDB User dependency
- user.controller: licence/poids/bg_image routed to SkydiverProfileService
  fixing the silent Sequelize save bug for those fields
2026-04-26 00:05:57 +02:00
julien ad7fc4384a feat(skydiver-profile): add SkydiverProfile MySQL model and service
Introduces the skydiver_profile table as an optional 1-to-1 extension
of the user table, holding skydive-specific fields (licence, poids,
bg_image). Wired up with Sequelize associations and a service with
upsert support. Migration, model, relationships, and service layer
all included.
2026-04-25 22:32:30 +02:00
julien 07de469acb chore(models): remove dead MongoDB Article, Comment, Tag models
Migration to MySQL is complete for these three entities — ArticleService,
CommentService, and TagService all use Sequelize exclusively. No remaining
references to the Mongoose versions anywhere in the codebase.
2026-04-25 21:49:03 +02:00
julien 65f70abf30 refactor(skydive): rename route files to *.routes.js convention
Aligns skydive domain with the naming used in cms, ecommerce, and herowars.
Pure rename — no logic changes.
2026-04-25 21:15:15 +02:00
julien e368e61c92 chore(skydive): remove duplicate profiles route
Dead code — identical logic already lives in cms/profiles.routes.js
(refactored with controllers). Frontend exclusively uses /cms/profiles/*.
2026-04-25 21:09:06 +02:00
julien 9a8f33d9bd chore(routes): remove legacy v1/v2/v3 backup folders
These directories were dead code — commented out in routes/index.js
and superseded by the domain-based structure (skydive/cms/ecommerce/herowars).
Also removes the now-pointless commented-out require lines.
2026-04-25 18:01:30 +02:00
julien 6acc16c0b9 security(env): remove credentials from nodemon.json
All secrets (DB passwords, API keys, JWT secret) are now sourced
exclusively from config/env/.env.local, which is gitignored.
Nodemon only sets APP_ENV=local so that app.js's dotenv loader
picks up the right env file at boot.

Note: the previously committed values are still in git history.
They must be rotated out-of-band (rotation list tracked separately).
2026-04-25 17:36:06 +02:00
julien 7dd2a077e5 chore(models): remove unused Product-tmp.js
Leftover scratch file, not registered in src/database/mysql.js and
not referenced anywhere in src/ or scripts/.
2026-04-25 17:22:08 +02:00
julien 137290cf67 docs: add CLAUDE.md with project guidance for AI assistants 2026-04-25 17:21:54 +02:00
julien 4746bccd66 docs(copilot): fix dev port reference (4201 -> 4200) 2026-04-25 17:21:46 +02:00
julien 12cba24db9 chore(gitignore): add patterns for SQL artifacts and sequelize-auto-config 2026-04-25 17:21:17 +02:00
julien c09b60131a refactor(routes): move product/products from cms to ecommerce domain
Product and products routes were sitting under /api/cms but conceptually
belong to a separate e-commerce domain. Split them out into a dedicated
ecommerce subrouter mounted at /api/ecommerce.
2026-04-25 17:20:36 +02:00
julien 64e33268e3 docs: add comprehensive migration completion summary and checklist 2026-04-24 00:51:16 +02:00
julien 74df96c298 docs: add migration documentation, finalization scripts, and update Swagger paths 2026-04-24 00:50:34 +02:00
julien 9f73ed07ba refactor: restructure API routes from v1/v2/v3 to domain-based (skydive/cms/herowars) 2026-04-24 00:47:58 +02:00
julien b99842f458 API Routes update 2026-03-29 23:57:09 +02:00
julien fe1bcc0ff7 Adding Swagger 2026-03-29 23:56:11 +02:00
julien fce91fa465 Improve create-user script: fix env path and add password masking 2026-03-21 02:36:58 +01:00
julien 3c7d65a443 Routes update 2026-03-21 02:33:04 +01:00
julien c318012363 Package.json update 2026-03-21 02:32:20 +01:00