// FIXME: Update this file to be null safe and then delete the line below #nullable disable using Bit.Core.Auth.Settings; namespace Bit.Core.Settings; public class GlobalSettings : IGlobalSettings { private string _mailTemplateDirectory; private string _licenseDirectory; public GlobalSettings() { BaseServiceUri = new BaseServiceUriSettings(this); Attachment = new FileStorageSettings(this, "attachments", "attachments"); Send = new FileStorageSettings(this, "attachments/send", "attachments/send"); DataProtection = new DataProtectionSettings(this); } public bool SelfHosted { get; set; } public bool LiteDeployment { get; set; } public virtual string KnownProxies { get; set; } public virtual string SiteName { get; set; } public virtual string ProjectName { get; set; } public virtual string LicenseDirectory { get => BuildDirectory(_licenseDirectory, "/core/licenses"); set => _licenseDirectory = value; } public virtual string MailTemplateDirectory { get => BuildDirectory(_mailTemplateDirectory, "/mail-templates"); set => _mailTemplateDirectory = value; } public string LicenseCertificatePassword { get; set; } public virtual string PushRelayBaseUri { get; set; } public virtual string InternalIdentityKey { get; set; } public virtual string OidcIdentityClientKey { get; set; } public virtual string HibpApiKey { get; set; } public virtual bool DisableUserRegistration { get; set; } public virtual bool DisableEmailNewDevice { get; set; } public virtual bool EnableNewDeviceVerification { get; set; } public virtual bool EnableCloudCommunication { get; set; } = false; public virtual int OrganizationInviteExpirationHours { get; set; } = 120; // 5 days public virtual string EventGridKey { get; set; } public virtual IInstallationSettings Installation { get; set; } = new InstallationSettings(); public virtual IBaseServiceUriSettings BaseServiceUri { get; set; } public virtual string DatabaseProvider { get; set; } public virtual SqlSettings SqlServer { get; set; } = new SqlSettings(); public virtual SqlSettings PostgreSql { get; set; } = new SqlSettings(); public virtual SqlSettings MySql { get; set; } = new SqlSettings(); public virtual SqlSettings Sqlite { get; set; } = new SqlSettings() { ConnectionString = "Data Source=:memory:" }; public virtual SlackSettings Slack { get; set; } = new SlackSettings(); public virtual TeamsSettings Teams { get; set; } = new TeamsSettings(); public virtual EventLoggingSettings EventLogging { get; set; } = new EventLoggingSettings(); public virtual MailSettings Mail { get; set; } = new MailSettings(); public virtual IConnectionStringSettings Storage { get; set; } = new ConnectionStringSettings(); public virtual ConnectionStringSettings Events { get; set; } = new ConnectionStringSettings(); public virtual DistributedCacheSettings DistributedCache { get; set; } = new DistributedCacheSettings(); public virtual NotificationsSettings Notifications { get; set; } = new NotificationsSettings(); public virtual IFileStorageSettings Attachment { get; set; } public virtual FileStorageSettings Send { get; set; } public virtual IdentityServerSettings IdentityServer { get; set; } = new IdentityServerSettings(); public virtual DataProtectionSettings DataProtection { get; set; } public virtual NotificationHubPoolSettings NotificationHubPool { get; set; } = new(); public virtual YubicoSettings Yubico { get; set; } = new YubicoSettings(); public virtual DuoSettings Duo { get; set; } = new DuoSettings(); public virtual BraintreeSettings Braintree { get; set; } = new BraintreeSettings(); public virtual ImportCiphersLimitationSettings ImportCiphersLimitation { get; set; } = new ImportCiphersLimitationSettings(); public virtual BitPaySettings BitPay { get; set; } = new BitPaySettings(); public virtual AmazonSettings Amazon { get; set; } = new AmazonSettings(); public virtual ServiceBusSettings ServiceBus { get; set; } = new ServiceBusSettings(); public virtual AppleIapSettings AppleIap { get; set; } = new AppleIapSettings(); public virtual ISsoSettings Sso { get; set; } = new SsoSettings(); public virtual StripeSettings Stripe { get; set; } = new StripeSettings(); public virtual DistributedIpRateLimitingSettings DistributedIpRateLimiting { get; set; } = new DistributedIpRateLimitingSettings(); public virtual IPasswordlessAuthSettings PasswordlessAuth { get; set; } = new PasswordlessAuthSettings(); public virtual IDomainVerificationSettings DomainVerification { get; set; } = new DomainVerificationSettings(); public virtual ILaunchDarklySettings LaunchDarkly { get; set; } = new LaunchDarklySettings(); public virtual string DevelopmentDirectory { get; set; } public virtual IWebPushSettings WebPush { get; set; } = new WebPushSettings(); public virtual IPhishingDomainSettings PhishingDomain { get; set; } = new PhishingDomainSettings(); public virtual int SendAccessTokenLifetimeInMinutes { get; set; } = 5; public virtual bool EnableEmailVerification { get; set; } public virtual string KdfDefaultHashKey { get; set; } /// /// This Hash Key is used to prevent enumeration attacks against the Send Access feature. /// public virtual string SendDefaultHashKey { get; set; } public virtual string PricingUri { get; set; } public virtual Fido2Settings Fido2 { get; set; } = new Fido2Settings(); public string BuildExternalUri(string explicitValue, string name) { if (!string.IsNullOrWhiteSpace(explicitValue)) { return explicitValue; } if (!SelfHosted) { return null; } return string.Format("{0}/{1}", BaseServiceUri.Vault, name); } public string BuildInternalUri(string explicitValue, string name) { if (!string.IsNullOrWhiteSpace(explicitValue)) { return explicitValue; } if (!SelfHosted) { return null; } return string.Format("http://{0}:5000", name); } public string BuildDirectory(string explicitValue, string appendedPath) { if (!string.IsNullOrWhiteSpace(explicitValue)) { return explicitValue; } if (!SelfHosted) { return null; } return string.Concat("/etc/bitwarden", appendedPath); } public class BaseServiceUriSettings : IBaseServiceUriSettings { private readonly GlobalSettings _globalSettings; private string _api; private string _identity; private string _admin; private string _notifications; private string _sso; private string _scim; private string _internalApi; private string _internalIdentity; private string _internalAdmin; private string _internalNotifications; private string _internalSso; private string _internalVault; private string _internalScim; private string _internalBilling; public BaseServiceUriSettings(GlobalSettings globalSettings) { _globalSettings = globalSettings; } public string CloudRegion { get; set; } public string Vault { get; set; } public string VaultWithHash => $"{Vault}/#"; public string VaultWithHashAndSecretManagerProduct => $"{Vault}/#/sm"; public string Api { get => _globalSettings.BuildExternalUri(_api, "api"); set => _api = value; } public string Identity { get => _globalSettings.BuildExternalUri(_identity, "identity"); set => _identity = value; } public string Admin { get => _globalSettings.BuildExternalUri(_admin, "admin"); set => _admin = value; } public string Notifications { get => _globalSettings.BuildExternalUri(_notifications, "notifications"); set => _notifications = value; } public string Sso { get => _globalSettings.BuildExternalUri(_sso, "sso"); set => _sso = value; } public string Scim { get => _globalSettings.BuildExternalUri(_scim, "scim"); set => _scim = value; } public string InternalNotifications { get => _globalSettings.BuildInternalUri(_internalNotifications, "notifications"); set => _internalNotifications = value; } public string InternalAdmin { get => _globalSettings.BuildInternalUri(_internalAdmin, "admin"); set => _internalAdmin = value; } public string InternalIdentity { get => _globalSettings.BuildInternalUri(_internalIdentity, "identity"); set => _internalIdentity = value; } public string InternalApi { get => _globalSettings.BuildInternalUri(_internalApi, "api"); set => _internalApi = value; } public string InternalVault { get => _globalSettings.BuildInternalUri(_internalVault, "web"); set => _internalVault = value; } public string InternalSso { get => _globalSettings.BuildInternalUri(_internalSso, "sso"); set => _internalSso = value; } public string InternalScim { get => _globalSettings.BuildInternalUri(_scim, "scim"); set => _internalScim = value; } public string InternalBilling { get => _globalSettings.BuildInternalUri(_internalBilling, "billing"); set => _internalBilling = value; } } public class SqlSettings { private string _connectionString; private string _readOnlyConnectionString; private string _jobSchedulerConnectionString; public bool SkipDatabasePreparation { get; set; } public bool DisableDatabaseMaintenanceJobs { get; set; } public string ConnectionString { get => _connectionString; set { // On development environment, the self-hosted overrides would not override the read-only connection string, since it is already set from the non-self-hosted connection string. // This causes a bug, where the read-only connection string is pointing to self-hosted database. if (!string.IsNullOrWhiteSpace(_readOnlyConnectionString) && _readOnlyConnectionString == _connectionString) { _readOnlyConnectionString = null; } _connectionString = value.Trim('"'); } } public string ReadOnlyConnectionString { get => string.IsNullOrWhiteSpace(_readOnlyConnectionString) ? _connectionString : _readOnlyConnectionString; set => _readOnlyConnectionString = value.Trim('"'); } public string JobSchedulerConnectionString { get => _jobSchedulerConnectionString; set => _jobSchedulerConnectionString = value.Trim('"'); } } public class SlackSettings { public virtual string ApiBaseUrl { get; set; } = "https://slack.com/api"; public virtual string ClientId { get; set; } public virtual string ClientSecret { get; set; } public virtual string Scopes { get; set; } } public class TeamsSettings { public virtual string LoginBaseUrl { get; set; } = "https://login.microsoftonline.com"; public virtual string GraphBaseUrl { get; set; } = "https://graph.microsoft.com/v1.0"; public virtual string ClientId { get; set; } public virtual string ClientSecret { get; set; } public virtual string Scopes { get; set; } } public class EventLoggingSettings { public AzureServiceBusSettings AzureServiceBus { get; set; } = new AzureServiceBusSettings(); public RabbitMqSettings RabbitMq { get; set; } = new RabbitMqSettings(); public int IntegrationCacheRefreshIntervalMinutes { get; set; } = 10; public int MaxRetries { get; set; } = 3; public class AzureServiceBusSettings { private string _connectionString; private string _eventTopicName; private string _integrationTopicName; public virtual int DefaultMaxConcurrentCalls { get; set; } = 1; public virtual int DefaultPrefetchCount { get; set; } = 0; public virtual string EventRepositorySubscriptionName { get; set; } = "events-write-subscription"; public virtual string SlackEventSubscriptionName { get; set; } = "events-slack-subscription"; public virtual string SlackIntegrationSubscriptionName { get; set; } = "integration-slack-subscription"; public virtual string WebhookEventSubscriptionName { get; set; } = "events-webhook-subscription"; public virtual string WebhookIntegrationSubscriptionName { get; set; } = "integration-webhook-subscription"; public virtual string HecEventSubscriptionName { get; set; } = "events-hec-subscription"; public virtual string HecIntegrationSubscriptionName { get; set; } = "integration-hec-subscription"; public virtual string DatadogEventSubscriptionName { get; set; } = "events-datadog-subscription"; public virtual string DatadogIntegrationSubscriptionName { get; set; } = "integration-datadog-subscription"; public virtual string TeamsEventSubscriptionName { get; set; } = "events-teams-subscription"; public virtual string TeamsIntegrationSubscriptionName { get; set; } = "integration-teams-subscription"; public string ConnectionString { get => _connectionString; set => _connectionString = value.Trim('"'); } public string EventTopicName { get => _eventTopicName; set => _eventTopicName = value.Trim('"'); } public string IntegrationTopicName { get => _integrationTopicName; set => _integrationTopicName = value.Trim('"'); } } public class RabbitMqSettings { private string _hostName; private string _username; private string _password; private string _eventExchangeName; private string _integrationExchangeName; public int RetryTiming { get; set; } = 30000; // 30s public bool UseDelayPlugin { get; set; } = false; public virtual string EventRepositoryQueueName { get; set; } = "events-write-queue"; public virtual string IntegrationDeadLetterQueueName { get; set; } = "integration-dead-letter-queue"; public virtual string SlackEventsQueueName { get; set; } = "events-slack-queue"; public virtual string SlackIntegrationQueueName { get; set; } = "integration-slack-queue"; public virtual string SlackIntegrationRetryQueueName { get; set; } = "integration-slack-retry-queue"; public virtual string WebhookEventsQueueName { get; set; } = "events-webhook-queue"; public virtual string WebhookIntegrationQueueName { get; set; } = "integration-webhook-queue"; public virtual string WebhookIntegrationRetryQueueName { get; set; } = "integration-webhook-retry-queue"; public virtual string HecEventsQueueName { get; set; } = "events-hec-queue"; public virtual string HecIntegrationQueueName { get; set; } = "integration-hec-queue"; public virtual string HecIntegrationRetryQueueName { get; set; } = "integration-hec-retry-queue"; public virtual string DatadogEventsQueueName { get; set; } = "events-datadog-queue"; public virtual string DatadogIntegrationQueueName { get; set; } = "integration-datadog-queue"; public virtual string DatadogIntegrationRetryQueueName { get; set; } = "integration-datadog-retry-queue"; public virtual string TeamsEventsQueueName { get; set; } = "events-teams-queue"; public virtual string TeamsIntegrationQueueName { get; set; } = "integration-teams-queue"; public virtual string TeamsIntegrationRetryQueueName { get; set; } = "integration-teams-retry-queue"; public string HostName { get => _hostName; set => _hostName = value.Trim('"'); } public string Username { get => _username; set => _username = value.Trim('"'); } public string Password { get => _password; set => _password = value.Trim('"'); } public string EventExchangeName { get => _eventExchangeName; set => _eventExchangeName = value.Trim('"'); } public string IntegrationExchangeName { get => _integrationExchangeName; set => _integrationExchangeName = value.Trim('"'); } } } public class ConnectionStringSettings : IConnectionStringSettings { private string _connectionString; public string ConnectionString { get => _connectionString; set => _connectionString = value.Trim('"'); } } public class FileStorageSettings : IFileStorageSettings { private readonly GlobalSettings _globalSettings; private readonly string _urlName; private readonly string _directoryName; private string _connectionString; private string _baseDirectory; private string _baseUrl; public FileStorageSettings(GlobalSettings globalSettings, string urlName, string directoryName) { _globalSettings = globalSettings; _urlName = urlName; _directoryName = directoryName; } public string ConnectionString { get => _connectionString; set => _connectionString = value.Trim('"'); } public string BaseDirectory { get => _globalSettings.BuildDirectory(_baseDirectory, string.Concat("/core/", _directoryName)); set => _baseDirectory = value; } public string BaseUrl { get => _globalSettings.BuildExternalUri(_baseUrl, _urlName); set => _baseUrl = value; } } public class MailSettings { private ConnectionStringSettings _connectionStringSettings; public string ConnectionString { get => _connectionStringSettings?.ConnectionString; set { if (_connectionStringSettings == null) { _connectionStringSettings = new ConnectionStringSettings(); } _connectionStringSettings.ConnectionString = value; } } public string ReplyToEmail { get; set; } public string AmazonConfigSetName { get; set; } public SmtpSettings Smtp { get; set; } = new SmtpSettings(); public string SendGridApiKey { get; set; } public int? SendGridPercentage { get; set; } public string SendGridApiHost { get; set; } = "https://api.sendgrid.com"; public class SmtpSettings { public string Host { get; set; } public int Port { get; set; } = 25; public bool StartTls { get; set; } = false; public bool Ssl { get; set; } = false; public bool SslOverride { get; set; } = false; public string Username { get; set; } public string Password { get; set; } public bool TrustServer { get; set; } = false; } } public class IdentityServerSettings { public string CertificateLocation { get; set; } = "identity.pfx"; public string CertificateThumbprint { get; set; } public string CertificatePassword { get; set; } public string RedisConnectionString { get; set; } public string CosmosConnectionString { get; set; } public string LicenseKey { get; set; } = "eyJhbGciOiJQUzI1NiIsImtpZCI6IklkZW50aXR5U2VydmVyTGljZW5zZUtleS83Y2VhZGJiNzgxMzA0NjllODgwNjg5MTAyNTQxNGYxNiIsInR5cCI6ImxpY2Vuc2Urand0In0.eyJpc3MiOiJodHRwczovL2R1ZW5kZXNvZnR3YXJlLmNvbSIsImF1ZCI6IklkZW50aXR5U2VydmVyIiwiaWF0IjoxNzY1MDY1NjAwLCJleHAiOjE3OTY1MTUyMDAsImNvbXBhbnlfbmFtZSI6IkJpdHdhcmRlbiBJbmMuIiwiY29udGFjdF9pbmZvIjoiY29udGFjdEBkdWVuZGVzb2Z0d2FyZS5jb20iLCJlZGl0aW9uIjoiU3RhcnRlciIsImlkIjoiOTUxNSIsImZlYXR1cmUiOlsiaXN2IiwidW5saW1pdGVkX2NsaWVudHMiXSwiY2xpZW50X2xpbWl0IjowfQ.rWUsq-XBKNwPG7BRKG-vShXHuyHLHJCh0sEWdWT4Rkz4ArIPOAepEp9wNya-hxFKkBTFlPaQ5IKk4wDTvkQkuq1qaI_v6kSCdaP9fvXp0rmh4KcFEffVLB-wAOK2S2Cld5DzdyCoskUUfwNQP7xuLsz2Ydxe_whSRIdv8bsMbvTC3Kl8PYZPZ4MxqW8rSZ_mEuCpSe5-Q40sB7aiu_7YmWLJaKrfBTIqYH-XuzQj36Aemoei0efcntej-gvxovy-5SiSEsGuRZj41rjEZYOuj5KgHihJViO1VDHK6CNtlu2Ks8bkv6G2hO-TkF16Y28ywEG_beLEf_s5dzhbDBDbvA"; /// /// Sliding lifetime of a refresh token in seconds. /// /// Each time the refresh token is used before the sliding window ends, its lifetime is extended by another SlidingRefreshTokenLifetimeSeconds. /// /// If AbsoluteRefreshTokenLifetimeSeconds > 0, the sliding extensions are bounded by the absolute maximum lifetime. /// If SlidingRefreshTokenLifetimeSeconds = 0, sliding mode is invalid (refresh tokens cannot be used). /// public int? SlidingRefreshTokenLifetimeSeconds { get; set; } /// /// Maximum lifetime of a refresh token in seconds. /// /// Token cannot be refreshed by any means beyond the absolute refresh expiration. /// /// When setting this value to 0, the following effect applies: /// If ApplyAbsoluteExpirationOnRefreshToken is set to true, the behavior is the same as when no refresh tokens are used. /// If ApplyAbsoluteExpirationOnRefreshToken is set to false, refresh tokens only expire after the SlidingRefreshTokenLifetimeSeconds has passed. /// public int? AbsoluteRefreshTokenLifetimeSeconds { get; set; } /// /// Controls whether refresh tokens expire absolutely or on a sliding window basis. /// /// Absolute: /// Token expires at a fixed point in time (defined by AbsoluteRefreshTokenLifetimeSeconds). Usage does not extend lifetime. /// /// Sliding(default): /// Token lifetime is renewed on each use, by the amount in SlidingRefreshTokenLifetimeSeconds. Extensions stop once AbsoluteRefreshTokenLifetimeSeconds is reached (if set > 0). /// public bool ApplyAbsoluteExpirationOnRefreshToken { get; set; } = false; } public class DataProtectionSettings { private readonly GlobalSettings _globalSettings; private string _directory; public DataProtectionSettings(GlobalSettings globalSettings) { _globalSettings = globalSettings; } public string CertificateThumbprint { get; set; } public string CertificatePassword { get; set; } public string Directory { get => _globalSettings.BuildDirectory(_directory, "/core/aspnet-dataprotection"); set => _directory = value; } } public class NotificationsSettings : ConnectionStringSettings { public string RedisConnectionString { get; set; } } public class NotificationHubSettings { private string _connectionString; public string ConnectionString { get => _connectionString; set => _connectionString = value?.Trim('"'); } public string HubName { get; set; } /// /// Enables TestSend on the Azure Notification Hub, which allows tracing of the request through the hub and to the platform-specific push notification service (PNS). /// Enabling this will result in delayed responses because the Hub must wait on delivery to the PNS. This should ONLY be enabled in a non-production environment, as results are throttled. /// public bool EnableSendTracing { get; set; } = false; /// /// The date and time at which registration will be enabled. /// /// **This value should not be updated once set, as it is used to determine installation location of devices.** /// /// If null, registration is disabled. /// /// public DateTime? RegistrationStartDate { get; set; } /// /// The date and time at which registration will be disabled. /// /// **This value should not be updated once set, as it is used to determine installation location of devices.** /// /// If null, hub registration has no yet known expiry. /// public DateTime? RegistrationEndDate { get; set; } } public class NotificationHubPoolSettings { /// /// List of Notification Hub settings to use for sending push notifications. /// /// Note that hubs on the same namespace share active device limits, so multiple namespaces should be used to increase capacity. /// public List NotificationHubs { get; set; } = new(); } public class YubicoSettings { public string ClientId { get; set; } public string Key { get; set; } public string[] ValidationUrls { get; set; } } public class DuoSettings { public string AKey { get; set; } } public class BraintreeSettings { public bool Production { get; set; } public string MerchantId { get; set; } public string PublicKey { get; set; } public string PrivateKey { get; set; } } public class ImportCiphersLimitationSettings { public int CiphersLimit { get; set; } public int CollectionRelationshipsLimit { get; set; } public int CollectionsLimit { get; set; } } public class BitPaySettings { public bool Production { get; set; } public string Token { get; set; } public string NotificationUrl { get; set; } public string WebhookKey { get; set; } } public class InstallationSettings : IInstallationSettings { private string _identityUri; private string _apiUri; public Guid Id { get; set; } public string Key { get; set; } public string IdentityUri { get => string.IsNullOrWhiteSpace(_identityUri) ? "https://identity.bitwarden.com" : _identityUri; set => _identityUri = value; } public string ApiUri { get => string.IsNullOrWhiteSpace(_apiUri) ? "https://api.bitwarden.com" : _apiUri; set => _apiUri = value; } } public class AmazonSettings { public string AccessKeyId { get; set; } public string AccessKeySecret { get; set; } public string Region { get; set; } } public class ServiceBusSettings : ConnectionStringSettings { public string ApplicationCacheTopicName { get; set; } public string ApplicationCacheSubscriptionName { get; set; } public string WebSiteInstanceId { get; set; } } public class AppleIapSettings { public string Password { get; set; } public bool AppInReview { get; set; } } public class SsoSettings : ISsoSettings { public int CacheLifetimeInSeconds { get; set; } = 60; public double SsoTokenLifetimeInSeconds { get; set; } = 5; public bool EnforceSsoPolicyForAllUsers { get; set; } } public class StripeSettings { public string ApiKey { get; set; } public int MaxNetworkRetries { get; set; } = 2; } public class PhishingDomainSettings : IPhishingDomainSettings { public string UpdateUrl { get; set; } public string ChecksumUrl { get; set; } } public class DistributedIpRateLimitingSettings { public string RedisConnectionString { get; set; } public bool Enabled { get; set; } = true; /// /// Maximum number of Redis timeouts that can be experienced within the sliding timeout /// window before IP rate limiting is temporarily disabled. /// TODO: Determine/discuss a suitable maximum /// public int MaxRedisTimeoutsThreshold { get; set; } = 10; /// /// Length of the sliding window in seconds to track Redis timeout exceptions. /// TODO: Determine/discuss a suitable sliding window /// public int SlidingWindowSeconds { get; set; } = 120; } public class PasswordlessAuthSettings : IPasswordlessAuthSettings { public bool KnownDevicesOnly { get; set; } = true; public TimeSpan UserRequestExpiration { get; set; } = TimeSpan.FromMinutes(15); public TimeSpan AdminRequestExpiration { get; set; } = TimeSpan.FromDays(7); public TimeSpan AfterAdminApprovalExpiration { get; set; } = TimeSpan.FromHours(12); } public class DomainVerificationSettings : IDomainVerificationSettings { public int VerificationInterval { get; set; } = 12; public int ExpirationPeriod { get; set; } = 7; } public class LaunchDarklySettings : ILaunchDarklySettings { public string SdkKey { get; set; } public string FlagDataFilePath { get; set; } = "flags.json"; public Dictionary FlagValues { get; set; } = new Dictionary(); } public class DistributedCacheSettings { public virtual IConnectionStringSettings Redis { get; set; } = new ConnectionStringSettings(); public virtual IConnectionStringSettings Cosmos { get; set; } = new ConnectionStringSettings(); public ExtendedCacheSettings DefaultExtendedCache { get; set; } = new ExtendedCacheSettings(); } /// /// A collection of Settings for customizing the FusionCache used in extended caching. Defaults are /// provided for every attribute so that only specific values need to be overridden if needed. /// public class ExtendedCacheSettings { public bool EnableDistributedCache { get; set; } = true; public bool UseSharedDistributedCache { get; set; } = true; public IConnectionStringSettings Redis { get; set; } = new ConnectionStringSettings(); public TimeSpan Duration { get; set; } = TimeSpan.FromMinutes(30); public bool IsFailSafeEnabled { get; set; } = true; public TimeSpan FailSafeMaxDuration { get; set; } = TimeSpan.FromHours(2); public TimeSpan FailSafeThrottleDuration { get; set; } = TimeSpan.FromSeconds(30); public float? EagerRefreshThreshold { get; set; } = 0.9f; public TimeSpan FactorySoftTimeout { get; set; } = TimeSpan.FromMilliseconds(100); public TimeSpan FactoryHardTimeout { get; set; } = TimeSpan.FromMilliseconds(1500); public TimeSpan DistributedCacheSoftTimeout { get; set; } = TimeSpan.FromSeconds(1); public TimeSpan DistributedCacheHardTimeout { get; set; } = TimeSpan.FromSeconds(2); public bool AllowBackgroundDistributedCacheOperations { get; set; } = true; public TimeSpan JitterMaxDuration { get; set; } = TimeSpan.FromSeconds(2); public TimeSpan DistributedCacheCircuitBreakerDuration { get; set; } = TimeSpan.FromSeconds(30); } public class WebPushSettings : IWebPushSettings { public string VapidPublicKey { get; set; } } public class Fido2Settings { public HashSet Origins { get; set; } } }