packages feed

synapse-cc-0.4.0: src/SynapseCC/AuthCheck.hs

{-# LANGUAGE OverloadedStrings #-}

-- | SAFE-6: detect backends that require cookie-based WebSocket auth
-- and warn when the target's generated @transport.ts@ lacks the
-- SAFE-7 cookie-auth marker.
--
-- Detection signal: the IR contains at least one method whose params
-- schema has a property annotated @x-plexus-source.from = "auth"@.
-- That annotation is emitted by REQ-6 from @#[from_auth(...)]@ method
-- params. Any presence means the backend is using cookie-driven auth
-- at dispatch time; clients without cookie support will fail silently.
--
-- The SAFE-7 marker is the string @SAFE-7-cookie-auth-marker@ embedded
-- in the transport template by @service-codegen@'s TS generator. Its
-- presence in an existing @transport.ts@ indicates the file was
-- produced by a SAFE-7-era service-codegen (i.e. cookie-capable).
module SynapseCC.AuthCheck
  ( detectCookieAuthMismatch
  , backendRequiresCookieAuth
  ) where

import Data.Aeson (Value(..))
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.KeyMap as KM
import qualified Data.ByteString as BS
import qualified Data.Text as T
import Data.Text (Text)
import qualified Data.Text.IO as TIO
import System.Directory (doesFileExist)
import System.FilePath ((</>))

-- | The marker string embedded by SAFE-7 in @transport.ts@. Kept in
-- sync with @service-codegen/src/generator/typescript/transport.rs@.
safe7Marker :: Text
safe7Marker = "SAFE-7-cookie-auth-marker"

-- | Inspect the IR JSON at @irFile@ and the target's output directory
-- @outputDir@. Returns @Just warningText@ when the backend requires
-- cookie auth (any method has @x-plexus-source.from = "auth"@) AND an
-- existing @transport.ts@ in the output directory lacks the SAFE-7
-- marker. Returns @Nothing@ otherwise (including first builds where
-- no transport.ts exists — nothing to mismatch with yet).
detectCookieAuthMismatch
  :: FilePath  -- ^ IR JSON file (e.g. cache's ir.json)
  -> FilePath  -- ^ Target output directory (will inspect transport.ts inside)
  -> IO (Maybe Text)
detectCookieAuthMismatch irFile outputDir = do
  irBytes <- BS.readFile irFile
  case Aeson.eitherDecodeStrict irBytes of
    Left _    -> pure Nothing  -- unreadable IR is not this module's problem
    Right (val :: Value) ->
      if backendRequiresCookieAuth val
        then inspectTarget outputDir
        else pure Nothing

-- | Returns 'True' when the IR describes at least one method that uses
-- @#[from_auth(...)]@ (i.e. a param with @x-plexus-source.from = "auth"@).
backendRequiresCookieAuth :: Value -> Bool
backendRequiresCookieAuth val = case val of
  Object top -> case KM.lookup "irMethods" top of
    Just (Object ms) -> any hasAuthParam (KM.elems ms)
    _                -> False
  _ -> False
  where
    hasAuthParam :: Value -> Bool
    hasAuthParam (Object m) = case KM.lookup "mdParams" m of
      Just (Array params) -> any paramIsAuth (foldr (:) [] params)
      _                   -> False
    hasAuthParam _ = False

    paramIsAuth :: Value -> Bool
    paramIsAuth (Object p) = case KM.lookup "pdSource" p of
      Just (Object src) -> case KM.lookup "from" src of
        Just (String "auth") -> True
        _                    -> False
      _ -> False
    paramIsAuth _ = False

-- | Inspect the target directory for a SAFE-7-marked transport.ts.
-- Returns a warning when transport.ts exists but lacks the marker.
-- Returns 'Nothing' when transport.ts doesn't exist (first build) or
-- does contain the marker (correctly configured).
inspectTarget :: FilePath -> IO (Maybe Text)
inspectTarget outputDir = do
  let tsFile = outputDir </> "transport.ts"
  exists <- doesFileExist tsFile
  if not exists
    then pure Nothing  -- first build; nothing to compare
    else do
      contents <- TIO.readFile tsFile
      if safe7Marker `T.isInfixOf` contents
        then pure Nothing
        else pure . Just $
          "[!] SAFE-6: backend requires cookie auth but target's transport.ts "
          <> "does not look like a SAFE-7-era service-codegen output (missing marker '"
          <> safe7Marker
          <> "'). Cookie-based WS upgrade may not work. "
          <> "Regenerate with an up-to-date service-codegen or pass --force."