0.13.20 : check environment variables at boot
Build release Docker image / Build Docker Images (push) Successful in 7m58s
Build release Docker image / Build Docker Images (push) Successful in 7m58s
Trois erreurs de configuration successives — clés R2 pointant sur l'ancien MinIO, `wwhsec_` au lieu de `whsec_`, `k_test_` amputé de son `s` — n'ont produit aucun message exploitable. Chacune se manifestait très loin de sa cause : un upload qui échoue, un webhook rejeté. src/env-check.ts vérifie au bootstrap la forme des variables sensibles (Stripe, R2, SMTP) : présence, préfixe, longueur, et les altérations de copier-coller (guillemets englobants, espaces parasites). Les trois erreurs ci-dessus sont détectées. Ne bloque pas le démarrage — une clé SMTP erronée ne doit pas empêcher le site de servir — mais journalise en erreur, une ligne par anomalie. Aucune valeur n'est journalisée, uniquement des longueurs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "harmony-back",
|
"name": "harmony-back",
|
||||||
"version": "0.13.19",
|
"version": "0.13.20",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "A Strapi application",
|
"description": "A Strapi application",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import type { Core } from "@strapi/strapi";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contrôle des variables d'environnement au démarrage.
|
||||||
|
*
|
||||||
|
* Motivation : trois erreurs de configuration successives (clés R2 pointant
|
||||||
|
* sur l'ancien MinIO, `wwhsec_` au lieu de `whsec_`, `k_test_` amputé de son
|
||||||
|
* `s`) n'ont produit aucun message exploitable. Chacune s'est manifestée très
|
||||||
|
* loin de sa cause — un upload qui échoue, un webhook rejeté — et a coûté des
|
||||||
|
* heures de diagnostic.
|
||||||
|
*
|
||||||
|
* Ce module ne vérifie que ce qui est vérifiable sans appel réseau : présence,
|
||||||
|
* préfixe, longueur, et les altérations de copier-coller (guillemets
|
||||||
|
* englobants, espaces parasites). Il ne valide évidemment pas qu'une clé est
|
||||||
|
* la *bonne* — seulement qu'elle a la forme attendue.
|
||||||
|
*
|
||||||
|
* Il ne bloque pas le démarrage : une clé SMTP erronée ne doit pas empêcher
|
||||||
|
* le site de servir. Il journalise en erreur, ce qui suffit à orienter le
|
||||||
|
* diagnostic dès le boot.
|
||||||
|
*
|
||||||
|
* ⚠️ Aucune valeur n'est journalisée, jamais — uniquement des longueurs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
type EnvRule = {
|
||||||
|
name: string;
|
||||||
|
/** Préfixe attendu, le cas échéant. */
|
||||||
|
prefix?: string;
|
||||||
|
/** Longueur exacte attendue. */
|
||||||
|
length?: number;
|
||||||
|
/** Longueur minimale attendue. */
|
||||||
|
minLength?: number;
|
||||||
|
/** Contexte affiché dans le message, pour orienter la correction. */
|
||||||
|
usedFor: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ENV_RULES: EnvRule[] = [
|
||||||
|
// --- Paiement
|
||||||
|
{
|
||||||
|
name: "STRIPE_SECRET_KEY",
|
||||||
|
prefix: "sk_",
|
||||||
|
minLength: 20,
|
||||||
|
usedFor: "appels à l'API Stripe",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "STRIPE_WEBHOOK_SECRET",
|
||||||
|
prefix: "whsec_",
|
||||||
|
minLength: 30,
|
||||||
|
usedFor: "vérification de signature des webhooks Stripe",
|
||||||
|
},
|
||||||
|
|
||||||
|
// --- Stockage des médias (Cloudflare R2)
|
||||||
|
{
|
||||||
|
name: "R2_ACCESS_KEY_ID",
|
||||||
|
length: 32,
|
||||||
|
usedFor: "upload des médias",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "R2_SECRET_ACCESS_KEY",
|
||||||
|
length: 64,
|
||||||
|
usedFor: "upload des médias",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "R2_ENDPOINT",
|
||||||
|
prefix: "https://",
|
||||||
|
usedFor: "upload des médias",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "R2_BUCKET",
|
||||||
|
minLength: 1,
|
||||||
|
usedFor: "upload des médias",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "R2_PUBLIC_URL",
|
||||||
|
prefix: "https://",
|
||||||
|
usedFor: "URLs publiques des médias",
|
||||||
|
},
|
||||||
|
|
||||||
|
// --- Email transactionnel
|
||||||
|
{
|
||||||
|
name: "SMTP_PASSWORD",
|
||||||
|
minLength: 10,
|
||||||
|
usedFor: "envoi des emails transactionnels",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Guillemets englobants ou espaces parasites laissés par un copier-coller. */
|
||||||
|
const findCopyPasteDamage = (raw: string): string | null => {
|
||||||
|
if (raw !== raw.trim()) {
|
||||||
|
return "espace ou retour à la ligne en début ou fin de valeur";
|
||||||
|
}
|
||||||
|
const quoted =
|
||||||
|
(raw.startsWith('"') && raw.endsWith('"')) ||
|
||||||
|
(raw.startsWith("'") && raw.endsWith("'"));
|
||||||
|
if (quoted && raw.length > 1) {
|
||||||
|
return "valeur entourée de guillemets (ils font partie de la valeur)";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkRule = (rule: EnvRule): string[] => {
|
||||||
|
const raw = process.env[rule.name];
|
||||||
|
|
||||||
|
if (raw === undefined || raw === "") {
|
||||||
|
return [`${rule.name} n'est pas définie — ${rule.usedFor}`];
|
||||||
|
}
|
||||||
|
|
||||||
|
const problems: string[] = [];
|
||||||
|
|
||||||
|
const damage = findCopyPasteDamage(raw);
|
||||||
|
if (damage) {
|
||||||
|
problems.push(`${rule.name} : ${damage}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = raw.trim();
|
||||||
|
|
||||||
|
if (rule.prefix && !value.startsWith(rule.prefix)) {
|
||||||
|
problems.push(
|
||||||
|
`${rule.name} ne commence pas par « ${rule.prefix} » — ${rule.usedFor}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.length !== undefined && value.length !== rule.length) {
|
||||||
|
problems.push(
|
||||||
|
`${rule.name} fait ${value.length} caractères, ${rule.length} attendus — ${rule.usedFor}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.minLength !== undefined && value.length < rule.minLength) {
|
||||||
|
problems.push(
|
||||||
|
`${rule.name} fait ${value.length} caractères, au moins ${rule.minLength} attendus — ${rule.usedFor}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return problems;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifie la forme des variables d'environnement et journalise les anomalies.
|
||||||
|
* @returns le nombre d'anomalies détectées
|
||||||
|
*/
|
||||||
|
export const checkEnvironment = (strapi: Core.Strapi): number => {
|
||||||
|
const problems = ENV_RULES.flatMap(checkRule);
|
||||||
|
|
||||||
|
if (problems.length === 0) {
|
||||||
|
strapi.log.info(
|
||||||
|
`[env-check] ${ENV_RULES.length} variables vérifiées, aucune anomalie de forme`,
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
strapi.log.error(
|
||||||
|
`[env-check] ${problems.length} anomalie(s) détectée(s) dans les variables d'environnement :`,
|
||||||
|
);
|
||||||
|
for (const problem of problems) {
|
||||||
|
strapi.log.error(`[env-check] ✗ ${problem}`);
|
||||||
|
}
|
||||||
|
strapi.log.error(
|
||||||
|
"[env-check] Ces valeurs viennent de l'environnement de déploiement " +
|
||||||
|
"(Dokploy en production, .env en local). Une variable modifiée n'est " +
|
||||||
|
"relue qu'au redémarrage du processus.",
|
||||||
|
);
|
||||||
|
|
||||||
|
return problems.length;
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Core } from "@strapi/strapi";
|
import type { Core } from "@strapi/strapi";
|
||||||
|
import { checkEnvironment } from "./env-check";
|
||||||
import { syncRolePermissions } from "./permissions-sync";
|
import { syncRolePermissions } from "./permissions-sync";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -26,6 +27,12 @@ export default {
|
|||||||
* run jobs, or perform some special logic.
|
* run jobs, or perform some special logic.
|
||||||
*/
|
*/
|
||||||
async bootstrap({ strapi }: { strapi: Core.Strapi }) {
|
async bootstrap({ strapi }: { strapi: Core.Strapi }) {
|
||||||
|
// Contrôle de forme des variables d'environnement (préfixes, longueurs,
|
||||||
|
// guillemets parasites). Ne bloque pas le démarrage, mais signale au boot
|
||||||
|
// les erreurs de configuration qui, sinon, ne se manifestent que très
|
||||||
|
// loin de leur cause — cf. src/env-check.ts.
|
||||||
|
checkEnvironment(strapi);
|
||||||
|
|
||||||
// Synchronise les permissions Users & Permissions (source de vérité
|
// Synchronise les permissions Users & Permissions (source de vérité
|
||||||
// versionnée dans src/permissions-sync.ts — additif, idempotent).
|
// versionnée dans src/permissions-sync.ts — additif, idempotent).
|
||||||
await syncRolePermissions(strapi);
|
await syncRolePermissions(strapi);
|
||||||
|
|||||||
Reference in New Issue
Block a user