No description
  • TypeScript 99.7%
  • Dockerfile 0.2%
Find a file
Vincent Billey 63d7bccf6c
Some checks failed
CI / ci (push) Has been cancelled
CI / e2e (push) Has been cancelled
feat(admin): force password change on first login (#52)
* feat(admin): force password change on first login (#33)

Accounts created via admin UI now require a password change before
accessing the dashboard. Adds password_change_required column, session
guard, and dedicated change-password route with integration tests.

* fix: address roborev findings for password change feature

- Re-set password_change_required when admin resets another user's
  password (skip when editing own account)
- Simplify Mon compte action to single updateAdminUser call path
- Use numeric 1/0 instead of string for boolean column values
- Add tests for admin reset flag behavior

* fix: address second roborev review findings

- Remove dead session-clearing branch from Mon compte action
- Clear password_change_required on self-edit via admin accounts page
- Make passwordChangeRequired an explicit parameter in createAdminUser

* chore: add db-state-invariant-audit rule

Codifies the lesson from roborev review — when adding a state-tracking
column, audit all mutation paths for semantic consistency.

* fix(admin): reject reusing current password on forced change

Compares new password against current hash to prevent users from
keeping the admin-set password, which defeats the forced change.

* feat(admin): allow login by email in addition to username

The login form now accepts either username or email as the identifier.
The query matches on username OR email (email column is nullable, so
users without email can still log in by username only).

* fix: address roborev round 3 — auth gate, action guard, unique email

- Move passwordChangeRequired guard into requireAdminSession so it
  blocks both loaders and actions (not just layout GET navigations)
- Add allowPasswordChange opt-out for change-password and logout routes
- Validate passwordChangeRequired flag in change-password action to
  prevent unauthorized password rotation via direct POST
- Add UNIQUE constraint on admin_users.email with migration
- Add isEmailTaken check in all email-setting routes (create, edit, self)
- Collapse isEditingSelf ternary to !isEditingSelf

* chore: add auth-in-session rule, expand db-audit rule

- New rule: authorization checks must live in requireAdminSession, not
  layout loaders (React Router actions bypass parent loaders)
- Expanded db-state-invariant-audit with column reuse section — audit
  constraints when an existing column gains new semantic meaning

* fix: resolve redirect loop in password change guard

The layout loader is a parent of the change-password route — calling
requireAdminSession without an exemption caused an infinite redirect.
Replaced the allowPasswordChange option with URL-based path checking
inside requireAdminSession, and updated E2E test to walk through the
forced password change flow after admin reset.

* fix: prevent cross-column collision in login lookup

Use @-based column selection instead of OR query — emails always
contain @, usernames never do. This makes the lookup deterministic
and prevents one user's username from matching another's email.

* fix: reject @ in usernames at both route and query level

Validates at the Nextcloud import route (user-facing error) and in
createAdminUser (invariant guard for any code path). This ensures the
@-based login column selection stays deterministic.

* fix: normalize empty emails, rename identifier param, add gate test

- isEmailTaken returns false for null/empty emails instead of querying
- Rename verifyAdminCredentials parameter from username to identifier
- Add integration tests for requireAdminSession gate: flagged session
  redirects on regular routes, passes through on change-password

* fix: catch ER_DUP_ENTRY on email update in account edit and Mon compte

The isEmailTaken precheck has a TOCTOU window — a concurrent update
could still hit the UNIQUE constraint. Catch ER_DUP_ENTRY and return
a friendly 409 instead of an unhandled 500.

* refactor: extract isDuplicateEntry type guard to shared utility

Replaces inline as casts and hasMysqlCode helper with a proper type
guard in app/db/errors.server.ts, used by all three routes that catch
ER_DUP_ENTRY.
2026-04-20 23:57:52 +02:00
.claude feat(admin): force password change on first login (#52) 2026-04-20 23:57:52 +02:00
.github/workflows fix(e2e): wait for app readiness via /healthz before running tests (#32) 2026-04-09 07:23:02 +02:00
.vscode remove antd and add logo 2026-03-01 16:48:53 +01:00
app feat(admin): force password change on first login (#52) 2026-04-20 23:57:52 +02:00
db feat(admin): force password change on first login (#52) 2026-04-20 23:57:52 +02:00
e2e feat(admin): force password change on first login (#52) 2026-04-20 23:57:52 +02:00
public/rr-assets feat: responsive petition images with WebP generation (#45) 2026-04-12 16:15:05 +02:00
scripts feat: responsive petition images with WebP generation (#45) 2026-04-12 16:15:05 +02:00
tests feat(admin): force password change on first login (#52) 2026-04-20 23:57:52 +02:00
.dockerignore feat(docker): production-ready Dockerfile with healthcheck endpoint (#26) 2026-04-08 21:10:55 +02:00
.gitignore feat(petitions): admin CRUD for petition management (#16) 2026-04-06 13:56:37 +02:00
.mise.toml feat: first draft, testing that it works with wordpress 2026-01-07 14:20:06 +01:00
.prettierignore remove antd and add logo 2026-03-01 16:48:53 +01:00
.prettierrc feat: first draft, testing that it works with wordpress 2026-01-07 14:20:06 +01:00
.roborev.toml feat(petitions): signature form with double opt-in email confirmation (#10) 2026-04-05 09:05:56 +02:00
CLAUDE.md feat(email): AWS SES bounce/complaint handling with admin dashboard (#48) 2026-04-13 22:03:59 +02:00
docker-compose.yml feat(docker): production-ready Dockerfile with healthcheck endpoint (#26) 2026-04-08 21:10:55 +02:00
docker-entrypoint.sh feat(docker): production-ready Dockerfile with healthcheck endpoint (#26) 2026-04-08 21:10:55 +02:00
Dockerfile fix(docker): include db/ directory in production image (#28) 2026-04-08 22:12:50 +02:00
images.config.ts feat: responsive petition images with WebP generation (#45) 2026-04-12 16:15:05 +02:00
package-lock.json feat(email): AWS SES bounce/complaint handling with admin dashboard (#48) 2026-04-13 22:03:59 +02:00
package.json feat(email): AWS SES bounce/complaint handling with admin dashboard (#48) 2026-04-13 22:03:59 +02:00
playwright.config.ts feat(petitions): door-to-door signing mode (#34) 2026-04-09 23:22:51 +02:00
react-router.config.ts initial commit 2026-01-07 14:12:04 +01:00
README.md feat(email): AWS SES bounce/complaint handling with admin dashboard (#48) 2026-04-13 22:03:59 +02:00
tsconfig.json test(ci): add Vitest DB integration tests and GitHub Actions CI (#8) 2026-04-03 07:18:08 +02:00
vite.config.ts initial commit 2026-01-07 14:12:04 +01:00
vitest.config.ts feat(petitions): IP-based rate limiting on admin login (#18) 2026-04-06 17:17:13 +02:00

JDM Frontend

Headless WordPress frontend for jeunessedumonde.fr — built with React Router 7 SSR, Tailwind CSS v4, MariaDB, and Redis.

Prerequisites

  • Mise — manages Node version
  • Docker — runs MariaDB, Redis, and Mailpit

Setup

1. Trust and install the Node version

mise trust
mise install

2. Install dependencies

npm install

3. Configure environment variables

Copy the example below into a .env file at the project root:

# WordPress REST API
WP_API_KEY=

# Redis
REDIS_URL=redis://localhost:6379

# MariaDB
DB_HOST=localhost
DB_PORT=3306
DB_USER=jdm
DB_PASSWORD=jdm
DB_NAME=jdm

# SMTP — use Mailpit locally (host=localhost, port=1025)
SMTP_HOST=
SMTP_PORT=
SMTP_USER=
SMTP_PASS=
SMTP_FROM=
SMTP_REJECT_UNAUTHORIZED= # set to "false" to skip TLS cert verification

# Admin session secret (any random string)
SESSION_SECRET=

# Public URL for share links (set to production domain behind a proxy)
APP_URL=

# Facebook App ID for Messenger share button
FACEBOOK_APP_ID=

# Nextcloud directory integration (optional — enables user import on accounts page)
NEXTCLOUD_URL=
NEXTCLOUD_USER=
NEXTCLOUD_PASSWORD=

# AWS SES event routing (optional — enables bounce/complaint tracking)
SES_CONFIGURATION_SET=

4. Start services

docker compose up -d

This starts MariaDB (port 3306), Redis (port 6379), and Mailpit (SMTP port 1025, web UI at http://localhost:8025).

5. Run database migrations and seed an admin account

npm run db:migrate
npm run db:seed

Development

npm run dev

The app is available at http://localhost:5173. Outgoing emails are caught by Mailpit at http://localhost:8025.

Other commands

Command Description
npm run build Production build
npm run start Run production server from build artifacts
npm run typecheck TypeScript check + React Router type generation
npm run format Format all files with Prettier
npm run format:check Validate formatting without writing
npm run db:migrate Apply pending SQL migrations
npm run db:seed Create an admin account interactively
npm run images Generate responsive WebP variants from hires sources