25 Commits

Author SHA1 Message Date
Serreau Jovann
11763405ab Enable WebAuthn / passkey on master and ecosplay realms
- Add a configure_webauthn helper to sync.sh that sets the
  WebAuthn policy (both 2FA and passwordless variants) on a
  realm and enables the webauthn-register and
  webauthn-register-passwordless required actions so users can
  self-enroll passkeys via the account console.
- Apply it to both master (RP "E-Cosplay Auth") and ecosplay
  (RP "E-Cosplay") on every sync run, idempotent.
- Mirror the same policy fields and required actions in the
  ecosplay realm import JSON for fresh installs. Sensible
  defaults: ES256/RS256/EdDSA, user verification preferred,
  no attestation, resident key not specified.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 18:23:30 +02:00
Serreau Jovann
4484b70c19 Disable PKCE on ecosplay_code client (Gitea compat)
Gitea 1.25.5 and earlier do not send PKCE code_challenge_method
on OIDC sources, so enforcing PKCE in Keycloak causes:

  Missing parameter: code_challenge_method

at the /auth endpoint. Drop the pkce.code.challenge.method
attribute from the ecosplay_code client block in the realm
import JSON, and add a set_client_pkce helper to sync.sh that
clears the attribute on existing installs. All other clients
(ecosplay_web, eticket) keep S256.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:53:17 +02:00
Serreau Jovann
7d31714908 Update ecosplay_code callback path (esy_lock -> ecosplay_code)
The Gitea OAuth2 provider name changed from esy_lock to
ecosplay_code, so the callback path follows:
  /user/oauth2/ecosplay_code/callback

Update both the realm import JSON and sync.sh reconciliation
for code.e-cosplay.fr and cos.local.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:51:02 +02:00
Serreau Jovann
40c36ef299 Drop Cloudflare DNS TLS block from Caddy vhost
Remove the tls { dns cloudflare ... } directive and fall back to
Caddy's default automatic HTTPS (HTTP-01 / TLS-ALPN). The
Cloudflare DNS plugin was causing issues during cert provisioning;
standard ACME works fine as long as port 80/443 reach the server.

Also drop the now-unused cloudflare_token variable from group_vars.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:48:56 +02:00
Serreau Jovann
cad8f5bb91 Add ecosplay_code OIDC client for Gitea SSO
New confidential client 'ecosplay_code' with PKCE S256, declared
in the realm import JSON for fresh installs and reconciled via
sync.sh (ensure_client + set_client_uris) for existing installs.

Redirect URIs match the Gitea OAuth2 callback format for the
esy_lock provider:
  https://code.e-cosplay.fr/user/oauth2/esy_lock/callback
  https://cos.local/user/oauth2/esy_lock/callback

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:24:28 +02:00
Serreau Jovann
5af94062d2 Add Postgres backup script
scripts/backup.sh runs pg_dump inside the ecosplay-auth-db
container, writes a gzipped, timestamped dump to /backup/
(overridable via BACKUP_DIR), and keeps the latest N dumps
(RETENTION=14 by default).

Only the Postgres volume carries state that isn't reproducible
from code (users, credentials, TOTP secrets, sessions, brute-
force counters), so the rest of the repo is not bundled here.

Intended as a base that can later feed a cron job or be piped
to off-site storage (S3, SFTP, borg).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:21:35 +02:00
Serreau Jovann
832be361c7 Switch admin to jovann@e-cosplay.fr + disable default admin
- Rename the bootstrap human admin from jovann@siteconseil.fr to
  jovann@e-cosplay.fr in docker-compose env vars and in the realm
  import JSON. Keycloak identifies users by username so a new user
  is created on the next sync run; the old jovann@siteconseil.fr
  is left in place and can be deleted manually from the admin UI.
- Introduce a service account client `sync-bot` in the master
  realm (confidential, service accounts enabled, direct grants off)
  granted the `admin` realm role. sync.sh now authenticates via
  client_credentials, falling back to the bootstrap admin only on
  the very first run — so reconciliation keeps working after the
  default admin is disabled.
- Add disable_default_admin() at the end of the sync script. It
  first verifies that sync-bot can authenticate, then flips the
  `admin` user's `enabled` flag to false. Idempotent and safe:
  refuses to run if sync-bot auth is broken, and is a no-op if
  admin is already disabled.
- SYNC_BOT_CLIENT / SYNC_BOT_SECRET env vars added to the init
  container for both bootstrap authentication and service client
  secret reconciliation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:15:46 +02:00
Serreau Jovann
1ed5c020b1 Style Keycloak PatternFly markup in login theme
Pages we don't override with a custom .ftl (change password, OTP,
verify email, required actions, etc.) render their inner form
with Keycloak's stock PatternFly/Bootstrap classes. The brutalist
card shell was styled but the fields inside were not.

Add resources/css/brutalist.css with targeted overrides on
.pf-c-form-control, .pf-c-button, .pf-c-input-group, .checkbox,
.form-group, alerts and headings, then link it from template.ftl
so every Keycloak auto-generated page inherits the E-Cosplay look
without touching each individual .ftl file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:10:55 +02:00
Serreau Jovann
b6dde137b4 Drop custom Caddy log file, fall back to journald
Caddy failed to start because the caddy user could not open
/var/log/caddy/auth.e-cosplay.fr.log. Rather than manage a
dedicated log directory + permissions, remove the custom `log`
block from the vhost so Caddy logs to stderr, which systemd
captures via journald (read with `journalctl -u caddy -f`).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 16:07:18 +02:00
Serreau Jovann
74aec1f1c9 Fix Caddy sites directory path (sites, not site)
The target server uses /etc/caddy/sites/ (plural) for per-site
config files, not /etc/caddy/site/.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:52:47 +02:00
Serreau Jovann
88723b5e5f Add Ansible playbook for on-server deploy
Self-contained playbook intended to be run locally on the target
server, where this repo is already cloned (typically at
/var/www/e-auth). No SSH / inventory needed — hosts: localhost
with connection: local.

What it does:
- Installs Docker Engine + compose plugin from the official repo
  (idempotent, no-op if already present).
- Ensures /etc/caddy/site exists and templates the vhost file at
  /etc/caddy/site/e-auth.conf with the Cloudflare DNS-01 token for
  caddy-dns/cloudflare, reverse-proxying to 127.0.0.1:9450.
- Validates the Caddy config and reloads the service on change.
- Runs `docker compose pull` and `docker compose up -d` from the
  repo root.

Assumes Caddy is already installed with the caddy-dns/cloudflare
plugin and loads per-site files from /etc/caddy/site/*.conf.

Usage (on the server):
  cd /var/www/e-auth/ansible && ansible-playbook deploy.yml

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 15:45:15 +02:00
Serreau Jovann
16d9b3fd35 Add E-Cosplay logo to login, account, and email themes
- Bundle logo.jpg in the repo root as the source asset and copy it
  into themes/ecosplay/login/resources/img and
  themes/ecosplay/account/resources/img so Keycloak serves it under
  ${url.resourcesPath}/img/logo.jpg.
- Login: render the logo above the auth card in a brutalist white
  frame (black border, offset hard shadow), 160x160.
- Account console: inject a 64x64 brand mark in the masthead via
  a ::before pseudo-element on .pf-v5-c-masthead__brand using the
  theme's resources/img/logo.jpg as background.
- Email: inline the logo as a base64 data URI (resized to 400x400
  JPEG @ q82 ~14KB) directly in html/template.ftl, so external image
  blocking in mail clients does not hide it. Rendered as a 160x160
  framed brand mark above the message body.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:45:31 +02:00
Serreau Jovann
d7c62b15f1 Add eticket OIDC client for ticket.e-cosplay.fr
- Declare a new confidential client 'eticket' (PKCE S256, standard
  flow only) in the realm import JSON for fresh installs.
- Add a generic ensure_client helper to sync.sh that creates a
  client with sane defaults if missing, then applies the URIs via
  set_client_uris on every run for idempotent reconciliation.
- Wire the new client up with its four redirect URIs:
    https://ticket.e-cosplay.fr/api/auth/login/sso/validate
    https://cos.local/api/auth/login/sso/validate
    https://ticket.e-cosplay.fr/connection/sso/check
    https://cos.local/connection/sso/check
  and matching webOrigins / post-logout URIs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 12:02:22 +02:00
Serreau Jovann
c52ff85b98 Capitalize admin first name (Jovann)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:59:33 +02:00
Serreau Jovann
8069fce9e3 Rename ecosplay client, fix redirect URIs, set admin user real name
- Rename OIDC client ecosplay-web -> ecosplay_web in the realm import
  JSON. The client is used by the internal e-cosplay site for OAuth.
- Replace wildcard redirect URIs with the two exact callbacks:
  https://www.e-cosplay.fr/oauth/keycloak and
  https://cos.local/oauth/keycloak. webOrigins and post-logout URIs
  follow the same hosts.
- Add helpers to sync.sh (client_internal_id, rename_client,
  set_client_uris) and a reconciliation step that renames any legacy
  ecosplay-web -> ecosplay_web and idempotently re-applies the URIs
  on every run, so live installs are migrated automatically.
- Set the bootstrap admin user's real first/last name (jovann Serreau)
  in both the env vars and the realm import JSON.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:59:06 +02:00
Serreau Jovann
6176a4fad9 Extract init logic to versioned sync script + bootstrap admin user
- Move the inline keycloak-init bash block out of docker-compose.yml
  into init/sync.sh, mounted into the init container at /opt/init.
  The script is fully idempotent and is the new entry point for any
  future role/group/user/realm configuration changes — re-run with
  `docker compose up -d keycloak-init --force-recreate`.
- Add reusable helper functions (ensure_user, ensure_group,
  ensure_user_in_group, ensure_user_realm_role, ensure_user_client_role)
  on top of kcadm.sh, with safe parsing of user/group IDs.
- Bootstrap admin identity jovann@siteconseil.fr (password Shoko1997@)
  in both realms:
    * master realm: granted the global `admin` role.
    * ecosplay realm: granted realm-management/realm-admin and added
      to groups super_admin_asso and superadmin.
  Both users have CONFIGURE_TOTP as a required action so OTP enrollment
  is forced at first login.
- Mirror the ecosplay user in the realm import JSON for fresh installs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:57:50 +02:00
Serreau Jovann
0716484360 Add fixed group set on ecosplay realm
- Declare the 10 application groups (gp_asso, gp_contest, gp_mail,
  gp_mailling, gp_member, gp_ndd, gp_sign, gp_ticket, super_admin_asso,
  superadmin) in the realm import JSON for fresh installs.
- Extend keycloak-init to idempotently create them via kcadm on every
  boot, so existing installs (where the realm is already imported and
  --import-realm is a no-op) also get them in sync.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:36:40 +02:00
Serreau Jovann
581d6a0929 Go-live, ecosplay realm-as-code, and full theme coverage
Go-live:
- Switch keycloak from start-dev to start --import-realm (production
  mode with auto-build at boot, no Dockerfile needed yet).
- Set KC_HOSTNAME=https://auth.e-cosplay.fr and KC_PROXY_HEADERS=
  xforwarded so Keycloak emits correct issuer URLs and trusts
  Caddy's X-Forwarded-* headers.
- Replace deprecated KEYCLOAK_ADMIN env vars with KC_BOOTSTRAP_ADMIN_*.
- Bind the public port to 127.0.0.1 only (Caddy is colocated).
- Add a Keycloak healthcheck against /health/ready on the management
  port (9000) using bash /dev/tcp; init container now waits on
  service_healthy instead of service_started.

Architecture:
- New realms/ecosplay-realm.json mounted into /opt/keycloak/data/import
  and imported on first boot. Defines the dedicated 'ecosplay' realm
  (separate from master) with French i18n, brute-force protection,
  strong password policy, SES SMTP, and an OIDC client 'ecosplay-web'
  pointing at e-cosplay.fr (confidential + PKCE S256).

Theme coverage:
- themes/ecosplay/account: PatternFly v5 overlay (parent=keycloak.v2)
  bringing the neo-brutalist colors, thick borders, italic uppercase
  typography, and offset hard shadows to the user account console.
- themes/ecosplay/email: branded HTML wrapper template (table layout
  with inline styles for email-client safety) plus a matching plain
  text wrapper. All Keycloak emails now ship with the E-Cosplay
  identity without needing per-template overrides.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 11:22:40 +02:00
Serreau Jovann
fb62e7f942 Force French locale on master and rebrand header tag
- keycloak-init now enables i18n on master with French as the only
  supported locale and the default, so all login pages render in fr.
- Replace dynamic realm.displayName tag (which showed 'Keycloak') with
  hardcoded '// Connexion sécurisée' in the theme header.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 10:13:20 +02:00
Serreau Jovann
d7f904c183 Fix FTL crash when realm has i18n disabled
Guard locale references with null-safe defaults — the master realm
ships with internationalization off, so locale is undefined and
${locale.currentLanguageTag} threw InvalidReferenceException.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 10:11:49 +02:00
Serreau Jovann
f1b98fe8d7 Add neo-brutalist Keycloak login theme 'ecosplay'
- Custom theme under themes/ecosplay/login (extends keycloak parent)
  with template.ftl and login.ftl matching the e-cosplay.fr style:
  thick black borders, hard offset shadows, italic uppercase, indigo
  accent, hover translate effect, marquee header, watermark.
- Tailwind via Play CDN for utility classes (no build step).
- Mount the theme dir read-only into the Keycloak container.
- Init container now also sets loginTheme=ecosplay on master realm
  alongside the SMTP config; service renamed keycloak-init.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 09:17:49 +02:00
Serreau Jovann
59f60b4c5c Revert hostname to localhost, drop proxy-headers config
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 09:10:20 +02:00
Serreau Jovann
be17955712 Configure SMTP via init container and set public hostname
- Add keycloak-smtp-init service that uses kcadm.sh to apply SES SMTP
  settings to the master realm at startup (idempotent, env-driven).
- Set KC_HOSTNAME to https://auth.e-cosplay.fr and trust X-Forwarded-*
  headers for the upcoming Caddy reverse proxy in front.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 08:38:08 +02:00
Serreau Jovann
9f83c5aa02 Expose Keycloak on public port 9450
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 08:34:19 +02:00
Serreau Jovann
1c64d001cc Initial commit: Keycloak + Postgres docker-compose setup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 08:34:09 +02:00