diff --git a/.ghcversion b/.ghcversion index 8c9af59f1adfc..97d6bcaa1ed27 100644 --- a/.ghcversion +++ b/.ghcversion @@ -1 +1 @@ -9.6.5 +9.10.1 diff --git a/scripts/make/ghcid.mk b/scripts/make/ghcid.mk index b2775abd54083..429bfa3ecf99f 100644 --- a/scripts/make/ghcid.mk +++ b/scripts/make/ghcid.mk @@ -66,6 +66,11 @@ ghcid-test-harness: ghcid-pg-client: $(call run_ghcid,pg-client) +.PHONY: ghcid-hasura-base +## ghcid-hasura-base: build and watch hasura-base in ghcid +ghcid-hasura-base: + $(call run_ghcid,hasura-base) + .PHONY: ghcid-api-tests-pro ## ghcid-api-tests-pro: build and watch api-tests in pro ghcid-api-tests-pro: diff --git a/server/lib/hasura-base/src/Hasura/Base/Error.hs b/server/lib/hasura-base/src/Hasura/Base/Error.hs index bcd373455ad9e..401c8ff46ecae 100644 --- a/server/lib/hasura-base/src/Hasura/Base/Error.hs +++ b/server/lib/hasura-base/src/Hasura/Base/Error.hs @@ -5,6 +5,7 @@ module Hasura.Base.Error ( Code (..), QErr (..), QErrExtra (..), + IncludeInternalErrors (..), overrideQErrStatus, prefixQErr, showQErr, @@ -227,11 +228,16 @@ prefixQErr prefix err = err {qeError = prefix <> qeError err} showQErr :: QErr -> Text showQErr = TL.toStrict . TL.decodeUtf8 . encode -encodeGQLErr :: Bool -> QErr -> Encoding +data IncludeInternalErrors = IncludeInternalErrors | HideInternalErrors + deriving (Eq, Ord, Show) + +encodeGQLErr :: IncludeInternalErrors -> QErr -> Encoding encodeGQLErr includeInternal (QErr jPath _ msg code maybeExtra) = pairs (("message" .= msg) <> (J.pair "extensions" extnsObj)) where - appendIf cond a b = if cond then a <> b else a + appendInternal a b = case includeInternal of + IncludeInternalErrors -> a <> b + HideInternalErrors -> a extnsObj = case maybeExtra of Nothing -> pairs codeAndPath @@ -240,15 +246,15 @@ encodeGQLErr includeInternal (QErr jPath _ msg code maybeExtra) = -- contains a `code` field: Just (ExtraExtensions v) -> toEncoding v Just (ExtraInternal v) -> - pairs $ appendIf includeInternal codeAndPath ("internal" .= v) + pairs $ appendInternal codeAndPath ("internal" .= v) codeAndPath = ("path" .= encodeJSONPath jPath) <> ("code" .= code) -- whether internal should be included or not -encodeQErr :: Bool -> QErr -> Encoding -encodeQErr True = toEncoding -encodeQErr False = toEncoding . removeInternalErr +encodeQErr :: IncludeInternalErrors -> QErr -> Encoding +encodeQErr IncludeInternalErrors = toEncoding +encodeQErr HideInternalErrors = toEncoding . removeInternalErr where removeInternalErr :: QErr -> QErr removeInternalErr err = err {qeInternal = Nothing} diff --git a/server/src-lib/Hasura/App.hs b/server/src-lib/Hasura/App.hs index 41c14738c1aa9..02defea842d69 100644 --- a/server/src-lib/Hasura/App.hs +++ b/server/src-lib/Hasura/App.hs @@ -1356,6 +1356,7 @@ mkHGEServer setupHook appStateRef consoleType ekgStore = do (leActionEvents lockedEventsCtx) Nothing appEnvAsyncActionsFetchBatchSize + HideInternalErrors (acHeaderPrecedence <$> getAppContext appStateRef) -- start a background thread to handle async action live queries diff --git a/server/src-lib/Hasura/GraphQL/Execute.hs b/server/src-lib/Hasura/GraphQL/Execute.hs index 9a05a758eb74a..5dd4cff8f2da7 100644 --- a/server/src-lib/Hasura/GraphQL/Execute.hs +++ b/server/src-lib/Hasura/GraphQL/Execute.hs @@ -388,6 +388,7 @@ getResolvedExecPlan traceQueryStatus = do let gCtx = makeGQLContext userInfo sc queryType tracesPropagator = getOtelTracesPropagator $ scOpenTelemetryConfig sc + includeInternalErrors = Init.shouldIncludeInternal (_uiRole userInfo) responseErrorsConfig -- Construct the full 'ResolvedExecutionPlan' from the 'queryParts :: SingleOperation'. (parameterizedQueryHash, resolvedExecPlan, modelInfoList') <- @@ -435,6 +436,7 @@ getResolvedExecPlan (scSetGraphqlIntrospectionOptions sc) reqId maybeOperationName + includeInternalErrors headerPrecedence traceQueryStatus Tracing.attachMetadata [("graphql.operation.type", "mutation")] diff --git a/server/src-lib/Hasura/GraphQL/Execute/Action.hs b/server/src-lib/Hasura/GraphQL/Execute/Action.hs index 9ac7bac540bae..b38af6162918a 100644 --- a/server/src-lib/Hasura/GraphQL/Execute/Action.hs +++ b/server/src-lib/Hasura/GraphQL/Execute/Action.hs @@ -156,9 +156,10 @@ resolveActionExecution :: IR.AnnActionExecution Void -> ActionExecContext -> Maybe GQLQueryText -> + IncludeInternalErrors -> HeaderPrecedence -> ActionExecution -resolveActionExecution httpManager env logger tracesPropagator prometheusMetrics IR.AnnActionExecution {..} ActionExecContext {..} gqlQueryText headerPrecedence = +resolveActionExecution httpManager env logger tracesPropagator prometheusMetrics IR.AnnActionExecution {..} ActionExecContext {..} gqlQueryText includeInternalErrors headerPrecedence = ActionExecution $ first (encJFromOrderedValue . makeActionResponseNoRelations _aaeFields _aaeOutputType _aaeOutputFields True) <$> runWebhook where handlerPayload = ActionWebhookPayload (ActionContext _aaeName) _aecSessionVariables _aaePayload gqlQueryText @@ -186,6 +187,7 @@ resolveActionExecution httpManager env logger tracesPropagator prometheusMetrics _aaeTimeOut _aaeRequestTransform _aaeResponseTransform + includeInternalErrors headerPrecedence throwUnexpected :: (MonadError QErr m) => Text -> m () @@ -382,9 +384,10 @@ resolveAsyncActionQuery userInfo annAction responseErrorsConfig = IR.AsyncId -> mkAnnFldFromPGCol idColumn IR.AsyncCreatedAt -> mkAnnFldFromPGCol createdAtColumn IR.AsyncErrors -> - if (shouldIncludeInternal (_uiRole userInfo) responseErrorsConfig) - then RS.mkAnnColumnField (fst errorsColumn) (ColumnScalar (snd errorsColumn)) NoRedaction Nothing - else + case shouldIncludeInternal (_uiRole userInfo) responseErrorsConfig of + IncludeInternalErrors -> + RS.mkAnnColumnField (fst errorsColumn) (ColumnScalar (snd errorsColumn)) NoRedaction Nothing + HideInternalErrors -> RS.mkAnnColumnField (fst errorsColumn) (ColumnScalar (snd errorsColumn)) @@ -418,7 +421,9 @@ resolveAsyncActionQuery userInfo annAction responseErrorsConfig = mkQErrFromErrorValue :: J.Value -> QErr mkQErrFromErrorValue actionLogResponseError = let internal = ExtraInternal <$> (actionLogResponseError ^? key "internal") - internal' = if shouldIncludeInternal (_uiRole userInfo) responseErrorsConfig then internal else Nothing + internal' = case shouldIncludeInternal (_uiRole userInfo) responseErrorsConfig of + IncludeInternalErrors -> internal + HideInternalErrors -> Nothing errorMessageText = fromMaybe "internal: error in parsing the action log" $ actionLogResponseError ^? key "error" . _String codeMaybe = actionLogResponseError ^? key "code" . _String code = maybe Unexpected ActionWebhookCode codeMaybe @@ -490,9 +495,10 @@ asyncActionsProcessor :: STM.TVar (Set LockedActionEventId) -> Maybe GH.GQLQueryText -> Int -> + IncludeInternalErrors -> IO HeaderPrecedence -> m (Forever m) -asyncActionsProcessor getEnvHook logger getSCFromRef' getFetchInterval lockedActionEvents gqlQueryText fetchBatchSize getHeaderPrecedence = +asyncActionsProcessor getEnvHook logger getSCFromRef' getFetchInterval lockedActionEvents gqlQueryText fetchBatchSize includeInternalErrors getHeaderPrecedence = return $ Forever () $ const @@ -573,6 +579,7 @@ asyncActionsProcessor getEnvHook logger getSCFromRef' getFetchInterval lockedAct timeout metadataRequestTransform metadataResponseTransform + includeInternalErrors headerPrecedence resE <- setActionStatus actionId $ case eitherRes of @@ -605,6 +612,7 @@ callWebhook :: Timeout -> Maybe RequestTransform -> Maybe MetadataResponseTransform -> + IncludeInternalErrors -> HeaderPrecedence -> m (ActionWebhookResponse, HTTP.ResponseHeaders) callWebhook @@ -624,6 +632,7 @@ callWebhook timeoutSeconds metadataRequestTransform metadataResponseTransform + includeInternalErrors headerPrecedence = do resolvedConfHeaders <- makeHeadersFromConf env confHeaders let clientHeaders = if forwardClientHeaders then mkClientHeadersForward ignoredClientHeaders reqHeaders else mempty @@ -684,9 +693,9 @@ callWebhook case httpResponse of Left e -> - throw500WithDetail "http exception when calling webhook" - $ J.toJSON - $ ActionInternalError (getHttpExceptionJson (ShowErrorInfo True) $ HttpException e) requestInfo Nothing + let msg = "http exception when calling webhook" + in throwInternalError msg includeInternalErrors + $ ActionInternalError (getHttpExceptionJson (ShowErrorInfo True) $ HttpException e) requestInfo Nothing Right responseWreq -> do -- TODO(SOLOMON): Remove 'wreq' let responseBody = responseWreq ^. Wreq.responseBody @@ -722,23 +731,34 @@ callWebhook (pmActionBytesReceived prometheusMetrics) responseBodySize logger :: (L.Logger L.Hasura) <- asks getter - L.unLoggerTracing logger $ ActionHandlerLog req transformedReq requestBodySize transformedReqSize responseBodySize actionName actionType + L.unLoggerTracing logger + $ ActionHandlerLog + req + transformedReq + requestBodySize + transformedReqSize + responseBodySize + actionName + actionType case J.eitherDecode transformedResponseBody of Left e -> do let responseInfo = mkResponseInfo $ J.String $ bsToTxt $ BL.toStrict responseBody - throw500WithDetail "not a valid json response from webhook" - $ J.toJSON - $ ActionInternalError (J.toJSON $ "invalid JSON: " <> e) requestInfo - $ Just responseInfo + msg = "not a valid json response from webhook" + in throwInternalError msg includeInternalErrors + $ ActionInternalError (J.toJSON $ "invalid JSON: " <> e) requestInfo + $ Just responseInfo Right responseValue -> do let responseInfo = mkResponseInfo responseValue addInternalToErr e = - let actionInternalError = - J.toJSON - $ ActionInternalError (J.String "unexpected response") requestInfo - $ Just responseInfo - in e {qeInternal = Just $ ExtraInternal actionInternalError} + case includeInternalErrors of + HideInternalErrors -> e + IncludeInternalErrors -> + let actionInternalError = + J.toJSON + $ ActionInternalError (J.String "unexpected response") requestInfo + $ Just responseInfo + in e {qeInternal = Just $ ExtraInternal actionInternalError} if | HTTP.statusIsSuccessful responseStatus -> do @@ -757,10 +777,19 @@ callWebhook J.toJSON $ "expecting 2xx or 4xx status code, but found " ++ show (HTTP.statusCode responseStatus) - throw500WithDetail "internal error" - $ J.toJSON - $ ActionInternalError err requestInfo - $ Just responseInfo + msg = "internal error" + in throwInternalError msg includeInternalErrors + $ ActionInternalError err requestInfo + $ Just responseInfo + +throwInternalError :: (MonadError QErr m) => Text -> IncludeInternalErrors -> ActionInternalError -> m a +throwInternalError msg includeInternalErrors actionInternalError = + case includeInternalErrors of + HideInternalErrors -> throwError (internalError msg) + IncludeInternalErrors -> + throw500WithDetail msg + $ J.toJSON + $ actionInternalError processOutputSelectionSet :: TF.ArgumentExp v -> diff --git a/server/src-lib/Hasura/GraphQL/Execute/Mutation.hs b/server/src-lib/Hasura/GraphQL/Execute/Mutation.hs index 97c224662c60e..cc1241def39b3 100644 --- a/server/src-lib/Hasura/GraphQL/Execute/Mutation.hs +++ b/server/src-lib/Hasura/GraphQL/Execute/Mutation.hs @@ -58,13 +58,14 @@ convertMutationAction :: HTTP.RequestHeaders -> Maybe GH.GQLQueryText -> ActionMutation Void -> + IncludeInternalErrors -> HeaderPrecedence -> m ActionExecutionPlan -convertMutationAction env logger tracesPropagator prometheusMetrics userInfo reqHeaders gqlQueryText action headerPrecedence = do +convertMutationAction env logger tracesPropagator prometheusMetrics userInfo reqHeaders gqlQueryText action includeInternalErrors headerPrecedence = do httpManager <- askHTTPManager case action of AMSync s -> - pure $ AEPSync $ resolveActionExecution httpManager env logger tracesPropagator prometheusMetrics s actionExecContext gqlQueryText headerPrecedence + pure $ AEPSync $ resolveActionExecution httpManager env logger tracesPropagator prometheusMetrics s actionExecContext gqlQueryText includeInternalErrors headerPrecedence AMAsync s -> AEPAsyncMutation <$> resolveActionMutationAsync s reqHeaders userSession where @@ -97,6 +98,7 @@ convertMutationSelectionSet :: RequestId -> -- | Graphql Operation Name Maybe G.Name -> + IncludeInternalErrors -> HeaderPrecedence -> TraceQueryStatus -> m (ExecutionPlan, ParameterizedQueryHash, [ModelInfoPart]) @@ -116,6 +118,7 @@ convertMutationSelectionSet introspectionDisabledRoles reqId maybeOperationName + includeInternalErrors headerPrecedence traceQueryStatus = do mutationParser <- @@ -130,7 +133,6 @@ convertMutationSelectionSet -- Process directives on the mutation _dirMap <- toQErr $ runParse (parseDirectives customDirectives (G.DLExecutable G.EDLMUTATION) resolvedDirectives) let parameterizedQueryHash = calculateParameterizedQueryHash resolvedSelSet - resolveExecutionSteps rootFieldName rootFieldUnpreparedValue = Tracing.newSpan ("Resolve execution step for " <>> rootFieldName) Tracing.SKInternal do case rootFieldUnpreparedValue of RFDB sourceName exists -> @@ -161,7 +163,7 @@ convertMutationSelectionSet (actionName, _fch) <- pure $ case noRelsDBAST of AMSync s -> (_aaeName s, _aaeForwardClientHeaders s) AMAsync s -> (_aamaName s, _aamaForwardClientHeaders s) - plan <- convertMutationAction env logger tracesPropagator prometheusMetrics userInfo reqHeaders (Just (GH._grQuery gqlUnparsed)) noRelsDBAST headerPrecedence + plan <- convertMutationAction env logger tracesPropagator prometheusMetrics userInfo reqHeaders (Just (GH._grQuery gqlUnparsed)) noRelsDBAST includeInternalErrors headerPrecedence let actionsModel = ModelInfoPart (toTxt actionName) ModelTypeAction Nothing Nothing (ModelOperationType G.OperationTypeMutation) pure $ (ExecStepAction plan (ActionsInfo actionName _fch) remoteJoins, [actionsModel]) -- `_fch` represents the `forward_client_headers` option from the action -- definition which is currently being ignored for actions that are mutations diff --git a/server/src-lib/Hasura/GraphQL/Execute/Query.hs b/server/src-lib/Hasura/GraphQL/Execute/Query.hs index 0c2a2cc884c48..aa8c2c3b86c2c 100644 --- a/server/src-lib/Hasura/GraphQL/Execute/Query.hs +++ b/server/src-lib/Hasura/GraphQL/Execute/Query.hs @@ -38,7 +38,7 @@ import Hasura.RQL.Types.GraphqlSchemaIntrospection import Hasura.RQL.Types.Schema.Options as Options import Hasura.RemoteSchema.Metadata.Base (RemoteSchemaName (..)) import Hasura.SQL.AnyBackend qualified as AB -import Hasura.Server.Init.Config (ResponseInternalErrorsConfig (..)) +import Hasura.Server.Init.Config (ResponseInternalErrorsConfig (..), shouldIncludeInternal) import Hasura.Server.Prometheus (PrometheusMetrics (..)) import Hasura.Server.Types (HeaderPrecedence, MonadGetPolicies, RequestId (..), TraceQueryStatus) import Hasura.Services.Network @@ -167,6 +167,7 @@ convertQuerySelSet s (ActionExecContext reqHeaders (_uiSession userInfo)) (Just (GH._grQuery gqlUnparsed)) + (shouldIncludeInternal (_uiRole userInfo) responseErrorsConfig) headerPrecedence, _aaeName s, _aaeForwardClientHeaders s diff --git a/server/src-lib/Hasura/GraphQL/Execute/Subscription/Poll/LiveQuery.hs b/server/src-lib/Hasura/GraphQL/Execute/Subscription/Poll/LiveQuery.hs index 9a3b7192bc9a5..1c3b3b941f8e8 100644 --- a/server/src-lib/Hasura/GraphQL/Execute/Subscription/Poll/LiveQuery.hs +++ b/server/src-lib/Hasura/GraphQL/Execute/Subscription/Poll/LiveQuery.hs @@ -238,7 +238,7 @@ pollLiveQuery pollerId pollerResponseState lqOpts (sourceName, sourceConfig) rol getCohortOperations cohorts = \case Left e -> -- TODO: this is internal error - let resp = throwError $ GQExecError [encodeGQLErr False e] + let resp = throwError $ GQExecError [encodeGQLErr HideInternalErrors e] in [(resp, cohortId, Nothing, snapshot) | (cohortId, snapshot) <- cohorts] Right responses -> do let cohortSnapshotMap = HashMap.fromList cohorts diff --git a/server/src-lib/Hasura/GraphQL/Execute/Subscription/Poll/StreamingQuery.hs b/server/src-lib/Hasura/GraphQL/Execute/Subscription/Poll/StreamingQuery.hs index 74052503a0f08..10790b7efd6dc 100644 --- a/server/src-lib/Hasura/GraphQL/Execute/Subscription/Poll/StreamingQuery.hs +++ b/server/src-lib/Hasura/GraphQL/Execute/Subscription/Poll/StreamingQuery.hs @@ -515,7 +515,7 @@ pollStreamingQuery pollerId pollerResponseState streamingQueryOpts (sourceName, getCohortOperations cohorts = \case Left e -> - let resp = throwError $ GQExecError [encodeGQLErr False e] + let resp = throwError $ GQExecError [encodeGQLErr HideInternalErrors e] in [(resp, cohortId, Nothing, Nothing, snapshot) | (cohortId, snapshot) <- cohorts] Right responses -> do let cohortSnapshotMap = HashMap.fromList cohorts diff --git a/server/src-lib/Hasura/GraphQL/Transport/HTTP/Protocol.hs b/server/src-lib/Hasura/GraphQL/Transport/HTTP/Protocol.hs index d9886f474e2aa..53e6bcc8fbd3e 100644 --- a/server/src-lib/Hasura/GraphQL/Transport/HTTP/Protocol.hs +++ b/server/src-lib/Hasura/GraphQL/Transport/HTTP/Protocol.hs @@ -208,7 +208,7 @@ getOpNameFromParsedReq reqParsed = where execDefs = unGQLExecDoc $ _grQuery reqParsed -encodeGQErr :: Bool -> QErr -> J.Encoding +encodeGQErr :: IncludeInternalErrors -> QErr -> J.Encoding encodeGQErr includeInternal qErr = J.pairs (J.pair "errors" $ J.list id [encodeGQLErr includeInternal qErr]) diff --git a/server/src-lib/Hasura/GraphQL/Transport/WSServerApp.hs b/server/src-lib/Hasura/GraphQL/Transport/WSServerApp.hs index dc05cc5513ebd..164b504ce07cc 100644 --- a/server/src-lib/Hasura/GraphQL/Transport/WSServerApp.hs +++ b/server/src-lib/Hasura/GraphQL/Transport/WSServerApp.hs @@ -142,6 +142,7 @@ createWSServerEnv appStateRef = do appEnvServerMetrics appEnvPrometheusMetrics appEnvTraceSamplingPolicy + appEnvLoggingSettings mkWSActions :: L.Logger L.Hasura -> WSSubProtocol -> WS.WSActions WSConnData mkWSActions logger subProtocol = diff --git a/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs b/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs index fd965d2c8de3b..1dac63ff30dee 100644 --- a/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs +++ b/server/src-lib/Hasura/GraphQL/Transport/WebSocket.hs @@ -371,7 +371,7 @@ onConn wsId requestHead ipAddress onConnHActions = do (HTTP.statusCode $ qeStatus qErr) (HTTP.statusMessage $ qeStatus qErr) [] - (LBS.toStrict $ J.encodingToLazyByteString $ encodeGQLErr False qErr) + (LBS.toStrict $ J.encodingToLazyByteString $ encodeGQLErr HideInternalErrors qErr) checkPath = case WS.requestPath requestHead of "/v1alpha1/graphql" -> return (ERTLegacy, E.QueryHasura) @@ -881,6 +881,7 @@ onStart enabledLogTypes agentLicenseKey serverEnv wsConn shouldCaptureVariables _keepAliveDelay _serverMetrics prometheusMetrics + _loggerSettings _ = serverEnv -- Hook to retrieve the latest subscription options(live query + stream query options) from the `appStateRef` @@ -909,7 +910,7 @@ onStart enabledLogTypes agentLicenseKey serverEnv wsConn shouldCaptureVariables sendMsg wsConn $ SMErr $ ErrorMsg opId - $ errFn False + $ errFn HideInternalErrors $ err400 StartFailed e liftIO $ logOpEv (ODProtoErr e) Nothing Nothing liftIO $ reportGQLQueryError granularPrometheusMetricsState mOpName Nothing Nothing @@ -928,7 +929,7 @@ onStart enabledLogTypes agentLicenseKey serverEnv wsConn shouldCaptureVariables QErr -> ExceptT () m () postExecErr granularPrometheusMetricsState reqId gqlOpType mOpName pqh qErr = do - let errFn = getErrFn errRespTy False + let errFn = getErrFn errRespTy HideInternalErrors liftIO $ logOpEv (ODQueryErr qErr) (Just reqId) Nothing postExecErr' granularPrometheusMetricsState gqlOpType mOpName pqh $ GQExecError $ pure $ errFn qErr @@ -947,8 +948,8 @@ onStart enabledLogTypes agentLicenseKey serverEnv wsConn shouldCaptureVariables let errFn = getErrFn errRespTy logOpEv (ODQueryErr qErr) (Just reqId) Nothing let err = case errRespTy of - ERTLegacy -> errFn False qErr - ERTGraphqlCompliant -> fmtErrorMessage [errFn False qErr] + ERTLegacy -> errFn HideInternalErrors qErr + ERTGraphqlCompliant -> fmtErrorMessage [errFn HideInternalErrors qErr] sendMsg wsConn (SMErr $ ErrorMsg opId err) sendSuccResp :: diff --git a/server/src-lib/Hasura/GraphQL/Transport/WebSocket/Types.hs b/server/src-lib/Hasura/GraphQL/Transport/WebSocket/Types.hs index 67e5aad3b498d..1994ed278e61e 100644 --- a/server/src-lib/Hasura/GraphQL/Transport/WebSocket/Types.hs +++ b/server/src-lib/Hasura/GraphQL/Transport/WebSocket/Types.hs @@ -24,6 +24,7 @@ import Hasura.Prelude import Hasura.Server.AppStateRef import Hasura.Server.Cors import Hasura.Server.Init.Config (KeepAliveDelay (..)) +import Hasura.Server.Logging (LoggingSettings (..)) import Hasura.Server.Metrics (ServerMetrics (..)) import Hasura.Server.Prometheus (PrometheusMetrics (..)) import Hasura.Server.Types (ReadOnlyMode (..)) @@ -82,7 +83,8 @@ data WSServerEnv impl = WSServerEnv _wseKeepAliveDelay :: !KeepAliveDelay, _wseServerMetrics :: !ServerMetrics, _wsePrometheusMetrics :: !PrometheusMetrics, - _wseTraceSamplingPolicy :: !Tracing.SamplingPolicy + _wseTraceSamplingPolicy :: !Tracing.SamplingPolicy, + _wseLoggingSettings :: !LoggingSettings } data SubscriberType diff --git a/server/src-lib/Hasura/Server/App.hs b/server/src-lib/Hasura/Server/App.hs index 10c32e536d334..ec4dee3185f79 100644 --- a/server/src-lib/Hasura/Server/App.hs +++ b/server/src-lib/Hasura/Server/App.hs @@ -314,7 +314,7 @@ mkSpockAction :: ) => AppStateRef impl -> -- | `QErr` JSON encoder function - (Bool -> QErr -> Encoding) -> + (IncludeInternalErrors -> QErr -> Encoding) -> -- | `QErr` modifier (QErr -> QErr) -> APIHandler m a -> @@ -346,7 +346,7 @@ mkSpockAction appStateRef qErrEncoder qErrModifier apiHandler = do let getInfo parsedRequest = do authenticationResp <- lift (resolveUserInfo (_lsLogger appEnvLoggers) appEnvManager headers acAuthMode parsedRequest) - authInfo <- authenticationResp `onLeft` (logErrorAndResp Nothing requestId req (reqBody, Nothing) False Nothing origHeaders (ExtraUserInfo Nothing) . qErrModifier) + authInfo <- authenticationResp `onLeft` (logErrorAndResp Nothing requestId req (reqBody, Nothing) HideInternalErrors Nothing origHeaders (ExtraUserInfo Nothing) . qErrModifier) let (userInfo, _, authHeaders, extraUserInfo) = authInfo appContext <- liftIO $ getAppContext appStateRef schemaCache <- liftIO $ getRebuildableSchemaCacheWithVersion appStateRef @@ -383,7 +383,7 @@ mkSpockAction appStateRef qErrEncoder qErrModifier apiHandler = do -- if the request fails to parse, call the webhook without a request body -- TODO should we signal this to the webhook somehow? (userInfo, _, _, _, extraUserInfo) <- getInfo Nothing - logErrorAndResp (Just userInfo) requestId req (reqBody, Nothing) False Nothing origHeaders extraUserInfo (qErrModifier e) + logErrorAndResp (Just userInfo) requestId req (reqBody, Nothing) HideInternalErrors Nothing origHeaders extraUserInfo (qErrModifier e) (userInfo, authHeaders, handlerState, includeInternal, extraUserInfo) <- getInfo (Just parsedReq) res <- lift $ runHandler (_lsLogger appEnvLoggers) handlerState $ handler parsedReq @@ -394,7 +394,7 @@ mkSpockAction appStateRef qErrEncoder qErrModifier apiHandler = do -- if the request fails to parse, call the webhook without a request body -- TODO should we signal this to the webhook somehow? (userInfo, _, _, _, extraUserInfo) <- getInfo Nothing - logErrorAndResp (Just userInfo) requestId req (reqBody, Nothing) False Nothing origHeaders extraUserInfo (qErrModifier e) + logErrorAndResp (Just userInfo) requestId req (reqBody, Nothing) HideInternalErrors Nothing origHeaders extraUserInfo (qErrModifier e) let newReq = case parsedReq of EqrGQLReq reqText -> Just reqText -- Note: We send only `ReqsText` to the webhook in case of `ExtPersistedQueryRequest` (persisted queries), @@ -433,7 +433,7 @@ mkSpockAction appStateRef qErrEncoder qErrModifier apiHandler = do RequestId -> Wai.Request -> (BL.ByteString, Maybe Value) -> - Bool -> + IncludeInternalErrors -> Maybe (DiffTime, DiffTime) -> [HTTP.Header] -> ExtraUserInfo -> @@ -863,7 +863,7 @@ spockInternalErrorHandler :: HTTP.Status -> Spock.ActionCtxT ctx IO () spockInternalErrorHandler _status = do -- Ignore the status code and always return 500 Spock.setStatus HTTP.status500 - Spock.lazyBytes $ J.encodingToLazyByteString $ encodeQErr False $ err500 Unexpected "Internal Server Error" + Spock.lazyBytes $ J.encodingToLazyByteString $ encodeQErr HideInternalErrors $ err500 Unexpected "Internal Server Error" -- | Logger for internal errors arising from spock spockInternalErrorLogger :: (Tracing.MonadTraceContext m, MonadIO m) => L.Logger L.Hasura -> Text -> m () @@ -967,6 +967,7 @@ httpApp setupHook appStateRef AppEnv {..} consoleType ekgStore closeWebsocketsOn Spock.lazyBytes $ encode $ object $ ["version" .= currentVersion] <> extraData responseErrorsConfig <- liftIO $ acResponseInternalErrorsConfig <$> getAppContext appStateRef + let customEndpointHandler :: RestRequest Spock.SpockMethod -> Handler m (HttpLogGraphQLInfo, APIResp) @@ -1180,7 +1181,7 @@ httpApp setupHook appStateRef AppEnv {..} consoleType ekgStore closeWebsocketsOn spockAction :: forall a. (FromJSON a) => - (Bool -> QErr -> Encoding) -> + (IncludeInternalErrors -> QErr -> Encoding) -> (QErr -> QErr) -> APIHandler m a -> Spock.ActionT m () @@ -1231,7 +1232,7 @@ onlyWhenApiEnabled isEnabled appStateRef endpointAction = do let qErr = err404 NotFound "resource does not exist" Spock.setStatus $ qeStatus qErr setHeader jsonHeader - Spock.lazyBytes . J.encodingToLazyByteString $ encodeQErr False qErr + Spock.lazyBytes . J.encodingToLazyByteString $ encodeQErr HideInternalErrors qErr raiseGenericApiError :: forall m. diff --git a/server/src-lib/Hasura/Server/Init/Config.hs b/server/src-lib/Hasura/Server/Init/Config.hs index 8f6339381ada8..d7b86f6b35eda 100644 --- a/server/src-lib/Hasura/Server/Init/Config.hs +++ b/server/src-lib/Hasura/Server/Init/Config.hs @@ -89,6 +89,7 @@ import Data.URL.Template qualified as Template import Database.PG.Query qualified as Query import Hasura.Authentication.Role (RoleName, adminRoleName) import Hasura.Backends.Postgres.Connection.MonadTx qualified as MonadTx +import Hasura.Base.Error (IncludeInternalErrors (..)) import Hasura.GraphQL.Execute.Subscription.Options qualified as Subscription.Options import Hasura.Logging qualified as Logging import Hasura.NativeQuery.Validation qualified as NativeQuery.Validation @@ -664,11 +665,14 @@ data ResponseInternalErrorsConfig | InternalErrorsDisabled deriving (Show, Eq) -shouldIncludeInternal :: RoleName -> ResponseInternalErrorsConfig -> Bool +shouldIncludeInternal :: RoleName -> ResponseInternalErrorsConfig -> IncludeInternalErrors shouldIncludeInternal role = \case - InternalErrorsAllRequests -> True - InternalErrorsAdminOnly -> role == adminRoleName - InternalErrorsDisabled -> False + InternalErrorsAllRequests -> IncludeInternalErrors + InternalErrorsAdminOnly -> + if role == adminRoleName + then IncludeInternalErrors + else HideInternalErrors + InternalErrorsDisabled -> HideInternalErrors -------------------------------------------------------------------------------- diff --git a/server/tests-py/test_actions.py b/server/tests-py/test_actions.py index f7513e3ac7cb1..8ec8d16956562 100644 --- a/server/tests-py/test_actions.py +++ b/server/tests-py/test_actions.py @@ -600,7 +600,6 @@ def test_async_actions_error_response_admin_role(self, hge_ctx): response, _ = check_query(hge_ctx, conf) assert 'errors' in response['data']['test_async_action_error_response'] - assert 'internal' in response['data']['test_async_action_error_response']['errors'] def test_create_user_success(self, hge_ctx): graphql_mutation = ''' @@ -1013,7 +1012,4 @@ def test_action_timeout_fail(self, hge_ctx): response, _ = check_query(hge_ctx, conf) assert 'errors' in response['data']['create_user'] - assert 'Response timeout' == response['data']['create_user']['errors']['internal']['error']['message'] - # tests that actions webhook url environment variable template did not serialize in the error message - assert "{{ACTION_WEBHOOK_HANDLER}}/create-user-timeout" == response['data']['create_user']['errors']['internal']['request']['url']