Commit 02fd8c70 authored by Kourser's avatar Kourser
Browse files

Installateur : déclarer un relais de courriel

Sans relais, l'instance tourne mais n'envoie rien : ni invitation, ni
activation de compte, ni réinitialisation de mot de passe. L'installateur
le proposait uniquement sous forme de six lignes commentées dans le .env,
que personne ne décommente au bon moment.

La question est posée dans les deux modes. En complétion, elle n'est
posée que si aucun SMTP_HOST n'est déclaré — c'est le cas le plus
fréquent, l'envoi de courriel n'étant presque jamais prêt le jour où
l'on installe.

TROIS CONTRAINTES DU PRODUIT, DITES AVANT LA SAISIE PLUTÔT QU'APRÈS.

TLS est exigé sans dérogation (requireTLS, TLS 1.2 minimum) : le produit
refuse une session en clair même quand le relais l'accepte. Or « relais
interne » désigne le plus souvent un MTA sur le port 25 sans STARTTLS,
qui ne conviendra donc pas. L'avertissement tombe avant la première
question, pas au premier envoi raté.

L'authentification est facultative — un relais qui reconnaît ses clients
à leur adresse réseau ne donne pas d'identifiants. Répondre « non » n'est
pas un contournement, et l'avertissement sur le mot de passe en clair ne
s'affiche alors pas : il n'y a pas de secret à protéger.

Les réglages de la console d'instance priment EN BLOC dès qu'un hôte y
est saisi, et chiffrent le mot de passe. Le .env est donc le chemin
d'amorçage, pas le bon endroit pour un secret : qui ne veut rien laisser
en clair décline ici et saisit tout depuis la console. Le script le dit.

Le mot de passe SMTP est écrit entre apostrophes. Sans elles, Compose
coupe la valeur au premier croisillon : « mdp avec espace #et suite »
devenait « mdp avec espace », et l'authentification échouait sur un
serveur pourtant bien configuré. Vérifié par « docker compose config ».

Le défaut du chiffrement suit le port — implicite sur 465, STARTTLS
ailleurs — parce que proposer l'inverse serait proposer une erreur.

Vérifié de bout en bout : le .env produit, relu par Compose, donne bien
« mailConfigured: true » à une API démarrée dessus, et « false » sans le
bloc. Parcours testés sur pseudo-terminal dans les deux langues, avec et
sans authentification, en création comme en complétion.

Co-Authored-By: default avatarClaude Opus 5 <noreply@anthropic.com>
parent da858c3d
Loading
Loading
Loading
Loading
+3 −1
Changes for README.md: 3 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -29,7 +29,8 @@ Plateforme **open-source** d'organisation et d'animation d'**exercices de gestio
```

L'installateur pose les questions nécessaires, écrit `.env`, lance la pile, attend que
l'API réponde et crée le premier compte d'administration. C'est le chemin recommandé.
l'API réponde et crée le premier compte d'administration. Il propose aussi de déclarer
un relais d'envoi de courriel. C'est le chemin recommandé.

Il est bilingue : la langue est devinée depuis la locale, puis confirmée à la première
question. `ENKADENN_LANGUE=en ./install.sh` (ou `=fr`) l'impose sans la demander.
@@ -131,6 +132,7 @@ docs/ Cahier des charges, ADR, spec technique, maquettes
| `SIGNUP_RETENTION_DAYS` | Purge des demandes d'inscription **traitées** après N jours (`0` = désactivé). Les demandes en attente ne sont jamais purgées | `90` |
| `WEBRTC_ICE_SERVERS` | Serveurs ICE pour les appels vocaux, en JSON. Vide = candidats locaux seulement | — |
| `PORT` | Port HTTP | `3000` |
| `SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE`, `SMTP_USER`, `SMTP_PASSWORD`, `MAIL_FROM` | Relais d'envoi de courriel. Sert à l'amorçage : **les réglages de la console d'instance priment en bloc** dès qu'un hôte y est saisi, et chiffrent le mot de passe, alors qu'ici il est lisible par un `docker inspect`. L'authentification est facultative ; **TLS est exigé sans dérogation**, un relais sans STARTTLS est refusé | — |

### Second facteur perdu : réarmer un compte

+212 −21
Changes for install.sh: 212 added lines, 21 removed lines.
Original line number Diff line number Diff line
@@ -240,6 +240,126 @@ secret_saisi() {
  done
}

# ------------------------------------------------------------- courriel -----
# Renseigne SMTP_HOST/PORT/SECURE/USER/PASSWORD et MAIL_FROM, ou les laisse
# vides si l'exploitant decline. Partagee par les deux modes : un relais doit
# pouvoir s'ajouter a une instance deja installee sans toucher au reste.
demander_smtp() {
  SMTP_HOST=''
  SMTP_PORT='587'
  SMTP_SECURE='false'
  SMTP_USER=''
  SMTP_PASSWORD=''
  MAIL_FROM=''

  info_b "Sans relais, l'instance tourne mais n'envoie RIEN : ni invitation, ni" \
    "Without a relay the instance runs but sends NOTHING: no invitation, no"
  info_b "activation de compte, ni reinitialisation de mot de passe. Les comptes" \
    "account activation, no password reset. Accounts then have to be created by"
  info_b "doivent alors etre crees a la main, et les acces transmis autrement." \
    "hand, and the credentials passed on some other way."
  alerte_b "TLS EXIGE, sans derogation : le produit refuse une session en clair," \
    "TLS IS REQUIRED, with no exception: the product refuses a cleartext session"
  alerte_b "meme quand le relais l'accepte. Un relais interne sur le port 25 sans" \
    "even where the relay would allow one. An internal relay on port 25 without"
  alerte_b "STARTTLS ne conviendra donc pas." "STARTTLS will therefore not do."

  if ! oui_non_b "Declarer un serveur de courriel maintenant ?" \
    "Declare a mail server now?" n; then
    info_b "Aucun relais : a configurer plus tard depuis la console d'instance." \
      "No relay: configure one later from the instance console."
    return 0
  fi

  local hote=''
  while :; do
    demande_b "Hote du relais SMTP (ex. smtp.mon-domaine.fr)" \
      "SMTP relay host (e.g. smtp.example.org)" ''
    hote="$REPONSE"
    case "$hote" in
      '') alerte_b "Un hote est attendu." "A host is expected." ;;
      *[[:space:]\'\"]*)
        alerte_b "Ni espace ni guillemet dans un nom d'hote." \
          "No whitespace or quotes in a host name."
        ;;
      *) break ;;
    esac
  done

  nombre_b "Port du relais (587 pour STARTTLS, 465 pour TLS implicite)" \
    "Relay port (587 for STARTTLS, 465 for implicit TLS)" '587' 1 65535
  local port="$REPONSE"

  # Le defaut suit le port : 465 est le port du TLS implicite, 587 celui de
  # STARTTLS. Proposer l'inverse serait proposer une erreur.
  local defaut_tls='n'
  if [ "$port" = '465' ]; then defaut_tls='o'; fi
  local secure='false'
  if oui_non_b "TLS implicite des la connexion ? (non = STARTTLS)" \
    "Implicit TLS from the start? (no = STARTTLS)" "$defaut_tls"; then
    secure='true'
  fi

  # L'authentification est FACULTATIVE cote produit : un relais interne qui
  # accepte ses clients sur leur adresse reseau n'a pas d'identifiants a donner.
  local utilisateur='' motdepasse=''
  if oui_non_b "Le relais demande-t-il une authentification ?" \
    "Does the relay require authentication?" o; then
    while :; do
      demande_b "Identifiant SMTP" "SMTP user name" ''
      utilisateur="$REPONSE"
      if [ -n "$utilisateur" ]; then break; fi
      alerte_b "Un identifiant est attendu." "A user name is expected."
    done
    while :; do
      secret_saisi 1 n
      motdepasse="$REPONSE"
      case "$motdepasse" in
        *\'*)
          alerte_b "L'apostrophe n'est pas supportee dans ce mot de passe." \
            "Single quotes are not supported in this password."
          ;;
        *) break ;;
      esac
    done
    REPONSE=''
  fi

  while :; do
    demande_b "Adresse d'expedition, telle qu'elle apparaitra aux destinataires" \
      "Sender address, as recipients will see it" ''
    case "$REPONSE" in
      *@*.*) break ;;
      *) alerte_b "Adresse de courriel attendue." "An email address is expected." ;;
    esac
  done

  SMTP_HOST="$hote"
  SMTP_PORT="$port"
  SMTP_SECURE="$secure"
  SMTP_USER="$utilisateur"
  SMTP_PASSWORD="$motdepasse"
  MAIL_FROM="$REPONSE"
  REPONSE=''

  # L'avertissement ne vaut que s'il y a un secret a proteger : un relais qui
  # authentifie ses clients sur leur adresse reseau n'en donne aucun.
  if [ -n "$SMTP_PASSWORD" ]; then
    alerte_b "Le mot de passe SMTP sera ecrit EN CLAIR dans .env, donc lisible par" \
      "The SMTP password will be written IN CLEAR in .env, readable by anyone who"
    alerte_b "quiconque peut faire un « docker inspect ». La console d'instance le" \
      "can run \"docker inspect\". The instance console encrypts it instead, and"
    alerte_b "chiffre, et ses reglages priment sur ceux-ci EN BLOC : pour ne rien" \
      "its settings take priority over these AS A WHOLE: to leave nothing in clear,"
    alerte_b "laisser en clair, declinez ici et saisissez tout depuis la console." \
      "decline here and enter everything from the console."
  else
    info_b "Relais sans authentification : aucun secret n'est ecrit dans .env." \
      "Relay without authentication: no secret is written to .env."
  fi
  return 0
}

# ---------------------------------------------------------------- docker ----
# Nom du projet Compose, tel que Compose le calculera lui-meme. Il prefixe les
# volumes et les conteneurs : sans lui, impossible de distinguer NOTRE base
@@ -422,6 +542,32 @@ if [ "$MODE_ENV" = 'completion' ]; then
      'Read .env again: the added values are the defaults.'
  fi

  # Ajouter un relais a une instance deja installee : c'est le cas le plus
  # frequent, l'envoi de courriel n'etant presque jamais pret le jour ou l'on
  # installe. Un bloc commente ne compte pas comme une declaration.
  if ! a_clef SMTP_HOST; then
    titre_b 'Envoi de courriel (optionnel)' 'Mail delivery (optional)'
    demander_smtp
    if [ -n "$SMTP_HOST" ]; then
      umask 077
      {
        printf '\n# --- %s ---\n' \
          "$(dire "Relais de courriel ajoute par install.sh le $(date '+%Y-%m-%d a %H:%M:%S')" \
            "Mail relay added by install.sh on $(date '+%Y-%m-%d at %H:%M:%S')")"
        printf 'SMTP_HOST=%s\n' "$SMTP_HOST"
        printf 'SMTP_PORT=%s\n' "$SMTP_PORT"
        printf 'SMTP_SECURE=%s\n' "$SMTP_SECURE"
        printf 'SMTP_USER=%s\n' "$SMTP_USER"
        printf "SMTP_PASSWORD='%s'\n" "$SMTP_PASSWORD"
        printf 'MAIL_FROM=%s\n' "$MAIL_FROM"
      } >> "$FICHIER_ENV"
      chmod 600 "$FICHIER_ENV"
      unset SMTP_PASSWORD
      info_b "Relais ajoute a .env. Il ne prendra effet qu'au redemarrage de la pile." \
        "Relay added to .env. It takes effect only when the stack restarts."
    fi
  fi

  # Une completion porte sur une instance deja installee : si son volume de
  # donnees existe, ses comptes aussi, et proposer d'en creer un premier n'a
  # pas de sens.
@@ -558,6 +704,11 @@ else
    'Number of trusted proxies in front of the application (0 = none)' '0' 0 10
  TRUSTED_PROXIES="$REPONSE"

  # -------------------------------------------------------------- courriel --
  titre_b 'Envoi de courriel (optionnel)' 'Mail delivery (optional)'

  demander_smtp

  # ------------------------------------------------------------------ RGPD --
  titre_b 'Conservation des donnees (RGPD)' 'Data retention (GDPR)'

@@ -590,6 +741,54 @@ else
    ICE_LIGNE="WEBRTC_ICE_SERVERS="
  fi

  # Bloc « envoi de courriel » du .env : renseigne si un relais a ete decrit,
  # commente sinon. Le mot de passe est place entre apostrophes — un mot de
  # passe SMTP contient volontiers une espace ou un croisillon, que le lecteur
  # de .env de Compose couperait.
  if [ -n "$SMTP_HOST" ]; then
    BLOC_SMTP="$(dire "\
# Relais de courriel, saisi a l'installation. Le mot de passe est EN CLAIR :
# lisible par quiconque peut faire un « docker inspect ». La console d'instance
# le chiffre avant stockage, et ses reglages priment sur ceux-ci EN BLOC.
SMTP_HOST=$SMTP_HOST
SMTP_PORT=$SMTP_PORT
SMTP_SECURE=$SMTP_SECURE
SMTP_USER=$SMTP_USER
SMTP_PASSWORD='$SMTP_PASSWORD'
MAIL_FROM=$MAIL_FROM" "\
# Mail relay, entered at install time. The password is IN CLEAR: readable by
# anyone who can run \"docker inspect\". The instance console encrypts it before
# storing, and its settings take priority over these AS A WHOLE.
SMTP_HOST=$SMTP_HOST
SMTP_PORT=$SMTP_PORT
SMTP_SECURE=$SMTP_SECURE
SMTP_USER=$SMTP_USER
SMTP_PASSWORD='$SMTP_PASSWORD'
MAIL_FROM=$MAIL_FROM")"
  else
    BLOC_SMTP="$(dire "\
# Envoi de courriel. La console d'instance est preferable : elle chiffre le
# mot de passe avant stockage, alors qu'ici il est lisible par quiconque peut
# faire un « docker inspect ». Ces variables ne sont lues qu'en l'absence de
# reglage en base.
#SMTP_HOST=
#SMTP_PORT=587
#SMTP_SECURE=false
#SMTP_USER=
#SMTP_PASSWORD=
#MAIL_FROM=" "\
# Mail delivery. The instance console is preferable: it encrypts the password
# before storing it, whereas here it is readable by anyone who can run
# \"docker inspect\". These variables are read only when no setting exists in
# the database.
#SMTP_HOST=
#SMTP_PORT=587
#SMTP_SECURE=false
#SMTP_USER=
#SMTP_PASSWORD=
#MAIL_FROM=")"
  fi

  # -------------------------------------------------------------- ecriture --
  titre_b 'Ecriture du fichier .env' 'Writing the .env file'

@@ -645,16 +844,7 @@ $ICE_LIGNE
#AUDIT_RETENTION_DAYS=730
#SIGNUP_RETENTION_DAYS=90
#
# Mail delivery. The instance console is preferable: it encrypts the password
# before storing it, whereas here it is readable by anyone who can run
# "docker inspect". These variables are read only when no setting exists in
# the database.
#SMTP_HOST=
#SMTP_PORT=587
#SMTP_SECURE=false
#SMTP_USER=
#SMTP_PASSWORD=
#MAIL_FROM=
$BLOC_SMTP
ENV
  else
    cat > "$FICHIER_ENV" <<ENV
@@ -704,21 +894,12 @@ $ICE_LIGNE
#AUDIT_RETENTION_DAYS=730
#SIGNUP_RETENTION_DAYS=90
#
# Envoi de courriel. La console d'instance est preferable : elle chiffre le
# mot de passe avant stockage, alors qu'ici il est lisible par quiconque peut
# faire un « docker inspect ». Ces variables ne sont lues qu'en l'absence de
# reglage en base.
#SMTP_HOST=
#SMTP_PORT=587
#SMTP_SECURE=false
#SMTP_USER=
#SMTP_PASSWORD=
#MAIL_FROM=
$BLOC_SMTP
ENV
  fi

  chmod 600 "$FICHIER_ENV"
  unset PG_PASSWORD JWT_SECRET
  unset PG_PASSWORD JWT_SECRET SMTP_PASSWORD
  info_b "Ecrit : .env (permissions 600)" "Written: .env (permissions 600)"

  # ----------------------------------------------------------- recapitulatif
@@ -742,6 +923,16 @@ ENV
  fi
  printf '  %-22s: %s\n' "$(dire 'Serveurs ICE' 'ICE servers')" \
    "${WEBRTC_ICE_SERVERS:-$(dire aucun none)}"
  if [ -n "$SMTP_HOST" ]; then
    printf '  %-22s: %s\n' "$(dire 'Relais de courriel' 'Mail relay')" \
      "$SMTP_HOST:$SMTP_PORT ($(if [ "$SMTP_SECURE" = 'true' ]; then dire 'TLS implicite' 'implicit TLS'; else printf STARTTLS; fi), $(if [ -n "$SMTP_USER" ]; then dire "identifiant $SMTP_USER" "user $SMTP_USER"; else dire 'sans authentification' 'no authentication'; fi))"
    printf '  %-22s: %s\n' "$(dire "Adresse d'expedition" 'Sender address')" "$MAIL_FROM"
    printf '  %-22s: %s\n' "$(dire 'Mot de passe SMTP' 'SMTP password')" \
      "$(dire 'ecrit EN CLAIR dans .env' 'written IN CLEAR to .env')"
  else
    printf '  %-22s: %s\n' "$(dire 'Relais de courriel' 'Mail relay')" \
      "$(dire 'aucun — aucun message ne partira' 'none — no message will be sent')"
  fi
  printf '  %-22s: %s\n' "$(dire 'Donnees existantes' 'Existing data')" \
    "$([ "$GARDER_BASE" = 'o' ] && dire 'conservees' 'kept' || dire 'base vierge' 'empty database')"
fi