fix: empty error messages for disabled openapi and /invalid/nested/paths

This commit is contained in:
Taimoor Zaeem
2025-03-21 11:51:40 -05:00
committed by GitHub
parent a1c0a8ce6d
commit fdc26d52fc
8 changed files with 60 additions and 20 deletions
+1
View File
@@ -49,6 +49,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
+ Return `PGRST301` error when `Bearer` in auth header is sent empty + Return `PGRST301` error when `Bearer` in auth header is sent empty
+ Diagnostic error messages instead of exposed internals + Diagnostic error messages instead of exposed internals
+ Return new `PGRST303` error when jwt claims decoding fails + Return new `PGRST303` error when jwt claims decoding fails
- #3906, Return `PGRST125` and `PGRST126` errors instead of empty json - @taimoorzaeem
## [12.2.8] - 2025-02-10 ## [12.2.8] - 2025-02-10
+9
View File
@@ -257,6 +257,15 @@ Related to the HTTP request elements.
| | | See :ref:`prefer_max_affected`. | | | | See :ref:`prefer_max_affected`. |
| PGRST124 | | | | PGRST124 | | |
+---------------+-------------+-------------------------------------------------------------+ +---------------+-------------+-------------------------------------------------------------+
| .. _pgrst125: | 404 | Invalid path is specified in request URL. |
| | | |
| PGRST125 | | |
+---------------+-------------+-------------------------------------------------------------+
| .. _pgrst126: | 404 | Open API config is disabled but API root path is |
| | | accessed. See :ref:`openapi-mode`. |
| PGRST126 | | |
+---------------+-------------+-------------------------------------------------------------+
.. _pgrst2**: .. _pgrst2**:
+7 -5
View File
@@ -165,13 +165,15 @@ userApiRequest conf req reqBody timezones = do
getResource :: AppConfig -> [Text] -> Either ApiRequestError Resource getResource :: AppConfig -> [Text] -> Either ApiRequestError Resource
getResource AppConfig{configOpenApiMode, configDbRootSpec} = \case getResource AppConfig{configOpenApiMode, configDbRootSpec} = \case
[] -> case configDbRootSpec of [] ->
Just (QualifiedIdentifier _ pathName) -> Right $ ResourceRoutine pathName case (configOpenApiMode,configDbRootSpec) of
Nothing | configOpenApiMode == OADisabled -> Left NotFound (OADisabled,_) -> Left OpenAPIDisabled
| otherwise -> Right ResourceSchema (_, Just qi) -> Right $ ResourceRoutine (qiName qi)
(_, Nothing) -> Right ResourceSchema
[table] -> Right $ ResourceRelation table [table] -> Right $ ResourceRelation table
["rpc", pName] -> Right $ ResourceRoutine pName ["rpc", pName] -> Right $ ResourceRoutine pName
_ -> Left NotFound _ -> Left InvalidResourcePath
getAction :: Resource -> Schema -> ByteString -> Either ApiRequestError Action getAction :: Resource -> Schema -> ByteString -> Either ApiRequestError Action
getAction resource schema method = getAction resource schema method =
+20 -4
View File
@@ -72,7 +72,6 @@ data ApiRequestError
| InvalidPreferences [ByteString] | InvalidPreferences [ByteString]
| InvalidRange RangeError | InvalidRange RangeError
| InvalidRpcMethod ByteString | InvalidRpcMethod ByteString
| NotFound
| NoRelBetween Text Text (Maybe Text) Text RelationshipsMap | NoRelBetween Text Text (Maybe Text) Text RelationshipsMap
| NoRpc Text Text [Text] MediaType Bool [QualifiedIdentifier] [Routine] | NoRpc Text Text [Text] MediaType Bool [QualifiedIdentifier] [Routine]
| NotEmbedded Text | NotEmbedded Text
@@ -91,6 +90,8 @@ data ApiRequestError
| SingularityError Integer | SingularityError Integer
| PGRSTParseError RaiseError | PGRSTParseError RaiseError
| MaxAffectedViolationError Integer | MaxAffectedViolationError Integer
| InvalidResourcePath
| OpenAPIDisabled
deriving Show deriving Show
data QPError = QPError Text Text data QPError = QPError Text Text
@@ -118,7 +119,6 @@ instance PgrstError ApiRequestError where
status InvalidPreferences{} = HTTP.status400 status InvalidPreferences{} = HTTP.status400
status InvalidRpcMethod{} = HTTP.status405 status InvalidRpcMethod{} = HTTP.status405
status InvalidRange{} = HTTP.status416 status InvalidRange{} = HTTP.status416
status NotFound = HTTP.status404
status NoRelBetween{} = HTTP.status400 status NoRelBetween{} = HTTP.status400
status NoRpc{} = HTTP.status404 status NoRpc{} = HTTP.status404
@@ -138,6 +138,8 @@ instance PgrstError ApiRequestError where
status SingularityError{} = HTTP.status406 status SingularityError{} = HTTP.status406
status PGRSTParseError{} = HTTP.status500 status PGRSTParseError{} = HTTP.status500
status MaxAffectedViolationError{} = HTTP.status400 status MaxAffectedViolationError{} = HTTP.status400
status InvalidResourcePath = HTTP.status404
status OpenAPIDisabled = HTTP.status404
headers _ = mempty headers _ = mempty
@@ -177,8 +179,6 @@ instance JSON.ToJSON ApiRequestError where
toJSON (MediaTypeError cts) = toJsonPgrstError toJSON (MediaTypeError cts) = toJsonPgrstError
ApiRequestErrorCode07 ("None of these media types are available: " <> T.intercalate ", " (map T.decodeUtf8 cts)) Nothing Nothing ApiRequestErrorCode07 ("None of these media types are available: " <> T.intercalate ", " (map T.decodeUtf8 cts)) Nothing Nothing
toJSON NotFound = JSON.object []
toJSON (NotEmbedded resource) = toJsonPgrstError toJSON (NotEmbedded resource) = toJsonPgrstError
ApiRequestErrorCode08 ApiRequestErrorCode08
("'" <> resource <> "' is not an embedded resource in this request") ("'" <> resource <> "' is not an embedded resource in this request")
@@ -245,6 +245,18 @@ instance JSON.ToJSON ApiRequestError where
(Just $ JSON.String $ T.unwords ["The query affects", show n, "rows"]) (Just $ JSON.String $ T.unwords ["The query affects", show n, "rows"])
Nothing Nothing
toJSON InvalidResourcePath = toJsonPgrstError
ApiRequestErrorCode25
"Invalid path specified in request URL"
Nothing
Nothing
toJSON OpenAPIDisabled = toJsonPgrstError
ApiRequestErrorCode26
"Root endpoint metadata is disabled"
Nothing
Nothing
toJSON (NoRelBetween parent child embedHint schema allRels) = toJsonPgrstError toJSON (NoRelBetween parent child embedHint schema allRels) = toJsonPgrstError
SchemaCacheErrorCode00 SchemaCacheErrorCode00
("Could not find a relationship between '" <> parent <> "' and '" <> child <> "' in the schema cache") ("Could not find a relationship between '" <> parent <> "' and '" <> child <> "' in the schema cache")
@@ -706,6 +718,8 @@ data ErrorCode
| ApiRequestErrorCode22 | ApiRequestErrorCode22
| ApiRequestErrorCode23 | ApiRequestErrorCode23
| ApiRequestErrorCode24 | ApiRequestErrorCode24
| ApiRequestErrorCode25
| ApiRequestErrorCode26
-- Schema Cache errors -- Schema Cache errors
| SchemaCacheErrorCode00 | SchemaCacheErrorCode00
| SchemaCacheErrorCode01 | SchemaCacheErrorCode01
@@ -755,6 +769,8 @@ buildErrorCode code = case code of
ApiRequestErrorCode22 -> "PGRST122" ApiRequestErrorCode22 -> "PGRST122"
ApiRequestErrorCode23 -> "PGRST123" ApiRequestErrorCode23 -> "PGRST123"
ApiRequestErrorCode24 -> "PGRST124" ApiRequestErrorCode24 -> "PGRST124"
ApiRequestErrorCode25 -> "PGRST125"
ApiRequestErrorCode26 -> "PGRST126"
SchemaCacheErrorCode00 -> "PGRST200" SchemaCacheErrorCode00 -> "PGRST200"
SchemaCacheErrorCode01 -> "PGRST201" SchemaCacheErrorCode01 -> "PGRST201"
+7 -6
View File
@@ -33,6 +33,7 @@ import qualified PostgREST.SchemaCache.Routine as Routine
import Data.Either.Combinators (mapLeft, mapRight) import Data.Either.Combinators (mapLeft, mapRight)
import Data.List (delete, lookup) import Data.List (delete, lookup)
import Data.Maybe (fromJust)
import Data.Tree (Tree (..)) import Data.Tree (Tree (..))
import PostgREST.ApiRequest (Action (..), import PostgREST.ApiRequest (Action (..),
@@ -964,18 +965,18 @@ mutatePlan mutation qi ApiRequest{iPreferences=Preferences{..}, ..} SchemaCache{
if preferRepresentation == Just None || isNothing preferRepresentation if preferRepresentation == Just None || isNothing preferRepresentation
then [] then []
else inferColsEmbedNeeds readReq pkCols else inferColsEmbedNeeds readReq pkCols
tbl = HM.lookup qi dbTables -- TODO: remove fromJust by refactoring later
pkCols = maybe mempty tablePKCols tbl -- we can use fromJust, we have already looked up the table before building mutatePlan
tbl = fromJust $ HM.lookup qi dbTables
pkCols = maybe mempty tablePKCols (Just tbl)
logic = map (resolveLogicTree ctx . snd) qsLogic logic = map (resolveLogicTree ctx . snd) qsLogic
combinedLogic = foldr (addFilterToLogicForest . resolveFilter ctx) logic qsFiltersRoot combinedLogic = foldr (addFilterToLogicForest . resolveFilter ctx) logic qsFiltersRoot
body = payRaw <$> iPayload -- the body is assumed to be json at this stage(ApiRequest validates) body = payRaw <$> iPayload -- the body is assumed to be json at this stage(ApiRequest validates)
applyDefaults = preferMissing == Just ApplyDefaults applyDefaults = preferMissing == Just ApplyDefaults
typedColumnsOrError = resolveOrError ctx tbl `traverse` S.toList iColumns typedColumnsOrError = resolveOrError ctx tbl `traverse` S.toList iColumns
resolveOrError :: ResolverContext -> Maybe Table -> FieldName -> Either ApiRequestError CoercibleField resolveOrError :: ResolverContext -> Table -> FieldName -> Either ApiRequestError CoercibleField
resolveOrError _ Nothing _ = Left NotFound -- TODO: control never reaches here since #3869, should be fixed when fixing #3906 resolveOrError ctx table field = case resolveTableFieldName table field Nothing of
resolveOrError ctx (Just table) field =
case resolveTableFieldName table field Nothing of
CoercibleField{cfIRType=""} -> Left $ ColumnNotFound (tableName table) field CoercibleField{cfIRType=""} -> Left $ ColumnNotFound (tableName table) field
cf -> Right $ withJsonParse ctx cf cf -> Right $ withJsonParse ctx cf
+3 -3
View File
@@ -228,10 +228,10 @@ actionResponse (MaybeDbResult InspectPlan{ipHdrsOnly=headersOnly} body) _ versio
(MediaType.toContentType MTOpenAPI : maybeToList (profileHeader schema negotiatedByProfile)) (MediaType.toContentType MTOpenAPI : maybeToList (profileHeader schema negotiatedByProfile))
(maybe mempty (\(x, y, z) -> if headersOnly then mempty else OpenAPI.encode versions conf sCache x y z) body) (maybe mempty (\(x, y, z) -> if headersOnly then mempty else OpenAPI.encode versions conf sCache x y z) body)
actionResponse (NoDbResult (RelInfoPlan identifier)) _ _ _ sCache _ _ = actionResponse (NoDbResult (RelInfoPlan qi@QualifiedIdentifier{..})) _ _ _ SchemaCache{dbTables} _ _ =
case HM.lookup identifier (dbTables sCache) of case HM.lookup qi dbTables of
Just tbl -> respondInfo $ allowH tbl Just tbl -> respondInfo $ allowH tbl
Nothing -> Left $ Error.ApiRequestError Error.NotFound Nothing -> Left $ Error.ApiRequestError $ Error.TableNotFound qiSchema qiName (HM.elems dbTables)
where where
allowH table = allowH table =
let hasPK = not . null $ tablePKCols table in let hasPK = not . null $ tablePKCols table in
@@ -3,8 +3,9 @@ module Feature.OpenApi.DisabledOpenApiSpec where
import Network.HTTP.Types import Network.HTTP.Types
import Network.Wai (Application) import Network.Wai (Application)
import Test.Hspec hiding (pendingWith) import Test.Hspec
import Test.Hspec.Wai import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import Protolude import Protolude
@@ -13,4 +14,7 @@ spec =
describe "Disabled OpenApi" $ do describe "Disabled OpenApi" $ do
it "responds with 404" $ it "responds with 404" $
request methodGet "/" request methodGet "/"
[("Accept","application/openapi+json")] "" `shouldRespondWith` 404 [("Accept","application/openapi+json")] ""
`shouldRespondWith`
[json| {"code":"PGRST126","details":null,"hint":null,"message":"Root endpoint metadata is disabled"} |]
{ matchStatus = 404 }
+7
View File
@@ -1625,3 +1625,10 @@ spec = do
get "/infinite_recursion?select=*" `shouldRespondWith` get "/infinite_recursion?select=*" `shouldRespondWith`
[json|{"code":"42P17","message":"infinite recursion detected in rules for relation \"infinite_recursion\"","details":null,"hint":null}|] [json|{"code":"42P17","message":"infinite recursion detected in rules for relation \"infinite_recursion\"","details":null,"hint":null}|]
{ matchStatus = 500 } { matchStatus = 500 }
context "invalid resource path" $ do
it "return http status 404" $
get "/first/second/third?select=*"
`shouldRespondWith`
[json| {"code":"PGRST125","details":null,"hint":null,"message":"Invalid path specified in request URL"} |]
{ matchStatus = 404 }