Gestion de bibliothèque personnelle auto-hébergée : catalogue, prêts, scan de code-barres, consultation hors-ligne. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
98 lines
3.4 KiB
C#
98 lines
3.4 KiB
C#
using MaBibli.Shared.Dtos;
|
|
|
|
namespace MaBibli.Api.Services.Identite;
|
|
|
|
public interface IFournisseurUtilisateur
|
|
{
|
|
/// <summary>Identité de la requête en cours, jamais <c>null</c> (au pire <see cref="UtilisateurCourant.Anonyme"/>).</summary>
|
|
UtilisateurCourant Obtenir();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Options de l'identité, section <c>Identite</c> de la configuration.
|
|
/// </summary>
|
|
public sealed class OptionsIdentite
|
|
{
|
|
public const string Section = "Identite";
|
|
|
|
/// <summary>
|
|
/// Utilisateur simulé en développement, où SSOwat n'existe pas.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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
|
|
/// <c>appsettings.Development.json</c> — en production, aucune valeur, donc aucun repli.
|
|
/// </remarks>
|
|
public string? UtilisateurSimule { get; set; }
|
|
|
|
public string? EmailSimule { get; set; }
|
|
|
|
public string? NomCompletSimule { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lit l'identité dans les en-têtes HTTP injectés par SSOwat.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// nginx authentifie le visiteur via le portail YunoHost puis pousse <c>YNH_USER</c>,
|
|
/// <c>YNH_USER_EMAIL</c> et <c>YNH_USER_FULLNAME</c>. L'application se contente de les lire.
|
|
/// <para>
|
|
/// ⚠️ Ces en-têtes ne sont dignes de confiance que parce que le service n'écoute que sur
|
|
/// <c>127.0.0.1</c> derrière nginx : exposé directement, n'importe qui pourrait forger
|
|
/// <c>YNH_USER</c>. Cette contrainte se règle dans <c>systemd.service</c> / <c>nginx.conf</c>,
|
|
/// qui vivent dans le dépôt <c>mabibli_ynh</c> (phase 7) — pas ici.
|
|
/// </para>
|
|
/// </remarks>
|
|
public sealed class FournisseurUtilisateurSsowat(
|
|
IHttpContextAccessor accesseur,
|
|
Microsoft.Extensions.Options.IOptions<OptionsIdentite> 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;
|
|
}
|
|
}
|