diff --git a/CHANGELOG.md b/CHANGELOG.md index f14a2b4fe34..f5102aacd60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ ## Next release (Add entries below in the order of server, console, cli, docs, others) + +- server: improve the event trigger logging on errors + NOTE: This change introduces a breaking change, earlier when there + was a client error when trying to process an event, then the status was reported as 1000. Now, the status 1000 has been removed and if any status was received by the graphql-engine from the webhook, the status + of the invocation will be the same otherwise it will be `NULL`. +- server: support `extensions` field in error responses from action webhook endpoints (fix #4001) - server: fix custom-check based permissions for MSSQL (#7429) - server: remove identity notion for table columns (fix #7557) - server: add webhook transformations for Actions and EventTriggers @@ -15,8 +21,6 @@ - server: prevent empty subscription roots in the schema (#6898) - console: support tracking of functions with return a single row -- server: support `extensions` field in error responses from action webhook endpoints (fix #4001) - ## v2.0.9 - server: fix export_metadata V2 bug which included cron triggers with `include_in_metadata: false` diff --git a/server/src-lib/Hasura/Backends/Postgres/DDL/EventTrigger.hs b/server/src-lib/Hasura/Backends/Postgres/DDL/EventTrigger.hs index 7e4f47e9593..65ea7454363 100644 --- a/server/src-lib/Hasura/Backends/Postgres/DDL/EventTrigger.hs +++ b/server/src-lib/Hasura/Backends/Postgres/DDL/EventTrigger.hs @@ -227,7 +227,7 @@ insertInvocation invo = do INSERT INTO hdb_catalog.event_invocation_logs (event_id, status, request, response) VALUES ($1, $2, $3, $4) |] ( iEventId invo - , fromIntegral $ iStatus invo :: Int64 + , fromIntegral <$> iStatus invo :: Maybe Int64 , Q.AltJ $ toJSON $ iRequest invo , Q.AltJ $ toJSON $ iResponse invo) True Q.unitQE defaultTxErrorHandler [Q.sql| diff --git a/server/src-lib/Hasura/Eventing/EventTrigger.hs b/server/src-lib/Hasura/Eventing/EventTrigger.hs index 5be1d8f0ce7..7da8a1fa5b1 100644 --- a/server/src-lib/Hasura/Eventing/EventTrigger.hs +++ b/server/src-lib/Hasura/Eventing/EventTrigger.hs @@ -79,6 +79,7 @@ import Hasura.Backends.Postgres.SQL.Types hiding (TableName) import Hasura.Base.Error import Hasura.Eventing.Common import Hasura.Eventing.HTTP +import Hasura.HTTP (getHTTPExceptionStatus) import Hasura.RQL.DDL.Headers import Hasura.RQL.DDL.RequestTransform import Hasura.RQL.Types @@ -372,7 +373,7 @@ processEventQueue logger logBehavior httpMgr getSchemaCache EventEngineCtx{..} L runExceptT (setRetry sourceConfig e (addUTCTime 60 currentTime) maintenanceModeVersion) >>= flip onLeft logQErr Right eti -> runTraceT (spanName eti) do - let webhookUrl = T.unpack $ wciCachedValue $ etiWebhookInfo eti + let webhook = wciCachedValue $ etiWebhookInfo eti retryConf = etiRetryConf eti timeoutSeconds = fromMaybe defaultTimeoutSeconds (rcTimeoutSec retryConf) httpTimeout = HTTP.responseTimeoutMicro (timeoutSeconds * 1000000) @@ -385,7 +386,7 @@ processEventQueue logger logBehavior httpMgr getSchemaCache EventEngineCtx{..} L res <- runExceptT $ do -- reqDetails contains the pre and post transformation -- request for logging purposes. - reqDetails <- mkRequest headers httpTimeout payload dataTransform webhookUrl + reqDetails <- mkRequest headers httpTimeout payload dataTransform webhook let logger' res details = logHTTPForET res extraLogCtx details logBehavior -- Event Triggers have a configuration parameter called -- HASURA_GRAPHQL_EVENTS_HTTP_WORKERS, which is used @@ -432,7 +433,7 @@ processSuccess sourceConfig e reqHeaders ep maintenanceModeVersion resp = do let respBody = hrsBody resp respHeaders = hrsHeaders resp respStatus = hrsStatus resp - invocation = mkInvocation ep respStatus reqHeaders respBody respHeaders + invocation = mkInvocation ep (Just respStatus) reqHeaders respBody respHeaders recordSuccess @b sourceConfig e invocation maintenanceModeVersion processError @@ -450,20 +451,17 @@ processError -> m (Either QErr ()) processError sourceConfig e retryConf reqHeaders ep maintenanceModeVersion err = do let invocation = case err of - HClient excp -> do - let errMsg = TBS.fromLBS $ encode $ show excp - mkInvocation ep 1000 reqHeaders errMsg [] - HParse _ detail -> do - let errMsg = TBS.fromLBS $ encode detail - mkInvocation ep 1001 reqHeaders errMsg [] + HClient httpException -> + let statusMaybe = getHTTPExceptionStatus httpException + in mkInvocation ep statusMaybe reqHeaders (TBS.fromLBS (encode httpException)) [] HStatus errResp -> do let respPayload = hrsBody errResp respHeaders = hrsHeaders errResp respStatus = hrsStatus errResp - mkInvocation ep respStatus reqHeaders respPayload respHeaders + mkInvocation ep (Just respStatus) reqHeaders respPayload respHeaders HOther detail -> do let errMsg = TBS.fromLBS $ encode detail - mkInvocation ep 500 reqHeaders errMsg [] + mkInvocation ep (Just 500) reqHeaders errMsg [] retryOrError <- retryOrSetError e retryConf err recordError @b sourceConfig e invocation retryOrError maintenanceModeVersion @@ -497,21 +495,25 @@ retryOrSetError e retryConf err = do mkInvocation :: Backend b => EventPayload b - -> Int + -> Maybe Int -> [HeaderConf] -> TBS.TByteString -> [HeaderConf] -> Invocation 'EventType -mkInvocation ep status reqHeaders respBody respHeaders - = let resp = if isClientError status - then mkClientErr respBody - else mkResp status respBody respHeaders - in - Invocation - (epId ep) - status - (mkWebhookReq (toJSON ep) reqHeaders invocationVersionET) - resp +mkInvocation eventPayload statusMaybe reqHeaders respBody respHeaders = + let resp = + case statusMaybe of + Nothing -> mkClientErr respBody + Just status -> + if status >= 200 && status < 300 + then mkResp status respBody respHeaders + else mkClientErr respBody + in + Invocation + (epId eventPayload) + statusMaybe + (mkWebhookReq (toJSON eventPayload) reqHeaders invocationVersionET) + resp logQErr :: ( MonadReader r m, Has (L.Logger L.Hasura) r, MonadIO m) => QErr -> m () logQErr err = do diff --git a/server/src-lib/Hasura/Eventing/HTTP.hs b/server/src-lib/Hasura/Eventing/HTTP.hs index 5e7b7c9cf92..39e1ccccf4c 100644 --- a/server/src-lib/Hasura/Eventing/HTTP.hs +++ b/server/src-lib/Hasura/Eventing/HTTP.hs @@ -27,6 +27,7 @@ module Hasura.Eventing.HTTP , mkClientErr , mkWebhookReq , mkResp + , mkInvocationResp , LogBehavior(..) , ResponseLogBehavior(..) , HeaderLogBehavior(..) @@ -57,11 +58,12 @@ import Data.Aeson.TH import Data.Either import Data.Has import Data.Int (Int64) -import Hasura.HTTP (addDefaultHeaders) +import Hasura.HTTP (HttpException (..), addDefaultHeaders) import Hasura.Logging import Hasura.Prelude import Hasura.RQL.DDL.Headers import Hasura.RQL.DDL.RequestTransform (RequestTransform, applyRequestTransform) +import Hasura.RQL.Types.Common (ResolvedWebhook (..)) import Hasura.RQL.Types.EventTrigger import Hasura.RQL.Types.Eventing import Hasura.Server.Version (HasVersion) @@ -95,7 +97,6 @@ data HTTPResp (a :: TriggerTypes) , hrsBody :: !TBS.TByteString , hrsSize :: !Int64 } deriving (Show, Eq) - $(deriveToJSON hasuraJSON{omitNothingFields=True} ''HTTPResp) instance ToEngineLog (HTTPResp 'EventType) Hasura where @@ -105,22 +106,18 @@ instance ToEngineLog (HTTPResp 'ScheduledType) Hasura where toEngineLog resp = (LevelInfo, scheduledTriggerLogType, toJSON resp) data HTTPErr (a :: TriggerTypes) - = HClient !HTTP.HttpException - | HParse !HTTP.Status !String + = HClient !HttpException | HStatus !(HTTPResp a) | HOther !String deriving (Show) instance ToJSON (HTTPErr a) where toJSON err = toObj $ case err of - (HClient e) -> ("client", toJSON $ show e) - (HParse st e) -> - ( "parse" - , toJSON (HTTP.statusCode st, show e) - ) + (HClient httpException) -> + ("client", toJSON httpException) (HStatus resp) -> ("status", toJSON resp) - (HOther e) -> ("internal", toJSON $ show e) + (HOther e) -> ("internal", toJSON e) where toObj :: (Text, Value) -> Value toObj (k, v) = object [ "type" .= k @@ -200,12 +197,13 @@ isNetworkError = \case HClient he -> isNetworkErrorHC he _ -> False -isNetworkErrorHC :: HTTP.HttpException -> Bool -isNetworkErrorHC = \case - HTTP.HttpExceptionRequest _ (HTTP.ConnectionFailure _) -> True - HTTP.HttpExceptionRequest _ HTTP.ConnectionTimeout -> True - HTTP.HttpExceptionRequest _ HTTP.ResponseTimeout -> True - _ -> False +isNetworkErrorHC :: HttpException -> Bool +isNetworkErrorHC (HttpException exception) = + case exception of + HTTP.HttpExceptionRequest _ (HTTP.ConnectionFailure _) -> True + HTTP.HttpExceptionRequest _ HTTP.ConnectionTimeout -> True + HTTP.HttpExceptionRequest _ HTTP.ResponseTimeout -> True + _ -> False anyBodyParser :: HTTP.Response LBS.ByteString -> Either (HTTPErr a) (HTTPResp a) anyBodyParser resp = do @@ -261,7 +259,7 @@ logHTTPForST eitherResp extraLogCtx reqDetails logBehavior = do runHTTP :: (MonadIO m) => HTTP.Manager -> HTTP.Request -> m (Either (HTTPErr a) (HTTPResp a)) runHTTP manager req = do res <- liftIO $ try $ HTTP.performRequest req manager - return $ either (Left . HClient) anyBodyParser res + return $ either (Left . HClient . HttpException) anyBodyParser res mkRequest :: MonadError (HTTPErr a) m @@ -272,11 +270,11 @@ mkRequest :: -- log the request size. As the logging happens outside the function, we pass -- it the final request body, instead of 'Value' -> Maybe RequestTransform - -> String + -> ResolvedWebhook -> m RequestDetails -mkRequest headers timeout payload mRequestTransform webhook = - case HTTP.mkRequestEither (T.pack webhook) of - Left excp -> throwError $ HClient excp +mkRequest headers timeout payload mRequestTransform (ResolvedWebhook webhook) = + case HTTP.mkRequestEither webhook of + Left excp -> throwError $ HClient $ HttpException excp Right initReq -> let req = initReq & set HTTP.method "POST" & set HTTP.headers headers @@ -319,8 +317,17 @@ mkClientErr message = mkWebhookReq :: Value -> [HeaderConf] -> InvocationVersion -> WebhookRequest mkWebhookReq payload headers = WebhookRequest payload headers +mkInvocationResp :: Maybe Int -> TBS.TByteString -> [HeaderConf] -> Response a +mkInvocationResp statusMaybe responseBody responseHeaders = + case statusMaybe of + Nothing -> mkClientErr responseBody + Just status -> + if isClientError status + then mkClientErr responseBody + else mkResp status responseBody responseHeaders + isClientError :: Int -> Bool -isClientError status = status >= 1000 +isClientError status = status >= 300 encodeHeader :: EventHeaderInfo -> HTTP.Header encodeHeader (EventHeaderInfo hconf cache) = diff --git a/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs b/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs index 8fc7c5a904b..0f7b59c6f63 100644 --- a/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs +++ b/server/src-lib/Hasura/Eventing/ScheduledTrigger.hs @@ -117,7 +117,6 @@ import qualified Data.HashMap.Strict as Map import qualified Data.List.NonEmpty as NE import qualified Data.Set as Set import qualified Data.TByteString as TBS -import qualified Data.Text as T import qualified Database.PG.Query as Q import qualified Network.HTTP.Client.Transformable as HTTP import qualified Text.Builder as TB @@ -140,6 +139,7 @@ import Hasura.Base.Error import Hasura.Eventing.Common import Hasura.Eventing.HTTP import Hasura.Eventing.ScheduledTrigger.Types +import Hasura.HTTP (getHTTPExceptionStatus) import Hasura.Metadata.Class import Hasura.RQL.DDL.EventTrigger (getHeaderInfosFromConf) import Hasura.RQL.DDL.Headers @@ -147,7 +147,6 @@ import Hasura.RQL.Types import Hasura.SQL.Types import Hasura.Server.Version (HasVersion) - -- | runCronEventsGenerator makes sure that all the cron triggers -- have an adequate buffer of cron events. runCronEventsGenerator @@ -240,14 +239,13 @@ processCronEvents logger logBehavior httpMgr cronEvents getSC lockedCronEvents = Nothing -> logInternalError $ err500 Unexpected "could not find cron trigger in cache" Just CronTriggerInfo{..} -> do - let webhookUrl = unResolvedWebhook ctiWebhookInfo - payload = ScheduledEventWebhookPayload id' (Just name) st + let payload = ScheduledEventWebhookPayload id' (Just name) st (fromMaybe J.Null ctiPayload) ctiComment Nothing retryCtx = RetryContext tries ctiRetryConf finally <- runMetadataStorageT $ flip runReaderT (logger, httpMgr) $ processScheduledEvent logBehavior id' ctiHeaders retryCtx - payload webhookUrl Cron + payload ctiWebhookInfo Cron removeEventFromLockedEvents id' lockedCronEvents onLeft finally logInternalError where @@ -276,14 +274,13 @@ processOneOffScheduledEvents env logger logBehavior httpMgr (either logInternalError pure) =<< runMetadataStorageT do webhookInfo <- resolveWebhook env _ooseWebhookConf headerInfo <- getHeaderInfosFromConf env _ooseHeaderConf - let webhookUrl = unResolvedWebhook webhookInfo - payload = ScheduledEventWebhookPayload _ooseId Nothing + let payload = ScheduledEventWebhookPayload _ooseId Nothing _ooseScheduledTime (fromMaybe J.Null _oosePayload) _ooseComment (Just _ooseCreatedAt) retryCtx = RetryContext _ooseTries _ooseRetryConf flip runReaderT (logger, httpMgr) $ - processScheduledEvent logBehavior _ooseId headerInfo retryCtx payload webhookUrl OneOff + processScheduledEvent logBehavior _ooseId headerInfo retryCtx payload webhookInfo OneOff removeEventFromLockedEvents _ooseId lockedOneOffScheduledEvents where logInternalError err = liftIO . L.unLogger logger $ ScheduledTriggerInternalErr err @@ -331,7 +328,7 @@ processScheduledEvent -> [EventHeaderInfo] -> RetryContext -> ScheduledEventWebhookPayload - -> Text + -> ResolvedWebhook -> ScheduledEventType -> m () processScheduledEvent logBehavior eventId eventHeaders retryCtx payload webhookUrl type' @@ -350,11 +347,10 @@ processScheduledEvent logBehavior eventId eventHeaders retryCtx payload webhookU extraLogCtx = ExtraLogContext eventId (sewpName payload) webhookReqBodyJson = J.toJSON payload webhookReqBody = J.encode webhookReqBodyJson - eitherRes <- runExceptT $ do -- reqDetails contains the pre and post transformation -- request for logging purposes. - reqDetails <- mkRequest headers httpTimeout webhookReqBody Nothing (T.unpack webhookUrl) + reqDetails <- mkRequest headers httpTimeout webhookReqBody Nothing webhookUrl let logger e d = logHTTPForST e extraLogCtx d logBehavior hoistEither =<< lift (invokeRequest reqDetails logger) case eitherRes of @@ -376,20 +372,17 @@ processError -> m () processError eventId retryCtx decodedHeaders type' reqJson err = do let invocation = case err of - HClient excp -> do - let errMsg = TBS.fromLBS $ J.encode $ show excp - mkInvocation eventId 1000 decodedHeaders errMsg [] reqJson - HParse _ detail -> do - let errMsg = TBS.fromLBS $ J.encode detail - mkInvocation eventId 1001 decodedHeaders errMsg [] reqJson + HClient httpException -> + let statusMaybe = getHTTPExceptionStatus httpException + in mkInvocation eventId statusMaybe decodedHeaders (TBS.fromLBS $ J.encode httpException) [] reqJson HStatus errResp -> do let respPayload = hrsBody errResp respHeaders = hrsHeaders errResp respStatus = hrsStatus errResp - mkInvocation eventId respStatus decodedHeaders respPayload respHeaders reqJson + mkInvocation eventId (Just respStatus) decodedHeaders respPayload respHeaders reqJson HOther detail -> do let errMsg = (TBS.fromLBS $ J.encode detail) - mkInvocation eventId 500 decodedHeaders errMsg [] reqJson + mkInvocation eventId (Just 500) decodedHeaders errMsg [] reqJson insertScheduledEventInvocation invocation type' retryOrMarkError eventId retryCtx err type' @@ -461,7 +454,7 @@ processSuccess eventId decodedHeaders type' reqBodyJson resp = do let respBody = hrsBody resp respHeaders = hrsHeaders resp respStatus = hrsStatus resp - invocation = mkInvocation eventId respStatus decodedHeaders respBody respHeaders reqBodyJson + invocation = mkInvocation eventId (Just respStatus) decodedHeaders respBody respHeaders reqBodyJson insertScheduledEventInvocation invocation type' setScheduledEventOp eventId (SEOpStatus SESDelivered) type' @@ -473,22 +466,18 @@ processDead eventId type' = mkInvocation :: ScheduledEventId - -> Int + -> Maybe Int -> [HeaderConf] -> TBS.TByteString -> [HeaderConf] -> J.Value -> (Invocation 'ScheduledType) -mkInvocation eventId status reqHeaders respBody respHeaders reqBodyJson - = let resp = if isClientError status - then mkClientErr respBody - else mkResp status respBody respHeaders - in - Invocation - eventId - status - (mkWebhookReq reqBodyJson reqHeaders invocationVersionST) - resp +mkInvocation eventId status reqHeaders respBody respHeaders reqBodyJson = + Invocation + eventId + status + (mkWebhookReq reqBodyJson reqHeaders invocationVersionST) + (mkInvocationResp status respBody respHeaders) -- metadata database transactions @@ -580,7 +569,7 @@ insertInvocationTx invo type' = do (event_id, status, request, response) VALUES ($1, $2, $3, $4) |] ( iEventId invo - , fromIntegral $ iStatus invo :: Int64 + , fromIntegral <$> iStatus invo :: Maybe Int64 , Q.AltJ $ J.toJSON $ iRequest invo , Q.AltJ $ J.toJSON $ iResponse invo) True Q.unitQE defaultTxErrorHandler [Q.sql| @@ -595,7 +584,7 @@ insertInvocationTx invo type' = do (event_id, status, request, response) VALUES ($1, $2, $3, $4) |] ( iEventId invo - , fromIntegral $ iStatus invo :: Int64 + , fromIntegral <$> iStatus invo :: Maybe Int64 , Q.AltJ $ J.toJSON $ iRequest invo , Q.AltJ $ J.toJSON $ iResponse invo) True Q.unitQE defaultTxErrorHandler [Q.sql| diff --git a/server/src-lib/Hasura/HTTP.hs b/server/src-lib/Hasura/HTTP.hs index e6ecb67524a..39a773bcb64 100644 --- a/server/src-lib/Hasura/HTTP.hs +++ b/server/src-lib/Hasura/HTTP.hs @@ -5,6 +5,8 @@ module Hasura.HTTP , addDefaultHeaders , HttpResponse(..) , addHttpResponseHeaders + , getHTTPExceptionStatus + , serializeHTTPExceptionMessage ) where import Hasura.Prelude @@ -14,10 +16,14 @@ import Data.CaseInsensitive (original) import Data.Text.Conversions (UTF8 (..), convertText) import qualified Data.Aeson as J +import qualified Data.HashMap.Strict as M +import qualified Data.Text as T +import qualified Data.Text.Encoding as TE import qualified Network.HTTP.Client as HTTP import qualified Network.HTTP.Types as HTTP import qualified Network.Wreq as Wreq +import Hasura.Server.Utils (redactSensitiveHeader) import Hasura.Server.Version (HasVersion, currentVersion) hdrsToText :: [HTTP.Header] -> [(Text, Text)] @@ -55,16 +61,68 @@ newtype HttpException { unHttpException :: HTTP.HttpException } deriving (Show) +getHTTPExceptionStatus :: HttpException -> Maybe Int +getHTTPExceptionStatus = \case + (HttpException (HTTP.HttpExceptionRequest _ httpExceptionContent)) -> + case httpExceptionContent of + HTTP.StatusCodeException response _ -> Just $ HTTP.statusCode $ HTTP.responseStatus response + HTTP.ProxyConnectException _ _ status -> Just $ HTTP.statusCode status + _ -> Nothing + (HttpException (HTTP.InvalidUrlException _ _)) -> Nothing + +serializeHTTPExceptionMessage :: HttpException -> Text +serializeHTTPExceptionMessage (HttpException (HTTP.HttpExceptionRequest _ httpExceptionContent)) = + case httpExceptionContent of + HTTP.StatusCodeException _ _ -> "unexpected" + HTTP.TooManyRedirects _ -> "Too many redirects" + HTTP.OverlongHeaders -> "Overlong headers" + HTTP.ResponseTimeout -> "Response timeout" + HTTP.ConnectionTimeout -> "Connection timeout" + HTTP.ConnectionFailure _ -> "Connection failure" + HTTP.InvalidStatusLine _ -> "Invalid HTTP Status Line" + HTTP.InternalException _ -> "Internal Exception" + HTTP.ProxyConnectException _ _ _ -> "Proxy connection exception" + HTTP.NoResponseDataReceived -> "No response data received" + HTTP.TlsNotSupported -> "TLS not supported" + HTTP.InvalidDestinationHost _ -> "Invalid destination host" + HTTP.InvalidHeader _ -> "Invalid Header" + HTTP.InvalidRequestHeader _ -> "Invalid Request Header" + HTTP.WrongRequestBodyStreamSize _ _ -> "Wrong request body stream size" + HTTP.ResponseBodyTooShort _ _ -> "Response body too short" + HTTP.InvalidChunkHeaders -> "Invalid chunk headers" + HTTP.IncompleteHeaders -> "Incomplete headers" + _ -> "unexpected" +serializeHTTPExceptionMessage (HttpException (HTTP.InvalidUrlException url reason)) = T.pack $ "URL: " <> url <> " is invalid because " <> reason + +encodeHTTPRequestJSON :: HTTP.Request -> J.Value +encodeHTTPRequestJSON request = + J.Object $ M.fromList + [ ("host", J.toJSON $ TE.decodeUtf8 $ HTTP.host request) + , ("port" , J.toJSON $ HTTP.port request) + , ("secure", J.toJSON $ HTTP.secure request) + , ("requestHeaders" , J.toJSON $ M.fromList $ hdrsToText $ map redactSensitiveHeader $ HTTP.requestHeaders request) + , ("path" , J.toJSON $ TE.decodeUtf8 $ HTTP.path request) + , ("queryString", J.toJSON $ TE.decodeUtf8 $ HTTP.queryString request) + , ("method", J.toJSON $ TE.decodeUtf8 $ HTTP.method request) + , ("responseTimeout", J.String $ tshow $ HTTP.responseTimeout request) + ] + instance J.ToJSON HttpException where - toJSON = \case - (HttpException (HTTP.InvalidUrlException _ e)) -> - J.object [ "type" J..= ("invalid_url" :: Text) - , "message" J..= e - ] - (HttpException (HTTP.HttpExceptionRequest _ cont)) -> - J.object [ "type" J..= ("http_exception" :: Text) - , "message" J..= show cont - ] + toJSON httpException = + case httpException of + (HttpException (HTTP.InvalidUrlException _ e)) -> + J.object [ "type" J..= ("invalid_url" :: Text) + , "message" J..= e + ] + (HttpException (HTTP.HttpExceptionRequest req _)) -> + let statusMaybe = getHTTPExceptionStatus httpException + exceptionContent = serializeHTTPExceptionMessage httpException + reqJSON = encodeHTTPRequestJSON req + in + J.object $ [ "type" J..= ("http_exception" :: Text) + , "message" J..= exceptionContent + , "request" J..= reqJSON + ] <> maybe mempty (\status -> [ "status" J..= status]) statusMaybe data HttpResponse a = HttpResponse diff --git a/server/src-lib/Hasura/RQL/DDL/EventTrigger.hs b/server/src-lib/Hasura/RQL/DDL/EventTrigger.hs index c337a78103e..82d726b82e6 100644 --- a/server/src-lib/Hasura/RQL/DDL/EventTrigger.hs +++ b/server/src-lib/Hasura/RQL/DDL/EventTrigger.hs @@ -302,13 +302,13 @@ getWebhookInfoFromConf => Env.Environment -> WebhookConf -> m WebhookConfInfo -getWebhookInfoFromConf env wc = case wc of +getWebhookInfoFromConf env webhookConf = case webhookConf of WCValue w -> do resolvedWebhook <- resolveWebhook env w - return $ WebhookConfInfo wc $ unResolvedWebhook resolvedWebhook - WCEnv we -> do - envVal <- getEnv env we - return $ WebhookConfInfo wc envVal + return $ WebhookConfInfo webhookConf resolvedWebhook + WCEnv webhookEnvVar -> do + envVal <- getEnv env webhookEnvVar + return $ WebhookConfInfo webhookConf (ResolvedWebhook envVal) buildEventTriggerInfo :: forall b m diff --git a/server/src-lib/Hasura/RQL/Types/Common.hs b/server/src-lib/Hasura/RQL/Types/Common.hs index f4ea08940ac..f927aba96d0 100644 --- a/server/src-lib/Hasura/RQL/Types/Common.hs +++ b/server/src-lib/Hasura/RQL/Types/Common.hs @@ -254,8 +254,10 @@ instance FromJSON NonNegativeDiffTime where False -> fail "negative value not allowed" newtype ResolvedWebhook - = ResolvedWebhook { unResolvedWebhook :: Text } - deriving ( Show, Eq, FromJSON, ToJSON, Hashable, ToTxt) + = ResolvedWebhook { unResolvedWebhook :: Text} + deriving ( Show, Eq, FromJSON, ToJSON, Hashable, ToTxt, Generic) +instance NFData ResolvedWebhook +instance Cacheable ResolvedWebhook newtype InputWebhook = InputWebhook {unInputWebhook :: URLTemplate} diff --git a/server/src-lib/Hasura/RQL/Types/EventTrigger.hs b/server/src-lib/Hasura/RQL/Types/EventTrigger.hs index 41efe1e894e..7d228b248b6 100644 --- a/server/src-lib/Hasura/RQL/Types/EventTrigger.hs +++ b/server/src-lib/Hasura/RQL/Types/EventTrigger.hs @@ -43,7 +43,7 @@ import Hasura.Incremental (Cacheable) import Hasura.RQL.DDL.Headers import Hasura.RQL.DDL.RequestTransform (MetadataTransform) import Hasura.RQL.Types.Backend -import Hasura.RQL.Types.Common (InputWebhook, SourceName) +import Hasura.RQL.Types.Common (InputWebhook, ResolvedWebhook, SourceName) import Hasura.RQL.Types.Eventing import Hasura.SQL.Backend @@ -143,9 +143,10 @@ instance FromJSON WebhookConf where data WebhookConfInfo = WebhookConfInfo { wciWebhookConf :: !WebhookConf - , wciCachedValue :: !Text + , wciCachedValue :: !ResolvedWebhook } deriving (Show, Eq, Generic) instance NFData WebhookConfInfo +instance Cacheable WebhookConfInfo $(deriveToJSON hasuraJSON{omitNothingFields=True} ''WebhookConfInfo) -- | The table operations on which the event trigger will be invoked. diff --git a/server/src-lib/Hasura/RQL/Types/Eventing.hs b/server/src-lib/Hasura/RQL/Types/Eventing.hs index 67f085d7ad1..62c9f69d5e6 100644 --- a/server/src-lib/Hasura/RQL/Types/Eventing.hs +++ b/server/src-lib/Hasura/RQL/Types/Eventing.hs @@ -81,7 +81,7 @@ instance ToJSON (Response 'ScheduledType) where data Invocation (a :: TriggerTypes) = Invocation { iEventId :: EventId - , iStatus :: Int + , iStatus :: Maybe Int , iRequest :: WebhookRequest , iResponse :: Response a } diff --git a/server/src-lib/Hasura/Server/Utils.hs b/server/src-lib/Hasura/Server/Utils.hs index 07637f3bf3e..1130bd3be17 100644 --- a/server/src-lib/Hasura/Server/Utils.hs +++ b/server/src-lib/Hasura/Server/Utils.hs @@ -307,3 +307,12 @@ deprecatedEnvVars = DeprecatedEnvVars , "HASURA_GRAPHQL_QUERY_PLAN_CACHE_SIZE" , "HASURA_GRAPHQL_STRIPES_PER_READ_REPLICA" ] + +sensitiveHeaders :: HashSet HTTP.HeaderName +sensitiveHeaders = Set.fromList + [ "Authorization" + , "Cookie" + ] + +redactSensitiveHeader :: HTTP.Header -> HTTP.Header +redactSensitiveHeader (headerName, value) = (headerName, if headerName `elem` sensitiveHeaders then "" else value) diff --git a/server/tests-py/test_actions.py b/server/tests-py/test_actions.py index 1d272f2d00d..8ec1f0ac052 100644 --- a/server/tests-py/test_actions.py +++ b/server/tests-py/test_actions.py @@ -664,4 +664,4 @@ class TestActionTimeout: time.sleep(4) response, _ = check_query(hge_ctx, conf) assert 'errors' in response['data']['create_user'] - assert 'ResponseTimeout' == response['data']['create_user']['errors']['internal']['error']['message'] + assert 'Response timeout' == response['data']['create_user']['errors']['internal']['error']['message']