namespace Bifrost.Web.Configuration; /// /// The OIDC provider surface (section Bifrost:Oidc). An absent section means the provider is not mapped at /// all; a present section is complete or startup fails. Keys and the client secret are named by file path, never inlined. /// public sealed class OidcIssuerOptions { public const string SectionName = "Bifrost:Oidc"; /// PEM file holding the EC P-256 private key that signs tokens (ES256). public string SigningKeyPath { get; init; } = string.Empty; /// File holding the 32-byte symmetric token-encryption key as base64. public string EncryptionKeyPath { get; init; } = string.Empty; public OidcClientsOptions Clients { get; init; } = new(); public OidcLimitsOptions Limits { get; init; } = new(); /// /// Optional path of the issuer's brake marker. While a file exists there, /connect/* answers 503 and nothing is issued; /// remove it to release. Checked on every request (one stat), so engaging and releasing need no restart. /// Unset: the issuer has no marker brake (removing this section and restarting is the other off switch). /// public string BrakePath { get; init; } = string.Empty; /// Null when the section is absent. public static OidcIssuerOptions? FromConfiguration(IConfiguration configuration) { var section = configuration.GetSection(SectionName); return section.Exists() ? section.Get() ?? new OidcIssuerOptions() : null; } } public sealed class OidcClientsOptions { /// Matrix Authentication Service, the one client of this provider. public OidcClientOptions Mas { get; init; } = new(); } public sealed class OidcClientOptions { public string ClientId { get; init; } = string.Empty; /// File holding the client secret. public string ClientSecretPath { get; init; } = string.Empty; public string RedirectUri { get; init; } = string.Empty; /// MAS's back-channel logout URL for this provider. Empty: no logout is sent and no session is ended. public string BackchannelLogoutUri { get; init; } = string.Empty; } /// /// One budget on /connect/authorize, /connect/token and /connect/userinfo together, per source address in one fixed window (a refused caller gets 429 with Retry-After), /// and the bound on what the anonymous authorize endpoint can park. No per-client budget: the client id is public. The defaults suit one client (MAS) behind one proxy. /// public sealed class OidcLimitsOptions { public int PerAddressPermits { get; init; } = 30; public TimeSpan Window { get; init; } = TimeSpan.FromMinutes(1); public int MaxTrackedKeys { get; init; } = 10_000; /// The most OpenIddict token rows (parked authorize requests, codes, access tokens) allowed to exist; a new authorize request beyond it is refused until the prune frees room. public int MaxTokenRows { get; init; } = 20_000; /// How often expired and orphaned authorizations and tokens are deleted. public TimeSpan PruneInterval { get; init; } = TimeSpan.FromMinutes(5); }