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);
}