Connexion réseaux : adresse de retour déduite de la requête, CSP sans upgrade en HTTP

Cliquer sur « Connecter » sans avoir enregistré l'URL publique du site
renvoyait une erreur, et la redirection retombait sur https://0.0.0.0 :
la directive upgrade-insecure-requests était posée même sur une page
servie en HTTP, le navigateur basculait donc la navigation vers un
https:// inexistant.

- `publicBaseUrl()` : réglage `site_public_url`, variables d'environnement,
  puis origine de la requête (X-Forwarded-Host / X-Forwarded-Proto). L'adresse
  de retour OAuth et le lien des publications l'utilisent ; `seo-server`
  s'appuie dessus au lieu de dupliquer la déduction.
- `src/lib/site-url.ts` : déduction pure et testée de l'origine, HTTP par
  défaut pour localhost et les IP nues, HTTPS pour un nom de domaine.
- Middleware : `upgrade-insecure-requests` uniquement quand la page est servie
  en HTTPS.
- Page Réseaux : l'adresse détectée est annoncée comme valeur par défaut, avec
  le rappel que Facebook et LinkedIn exigent du HTTPS sur un domaine.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QXNXRC4j5VLfKvpyyisrnb
This commit is contained in:
Claude committed 2026-09-06 10:11:06 +00:00
1 parent 23d1d14a49
commit 17511a73c3
10 files changed
+157 -41

No files matched your search

+24 -1
View File
@@ -1,7 +1,9 @@
import { eq } from "drizzle-orm";
import { headers } from "next/headers";
import { db } from "@/db";
import { siteSettings, socialAccounts, type SocialAccount, type SocialNetwork } from "@/db/schema";
import { decryptSecret, encryptSecret } from "./crypto";
import { originFromHeaders } from "./site-url";
/**
* Configuration des comptes Facebook / LinkedIn.
@@ -43,8 +45,29 @@ export async function siteUrl(): Promise<string> {
return (stored || env("NEXT_PUBLIC_SITE_URL") || env("AUTH_URL")).replace(/\/+$/, "");
}
/**
* URL publique effective : le réglage s'il existe, sinon l'adresse par laquelle
* la requête en cours est arrivée (en-têtes du reverse proxy compris). Chaîne
* vide hors requête si rien n'est configuré.
*/
export async function publicBaseUrl(): Promise<string> {
const configured = await siteUrl().catch(() => "");
if (configured) return configured;
try {
return originFromHeaders(await headers());
} catch {
// Hors requête (génération de robots/sitemap au build par exemple).
return "";
}
}
/**
* Adresse de retour OAuth. Elle doit être identique au clic sur « Connecter »
* et au retour du réseau : les deux passent par le même hôte, la déduction
* depuis la requête est donc stable.
*/
export async function redirectUri(network: SocialNetwork): Promise<string> {
return `${await siteUrl()}/api/social/${network}/callback`;
return `${await publicBaseUrl()}/api/social/${network}/callback`;
}
// ---- Lecture ----