From f7ef89fa82ae00b2ba5697845d195c83fdf140cf Mon Sep 17 00:00:00 2001 From: R3D347HR4Y Date: Sat, 20 Jun 2026 01:21:30 +0200 Subject: [PATCH] feat(authentik): recovery email links to embedded reset-password UI Custom email template rendered via AUTH_APP_URL, mounted in Authentik, and gitignored rendered HTML to avoid localhost hardcoding in prod. --- .env.example | 4 +- .gitignore | 3 ++ deploy/authentik/README.md | 18 +++++++- .../blueprints/05-ulti-recovery.yaml | 2 +- deploy/authentik/render-blueprints.sh | 15 ++++++- .../email/ulti_password_reset.html.template | 42 +++++++++++++++++++ deploy/docker-compose.yml | 2 + 7 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 deploy/authentik/templates/email/ulti_password_reset.html.template diff --git a/.env.example b/.env.example index b7623fc..bc2702a 100644 --- a/.env.example +++ b/.env.example @@ -292,7 +292,9 @@ MAIL_MICROSOFT_OAUTH_CLIENT_SECRET= MAIL_MICROSOFT_OAUTH_TENANT=common MAIL_OAUTH_REDIRECT_URL= MAIL_APP_URL=http{{SECURE}}://{{PUBLIC_HOST}}/mail -# Cible nginx → suite frontend unifié mail+drive (dev: Next sur l'hôte :3004 ; prod: suite-frontend:3000) +# Origine app pour pages auth (/login, /reset-password) — template e-mail recovery Authentik. +# Dev local Next.js : AUTH_APP_URL=http://localhost:3004 +AUTH_APP_URL=http{{SECURE}}://{{PUBLIC_HOST}} # ----------------------------------------------------------------------------- # Stalwart hosted mail (optional — enable for @ultisuite.fr / custom domains) diff --git a/.gitignore b/.gitignore index f72f478..f900c64 100644 --- a/.gitignore +++ b/.gitignore @@ -32,5 +32,8 @@ Thumbs.db # Docker data (local volumes) docker-data/ +# Rendered Authentik email templates (generated by deploy/authentik/render-blueprints.sh) +deploy/authentik/templates/**/*.html + # Node.js node_modules/ diff --git a/deploy/authentik/README.md b/deploy/authentik/README.md index f663eb2..ee97d31 100644 --- a/deploy/authentik/README.md +++ b/deploy/authentik/README.md @@ -8,7 +8,7 @@ Blueprints in `blueprints/` are mounted into Authentik at `/blueprints/custom` a | `02-ulti-brand.yaml` | Branding UltiSuite + lien « Créer un compte » sur login | | `03-ulti-suite-groups.yaml` | Claim OIDC `groups` (RBAC contacts/calendar/drive/photos) | | `04-ulti-post-migration-security.yaml` | Flow WebAuthn/TOTP post-migration (`ulti-post-migration-security`) | -| `05-ulti-recovery.yaml` | Mot de passe oublié (`ulti-recovery`) | +| `05-ulti-recovery.yaml` | Mot de passe oublié (`ulti-recovery`) + e-mail → `/reset-password` | | `ulti-oidc.yaml` | App OIDC Ultimail | | `nextcloud-oidc.yaml` | App OIDC Nextcloud | | `onlyoffice-oidc.yaml` | App OIDC OnlyOffice | @@ -36,6 +36,22 @@ cd ../ulti-backend docker exec deploy-authentik-server-1 ak apply_blueprint /blueprints/custom/02-ulti-brand.yaml ``` +### Mot de passe oublié (recovery embedded) + +1. L'utilisateur saisit son e-mail sur `/forgot-password` (UI Ulti). +2. Authentik envoie un e-mail avec lien vers `{AUTH_APP_URL}/reset-password?flow_token=…` (template `email/ulti_password_reset.html`). +3. `/reset-password` reprend le flow `ulti-recovery` via le BFF (`is_restored` → étapes mot de passe embedded). + +Variable `.env` : `AUTH_APP_URL` (défaut dev `http://localhost:3004`). Régénérée dans le template e-mail par `./deploy/authentik/render-blueprints.sh`. + +Appliquer après changement recovery : + +```bash +./deploy/authentik/render-blueprints.sh # depuis ulti-backend avec .env.resolved sourcé +docker exec deploy-authentik-server-1 ak apply_blueprint /blueprints/custom/05-ulti-recovery.yaml +./deploy/compose-up.sh restart authentik-worker +``` + ## Inscription utilisateur Flow public : `http://localhost/auth/if/flow/ulti-enrollment/` diff --git a/deploy/authentik/blueprints/05-ulti-recovery.yaml b/deploy/authentik/blueprints/05-ulti-recovery.yaml index cd89603..15cdbfe 100644 --- a/deploy/authentik/blueprints/05-ulti-recovery.yaml +++ b/deploy/authentik/blueprints/05-ulti-recovery.yaml @@ -31,7 +31,7 @@ entries: use_global_settings: true token_expiry: 1800 subject: UltiSuite — Réinitialisation du mot de passe - template: email/password_reset.html + template: email/ulti_password_reset.html activate_user_on_success: true recovery_max_attempts: 5 recovery_cache_timeout: 300 diff --git a/deploy/authentik/render-blueprints.sh b/deploy/authentik/render-blueprints.sh index 127db3b..8de99dc 100755 --- a/deploy/authentik/render-blueprints.sh +++ b/deploy/authentik/render-blueprints.sh @@ -1,15 +1,22 @@ #!/usr/bin/env bash -# Render Authentik blueprint templates using PUBLIC_HOST / SECURE / SUITE_ORIGIN from .env.resolved. +# Render Authentik blueprint + email templates using .env.resolved variables. set -euo pipefail ROOT="$(cd "$(dirname "$0")/../.." && pwd)" BP_DIR="$ROOT/deploy/authentik/blueprints" +TPL_DIR="$ROOT/deploy/authentik/templates" if [[ -z "${SUITE_ORIGIN:-}" || -z "${PUBLIC_HOST:-}" ]]; then echo "render-blueprints: SUITE_ORIGIN and PUBLIC_HOST must be set (source .env.resolved first)" >&2 exit 1 fi +# Frontend origin for auth pages (/login, /reset-password). Falls back to SUITE_ORIGIN. +AUTH_APP_URL="${AUTH_APP_URL:-${NEXT_PUBLIC_APP_URL:-${SUITE_ORIGIN}}}" +if [[ "$AUTH_APP_URL" == */mail ]]; then + AUTH_APP_URL="${AUTH_APP_URL%/mail}" +fi + render_one() { local tpl="$1" local out="${tpl%.template}" @@ -17,6 +24,7 @@ render_one() { -e "s|{{SUITE_ORIGIN}}|${SUITE_ORIGIN}|g" \ -e "s|{{PUBLIC_HOST}}|${PUBLIC_HOST}|g" \ -e "s|{{SECURE}}|${SECURE:-}|g" \ + -e "s|{{AUTH_APP_URL}}|${AUTH_APP_URL}|g" \ "$tpl" > "$out" echo "render-blueprints: ${out##*/}" } @@ -25,3 +33,8 @@ shopt -s nullglob for tpl in "$BP_DIR"/*.yaml.template; do render_one "$tpl" done + +for tpl in "$TPL_DIR"/**/*.template "$TPL_DIR"/*/*.template; do + [[ -f "$tpl" ]] || continue + render_one "$tpl" +done diff --git a/deploy/authentik/templates/email/ulti_password_reset.html.template b/deploy/authentik/templates/email/ulti_password_reset.html.template new file mode 100644 index 0000000..0ec14bb --- /dev/null +++ b/deploy/authentik/templates/email/ulti_password_reset.html.template @@ -0,0 +1,42 @@ +{% extends "email/base.html" %} + +{% load i18n %} +{% load humanize %} + +{% block content %} + + +

{% trans "Réinitialiser votre mot de passe UltiSuite" %}

+ + + + + + + + + + + +
+ {% blocktrans with username=user.username %} + Bonjour {{ username }}, utilisez le bouton ci-dessous pour choisir un nouveau mot de passe UltiSpace. + {% endblocktrans %} +
+ + {% trans "Réinitialiser le mot de passe" %} + +
+ + +{% endblock %} + +{% block sub_content %} + + + {% blocktrans with expires=expires|naturaltime %} + Si vous n'êtes pas à l'origine de cette demande, ignorez cet e-mail. Le lien est valide {{ expires }}. + {% endblocktrans %} + + +{% endblock %} diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 56c313c..b431cab 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -110,6 +110,7 @@ services: env_file: ../.env.resolved volumes: - ./authentik/blueprints:/blueprints/custom:ro + - ./authentik/templates:/templates:ro - ./authentik/branding/ultisuite-logo-light.png:/web/dist/assets/branding/ultisuite-logo-light.png:ro - ./authentik/branding/ultisuite-logo-dark.png:/web/dist/assets/branding/ultisuite-logo-dark.png:ro - ./authentik/branding/ultisuite-favicon.png:/web/dist/assets/branding/ultisuite-favicon.png:ro @@ -145,6 +146,7 @@ services: env_file: ../.env.resolved volumes: - ./authentik/blueprints:/blueprints/custom:ro + - ./authentik/templates:/templates:ro - ./authentik/branding/ultisuite-logo-light.png:/web/dist/assets/branding/ultisuite-logo-light.png:ro - ./authentik/branding/ultisuite-logo-dark.png:/web/dist/assets/branding/ultisuite-logo-dark.png:ro - ./authentik/branding/ultisuite-favicon.png:/web/dist/assets/branding/ultisuite-favicon.png:ro