0.13.14 : fix media uploads, move plugin secrets to env vars
Build release Docker image / Build Docker Images (push) Successful in 7m54s
Build release Docker image / Build Docker Images (push) Successful in 7m54s
Tous les uploads échouaient depuis mai 2026 sur « Credential access key has length 5, should be 32 ». config/env/production/plugins.ts surchargeait l'upload avec l'ancien MinIO auto-hébergé (accessKeyId "admin", 5 caractères, endpoint container.harmonylab.ovh aujourd'hui injoignable). Strapi fusionnant les surcharges d'environnement en profondeur, la config effective en prod n'était ni l'une ni l'autre : endpoint R2 hérité de config/plugins.ts, mais identifiants MinIO, le provider lisant s3Options.credentials en priorité sur les options à plat. - surcharge production supprimée : une seule config plugins, les différences d'environnement passent par les variables d'environnement - upload en forme s3Options attendue par @strapi/provider-upload-aws-s3 5.x (à plat, le provider n'accepte plus qu'au prix d'une dépréciation) - secrets R2 et SMTP sortis du fichier vers env(), .env.example à jour - CSP de production : les médias étaient autorisés depuis container.harmonylab.ovh et 192.168.0.211:9000, jamais depuis le domaine R2 — l'hôte est désormais dérivé de R2_PUBLIC_URL - la surcharge production des middlewares réduisait strapi::body à sa forme par défaut, perdant includeUnparsed nécessaire au webhook Stripe ⚠️ Déploiement : poser R2_* et SMTP_PASSWORD dans Dokploy AVANT de déployer. Les clés historiques restent dans l'historique git : rotation R2 et ZeptoMail à faire. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,3 +5,28 @@ API_TOKEN_SALT=tobemodified
|
|||||||
ADMIN_JWT_SECRET=tobemodified
|
ADMIN_JWT_SECRET=tobemodified
|
||||||
TRANSFER_TOKEN_SALT=tobemodified
|
TRANSFER_TOKEN_SALT=tobemodified
|
||||||
JWT_SECRET=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://<account-id>.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 <noreply@choralsync.com>"
|
||||||
|
SMTP_DEFAULT_REPLY_TO=contact@choralsync.com
|
||||||
|
SMTP_DEBUG=false
|
||||||
|
|
||||||
|
# Stripe
|
||||||
|
STRIPE_SECRET_KEY=tobemodified
|
||||||
|
STRIPE_WEBHOOK_SECRET=tobemodified
|
||||||
|
|||||||
@@ -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
|
fichier (et jamais cochée uniquement dans l'admin, sinon elle sera signalée
|
||||||
comme non déclarée).
|
comme non déclarée).
|
||||||
- Secrets uniquement en variables d'environnement. `.env` jamais commité ;
|
- Secrets uniquement en variables d'environnement. `.env` jamais commité ;
|
||||||
`.env.example` maintenu à jour.
|
`.env.example` maintenu à jour. Depuis le 26/07/2026, `config/plugins.ts`
|
||||||
⚠️ **DETTE CRITIQUE : `config/plugins.ts` contient des secrets en dur**
|
ne contient plus aucun secret (R2 et SMTP passent par `env()`).
|
||||||
(clé API SMTP ZeptoMail, access/secret keys Cloudflare R2). À migrer vers
|
⚠️ **Les clés historiques ont vécu en clair dans l'historique git** :
|
||||||
`env()` et à faire tourner (rotation des clés).
|
rotation R2 + ZeptoMail à faire, l'historique n'étant pas réécrit.
|
||||||
|
- ⚠️ **Pas de `config/env/<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
|
## Points d'audit récurrents
|
||||||
|
|
||||||
|
|||||||
Vendored
+21
-18
@@ -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 [
|
export default [
|
||||||
"strapi::logger",
|
"strapi::logger",
|
||||||
"strapi::errors",
|
"strapi::errors",
|
||||||
//'strapi::security',
|
|
||||||
{
|
{
|
||||||
name: "strapi::security",
|
name: "strapi::security",
|
||||||
config: {
|
config: {
|
||||||
@@ -9,22 +21,8 @@ export default [
|
|||||||
useDefaults: true,
|
useDefaults: true,
|
||||||
directives: {
|
directives: {
|
||||||
"connect-src": ["'self'", "https:"],
|
"connect-src": ["'self'", "https:"],
|
||||||
"img-src": [
|
"img-src": mediaSources,
|
||||||
"'self'",
|
"media-src": mediaSources,
|
||||||
"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",
|
|
||||||
],
|
|
||||||
upgradeInsecureRequests: null,
|
upgradeInsecureRequests: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -33,7 +31,12 @@ export default [
|
|||||||
"strapi::cors",
|
"strapi::cors",
|
||||||
"strapi::poweredBy",
|
"strapi::poweredBy",
|
||||||
"strapi::query",
|
"strapi::query",
|
||||||
"strapi::body",
|
{
|
||||||
|
name: "strapi::body",
|
||||||
|
config: {
|
||||||
|
includeUnparsed: true, // INDISPENSABLE pour les webhooks
|
||||||
|
},
|
||||||
|
},
|
||||||
"strapi::session",
|
"strapi::session",
|
||||||
"strapi::favicon",
|
"strapi::favicon",
|
||||||
"strapi::public",
|
"strapi::public",
|
||||||
|
|||||||
Vendored
-43
@@ -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 <noreply@choralsync.com>",
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
+35
-17
@@ -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: {
|
email: {
|
||||||
config: {
|
config: {
|
||||||
provider: "nodemailer",
|
provider: "nodemailer",
|
||||||
providerOptions: {
|
providerOptions: {
|
||||||
host: "smtp.zeptomail.eu",
|
host: env("SMTP_HOST", "smtp.zeptomail.eu"),
|
||||||
port: 587,
|
port: env.int("SMTP_PORT", 587),
|
||||||
auth: {
|
auth: {
|
||||||
user: "emailapikey",
|
user: env("SMTP_USERNAME", "emailapikey"),
|
||||||
pass: "yA6KbHsJ4lrywWtTFUc+0pSC94lm/aE/2nzks3i2fpZ1LYXp3qE71RBvd4O4c2CLjdfT5a9UbIkVJoCwvIpbfpczPIBXJpTGTuv4P2uV48xh8ciEYNYjhJivALIWFqVOeBsnDyo4QfEjWA==",
|
pass: env("SMTP_PASSWORD"),
|
||||||
},
|
},
|
||||||
debug: true,
|
debug: env.bool("SMTP_DEBUG", false),
|
||||||
logger: true,
|
logger: env.bool("SMTP_DEBUG", false),
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
defaultFrom: "ChoralSync <noreply@choralsync.com>",
|
defaultFrom: env(
|
||||||
defaultReplyTo: "contact@choralsync.com",
|
"SMTP_DEFAULT_FROM",
|
||||||
|
"ChoralSync <noreply@choralsync.com>",
|
||||||
|
),
|
||||||
|
defaultReplyTo: env("SMTP_DEFAULT_REPLY_TO", "contact@choralsync.com"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -23,18 +37,22 @@ export default () => ({
|
|||||||
config: {
|
config: {
|
||||||
provider: "aws-s3",
|
provider: "aws-s3",
|
||||||
providerOptions: {
|
providerOptions: {
|
||||||
baseUrl: "https://container.choralsync.com",
|
// 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: {
|
credentials: {
|
||||||
accessKeyId: "a6f474b04f010543022788efc626cbfe",
|
accessKeyId: env("R2_ACCESS_KEY_ID"),
|
||||||
secretAccessKey:
|
secretAccessKey: env("R2_SECRET_ACCESS_KEY"),
|
||||||
"eeeb631194378edee5145b3ef6314630b152fa186b3dab2fefdf8b48952cde21",
|
|
||||||
},
|
},
|
||||||
endpoint:
|
endpoint: env("R2_ENDPOINT"),
|
||||||
"https://4c59d3c67191bdd08e311a5a49c3ac98.r2.cloudflarestorage.com",
|
region: env("R2_REGION", "auto"),
|
||||||
forcePathStyle: true,
|
forcePathStyle: true,
|
||||||
region: "weur",
|
|
||||||
params: {
|
params: {
|
||||||
Bucket: "choralsync",
|
Bucket: env("R2_BUCKET"),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "harmony-back",
|
"name": "harmony-back",
|
||||||
"version": "0.13.13",
|
"version": "0.13.14",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "A Strapi application",
|
"description": "A Strapi application",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Reference in New Issue
Block a user