2026-10-27 04:57:16 +01:00
namespace Bifrost.Web.Configuration ;
/// <summary>
/// The OIDC provider surface (section <c>Bifrost:Oidc</c>). 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.
/// </summary>
public sealed class OidcIssuerOptions
{
public const string SectionName = "Bifrost:Oidc" ;
/// <summary>PEM file holding the EC P-256 private key that signs tokens (ES256).</summary>
public string SigningKeyPath { get ; init ; } = string . Empty ;
/// <summary>File holding the 32-byte symmetric token-encryption key as base64.</summary>
public string EncryptionKeyPath { get ; init ; } = string . Empty ;
public OidcClientsOptions Clients { get ; init ; } = new ( ) ;
2026-11-11 08:12:48 +00:00
public OidcLimitsOptions Limits { get ; init ; } = new ( ) ;
/// <summary>
/// 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).
/// </summary>
public string BrakePath { get ; init ; } = string . Empty ;
2026-10-27 04:57:16 +01:00
/// <summary>Null when the section is absent.</summary>
public static OidcIssuerOptions ? FromConfiguration ( IConfiguration configuration )
{
var section = configuration . GetSection ( SectionName ) ;
return section . Exists ( ) ? section . Get < OidcIssuerOptions > ( ) ? ? new OidcIssuerOptions ( ) : null ;
}
}
public sealed class OidcClientsOptions
{
/// <summary>Matrix Authentication Service, the one client of this provider.</summary>
public OidcClientOptions Mas { get ; init ; } = new ( ) ;
}
public sealed class OidcClientOptions
{
public string ClientId { get ; init ; } = string . Empty ;
/// <summary>File holding the client secret.</summary>
public string ClientSecretPath { get ; init ; } = string . Empty ;
public string RedirectUri { get ; init ; } = string . Empty ;
2026-10-28 00:38:15 +01:00
/// <summary>MAS's back-channel logout URL for this provider. Empty: no logout is sent and no session is ended.</summary>
public string BackchannelLogoutUri { get ; init ; } = string . Empty ;
2026-10-27 04:57:16 +01:00
}
2026-11-11 08:12:48 +00:00
/// <summary>
/// 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.
/// </summary>
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 ;
/// <summary>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.</summary>
public int MaxTokenRows { get ; init ; } = 20_000 ;
/// <summary>How often expired and orphaned authorizations and tokens are deleted.</summary>
public TimeSpan PruneInterval { get ; init ; } = TimeSpan . FromMinutes ( 5 ) ;
}