583 lines
23 KiB
Haskell
583 lines
23 KiB
Haskell
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||
{-# OPTIONS_GHC -fno-warn-deprecations #-} -- memcached-binary requires bump
|
||
|
||
-- | Settings are centralized, as much as possible, into this file. This
|
||
-- includes database connection settings, static file locations, etc.
|
||
-- In addition, you can configure a number of different aspects of Yesod
|
||
-- by overriding methods in the Yesod typeclass. That instance is
|
||
-- declared in the Foundation.hs file.
|
||
module Settings
|
||
( module Settings
|
||
, module Settings.Cluster
|
||
, module Settings.Mime
|
||
, module Settings.Cookies
|
||
, module Settings.Log
|
||
) where
|
||
|
||
import Import.NoModel
|
||
import qualified Control.Exception as Exception
|
||
import Data.Aeson (fromJSON, withObject, withScientific
|
||
,(.!=), (.:?)
|
||
)
|
||
import qualified Data.Aeson.Types as Aeson
|
||
import Data.FileEmbed (embedFile)
|
||
import Data.Yaml (decodeEither')
|
||
import Database.Persist.Postgresql (PostgresConf)
|
||
import Network.Wai.Handler.Warp (HostPreference)
|
||
#ifdef DEVELOPMENT
|
||
import Yesod.Default.Util (WidgetFileSettings, widgetFileReload)
|
||
import Language.Haskell.TH.Syntax (Exp, Q, location, Loc(..))
|
||
|
||
import Text.Blaze.Html (preEscapedToHtml)
|
||
#else
|
||
import Yesod.Default.Util (WidgetFileSettings, widgetFileNoReload, widgetFileReload)
|
||
import Language.Haskell.TH.Syntax (Exp, Q)
|
||
#endif
|
||
import qualified Yesod.Auth.Util.PasswordStore as PWStore
|
||
|
||
import Data.Scientific (Scientific, toBoundedInteger)
|
||
import Data.Word (Word16)
|
||
|
||
import qualified Data.Text as Text
|
||
import qualified Data.Text.Encoding as Text
|
||
|
||
import qualified Ldap.Client as Ldap
|
||
|
||
import qualified Network.HaskellNet.Auth as HaskellNet (UserName, Password, AuthType(..))
|
||
import qualified Network.Socket as HaskellNet
|
||
|
||
import Network.Mail.Mime.Instances ()
|
||
|
||
import qualified Database.Memcached.Binary.Types as Memcached
|
||
|
||
import Model
|
||
import Settings.Cluster
|
||
import Settings.Mime
|
||
import Settings.Cookies
|
||
import Settings.Log
|
||
|
||
import qualified System.FilePath as FilePath
|
||
|
||
import Jose.Jwt (JwtEncoding(..))
|
||
|
||
import System.FilePath.Glob
|
||
import Handler.Utils.Submission.TH
|
||
|
||
import qualified Web.ServerSession.Core as ServerSession
|
||
|
||
import Text.Show (showParen, showString)
|
||
|
||
import qualified Data.List.PointedList as P
|
||
|
||
import qualified Network.Minio as Minio
|
||
|
||
|
||
-- | Runtime settings to configure this application. These settings can be
|
||
-- loaded from various sources: defaults, environment variables, config files,
|
||
-- theoretically even a database.
|
||
data AppSettings = AppSettings
|
||
{ appStaticDir :: FilePath
|
||
-- ^ Directory from which to serve static files.
|
||
, appWebpackEntrypoints :: FilePath
|
||
, appWellKnownDir :: FilePath
|
||
, appWellKnownLinkFile :: FilePath
|
||
, appDatabaseConf :: PostgresConf
|
||
-- ^ Configuration settings for accessing the database.
|
||
, appAutoDbMigrate :: Bool
|
||
, appLdapConf :: Maybe (PointedList LdapConf)
|
||
-- ^ Configuration settings for accessing the LDAP-directory
|
||
, appSmtpConf :: Maybe SmtpConf
|
||
-- ^ Configuration settings for accessing a SMTP Mailserver
|
||
, appWidgetMemcachedConf :: Maybe WidgetMemcachedConf
|
||
-- ^ Configuration settings for accessing a Memcached instance for use with `addStaticContent`
|
||
, appRoot :: Maybe Text
|
||
-- ^ Base for all generated URLs. If @Nothing@, determined
|
||
-- from the request headers.
|
||
, appHost :: HostPreference
|
||
-- ^ Host/interface the server should bind to.
|
||
, appPort :: Int
|
||
-- ^ Port to listen on
|
||
, appIpFromHeader :: Bool
|
||
-- ^ Get the IP address from the header when logging. Useful when sitting
|
||
-- behind a reverse proxy.
|
||
|
||
, appServerSessionConfig :: ServerSessionSettings
|
||
, appServerSessionAcidFallback :: Bool
|
||
, appSessionMemcachedConf :: Maybe MemcachedConf
|
||
, appSessionTokenExpiration :: Maybe NominalDiffTime
|
||
, appSessionTokenEncoding :: JwtEncoding
|
||
|
||
, appMailFrom :: Address
|
||
, appMailObjectDomain :: Text
|
||
, appMailVerp :: VerpMode
|
||
, appMailSupport :: Address
|
||
, appJobWorkers :: Natural
|
||
, appJobFlushInterval :: Maybe NominalDiffTime
|
||
, appJobCronInterval :: NominalDiffTime
|
||
, appJobStaleThreshold :: NominalDiffTime
|
||
, appNotificationRateLimit :: NominalDiffTime
|
||
, appNotificationCollateDelay :: NominalDiffTime
|
||
, appNotificationExpiration :: NominalDiffTime
|
||
, appSessionTimeout :: NominalDiffTime
|
||
, appMaximumContentLength :: Maybe Word64
|
||
, appBearerExpiration :: Maybe NominalDiffTime
|
||
, appBearerEncoding :: JwtEncoding
|
||
|
||
, appHealthCheckInterval :: HealthCheck -> Maybe NominalDiffTime
|
||
, appHealthCheckDelayNotify :: Bool
|
||
, appHealthCheckHTTP :: Bool
|
||
|
||
, appHealthCheckActiveJobExecutorsTimeout :: NominalDiffTime
|
||
, appHealthCheckActiveWidgetMemcachedTimeout :: NominalDiffTime
|
||
, appHealthCheckSMTPConnectTimeout :: NominalDiffTime
|
||
, appHealthCheckLDAPAdminsTimeout :: NominalDiffTime
|
||
, appHealthCheckHTTPReachableTimeout :: NominalDiffTime
|
||
, appHealthCheckMatchingClusterConfigTimeout :: NominalDiffTime
|
||
|
||
, appSynchroniseLdapUsersWithin :: Maybe NominalDiffTime
|
||
, appSynchroniseLdapUsersInterval :: NominalDiffTime
|
||
|
||
, appLdapReTestFailover :: DiffTime
|
||
|
||
, appSessionFilesExpire :: NominalDiffTime
|
||
, appPruneUnreferencedFiles :: Maybe NominalDiffTime
|
||
, appKeepUnreferencedFiles :: NominalDiffTime
|
||
|
||
, appInitialLogSettings :: LogSettings
|
||
|
||
, appTransactionLogIPRetentionTime :: NominalDiffTime
|
||
|
||
, appAllocationGradeScale
|
||
, appAllocationGradeOrdinalProportion :: Rational
|
||
|
||
, appReloadTemplates :: Bool
|
||
-- ^ Use the reload version of templates
|
||
, appMutableStatic :: Bool
|
||
-- ^ Assume that files in the static dir may change after compilation
|
||
, appSkipCombining :: Bool
|
||
-- ^ Perform no stylesheet/script combining
|
||
, appAuthDummyLogin :: Bool
|
||
-- ^ Indicate if auth dummy login should be enabled.
|
||
, appAllowDeprecated :: Bool
|
||
-- ^ Indicate if deprecated routes are accessible for everyone
|
||
, appEncryptErrors :: Bool
|
||
|
||
, appUserDefaults :: UserDefaultConf
|
||
, appAuthPWHash :: PWHashConf
|
||
|
||
, appCookieSettings :: RegisteredCookie -> CookieSettings
|
||
|
||
, appMemcachedConf :: Maybe MemcachedConf
|
||
|
||
, appUploadCacheConf :: Maybe Minio.ConnectInfo
|
||
, appUploadCacheBucket :: Minio.Bucket
|
||
, appInjectFiles :: Maybe NominalDiffTime
|
||
|
||
, appFavouritesQuickActionsBurstsize
|
||
, appFavouritesQuickActionsAvgInverseRate :: Word64
|
||
, appFavouritesQuickActionsTimeout :: DiffTime
|
||
, appFavouritesQuickActionsCacheTTL :: Maybe DiffTime
|
||
|
||
, appPersistentTokenBuckets :: TokenBucketIdent -> TokenBucketConf
|
||
|
||
, appInitialInstanceID :: Maybe (Either FilePath UUID)
|
||
, appRibbon :: Maybe Text
|
||
} deriving Show
|
||
|
||
|
||
newtype ServerSessionSettings
|
||
= ServerSessionSettings { applyServerSessionSettings :: forall a. ServerSession.State a -> ServerSession.State a }
|
||
|
||
instance Show ServerSessionSettings where
|
||
showsPrec d _ = showParen (d > 10) $ showString "ServerSessionSettings _"
|
||
|
||
data UserDefaultConf = UserDefaultConf
|
||
{ userDefaultTheme :: Theme
|
||
, userDefaultMaxFavourites, userDefaultMaxFavouriteTerms :: Int
|
||
, userDefaultDateTimeFormat, userDefaultDateFormat, userDefaultTimeFormat :: DateTimeFormat
|
||
, userDefaultDownloadFiles :: Bool
|
||
, userDefaultWarningDays :: NominalDiffTime
|
||
, userDefaultShowSex :: Bool
|
||
} deriving (Show)
|
||
|
||
data PWHashConf = PWHashConf
|
||
{ pwHashAlgorithm :: PWHashAlgorithm
|
||
, pwHashStrength :: Int
|
||
}
|
||
|
||
instance Show PWHashConf where
|
||
show PWHashConf{..} = "PWHashConf { pwHashStrength = " <> show pwHashStrength <> ", .. }"
|
||
|
||
instance FromJSON PWHashConf where
|
||
parseJSON = withObject "PWHashConf" $ \o -> do
|
||
pwHashAlgorithm' <- o .: "algorithm" :: Aeson.Parser Text
|
||
pwHashAlgorithm <- if
|
||
| pwHashAlgorithm' == "pbkdf1" -> return PWStore.pbkdf1
|
||
| pwHashAlgorithm' == "pbkdf2" -> return PWStore.pbkdf2
|
||
| otherwise -> fail "Unsupported hash algorithm"
|
||
pwHashStrength <- o .: "strength"
|
||
|
||
return PWHashConf{..}
|
||
|
||
data LdapConf = LdapConf
|
||
{ ldapHost :: Ldap.Host, ldapPort :: Ldap.PortNumber
|
||
, ldapDn :: Ldap.Dn, ldapPassword :: Ldap.Password
|
||
, ldapBase :: Ldap.Dn
|
||
, ldapScope :: Ldap.Scope
|
||
, ldapTimeout :: NominalDiffTime
|
||
, ldapSearchTimeout :: Int32
|
||
, ldapPool :: ResourcePoolConf
|
||
} deriving (Show)
|
||
|
||
data SmtpConf = SmtpConf
|
||
{ smtpHost :: HaskellNet.HostName
|
||
, smtpPort :: HaskellNet.PortNumber
|
||
, smtpAuth :: Maybe SmtpAuthConf
|
||
, smtpSsl :: SmtpSslMode
|
||
, smtpPool :: ResourcePoolConf
|
||
} deriving (Show)
|
||
|
||
data WidgetMemcachedConf = WidgetMemcachedConf
|
||
{ widgetMemcachedConf :: MemcachedConf
|
||
, widgetMemcachedBaseUrl :: Text
|
||
} deriving (Show)
|
||
|
||
data MemcachedConf = MemcachedConf
|
||
{ memcachedConnectInfo :: Memcached.ConnectInfo
|
||
, memcachedExpiry :: Maybe NominalDiffTime
|
||
} deriving (Show)
|
||
|
||
instance FromJSON Memcached.Auth where
|
||
parseJSON = Aeson.withText "Auth" $ \(Text.breakOn "@" -> (encodeUtf8 -> user, encodeUtf8 -> pw)) -> return $ Memcached.Plain user pw
|
||
|
||
instance FromJSON MemcachedConf where
|
||
parseJSON = withObject "MemcachedConf" $ \o -> do
|
||
connectHost <- o .:? "host" .!= ""
|
||
connectPort <- o .: "port"
|
||
connectAuth <- o .: "auth"
|
||
numConnection <- o .: "limit"
|
||
connectionIdleTime <- o .: "timeout"
|
||
memcachedExpiry <- assertM (maybe True $ \t -> 0 < t && t <= 30 * nominalDay) $ o .:? "expiration"
|
||
|
||
return MemcachedConf{ memcachedConnectInfo = Memcached.ConnectInfo{..}, .. }
|
||
|
||
instance FromJSON WidgetMemcachedConf where
|
||
parseJSON v = flip (withObject "WidgetMemcachedConf") v $ \o -> do
|
||
widgetMemcachedConf <- parseJSON v
|
||
widgetMemcachedBaseUrl <- o .:? "base-url" .!= ""
|
||
return WidgetMemcachedConf{..}
|
||
|
||
data ResourcePoolConf = ResourcePoolConf
|
||
{ poolStripes :: Int
|
||
, poolTimeout :: NominalDiffTime
|
||
, poolLimit :: Int
|
||
} deriving (Show)
|
||
|
||
data SmtpSslMode = SmtpSslNone | SmtpSslSmtps | SmtpSslStarttls
|
||
deriving (Show)
|
||
|
||
data SmtpAuthConf = SmtpAuthConf
|
||
{ smtpAuthType :: HaskellNet.AuthType
|
||
, smtpAuthUsername :: HaskellNet.UserName
|
||
, smtpAuthPassword :: HaskellNet.Password
|
||
} deriving (Show)
|
||
|
||
data TokenBucketConf = TokenBucketConf
|
||
{ tokenBucketDepth :: Word64
|
||
, tokenBucketInvRate :: NominalDiffTime
|
||
, tokenBucketInitialValue :: Int64
|
||
} deriving (Eq, Ord, Show, Generic, Typeable)
|
||
|
||
deriveJSON defaultOptions
|
||
{ fieldLabelModifier = camelToPathPiece' 2
|
||
} ''TokenBucketConf
|
||
|
||
deriveFromJSON defaultOptions ''Ldap.Scope
|
||
deriveFromJSON defaultOptions
|
||
{ fieldLabelModifier = camelToPathPiece' 2
|
||
} ''UserDefaultConf
|
||
|
||
instance FromJSON LdapConf where
|
||
parseJSON = withObject "LdapConf" $ \o -> do
|
||
ldapTls <- o .:? "tls"
|
||
tlsSettings <- case ldapTls :: Maybe String of
|
||
Just spec
|
||
| spec == "insecure" -> return $ Just Ldap.insecureTlsSettings
|
||
| spec == "default" -> return $ Just Ldap.defaultTlsSettings
|
||
| null spec -> return Nothing
|
||
Nothing -> return Nothing
|
||
_otherwise -> fail "Could not parse LDAP TLSSettings"
|
||
ldapHost <- maybe Ldap.Plain (flip Ldap.Tls) tlsSettings <$> o .:? "host" .!= ""
|
||
ldapPort <- (fromIntegral :: Int -> Ldap.PortNumber) <$> o .: "port"
|
||
ldapDn <- Ldap.Dn <$> o .:? "user" .!= ""
|
||
ldapPassword <- Ldap.Password . Text.encodeUtf8 <$> o .:? "pass" .!= ""
|
||
ldapBase <- Ldap.Dn <$> o .:? "baseDN" .!= ""
|
||
ldapScope <- o .: "scope"
|
||
ldapTimeout <- o .: "timeout"
|
||
ldapSearchTimeout <- o .: "search-timeout"
|
||
ldapPool <- o .: "pool"
|
||
return LdapConf{..}
|
||
|
||
deriveFromJSON
|
||
defaultOptions
|
||
{ fieldLabelModifier = intercalate "-" . map toLower . drop 1 . splitCamel
|
||
}
|
||
''ResourcePoolConf
|
||
|
||
instance FromJSON HaskellNet.PortNumber where
|
||
parseJSON = withScientific "PortNumber" $ \sciNum -> case toBoundedInteger sciNum of
|
||
Just int -> return $ fromIntegral (int :: Word16)
|
||
Nothing -> fail "Expected whole number of plausible size to denote port"
|
||
|
||
deriveFromJSON
|
||
defaultOptions
|
||
{ constructorTagModifier = unpack . intercalate "-" . Text.splitOn "_" . toLower . pack
|
||
, allNullaryToStringTag = True
|
||
}
|
||
''HaskellNet.AuthType
|
||
|
||
instance FromJSON SmtpConf where
|
||
parseJSON = withObject "SmtpConf" $ \o -> do
|
||
smtpHost <- o .:? "host" .!= ""
|
||
smtpPort <- o .: "port"
|
||
smtpAuth <- assertM (not . null . smtpAuthUsername) <$> o .:? "auth"
|
||
smtpSsl <- o .: "ssl"
|
||
smtpPool <- o .: "pool"
|
||
return SmtpConf{..}
|
||
|
||
deriveFromJSON
|
||
defaultOptions
|
||
{ constructorTagModifier = intercalate "-" . map toLower . drop 2 . splitCamel
|
||
, allNullaryToStringTag = True
|
||
}
|
||
''SmtpSslMode
|
||
|
||
instance FromJSON SmtpAuthConf where
|
||
parseJSON = withObject "SmtpAuthConf" $ \o -> do
|
||
smtpAuthType <- o .: "type"
|
||
smtpAuthUsername <- o .:? "user" .!= ""
|
||
smtpAuthPassword <- o .:? "pass" .!= ""
|
||
return SmtpAuthConf{..}
|
||
|
||
instance FromJSON JwtEncoding where
|
||
parseJSON v@(String _) = JwsEncoding <$> parseJSON v
|
||
parseJSON v = flip (withObject "JwtEncoding") v $ \obj -> asum
|
||
[ do
|
||
alg <- obj .: "alg"
|
||
return $ JwsEncoding alg
|
||
, do
|
||
alg <- obj .: "alg"
|
||
enc <- obj .: "enc"
|
||
return $ JweEncoding alg enc
|
||
]
|
||
|
||
instance FromJSON Minio.ConnectInfo where
|
||
parseJSON v@(String _) = fromString <$> parseJSON v
|
||
parseJSON v = flip (withObject "ConnectInfo") v $ \o -> do
|
||
connectHost <- o .:? "host" .!= ""
|
||
connectPort <- o .: "port"
|
||
connectAccessKey <- o .:? "access-key" .!= ""
|
||
connectSecretKey <- o .:? "secret-key" .!= ""
|
||
connectIsSecure <- o .: "is-secure"
|
||
connectRegion <- o .:? "region" .!= ""
|
||
connectAutoDiscoverRegion <- o .:? "auto-discover-region" .!= True
|
||
connectDisableTLSCertValidation <- o .:? "disable-cert-validation" .!= False
|
||
return Minio.ConnectInfo{..}
|
||
|
||
|
||
instance FromJSON ServerSessionSettings where
|
||
parseJSON = withObject "ServerSession.State" $ \o -> do
|
||
idleTimeout <- o .:? "idle-timeout"
|
||
absoluteTimeout <- o .:? "absolute-timeout"
|
||
timeoutResolution <- o .:? "timeout-resolution"
|
||
persistentCookies <- o .:? "persistent-cookies"
|
||
return $ ServerSessionSettings (appEndo . foldMap Endo $ catMaybes
|
||
[ pure $ ServerSession.setIdleTimeout idleTimeout
|
||
, pure $ ServerSession.setAbsoluteTimeout absoluteTimeout
|
||
, pure $ ServerSession.setTimeoutResolution timeoutResolution
|
||
, ServerSession.setPersistentCookies <$> persistentCookies
|
||
])
|
||
|
||
|
||
instance FromJSON AppSettings where
|
||
parseJSON = withObject "AppSettings" $ \o -> do
|
||
let defaultDev =
|
||
#ifdef DEVELOPMENT
|
||
True
|
||
#else
|
||
False
|
||
#endif
|
||
appStaticDir <- o .: "static-dir"
|
||
appWellKnownDir <- o .: "well-known-dir"
|
||
appWellKnownLinkFile <- o .: "well-known-link-file"
|
||
appWebpackEntrypoints <- o .: "webpack-manifest"
|
||
appDatabaseConf <- o .: "database"
|
||
appAutoDbMigrate <- o .: "auto-db-migrate"
|
||
let nonEmptyHost LdapConf{..} = case ldapHost of
|
||
Ldap.Tls host _ -> not $ null host
|
||
Ldap.Plain host -> not $ null host
|
||
appLdapConf <- P.fromList . mapMaybe (assertM nonEmptyHost) <$> o .:? "ldap" .!= []
|
||
appSmtpConf <- assertM (not . null . smtpHost) <$> o .:? "smtp"
|
||
let validMemcachedConf MemcachedConf{memcachedConnectInfo = Memcached.ConnectInfo{..}, ..} = and
|
||
[ not $ null connectHost
|
||
, numConnection > 0
|
||
, connectionIdleTime >= 0
|
||
]
|
||
validWidgetMemcachedConf WidgetMemcachedConf{..} = and
|
||
[ not $ null widgetMemcachedBaseUrl
|
||
, validMemcachedConf widgetMemcachedConf
|
||
]
|
||
appWidgetMemcachedConf <- assertM validWidgetMemcachedConf <$> o .:? "widget-memcached"
|
||
appSessionMemcachedConf <- assertM validMemcachedConf <$> o .:? "session-memcached"
|
||
appMemcachedConf <- assertM validMemcachedConf <$> o .:? "memcached"
|
||
appRoot <- o .:? "approot"
|
||
appHost <- fromString <$> o .: "host"
|
||
appPort <- o .: "port"
|
||
appIpFromHeader <- o .: "ip-from-header"
|
||
|
||
appMailFrom <- o .: "mail-from"
|
||
appMailObjectDomain <- o .: "mail-object-domain"
|
||
appMailVerp <- fromMaybe VerpNone . join <$> (o .:? "mail-verp" <|> pure Nothing)
|
||
appMailSupport <- o .: "mail-support"
|
||
|
||
appJobWorkers <- o .: "job-workers"
|
||
appJobFlushInterval <- o .:? "job-flush-interval"
|
||
appJobCronInterval <- o .: "job-cron-interval"
|
||
appJobStaleThreshold <- o .: "job-stale-threshold"
|
||
appNotificationRateLimit <- o .: "notification-rate-limit"
|
||
appNotificationCollateDelay <- o .: "notification-collate-delay"
|
||
appNotificationExpiration <- o .: "notification-expiration"
|
||
appBearerExpiration <- o .:? "bearer-expiration"
|
||
appBearerEncoding <- o .: "bearer-encoding"
|
||
|
||
appHealthCheckInterval <- (assertM' (> 0) . ) <$> o .: "health-check-interval"
|
||
appHealthCheckDelayNotify <- o .: "health-check-delay-notify"
|
||
appHealthCheckHTTP <- o .: "health-check-http"
|
||
|
||
appHealthCheckActiveJobExecutorsTimeout <- o .: "health-check-active-job-executors-timeout"
|
||
appHealthCheckActiveWidgetMemcachedTimeout <- o .: "health-check-active-widget-memcached-timeout"
|
||
appHealthCheckSMTPConnectTimeout <- o .: "health-check-smtp-connect-timeout"
|
||
appHealthCheckLDAPAdminsTimeout <- o .: "health-check-ldap-admins-timeout"
|
||
appHealthCheckHTTPReachableTimeout <- o .: "health-check-http-reachable-timeout"
|
||
appHealthCheckMatchingClusterConfigTimeout <- o .: "health-check-matching-cluster-config-timeout"
|
||
|
||
appSessionTimeout <- o .: "session-timeout"
|
||
|
||
appSynchroniseLdapUsersWithin <- o .:? "synchronise-ldap-users-within"
|
||
appSynchroniseLdapUsersInterval <- o .: "synchronise-ldap-users-interval"
|
||
|
||
appLdapReTestFailover <- o .: "ldap-re-test-failover"
|
||
|
||
appSessionFilesExpire <- o .: "session-files-expire"
|
||
appPruneUnreferencedFiles <- o .:? "prune-unreferenced-files"
|
||
appKeepUnreferencedFiles <- o .:? "keep-unreferenced-files" .!= 0
|
||
appInjectFiles <- o .:? "inject-files"
|
||
|
||
appMaximumContentLength <- o .: "maximum-content-length"
|
||
|
||
appReloadTemplates <- o .:? "reload-templates" .!= defaultDev
|
||
appMutableStatic <- o .:? "mutable-static" .!= defaultDev
|
||
appSkipCombining <- o .:? "skip-combining" .!= defaultDev
|
||
appAuthDummyLogin <- o .:? "auth-dummy-login" .!= defaultDev
|
||
appAllowDeprecated <- o .:? "allow-deprecated" .!= defaultDev
|
||
appEncryptErrors <- o .:? "encrypt-errors" .!= not defaultDev
|
||
appServerSessionAcidFallback <- o .:? "server-session-acid-fallback" .!= defaultDev
|
||
|
||
appInitialLogSettings <- o .: "log-settings"
|
||
|
||
appTransactionLogIPRetentionTime <- o .: "ip-retention-time"
|
||
|
||
appAllocationGradeScale <- o .: "allocation-grade-scale" <|> fmap toRational (o .: "allocation-grade-scale" :: Aeson.Parser Scientific)
|
||
appAllocationGradeOrdinalProportion <- o .: "allocation-grade-ordinal-proportion" <|> fmap toRational (o .: "allocation-grade-ordinal-proportion" :: Aeson.Parser Scientific)
|
||
|
||
appUserDefaults <- o .: "user-defaults"
|
||
appAuthPWHash <- o .: "auth-pw-hash"
|
||
|
||
appInitialInstanceID <- runMaybeT $ do
|
||
val <- MaybeT (o .:? "instance-id")
|
||
val' <- lift $ (Right <$> parseJSON val) <|> (Left <$> parseJSON val)
|
||
case val' of
|
||
Left fp -> guard $ FilePath.isValid fp
|
||
_ -> return ()
|
||
return val'
|
||
|
||
appRibbon <- assertM (not . Text.null) . fmap Text.strip <$> o.:? "ribbon"
|
||
|
||
appCookieSettings <- o .: "cookies"
|
||
|
||
appServerSessionConfig' <- o .: "server-sessions"
|
||
let appServerSessionConfig = ServerSessionSettings $ httpOnlyCookie . secureCookie . applyServerSessionSettings appServerSessionConfig'
|
||
where httpOnlyCookie :: forall a. ServerSession.State a -> ServerSession.State a
|
||
httpOnlyCookie = maybe id ServerSession.setHttpOnlyCookies . cookieHttpOnly $ appCookieSettings CookieSession
|
||
secureCookie :: forall a. ServerSession.State a -> ServerSession.State a
|
||
secureCookie = maybe id ServerSession.setSecureCookies . cookieSecure $ appCookieSettings CookieSession
|
||
appSessionTokenExpiration <- o .:? "session-token-expiration"
|
||
appSessionTokenEncoding <- o .: "session-token-encoding"
|
||
|
||
appFavouritesQuickActionsBurstsize <- o .: "favourites-quick-actions-burstsize"
|
||
appFavouritesQuickActionsAvgInverseRate <- o .: "favourites-quick-actions-avg-inverse-rate"
|
||
appFavouritesQuickActionsTimeout <- o .: "favourites-quick-actions-timeout"
|
||
appFavouritesQuickActionsCacheTTL <- o .: "favourites-quick-actions-cache-ttl"
|
||
|
||
appPersistentTokenBuckets <- o .: "token-buckets"
|
||
|
||
appUploadCacheConf <- assertM (not . null . Minio.connectHost) <$> o .:? "upload-cache"
|
||
appUploadCacheBucket <- o .: "upload-cache-bucket"
|
||
|
||
return AppSettings{..}
|
||
|
||
makeClassy_ ''AppSettings
|
||
|
||
-- | Settings for 'widgetFile', such as which template languages to support and
|
||
-- default Hamlet settings.
|
||
--
|
||
-- For more information on modifying behavior, see:
|
||
--
|
||
-- https://github.com/yesodweb/yesod/wiki/Overriding-widgetFile
|
||
widgetFileSettings :: WidgetFileSettings
|
||
widgetFileSettings = def
|
||
|
||
|
||
submissionBlacklist :: [Pattern]
|
||
submissionBlacklist = $(patternFile compDefault "config/submission-blacklist")
|
||
|
||
-- The rest of this file contains settings which rarely need changing by a
|
||
-- user.
|
||
|
||
widgetFile :: String -> Q Exp
|
||
#ifdef DEVELOPMENT
|
||
widgetFile nameBase = do
|
||
Loc{..} <- location
|
||
let nameBase' = "templates" </> nameBase
|
||
before, after :: Text
|
||
before = [st|<!-- BEGIN ‘#{nameBase'}.*’ IN ‘#{loc_filename}’ #{tshow loc_start}–#{tshow loc_end} -->|]
|
||
after = [st|<!-- END ‘#{nameBase'}.*’ -->|]
|
||
[e| do
|
||
toWidget $ preEscapedToHtml before
|
||
$(widgetFileReload widgetFileSettings nameBase)
|
||
toWidget $ preEscapedToHtml after
|
||
|]
|
||
#else
|
||
widgetFile
|
||
| appReloadTemplates compileTimeAppSettings
|
||
= widgetFileReload widgetFileSettings
|
||
| otherwise
|
||
= widgetFileNoReload widgetFileSettings
|
||
#endif
|
||
|
||
-- | Raw bytes at compile time of @config/settings.yml@
|
||
configSettingsYmlBS :: ByteString
|
||
configSettingsYmlBS = $(embedFile configSettingsYml)
|
||
|
||
-- | @config/settings.yml@, parsed to a @Value@.
|
||
configSettingsYmlValue :: Value
|
||
configSettingsYmlValue = either Exception.throw id
|
||
$ decodeEither' configSettingsYmlBS
|
||
|
||
-- | A version of @AppSettings@ parsed at compile time from @config/settings.yml@.
|
||
compileTimeAppSettings :: AppSettings
|
||
compileTimeAppSettings =
|
||
case fromJSON $ applyEnvValue False mempty configSettingsYmlValue of
|
||
Aeson.Error e -> error e
|
||
Aeson.Success settings -> settings
|