diff --git a/.env.example b/.env.example index ebfc96a..e71995b 100644 --- a/.env.example +++ b/.env.example @@ -5,3 +5,28 @@ API_TOKEN_SALT=tobemodified ADMIN_JWT_SECRET=tobemodified TRANSFER_TOKEN_SALT=tobemodified JWT_SECRET=tobemodified + +# Base de données — sqlite par défaut en dev, postgres/mysql sinon +DATABASE_CLIENT=sqlite + +# Stockage des médias (Cloudflare R2, compatible S3) +R2_ACCESS_KEY_ID=tobemodified +R2_SECRET_ACCESS_KEY=tobemodified +R2_ENDPOINT=https://.r2.cloudflarestorage.com +R2_BUCKET=choralsync +R2_REGION=auto +# Domaine public servant les médias du bucket +R2_PUBLIC_URL=https://container.choralsync.com + +# Email transactionnel (ZeptoMail via nodemailer) +SMTP_HOST=smtp.zeptomail.eu +SMTP_PORT=587 +SMTP_USERNAME=emailapikey +SMTP_PASSWORD=tobemodified +SMTP_DEFAULT_FROM="ChoralSync " +SMTP_DEFAULT_REPLY_TO=contact@choralsync.com +SMTP_DEBUG=false + +# Stripe +STRIPE_SECRET_KEY=tobemodified +STRIPE_WEBHOOK_SECRET=tobemodified diff --git a/CLAUDE.md b/CLAUDE.md index c0f114b..72b2f98 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,10 +73,19 @@ Vue par domaine (34 au total, liste exhaustive dans `src/api/`) : fichier (et jamais cochée uniquement dans l'admin, sinon elle sera signalée comme non déclarée). - Secrets uniquement en variables d'environnement. `.env` jamais commité ; - `.env.example` maintenu à jour. - ⚠️ **DETTE CRITIQUE : `config/plugins.ts` contient des secrets en dur** - (clé API SMTP ZeptoMail, access/secret keys Cloudflare R2). À migrer vers - `env()` et à faire tourner (rotation des clés). + `.env.example` maintenu à jour. Depuis le 26/07/2026, `config/plugins.ts` + ne contient plus aucun secret (R2 et SMTP passent par `env()`). + ⚠️ **Les clés historiques ont vécu en clair dans l'historique git** : + rotation R2 + ZeptoMail à faire, l'historique n'étant pas réécrit. +- ⚠️ **Pas de `config/env//plugins.ts`.** Strapi fusionne les surcharges + d'environnement en profondeur : une surcharge partielle de `providerOptions` + avait produit une config d'upload hybride (endpoint R2 de la base + + identifiants MinIO de la surcharge, prioritaires via `s3Options.credentials`) + et cassé tous les uploads de mai à juillet 2026. Les différences entre + environnements passent par les variables d'environnement, pas par un + second fichier. Même vigilance pour `config/env/production/middlewares.ts`, + qui doit répéter la config `strapi::body` (`includeUnparsed`) sous peine de + casser le webhook Stripe. ## Points d'audit récurrents diff --git a/config/env/production/middlewares.ts b/config/env/production/middlewares.ts index dea5b92..8b975df 100644 --- a/config/env/production/middlewares.ts +++ b/config/env/production/middlewares.ts @@ -1,7 +1,19 @@ +/** + * Surcharge production : uniquement la politique CSP (les médias sont servis + * par un domaine externe). Le reste de la pile reprend `config/middlewares.ts` + * à l'identique — y compris `includeUnparsed`, indispensable au webhook + * Stripe, que la fusion d'environnement écraserait si on laissait ici un + * simple `"strapi::body"`. + */ +const mediaHost = process.env.R2_PUBLIC_URL + ? new URL(process.env.R2_PUBLIC_URL).host + : "container.choralsync.com"; + +const mediaSources = ["'self'", "data:", "blob:", "market-assets.strapi.io", mediaHost]; + export default [ "strapi::logger", "strapi::errors", - //'strapi::security', { name: "strapi::security", config: { @@ -9,22 +21,8 @@ export default [ useDefaults: true, directives: { "connect-src": ["'self'", "https:"], - "img-src": [ - "'self'", - "data:", - "blob:", - "market-assets.strapi.io", - "192.168.0.211:9000", - "container.harmonylab.ovh", - ], - "media-src": [ - "'self'", - "data:", - "blob:", - "market-assets.strapi.io", - "192.168.0.211:9000", - "container.harmonylab.ovh", - ], + "img-src": mediaSources, + "media-src": mediaSources, upgradeInsecureRequests: null, }, }, @@ -33,7 +31,12 @@ export default [ "strapi::cors", "strapi::poweredBy", "strapi::query", - "strapi::body", + { + name: "strapi::body", + config: { + includeUnparsed: true, // INDISPENSABLE pour les webhooks + }, + }, "strapi::session", "strapi::favicon", "strapi::public", diff --git a/config/env/production/plugins.ts b/config/env/production/plugins.ts deleted file mode 100644 index 5ed2fed..0000000 --- a/config/env/production/plugins.ts +++ /dev/null @@ -1,43 +0,0 @@ -export default () => ({ - email: { - config: { - provider: "nodemailer", - providerOptions: { - host: "smtp.zeptomail.eu", - port: 587, - auth: { - user: "emailapikey", - pass: "yA6KbHsJ4lrywWtTFUc+0pSC94lm/aE/2nzks3i2fpZ1LYXp3qE71RBvd4O4c2CLjdfT5a9UbIkVJoCwvIpbfpczPIBXJpTGTuv4P2uV48xh8ciEYNYjhJivALIWFqVOeBsnDyo4QfEjWA==", - }, - debug: true, - logger: true, - }, - settings: { - defaultFrom: "ChoralSync ", - defaultReplyTo: "contact@choralsync.com", - }, - }, - }, - upload: { - config: { - provider: "aws-s3", - providerOptions: { - //baseUrl: "http://192.168.0.211:9000/harmony", - baseUrl: "https://container.harmonylab.ovh/harmony", - s3Options: { - credentials: { - accessKeyId: "admin", - secretAccessKey: "Apslxnap12bn23", - }, - //endpoint: "http://192.168.0.211:9000", - endpoint: "https://container.harmonylab.ovh", - region: "eu-west-3", - forcePathStyle: true, - params: { - Bucket: "harmony", - }, - }, - }, - }, - }, -}); diff --git a/config/plugins.ts b/config/plugins.ts index da48ba7..7caaaf3 100644 --- a/config/plugins.ts +++ b/config/plugins.ts @@ -1,20 +1,34 @@ -export default () => ({ +/** + * Configuration des plugins — source de vérité unique, tous environnements. + * + * ⚠️ Il n'existe volontairement PAS de `config/env/production/plugins.ts` : + * Strapi fusionne les surcharges d'environnement en profondeur, et cette + * fusion avait produit une configuration hybride (endpoint R2 de ce fichier + + * identifiants MinIO de la surcharge, lus en priorité par le provider via + * `s3Options.credentials`). Tous les uploads échouaient depuis mai 2026 sur + * « Credential access key has length 5, should be 32 ». Toute différence + * entre environnements passe désormais par les variables d'environnement. + */ +export default ({ env }) => ({ email: { config: { provider: "nodemailer", providerOptions: { - host: "smtp.zeptomail.eu", - port: 587, + host: env("SMTP_HOST", "smtp.zeptomail.eu"), + port: env.int("SMTP_PORT", 587), auth: { - user: "emailapikey", - pass: "yA6KbHsJ4lrywWtTFUc+0pSC94lm/aE/2nzks3i2fpZ1LYXp3qE71RBvd4O4c2CLjdfT5a9UbIkVJoCwvIpbfpczPIBXJpTGTuv4P2uV48xh8ciEYNYjhJivALIWFqVOeBsnDyo4QfEjWA==", + user: env("SMTP_USERNAME", "emailapikey"), + pass: env("SMTP_PASSWORD"), }, - debug: true, - logger: true, + debug: env.bool("SMTP_DEBUG", false), + logger: env.bool("SMTP_DEBUG", false), }, settings: { - defaultFrom: "ChoralSync ", - defaultReplyTo: "contact@choralsync.com", + defaultFrom: env( + "SMTP_DEFAULT_FROM", + "ChoralSync ", + ), + defaultReplyTo: env("SMTP_DEFAULT_REPLY_TO", "contact@choralsync.com"), }, }, }, @@ -23,18 +37,22 @@ export default () => ({ config: { provider: "aws-s3", providerOptions: { - baseUrl: "https://container.choralsync.com", - credentials: { - accessKeyId: "a6f474b04f010543022788efc626cbfe", - secretAccessKey: - "eeeb631194378edee5145b3ef6314630b152fa186b3dab2fefdf8b48952cde21", - }, - endpoint: - "https://4c59d3c67191bdd08e311a5a49c3ac98.r2.cloudflarestorage.com", - forcePathStyle: true, - region: "weur", - params: { - Bucket: "choralsync", + // URL publique servant les médias (domaine personnalisé du bucket). + baseUrl: env("R2_PUBLIC_URL"), + // Le provider 5.x attend les options S3 sous `s3Options` ; à plat, + // elles ne sont acceptées qu'au prix d'un avertissement de dépréciation + // — et une surcharge partielle produit une config incohérente. + s3Options: { + credentials: { + accessKeyId: env("R2_ACCESS_KEY_ID"), + secretAccessKey: env("R2_SECRET_ACCESS_KEY"), + }, + endpoint: env("R2_ENDPOINT"), + region: env("R2_REGION", "auto"), + forcePathStyle: true, + params: { + Bucket: env("R2_BUCKET"), + }, }, }, }, diff --git a/package.json b/package.json index 182259d..3e28a29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "harmony-back", - "version": "0.13.13", + "version": "0.13.14", "private": true, "description": "A Strapi application", "scripts": {