store: refuse a site build that is not there

This commit is contained in:
shum committed 2026-10-26 16:10:35 +00:00
1 parent 0d882b9eb5
commit 98effe5358
4 files changed
+39 -4

No files matched your search

+2 -1
View File
@@ -394,7 +394,8 @@ The names site is on when, and only when, the ini has `[names]`. Its `[listener]
beside `badges_port` (by default 8080), which serves the badges site. The two must differ.
The service refuses to start with any of these keys but no `[names]`, and with `names_port` and
`serve_webapp = off`.
`serve_webapp = off`. It also refuses to start when a build it serves or exports is not a
directory, naming the setting, so a local run that keeps a default path stops there.
With `serve_webapp = on` (all in one), the service listens on both ports, and each serves only its
own site: every path outside `/api/` and `/webhooks/` is a file of that site, `/` and `/index.html`
@@ -53,7 +53,7 @@ import StoreService.StoreReceipts.Apple (readAppleRoot, verifyAppleTransaction)
import StoreService.StoreReceipts.Google (playStoreVerifier)
import StoreService.StoreReceipts.Mock (mockStoreVerifier)
import StoreService.Waiters (Waiters, newWaiters)
import StoreService.Web.Server (exportWebapp, newWebEnv, runWebListeners)
import StoreService.Web.Server (checkSiteDirs, exportWebapp, newWebEnv, runWebListeners)
import Control.Applicative (optional)
import Control.Concurrent.STM
import StoreService.Log (logError, logInfo, logWarn)
@@ -213,6 +213,7 @@ configureStoreVerifier serviceCfg env@ServiceState {storeVerifier = v} = case se
storeService :: StoreServiceOpts -> ChatConfig -> ServiceState -> IO ()
storeService opts@StoreServiceOpts {serviceConfigFile} baseCfg env = do
serviceCfg <- traverse readConfigOrExit serviceConfigFile
forM_ serviceCfg $ \sc -> checkSiteDirs (listener sc) >>= orExit
cfg <- requireDevKeys serviceCfg baseCfg
key <- requireIssuerKey opts serviceCfg cfg
waiters <- newWaiters
@@ -12,6 +12,7 @@ module StoreService.Web.Server
webListeners,
webSettings,
runWebListeners,
checkSiteDirs,
)
where
@@ -34,7 +35,7 @@ import StoreService.Web.Names.Registration (heldFor, registrationChangedResponse
import Control.Concurrent.Async (race)
import qualified Control.Exception as E
import StoreService.Log (logError, logInfo, logWarn)
import Control.Monad (forM_, void, when)
import Control.Monad (filterM, forM_, void, when)
import Data.Aeson ((.=))
import qualified Data.Aeson as J
import qualified Data.Aeson.KeyMap as KM
@@ -42,8 +43,9 @@ import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LB
import Data.Char (toLower)
import Data.Functor ((<&>))
import Data.List (isPrefixOf)
import Data.Maybe (catMaybes, fromMaybe)
import Data.Maybe (catMaybes, fromMaybe, isJust)
import Data.String (fromString)
import Data.Text (Text)
import qualified Data.Text as T
@@ -556,6 +558,17 @@ runWebListeners env = do
where
lc = listenerConfig env
-- | Each build that is served or exported must be a directory, or the service would serve nothing or fail its export.
checkSiteDirs :: ListenerConfig -> IO (Either String ())
checkSiteDirs ListenerConfig {lServeWebapp, lBadgesStaticDir, lBadgesExportDir, lNamesStaticDir, lNamesExportDir, lNamesPort} =
filterM (fmap not . doesDirectoryExist . snd) used <&> \case
(site, dir) : _ -> Left ("listener." <> site <> "_static_dir = " <> dir <> " is not a directory; set it to the " <> site <> " build, since the default is the Docker image's path")
[] -> Right ()
where
used =
[("badges", lBadgesStaticDir) | lServeWebapp || isJust lBadgesExportDir]
<> [("names", dir) | Just dir <- [lNamesStaticDir], isJust lNamesPort || isJust lNamesExportDir]
warnIfHeaderTrustIsExposed :: ListenerConfig -> IO ()
warnIfHeaderTrustIsExposed ListenerConfig {lHost, lTrustForwardedFor} =
when (lTrustForwardedFor && not (isLoopbackHost lHost)) $