Files
PleinR/next.config.mjs
T
Claude 8d4443a1a1 Boîte mail de l'association : trois transports, une file d'attente
Le site savait composer de beaux messages et ne savait pas les envoyer :
`email-client.ts` ne proposait que la copie ou le téléchargement d'un
brouillon Outlook, complété à la main. `/backend/boite-mail` branche
désormais la boîte de l'association, sur le modèle exact de l'écran
Réseaux sociaux — secrets chiffrés, jamais renvoyés au navigateur, champ
laissé vide qui conserve la valeur enregistrée, contrôle de santé qui ne
lève jamais.

Trois transports, un seul actif à la fois, désigné par l'administrateur —
pas de cascade automatique : si Google se bloque, la bascule se voit.

- **Google** passe par l'API Gmail plutôt que par SMTP avec XOAUTH2 :
  celui-ci exigerait `https://mail.google.com/`, portée *restreinte* et
  donc audit de sécurité, là où `gmail.send` est simplement *sensible*.
- **Microsoft** passe par Graph : l'authentification basique SMTP est
  désactivée depuis 2024, y compris sur outlook.com et hotmail.com.
- **SMTP** couvre le reste via `nodemailer`, seule dépendance ajoutée.

`from_address` est **lu chez le fournisseur** et non saisi : Gmail expédie
comme l'utilisateur authentifié, Graph comme la boîte. Une adresse d'un
autre domaine ferait tomber SPF et DKIM.

La file `mail_messages` porte un destinataire unique par ligne — la
confidentialité d'une diffusion est structurelle, aucune copie partagée
n'est possible. Elle est vidée par la boucle de fond, avec réclamation en
`FOR UPDATE SKIP LOCKED`, réessais espacés et reprise des verrous laissés
par un conteneur arrêté en plein envoi.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QsLjRAnuLwivqxCbM4WeP
2026-09-14 16:00:11 +00:00

58 lines
2.3 KiB
JavaScript

/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone",
reactStrictMode: true,
// Pas d'en-tête X-Powered-By : inutile de renseigner un attaquant sur la pile.
poweredByHeader: false,
// `nodemailer` charge net/tls/dns par des requires dynamiques : laissé au
// bundler, il se retrouve à moitié inliné et casse à l'exécution.
serverExternalPackages: ["nodemailer"],
experimental: {
// Les images d'entête/logo sont envoyées en data-URL via server action.
serverActions: { bodySizeLimit: "4mb" },
},
// `instrumentation.ts` est compilé pour les deux runtimes dès qu'un
// middleware existe. Son chargement du libérateur de publications est déjà
// conditionné à `NEXT_RUNTIME === "nodejs"`, mais webpack suit quand même
// l'import et tente d'embarquer `pg` (donc `fs`, `path`, `stream`) dans le
// bundle edge, où ces modules n'existent pas. On coupe la branche à la
// source : côté edge, le module Node n'est tout simplement pas résolu.
webpack: (config, { nextRuntime, webpack }) => {
if (nextRuntime === "edge") {
config.plugins.push(
new webpack.IgnorePlugin({ resourceRegExp: /^\.\/instrumentation-node$/ })
);
}
return config;
},
async headers() {
return [
{
source: "/:path*",
headers: [
// HSTS : une fois le site vu en HTTPS, le navigateur refuse le HTTP
// clair pendant un an. Sans effet tant que le site est servi en HTTP.
{
key: "Strict-Transport-Security",
value: "max-age=31536000; includeSubDomains",
},
// Empêche l'interprétation d'une réponse selon un type deviné.
{ key: "X-Content-Type-Options", value: "nosniff" },
// Le site ne doit pas être encadré par un tiers (clickjacking).
{ key: "X-Frame-Options", value: "SAMEORIGIN" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=(), interest-cohort=()",
},
// La CSP complète est posée par le middleware : elle a besoin d'un
// nonce différent à chaque requête, ce qu'une valeur statique ne peut
// pas fournir.
],
},
];
},
};
export default nextConfig;