refactor: group jwt errors

This commit is contained in:
Taimoor Zaeem
2025-03-04 16:54:42 -05:00
committed by Steve Chavez
parent c9a625ced6
commit 4ddf33df76
2 changed files with 32 additions and 18 deletions
+7 -7
View File
@@ -48,7 +48,7 @@ import PostgREST.Auth.JwtCache (lookupJwtCache)
import PostgREST.Auth.Types (AuthResult (..)) import PostgREST.Auth.Types (AuthResult (..))
import PostgREST.Config (AppConfig (..), FilterExp (..), import PostgREST.Config (AppConfig (..), FilterExp (..),
JSPath, JSPathExp (..)) JSPath, JSPathExp (..))
import PostgREST.Error (Error (..)) import PostgREST.Error (Error (..), JwtError (..))
import Protolude import Protolude
@@ -57,20 +57,20 @@ import Protolude
parseToken :: AppConfig -> ByteString -> UTCTime -> ExceptT Error IO JSON.Value parseToken :: AppConfig -> ByteString -> UTCTime -> ExceptT Error IO JSON.Value
parseToken _ "" _ = return JSON.emptyObject parseToken _ "" _ = return JSON.emptyObject
parseToken AppConfig{..} token time = do parseToken AppConfig{..} token time = do
secret <- liftEither . maybeToRight JwtTokenMissing $ configJWKS secret <- liftEither . maybeToRight (JwtErr JwtTokenMissing) $ configJWKS
eitherContent <- liftIO $ JWT.decode (JWT.keys secret) Nothing token eitherContent <- liftIO $ JWT.decode (JWT.keys secret) Nothing token
content <- liftEither . mapLeft jwtDecodeError $ eitherContent content <- liftEither . mapLeft (JwtErr . jwtDecodeError) $ eitherContent
liftEither $ verifyClaims content liftEither $ mapLeft JwtErr $ verifyClaims content
where where
-- TODO: Improve errors, those were just taken as-is from hs-jose to avoid -- TODO: Improve errors, those were just taken as-is from hs-jose to avoid
-- breaking changes. -- breaking changes.
jwtDecodeError :: JWT.JwtError -> Error jwtDecodeError :: JWT.JwtError -> JwtError
jwtDecodeError (JWT.KeyError _) = JwtTokenInvalid "JWSError JWSInvalidSignature" jwtDecodeError (JWT.KeyError _) = JwtTokenInvalid "JWSError JWSInvalidSignature"
jwtDecodeError JWT.BadCrypto = JwtTokenInvalid "JWSError (CompactDecodeError Invalid number of parts: Expected 3 parts; got 2)" jwtDecodeError JWT.BadCrypto = JwtTokenInvalid "JWSError (CompactDecodeError Invalid number of parts: Expected 3 parts; got 2)"
jwtDecodeError (JWT.BadAlgorithm _) = JwtTokenInvalid "JWSError JWSNoSignatures" jwtDecodeError (JWT.BadAlgorithm _) = JwtTokenInvalid "JWSError JWSNoSignatures"
jwtDecodeError e = JwtTokenInvalid $ show e jwtDecodeError e = JwtTokenInvalid $ show e
verifyClaims :: JWT.JwtContent -> Either Error JSON.Value verifyClaims :: JWT.JwtContent -> Either JwtError JSON.Value
verifyClaims (JWT.Jws (_, claims)) = case JSON.decodeStrict claims of verifyClaims (JWT.Jws (_, claims)) = case JSON.decodeStrict claims of
Nothing -> Left $ JwtTokenInvalid "Parsing claims failed" Nothing -> Left $ JwtTokenInvalid "Parsing claims failed"
Just (JSON.Object mclaims) Just (JSON.Object mclaims)
@@ -110,7 +110,7 @@ parseClaims :: Monad m =>
AppConfig -> JSON.Value -> ExceptT Error m AuthResult AppConfig -> JSON.Value -> ExceptT Error m AuthResult
parseClaims AppConfig{..} jclaims@(JSON.Object mclaims) = do parseClaims AppConfig{..} jclaims@(JSON.Object mclaims) = do
-- role defaults to anon if not specified in jwt -- role defaults to anon if not specified in jwt
role <- liftEither . maybeToRight JwtTokenRequired $ role <- liftEither . maybeToRight (JwtErr JwtTokenRequired) $
unquoted <$> walkJSPath (Just jclaims) configJwtRoleClaimKey <|> configDbAnonRole unquoted <$> walkJSPath (Just jclaims) configJwtRoleClaimKey <|> configDbAnonRole
return AuthResult return AuthResult
{ authClaims = mclaims & KM.insert "role" (JSON.toJSON $ decodeUtf8 role) { authClaims = mclaims & KM.insert "role" (JSON.toJSON $ decodeUtf8 role)
+25 -11
View File
@@ -12,6 +12,7 @@ module PostgREST.Error
, RangeError(..) , RangeError(..)
, PgError(..) , PgError(..)
, Error(..) , Error(..)
, JwtError (..)
, errorPayload , errorPayload
, status , status
) where ) where
@@ -567,32 +568,47 @@ pgErrorStatus authed (SQL.SessionUsageError (SQL.QueryError _ _ (SQL.ResultError
_ -> HTTP.status500 _ -> HTTP.status500
-- TODO: separate "SchemaCacheError" from ApiRequestError similar to how we
-- group them in docs
data Error data Error
= ApiRequestError ApiRequestError = ApiRequestError ApiRequestError
| JwtTokenInvalid Text | JwtErr JwtError
| JwtTokenMissing
| JwtTokenRequired
| NoSchemaCacheError | NoSchemaCacheError
| PgErr PgError | PgErr PgError
data JwtError
= JwtTokenInvalid Text
| JwtTokenMissing
| JwtTokenRequired
instance PgrstError Error where instance PgrstError Error where
status (ApiRequestError err) = status err status (ApiRequestError err) = status err
status JwtTokenInvalid{} = HTTP.unauthorized401 status (JwtErr err) = status err
status JwtTokenMissing = HTTP.status500
status JwtTokenRequired = HTTP.unauthorized401
status NoSchemaCacheError = HTTP.status503 status NoSchemaCacheError = HTTP.status503
status (PgErr err) = status err status (PgErr err) = status err
headers (ApiRequestError err) = headers err headers (ApiRequestError err) = headers err
headers (JwtTokenInvalid m) = [invalidTokenHeader m] headers (JwtErr err) = headers err
headers JwtTokenRequired = [requiredTokenHeader]
headers (PgErr err) = headers err headers (PgErr err) = headers err
headers _ = mempty headers _ = mempty
instance PgrstError JwtError where
status JwtTokenInvalid{} = HTTP.unauthorized401
status JwtTokenMissing = HTTP.status500
status JwtTokenRequired = HTTP.unauthorized401
headers (JwtTokenInvalid m) = [invalidTokenHeader m]
headers JwtTokenRequired = [requiredTokenHeader]
headers _ = mempty
instance JSON.ToJSON Error where instance JSON.ToJSON Error where
toJSON NoSchemaCacheError = toJsonPgrstError toJSON (ApiRequestError err) = JSON.toJSON err
toJSON (JwtErr err) = JSON.toJSON err
toJSON (PgErr err) = JSON.toJSON err
toJSON NoSchemaCacheError = toJsonPgrstError
ConnectionErrorCode02 "Could not query the database for the schema cache. Retrying." Nothing Nothing ConnectionErrorCode02 "Could not query the database for the schema cache. Retrying." Nothing Nothing
instance JSON.ToJSON JwtError where
toJSON JwtTokenMissing = toJsonPgrstError toJSON JwtTokenMissing = toJsonPgrstError
JWTErrorCode00 "Server lacks JWT secret" Nothing Nothing JWTErrorCode00 "Server lacks JWT secret" Nothing Nothing
@@ -602,8 +618,6 @@ instance JSON.ToJSON Error where
toJSON JwtTokenRequired = toJsonPgrstError toJSON JwtTokenRequired = toJsonPgrstError
JWTErrorCode02 "Anonymous access is disabled" Nothing Nothing JWTErrorCode02 "Anonymous access is disabled" Nothing Nothing
toJSON (PgErr err) = JSON.toJSON err
toJSON (ApiRequestError err) = JSON.toJSON err
invalidTokenHeader :: Text -> Header invalidTokenHeader :: Text -> Header
invalidTokenHeader m = invalidTokenHeader m =