corpus

Log | Files | Refs | README | LICENSE

commit 3b2e9bae48972f9f815fa7a4ba2dfe672833efd5
parent 767f83af87d7a23b67d8a4bb383cc61a5e32f915
Author: mtmn <miro@haravara.org>
Date:   Sat, 25 Apr 2026 16:02:35 +0200

fix: add corsOrigin config option

Diffstat:
MREADME.md | 1+
Msrc/Config.purs | 5++++-
Msrc/Main.purs | 19++++++++++---------
3 files changed, 15 insertions(+), 10 deletions(-)

diff --git a/README.md b/README.md @@ -90,6 +90,7 @@ The endpoint accepts standard ListenBrainz JSON payloads. See the [ListenBrainz | `AWS_S3_ADDRESSING_STYLE` | — | Set to `path` for path-style S3 URLs | | `COSINE_API_KEY` | — | [cosine.club](https://cosine.club) API key for similar tracks | | `METRICS_ENABLED` | `false` | Set to `true` to expose Prometheus metrics at `/metrics` | +| `CORS_ORIGIN` | `*` | Value for the `Access-Control-Allow-Origin` header on `/proxy` responses (e.g. `https://mtmn.name`) | ### Per-user configuration diff --git a/src/Config.purs b/src/Config.purs @@ -45,6 +45,7 @@ type AppConfig = { port :: Int , host :: String , metricsEnabled :: Boolean + , corsOrigin :: String , users :: Array UserEntry } @@ -93,6 +94,7 @@ loadConfig path = do awsS3AddressingStyle <- liftEffect $ lookupEnv "AWS_S3_ADDRESSING_STYLE" databasePath <- liftEffect $ lookupEnv "DATABASE_PATH" metricsEnabledStr <- liftEffect $ lookupEnv "METRICS_ENABLED" + corsOriginStr <- liftEffect $ lookupEnv "CORS_ORIGIN" defaultPath <- liftEffect cwd let resolvePath file = case databasePath of @@ -114,7 +116,8 @@ loadConfig path = do port = fromMaybe 8000 (portStr >>= Data.Int.fromString) host = fromMaybe "127.0.0.1" hostStr metricsEnabled = metricsEnabledStr == Just "true" - fullConfig = { port, host, metricsEnabled, users: map (\u -> u { config = fillCreds u.config }) rawUsers } + corsOrigin = fromMaybe "*" corsOriginStr + fullConfig = { port, host, metricsEnabled, corsOrigin, users: map (\u -> u { config = fillCreds u.config }) rawUsers } case validateConfig fullConfig of Left msg -> throwError (error msg) Right cfg -> pure cfg diff --git a/src/Main.purs b/src/Main.purs @@ -71,8 +71,8 @@ normalizePath path = case stripPrefix (Pattern "/u/") path of -- Request handler -- API endpoints (/proxy, /stats, /cover, /healthz) select the user via ?user=<slug>. -- Index pages are served at / (root user) and /u/<slug> (named users). -handleRequest :: Boolean -> Array UserContext -> Request -> Response -> Effect Unit -handleRequest metricsEnabled contexts req res = do +handleRequest :: Boolean -> String -> Array UserContext -> Request -> Response -> Effect Unit +handleRequest metricsEnabled corsOrigin contexts req res = do let method = IM.method req let rawUrl = IM.url req let allUsers = map (\ctx -> { slug: ctx.slug, name: ctx.displayName }) contexts @@ -83,7 +83,7 @@ handleRequest metricsEnabled contexts req res = do let path = URL.pathname url Metrics.wrapRequest method (normalizePath path) Log.info req res do launchAff_ $ do - result <- try $ routeRequest metricsEnabled contexts req url path allUsers res + result <- try $ routeRequest metricsEnabled corsOrigin contexts req url path allUsers res case result of Left err -> do Log.error $ "Internal server error: " <> Exception.message err @@ -91,8 +91,8 @@ handleRequest metricsEnabled contexts req res = do Right _ -> pure unit -routeRequest :: Boolean -> Array UserContext -> Request -> URL -> String -> Array { slug :: String, name :: String } -> Response -> Aff Unit -routeRequest metricsEnabled contexts req url path allUsers res = liftEffect $ case path of +routeRequest :: Boolean -> String -> Array UserContext -> Request -> URL -> String -> Array { slug :: String, name :: String } -> Response -> Aff Unit +routeRequest metricsEnabled corsOrigin contexts req url path allUsers res = liftEffect $ case path of "/client.js" -> serveClientJs res "/favicon.png" -> @@ -105,7 +105,7 @@ routeRequest metricsEnabled contexts req url path allUsers res = liftEffect $ ca Log.warn "Path not found: /metrics" serveNotFound res "/proxy" -> - withUser url \ctx -> serveProxy ctx.conn url res + withUser url \ctx -> serveProxy corsOrigin ctx.conn url res "/stats" -> withUser url \ctx -> serveStats ctx.conn url res "/cover" -> @@ -221,9 +221,10 @@ parseFilterField "genre" = Just FilterGenre parseFilterField "track" = Just FilterTrack parseFilterField _ = Nothing -serveProxy :: Connection -> URL -> Response -> Effect Unit -serveProxy db url res = do +serveProxy :: String -> Connection -> URL -> Response -> Effect Unit +serveProxy corsOrigin db url res = do setHeader "Content-Type" "application/json" (toOutgoingMessage res) + setHeader "Access-Control-Allow-Origin" corsOrigin (toOutgoingMessage res) launchAff_ do let limit = fromMaybe 25 (getQueryParam "limit" url >>= fromString) @@ -453,7 +454,7 @@ main = do contexts <- traverse startUser appConfig.users liftEffect $ do server <- createServer - server # on_ Server.requestH (handleRequest appConfig.metricsEnabled contexts) + server # on_ Server.requestH (handleRequest appConfig.metricsEnabled appConfig.corsOrigin contexts) let netServer = Server.toNetServer server netServer # on_ listeningH do