using MaBibli.Shared.Dtos; namespace MaBibli.Api.Services.Identite; public interface IFournisseurUtilisateur { /// Identité de la requête en cours, jamais null (au pire ). UtilisateurCourant Obtenir(); } /// /// Options de l'identité, section Identite de la configuration. /// public sealed class OptionsIdentite { public const string Section = "Identite"; /// /// Utilisateur simulé en développement, où SSOwat n'existe pas. /// /// /// CLAUDE.md : « prévoir un utilisateur simulé plutôt que de désactiver l'auth ». /// Il n'est utilisé que si les en-têtes SSOwat sont absents, et n'est renseigné que dans /// appsettings.Development.json — en production, aucune valeur, donc aucun repli. /// public string? UtilisateurSimule { get; set; } public string? EmailSimule { get; set; } public string? NomCompletSimule { get; set; } } /// /// Lit l'identité dans les en-têtes HTTP injectés par SSOwat. /// /// /// nginx authentifie le visiteur via le portail YunoHost puis pousse YNH_USER, /// YNH_USER_EMAIL et YNH_USER_FULLNAME. L'application se contente de les lire. /// /// ⚠️ Ces en-têtes ne sont dignes de confiance que parce que le service n'écoute que sur /// 127.0.0.1 derrière nginx : exposé directement, n'importe qui pourrait forger /// YNH_USER. Cette contrainte se règle dans systemd.service / nginx.conf, /// qui vivent dans le dépôt mabibli_ynh (phase 7) — pas ici. /// /// public sealed class FournisseurUtilisateurSsowat( IHttpContextAccessor accesseur, Microsoft.Extensions.Options.IOptions options) : IFournisseurUtilisateur { public const string EnteteUtilisateur = "YNH_USER"; public const string EnteteEmail = "YNH_USER_EMAIL"; public const string EnteteNomComplet = "YNH_USER_FULLNAME"; public UtilisateurCourant Obtenir() { var entetes = accesseur.HttpContext?.Request.Headers; var identifiant = Valeur(entetes, EnteteUtilisateur); if (identifiant is not null) { return new UtilisateurCourant { Identifiant = identifiant, Email = Valeur(entetes, EnteteEmail), NomComplet = Valeur(entetes, EnteteNomComplet), Simule = false, }; } var simule = options.Value; if (!string.IsNullOrWhiteSpace(simule.UtilisateurSimule)) { return new UtilisateurCourant { Identifiant = simule.UtilisateurSimule, Email = simule.EmailSimule, NomComplet = simule.NomCompletSimule, Simule = true, }; } // Ni en-tête, ni utilisateur simulé : on n'invente pas d'identité. // Le livre sera enregistré sans traçabilité plutôt que sous un faux nom. return UtilisateurCourant.Anonyme; } private static string? Valeur(IHeaderDictionary? entetes, string nom) { if (entetes is null || !entetes.TryGetValue(nom, out var valeurs)) { return null; } var valeur = valeurs.ToString().Trim(); return valeur.Length == 0 ? null : valeur; } }