Files
PleinR/src/lib/site-url.ts
T
Claude 17511a73c3 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
2026-09-06 10:11:06 +00:00

51 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Déduction de l'origine publique du site à partir des en-têtes d'une requête.
*
* Sans dépendance à la base ni à Next : utilisable partout et testable. Sert de
* repli quand aucune URL publique n'est enregistrée dans Backend › Réseaux
* sociaux : derrière Caddy ou Nginx Proxy Manager, `X-Forwarded-Host` et
* `X-Forwarded-Proto` décrivent l'adresse vue par le visiteur.
*/
type HeaderReader = { get(name: string): string | null };
const LOCAL_HOSTS = /^(localhost|.*\.localhost|127(?:\.\d{1,3}){3}|0\.0\.0\.0|\[::1\]|\[::\])$/i;
const IPV4 = /^\d{1,3}(?:\.\d{1,3}){3}$/;
function first(value: string | null): string {
return (value ?? "").split(",")[0]?.trim() ?? "";
}
/** Nom d'hôte sans port, en minuscules. */
function hostname(host: string): string {
const match = /^(\[[^\]]*\]|[^:]+)/.exec(host);
return (match?.[1] ?? host).toLowerCase();
}
/**
* Sans `X-Forwarded-Proto`, on présume HTTPS pour un nom de domaine — c'est le
* cas de tout déploiement réel — et HTTP pour une adresse locale ou une IP nue,
* qu'aucun certificat ne couvre.
*/
export function defaultProtocol(host: string): "http" | "https" {
const name = hostname(host);
if (!name || LOCAL_HOSTS.test(name) || IPV4.test(name)) return "http";
return "https";
}
/**
* Origine (`https://pleinr.example.fr`) vue par le visiteur, ou chaîne vide si
* la requête ne porte aucun hôte.
*/
export function originFromHeaders(headers: HeaderReader): string {
const host = first(headers.get("x-forwarded-host")) || first(headers.get("host"));
if (!host || /[\s/\\]/.test(host)) return "";
const forwarded = first(headers.get("x-forwarded-proto")).toLowerCase();
const protocol = forwarded === "https" || forwarded === "http" ? forwarded : defaultProtocol(host);
try {
return new URL(`${protocol}://${host}`).origin;
} catch {
return "";
}
}