refactor: Make JWT authorization a middleware
This follows the style of wai-middleware-auth package and makes the JWT parsing a middleware. Co-authored-by: Wolfgang Walther <walther@technowledgy.de>
This commit is contained in:
committed by
Wolfgang Walther
co-authored by
Wolfgang Walther
parent
d97bf3b864
commit
3c17f97c87
@@ -108,6 +108,7 @@ library
|
|||||||
, text >= 1.2.2 && < 1.3
|
, text >= 1.2.2 && < 1.3
|
||||||
, time >= 1.6 && < 1.11
|
, time >= 1.6 && < 1.11
|
||||||
, unordered-containers >= 0.2.8 && < 0.3
|
, unordered-containers >= 0.2.8 && < 0.3
|
||||||
|
, vault >= 0.3.1.5 && < 0.4
|
||||||
, vector >= 0.11 && < 0.13
|
, vector >= 0.11 && < 0.13
|
||||||
, wai >= 3.2.1 && < 3.3
|
, wai >= 3.2.1 && < 3.3
|
||||||
, wai-cors >= 0.2.5 && < 0.3
|
, wai-cors >= 0.2.5 && < 0.3
|
||||||
|
|||||||
+35
-35
@@ -20,8 +20,8 @@ module PostgREST.App
|
|||||||
import Control.Monad.Except (liftEither)
|
import Control.Monad.Except (liftEither)
|
||||||
import Data.Either.Combinators (mapLeft)
|
import Data.Either.Combinators (mapLeft)
|
||||||
import Data.List (union)
|
import Data.List (union)
|
||||||
|
import Data.Maybe (fromJust)
|
||||||
import Data.String (IsString (..))
|
import Data.String (IsString (..))
|
||||||
import Data.Time.Clock (UTCTime)
|
|
||||||
import Network.Wai.Handler.Warp (defaultSettings, setHost, setPort,
|
import Network.Wai.Handler.Warp (defaultSettings, setHost, setPort,
|
||||||
setServerName)
|
setServerName)
|
||||||
import System.Posix.Types (FileMode)
|
import System.Posix.Types (FileMode)
|
||||||
@@ -56,6 +56,7 @@ import qualified PostgREST.Request.ApiRequest as ApiRequest
|
|||||||
import qualified PostgREST.Request.DbRequestBuilder as ReqBuilder
|
import qualified PostgREST.Request.DbRequestBuilder as ReqBuilder
|
||||||
|
|
||||||
import PostgREST.AppState (AppState)
|
import PostgREST.AppState (AppState)
|
||||||
|
import PostgREST.Auth (AuthResult (..))
|
||||||
import PostgREST.Config (AppConfig (..),
|
import PostgREST.Config (AppConfig (..),
|
||||||
LogLevel (..),
|
LogLevel (..),
|
||||||
OpenAPIMode (..))
|
OpenAPIMode (..))
|
||||||
@@ -149,28 +150,32 @@ serverSettings AppConfig{..} =
|
|||||||
-- | PostgREST application
|
-- | PostgREST application
|
||||||
postgrest :: LogLevel -> AppState.AppState -> IO () -> Wai.Application
|
postgrest :: LogLevel -> AppState.AppState -> IO () -> Wai.Application
|
||||||
postgrest logLevel appState connWorker =
|
postgrest logLevel appState connWorker =
|
||||||
Logger.middleware logLevel .
|
Cors.middleware .
|
||||||
Cors.middleware $
|
Auth.middleware appState .
|
||||||
\req respond -> do
|
Logger.middleware logLevel $
|
||||||
time <- AppState.getTime appState
|
-- fromJust can be used, because the auth middleware will **always** add
|
||||||
conf <- AppState.getConfig appState
|
-- some AuthResult to the vault.
|
||||||
maybeDbStructure <- AppState.getDbStructure appState
|
\req respond -> case fromJust $ Auth.getResult req of
|
||||||
pgVer <- AppState.getPgVersion appState
|
Left err -> respond $ Error.errorResponseFor err
|
||||||
jsonDbS <- AppState.getJsonDbS appState
|
Right authResult -> do
|
||||||
|
conf <- AppState.getConfig appState
|
||||||
|
maybeDbStructure <- AppState.getDbStructure appState
|
||||||
|
pgVer <- AppState.getPgVersion appState
|
||||||
|
jsonDbS <- AppState.getJsonDbS appState
|
||||||
|
|
||||||
let
|
let
|
||||||
eitherResponse :: IO (Either Error Wai.Response)
|
eitherResponse :: IO (Either Error Wai.Response)
|
||||||
eitherResponse =
|
eitherResponse =
|
||||||
runExceptT $ postgrestResponse conf maybeDbStructure jsonDbS pgVer (AppState.getPool appState) time req
|
runExceptT $ postgrestResponse conf maybeDbStructure jsonDbS pgVer (AppState.getPool appState) authResult req
|
||||||
|
|
||||||
response <- either Error.errorResponseFor identity <$> eitherResponse
|
response <- either Error.errorResponseFor identity <$> eitherResponse
|
||||||
-- Launch the connWorker when the connection is down. The postgrest
|
-- Launch the connWorker when the connection is down. The postgrest
|
||||||
-- function can respond successfully (with a stale schema cache) before
|
-- function can respond successfully (with a stale schema cache) before
|
||||||
-- the connWorker is done.
|
-- the connWorker is done.
|
||||||
let isPGAway = Wai.responseStatus response == HTTP.status503
|
let isPGAway = Wai.responseStatus response == HTTP.status503
|
||||||
when isPGAway connWorker
|
when isPGAway connWorker
|
||||||
resp <- addRetryHint isPGAway appState response
|
resp <- addRetryHint isPGAway appState response
|
||||||
respond resp
|
respond resp
|
||||||
|
|
||||||
addRetryHint :: Bool -> AppState -> Wai.Response -> IO Wai.Response
|
addRetryHint :: Bool -> AppState -> Wai.Response -> IO Wai.Response
|
||||||
addRetryHint shouldAdd appState response = do
|
addRetryHint shouldAdd appState response = do
|
||||||
@@ -184,10 +189,10 @@ postgrestResponse
|
|||||||
-> ByteString
|
-> ByteString
|
||||||
-> PgVersion
|
-> PgVersion
|
||||||
-> SQL.Pool
|
-> SQL.Pool
|
||||||
-> UTCTime
|
-> AuthResult
|
||||||
-> Wai.Request
|
-> Wai.Request
|
||||||
-> Handler IO Wai.Response
|
-> Handler IO Wai.Response
|
||||||
postgrestResponse conf maybeDbStructure jsonDbS pgVer pool time req = do
|
postgrestResponse conf@AppConfig{..} maybeDbStructure jsonDbS pgVer pool AuthResult{..} req = do
|
||||||
body <- lift $ Wai.strictRequestBody req
|
body <- lift $ Wai.strictRequestBody req
|
||||||
|
|
||||||
dbStructure <-
|
dbStructure <-
|
||||||
@@ -197,30 +202,25 @@ postgrestResponse conf maybeDbStructure jsonDbS pgVer pool time req = do
|
|||||||
Nothing ->
|
Nothing ->
|
||||||
throwError Error.NoSchemaCacheError
|
throwError Error.NoSchemaCacheError
|
||||||
|
|
||||||
apiRequest@ApiRequest{..} <-
|
apiRequest <-
|
||||||
liftEither . mapLeft Error.ApiRequestError $
|
liftEither . mapLeft Error.ApiRequestError $
|
||||||
ApiRequest.userApiRequest conf dbStructure req body
|
ApiRequest.userApiRequest conf dbStructure req body
|
||||||
|
|
||||||
-- The JWT must be checked before touching the db
|
let handleReq apiReq = handleRequest $ RequestContext conf dbStructure apiReq pgVer
|
||||||
jwtClaims <- Auth.jwtClaims conf (toUtf8Lazy iJWT) time
|
|
||||||
|
|
||||||
let
|
runDbHandler pool (txMode apiRequest) (authRole /= configDbAnonRole) configDbPreparedStatements .
|
||||||
handleReq apiReq =
|
|
||||||
handleRequest $ RequestContext conf dbStructure apiReq pgVer
|
|
||||||
|
|
||||||
runDbHandler pool (txMode apiRequest) jwtClaims (configDbPreparedStatements conf) .
|
|
||||||
Middleware.optionalRollback conf apiRequest $
|
Middleware.optionalRollback conf apiRequest $
|
||||||
Middleware.runPgLocals conf jwtClaims handleReq apiRequest jsonDbS pgVer
|
Middleware.runPgLocals conf authClaims authRole handleReq apiRequest jsonDbS pgVer
|
||||||
|
|
||||||
runDbHandler :: SQL.Pool -> SQL.Mode -> Auth.JWTClaims -> Bool -> DbHandler a -> Handler IO a
|
runDbHandler :: SQL.Pool -> SQL.Mode -> Bool -> Bool -> DbHandler a -> Handler IO a
|
||||||
runDbHandler pool mode jwtClaims prepared handler = do
|
runDbHandler pool mode authenticated prepared handler = do
|
||||||
dbResp <-
|
dbResp <-
|
||||||
let transaction = if prepared then SQL.transaction else SQL.unpreparedTransaction in
|
let transaction = if prepared then SQL.transaction else SQL.unpreparedTransaction in
|
||||||
lift . SQL.use pool . transaction SQL.ReadCommitted mode $ runExceptT handler
|
lift . SQL.use pool . transaction SQL.ReadCommitted mode $ runExceptT handler
|
||||||
|
|
||||||
resp <-
|
resp <-
|
||||||
liftEither . mapLeft Error.PgErr $
|
liftEither . mapLeft Error.PgErr $
|
||||||
mapLeft (Error.PgError $ Auth.containsRole jwtClaims) dbResp
|
mapLeft (Error.PgError authenticated) dbResp
|
||||||
|
|
||||||
liftEither resp
|
liftEither resp
|
||||||
|
|
||||||
|
|||||||
+70
-33
@@ -12,40 +12,54 @@ very simple authentication system inside the PostgreSQL database.
|
|||||||
-}
|
-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
module PostgREST.Auth
|
module PostgREST.Auth
|
||||||
( containsRole
|
( AuthResult (..)
|
||||||
, jwtClaims
|
, getResult
|
||||||
, JWTClaims
|
, getRole
|
||||||
|
, middleware
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Crypto.JWT as JWT
|
import qualified Crypto.JWT as JWT
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
import qualified Data.HashMap.Strict as M
|
import qualified Data.Aeson.Types as JSON
|
||||||
import qualified Data.Vector as V
|
import qualified Data.ByteString.Lazy.Char8 as LBS
|
||||||
|
import qualified Data.HashMap.Strict as M
|
||||||
|
import qualified Data.Text.Encoding as T
|
||||||
|
import qualified Data.Vault.Lazy as Vault
|
||||||
|
import qualified Data.Vector as V
|
||||||
|
import qualified Network.HTTP.Types.Header as HTTP
|
||||||
|
import qualified Network.Wai as Wai
|
||||||
|
import qualified Network.Wai.Middleware.HttpAuth as Wai
|
||||||
|
|
||||||
import Control.Lens (set)
|
import Control.Lens (set)
|
||||||
import Control.Monad.Except (liftEither)
|
import Control.Monad.Except (liftEither)
|
||||||
import Data.Either.Combinators (mapLeft)
|
import Data.Either.Combinators (mapLeft, mapRight)
|
||||||
|
import Data.List (lookup)
|
||||||
import Data.Time.Clock (UTCTime)
|
import Data.Time.Clock (UTCTime)
|
||||||
|
import System.IO.Unsafe (unsafePerformIO)
|
||||||
|
|
||||||
import PostgREST.Config (AppConfig (..), JSPath, JSPathExp (..))
|
import PostgREST.AppState (AppState, getConfig, getTime)
|
||||||
import PostgREST.Error (Error (..))
|
import PostgREST.Config (AppConfig (..), JSPath, JSPathExp (..))
|
||||||
|
import PostgREST.Error (Error (..))
|
||||||
|
|
||||||
import Protolude
|
import Protolude
|
||||||
|
|
||||||
|
|
||||||
type JWTClaims = M.HashMap Text JSON.Value
|
data AuthResult = AuthResult
|
||||||
|
{ authClaims :: M.HashMap Text JSON.Value
|
||||||
|
, authRole :: Text
|
||||||
|
}
|
||||||
|
|
||||||
-- | Receives the JWT secret and audience (from config) and a JWT and returns a
|
-- | Receives the JWT secret and audience (from config) and a JWT and returns a
|
||||||
-- map of JWT claims.
|
-- JSON object of JWT claims.
|
||||||
jwtClaims :: Monad m =>
|
parseToken :: Monad m =>
|
||||||
AppConfig -> LByteString -> UTCTime -> ExceptT Error m JWTClaims
|
AppConfig -> LByteString -> UTCTime -> ExceptT Error m JSON.Value
|
||||||
jwtClaims _ "" _ = return M.empty
|
parseToken _ "" _ = return JSON.emptyObject
|
||||||
jwtClaims AppConfig{..} payload time = do
|
parseToken AppConfig{..} token time = do
|
||||||
secret <- liftEither . maybeToRight JwtTokenMissing $ configJWKS
|
secret <- liftEither . maybeToRight JwtTokenMissing $ configJWKS
|
||||||
eitherClaims <-
|
eitherClaims <-
|
||||||
lift . runExceptT $
|
lift . runExceptT $
|
||||||
JWT.verifyClaimsAt validation secret time =<< JWT.decodeCompact payload
|
JWT.verifyClaimsAt validation secret time =<< JWT.decodeCompact token
|
||||||
liftEither . mapLeft jwtClaimsError $ claimsMap configJwtRoleClaimKey <$> eitherClaims
|
liftEither . mapLeft jwtClaimsError $ JSON.toJSON <$> eitherClaims
|
||||||
where
|
where
|
||||||
validation =
|
validation =
|
||||||
JWT.defaultJWTValidationSettings audienceCheck & set JWT.allowedSkew 1
|
JWT.defaultJWTValidationSettings audienceCheck & set JWT.allowedSkew 1
|
||||||
@@ -57,19 +71,15 @@ jwtClaims AppConfig{..} payload time = do
|
|||||||
jwtClaimsError JWT.JWTExpired = JwtTokenInvalid "JWT expired"
|
jwtClaimsError JWT.JWTExpired = JwtTokenInvalid "JWT expired"
|
||||||
jwtClaimsError e = JwtTokenInvalid $ show e
|
jwtClaimsError e = JwtTokenInvalid $ show e
|
||||||
|
|
||||||
-- | Turn JWT ClaimSet into something easier to work with.
|
parseClaims :: AppConfig -> JSON.Value -> AuthResult
|
||||||
--
|
parseClaims AppConfig{..} jclaims@(JSON.Object mclaims) =
|
||||||
-- Also, here the jspath is applied to put the "role" in the map.
|
AuthResult
|
||||||
claimsMap :: JSPath -> JWT.ClaimsSet -> JWTClaims
|
{ authClaims = mclaims & M.insert "role" (JSON.toJSON role)
|
||||||
claimsMap jspath claims =
|
, authRole = role
|
||||||
case JSON.toJSON claims of
|
}
|
||||||
val@(JSON.Object o) ->
|
|
||||||
M.delete "role" o `M.union` role val
|
|
||||||
_ ->
|
|
||||||
M.empty
|
|
||||||
where
|
where
|
||||||
role value =
|
-- role defaults to anon if not specified in jwt
|
||||||
maybe M.empty (M.singleton "role") $ walkJSPath (Just value) jspath
|
role = maybe configDbAnonRole unquoted (walkJSPath (Just jclaims) configJwtRoleClaimKey)
|
||||||
|
|
||||||
walkJSPath :: Maybe JSON.Value -> JSPath -> Maybe JSON.Value
|
walkJSPath :: Maybe JSON.Value -> JSPath -> Maybe JSON.Value
|
||||||
walkJSPath x [] = x
|
walkJSPath x [] = x
|
||||||
@@ -77,6 +87,33 @@ claimsMap jspath claims =
|
|||||||
walkJSPath (Just (JSON.Array ar)) (JSPIdx idx:rest) = walkJSPath (ar V.!? idx) rest
|
walkJSPath (Just (JSON.Array ar)) (JSPIdx idx:rest) = walkJSPath (ar V.!? idx) rest
|
||||||
walkJSPath _ _ = Nothing
|
walkJSPath _ _ = Nothing
|
||||||
|
|
||||||
-- | Whether a response from jwtClaims contains a role claim
|
unquoted :: JSON.Value -> Text
|
||||||
containsRole :: JWTClaims -> Bool
|
unquoted (JSON.String t) = t
|
||||||
containsRole = M.member "role"
|
unquoted v = T.decodeUtf8 . LBS.toStrict $ JSON.encode v
|
||||||
|
-- impossible case - just added to please -Wincomplete-patterns
|
||||||
|
parseClaims _ _ = AuthResult { authClaims = M.empty, authRole = mempty }
|
||||||
|
|
||||||
|
-- | Validate authorization header.
|
||||||
|
-- Parse and store JWT claims for future use in the request.
|
||||||
|
middleware :: AppState -> Wai.Middleware
|
||||||
|
middleware appState app req respond = do
|
||||||
|
conf <- getConfig appState
|
||||||
|
time <- getTime appState
|
||||||
|
|
||||||
|
let token = fromMaybe "" $ Wai.extractBearerAuth =<< lookup HTTP.hAuthorization (Wai.requestHeaders req)
|
||||||
|
claims <- runExceptT $ parseToken conf (LBS.fromStrict token) time
|
||||||
|
|
||||||
|
let
|
||||||
|
authResult = mapRight (parseClaims conf) claims
|
||||||
|
req' = req { Wai.vault = Wai.vault req & Vault.insert authResultKey authResult }
|
||||||
|
app req' respond
|
||||||
|
|
||||||
|
authResultKey :: Vault.Key (Either Error AuthResult)
|
||||||
|
authResultKey = unsafePerformIO Vault.newKey
|
||||||
|
{-# NOINLINE authResultKey #-}
|
||||||
|
|
||||||
|
getResult :: Wai.Request -> Maybe (Either Error AuthResult)
|
||||||
|
getResult = Vault.lookup authResultKey . Wai.vault
|
||||||
|
|
||||||
|
getRole :: Wai.Request -> Maybe Text
|
||||||
|
getRole req = authRole <$> (rightToMaybe =<< getResult req)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import qualified Hasql.DynamicStatements.Statement as SQL
|
|||||||
import qualified Hasql.Transaction as SQL
|
import qualified Hasql.Transaction as SQL
|
||||||
import qualified Network.Wai as Wai
|
import qualified Network.Wai as Wai
|
||||||
|
|
||||||
|
|
||||||
import Control.Arrow ((***))
|
import Control.Arrow ((***))
|
||||||
|
|
||||||
import Data.Scientific (FPFormat (..), formatScientific, isInteger)
|
import Data.Scientific (FPFormat (..), formatScientific, isInteger)
|
||||||
@@ -37,10 +38,10 @@ import PostgREST.Request.Preferences
|
|||||||
import Protolude
|
import Protolude
|
||||||
|
|
||||||
-- | Runs local(transaction scoped) GUCs for every request, plus the pre-request function
|
-- | Runs local(transaction scoped) GUCs for every request, plus the pre-request function
|
||||||
runPgLocals :: AppConfig -> M.HashMap Text JSON.Value ->
|
runPgLocals :: AppConfig -> M.HashMap Text JSON.Value -> Text ->
|
||||||
(ApiRequest -> ExceptT Error SQL.Transaction Wai.Response) ->
|
(ApiRequest -> ExceptT Error SQL.Transaction Wai.Response) ->
|
||||||
ApiRequest -> ByteString -> PgVersion -> ExceptT Error SQL.Transaction Wai.Response
|
ApiRequest -> ByteString -> PgVersion -> ExceptT Error SQL.Transaction Wai.Response
|
||||||
runPgLocals conf claims app req jsonDbS actualPgVersion = do
|
runPgLocals conf claims role app req jsonDbS actualPgVersion = do
|
||||||
lift $ SQL.statement mempty $ SQL.dynamicallyParameterized
|
lift $ SQL.statement mempty $ SQL.dynamicallyParameterized
|
||||||
("select " <> intercalateSnippet ", " (searchPathSql : roleSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ appSettingsSql ++ specSql))
|
("select " <> intercalateSnippet ", " (searchPathSql : roleSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ appSettingsSql ++ specSql))
|
||||||
HD.noResult (configDbPreparedStatements conf)
|
HD.noResult (configDbPreparedStatements conf)
|
||||||
@@ -55,13 +56,10 @@ runPgLocals conf claims app req jsonDbS actualPgVersion = do
|
|||||||
cookiesSql = if usesLegacyGucs
|
cookiesSql = if usesLegacyGucs
|
||||||
then setConfigLocal "request.cookie." <$> iCookies req
|
then setConfigLocal "request.cookie." <$> iCookies req
|
||||||
else setConfigLocalJson "request.cookies" (iCookies req)
|
else setConfigLocalJson "request.cookies" (iCookies req)
|
||||||
claimsWithRole =
|
|
||||||
let anon = JSON.String . toS $ configDbAnonRole conf in -- role claim defaults to anon if not specified in jwt
|
|
||||||
M.union claims (M.singleton "role" anon)
|
|
||||||
claimsSql = if usesLegacyGucs
|
claimsSql = if usesLegacyGucs
|
||||||
then setConfigLocal "request.jwt.claim." <$> [(toUtf8 c, toUtf8 $ unquoted v) | (c,v) <- M.toList claimsWithRole]
|
then setConfigLocal "request.jwt.claim." <$> [(toUtf8 c, toUtf8 $ unquoted v) | (c,v) <- M.toList claims]
|
||||||
else [setConfigLocal mempty ("request.jwt.claims", LBS.toStrict $ JSON.encode claimsWithRole)]
|
else [setConfigLocal mempty ("request.jwt.claims", LBS.toStrict $ JSON.encode claims)]
|
||||||
roleSql = maybeToList $ (\x -> setConfigLocal mempty ("role", toUtf8 $ unquoted x)) <$> M.lookup "role" claimsWithRole
|
roleSql = [setConfigLocal mempty ("role", toUtf8 role)]
|
||||||
appSettingsSql = setConfigLocal mempty <$> (join bimap toUtf8 <$> configAppSettings conf)
|
appSettingsSql = setConfigLocal mempty <$> (join bimap toUtf8 <$> configAppSettings conf)
|
||||||
searchPathSql =
|
searchPathSql =
|
||||||
let schemas = T.intercalate ", " (iSchema req : configDbExtraSearchPath conf) in
|
let schemas = T.intercalate ", " (iSchema req : configDbExtraSearchPath conf) in
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import qualified Data.HashMap.Strict as M
|
|||||||
import qualified Data.List as L
|
import qualified Data.List as L
|
||||||
import qualified Data.List.NonEmpty as NonEmptyList
|
import qualified Data.List.NonEmpty as NonEmptyList
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
import qualified Data.Text as T
|
|
||||||
import qualified Data.Text.Encoding as T
|
import qualified Data.Text.Encoding as T
|
||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ import Data.Aeson.Types (emptyArray, emptyObject)
|
|||||||
import Data.List (lookup, union)
|
import Data.List (lookup, union)
|
||||||
import Data.Maybe (fromJust)
|
import Data.Maybe (fromJust)
|
||||||
import Data.Ranged.Ranges (emptyRange, rangeIntersection)
|
import Data.Ranged.Ranges (emptyRange, rangeIntersection)
|
||||||
import Network.HTTP.Types.Header (hAuthorization, hCookie)
|
import Network.HTTP.Types.Header (hCookie)
|
||||||
import Network.HTTP.Types.URI (parseSimpleQuery)
|
import Network.HTTP.Types.URI (parseSimpleQuery)
|
||||||
import Network.Wai (Request (..))
|
import Network.Wai (Request (..))
|
||||||
import Network.Wai.Parse (parseHttpAccept)
|
import Network.Wai.Parse (parseHttpAccept)
|
||||||
@@ -157,7 +156,6 @@ data ApiRequest = ApiRequest {
|
|||||||
, iPreferTransaction :: Maybe PreferTransaction -- ^ Whether the clients wants to commit or rollback the transaction
|
, iPreferTransaction :: Maybe PreferTransaction -- ^ Whether the clients wants to commit or rollback the transaction
|
||||||
, iQueryParams :: QueryParams.QueryParams
|
, iQueryParams :: QueryParams.QueryParams
|
||||||
, iColumns :: S.Set FieldName -- ^ parsed colums from &columns parameter and payload
|
, iColumns :: S.Set FieldName -- ^ parsed colums from &columns parameter and payload
|
||||||
, iJWT :: Text -- ^ JSON Web Token
|
|
||||||
, iHeaders :: [(ByteString, ByteString)] -- ^ HTTP request headers
|
, iHeaders :: [(ByteString, ByteString)] -- ^ HTTP request headers
|
||||||
, iCookies :: [(ByteString, ByteString)] -- ^ Request Cookies
|
, iCookies :: [(ByteString, ByteString)] -- ^ Request Cookies
|
||||||
, iPath :: ByteString -- ^ Raw request path
|
, iPath :: ByteString -- ^ Raw request path
|
||||||
@@ -195,7 +193,6 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
|
|||||||
, iPreferTransaction = preferTransaction
|
, iPreferTransaction = preferTransaction
|
||||||
, iQueryParams = queryparams
|
, iQueryParams = queryparams
|
||||||
, iColumns = payloadColumns
|
, iColumns = payloadColumns
|
||||||
, iJWT = tokenStr
|
|
||||||
, iHeaders = [ (CI.foldedCase k, v) | (k,v) <- hdrs, k /= hCookie]
|
, iHeaders = [ (CI.foldedCase k, v) | (k,v) <- hdrs, k /= hCookie]
|
||||||
, iCookies = maybe [] parseCookies $ lookupHeader "Cookie"
|
, iCookies = maybe [] parseCookies $ lookupHeader "Cookie"
|
||||||
, iPath = rawPathInfo req
|
, iPath = rawPathInfo req
|
||||||
@@ -324,11 +321,6 @@ apiRequest conf@AppConfig{..} dbStructure req reqBody queryparams@QueryParams{..
|
|||||||
hdrs = requestHeaders req
|
hdrs = requestHeaders req
|
||||||
lookupHeader = flip lookup hdrs
|
lookupHeader = flip lookup hdrs
|
||||||
Preferences.Preferences{..} = Preferences.fromHeaders hdrs
|
Preferences.Preferences{..} = Preferences.fromHeaders hdrs
|
||||||
auth = fromMaybe "" $ lookupHeader hAuthorization
|
|
||||||
tokenStr = case T.split (== ' ') (T.decodeUtf8 auth) of
|
|
||||||
("Bearer" : t : _) -> t
|
|
||||||
("bearer" : t : _) -> t
|
|
||||||
_ -> ""
|
|
||||||
headerRange = rangeRequested hdrs
|
headerRange = rangeRequested hdrs
|
||||||
|
|
||||||
ranges = M.insert "limit" (rangeIntersection headerRange (fromMaybe allRange (M.lookup "limit" qsRanges))) qsRanges
|
ranges = M.insert "limit" (rangeIntersection headerRange (fromMaybe allRange (M.lookup "limit" qsRanges))) qsRanges
|
||||||
|
|||||||
@@ -73,9 +73,3 @@ create function reload_pgrst_config() returns void as $_$
|
|||||||
begin
|
begin
|
||||||
perform pg_notify('pgrst', 'reload config');
|
perform pg_notify('pgrst', 'reload config');
|
||||||
end $_$ language plpgsql ;
|
end $_$ language plpgsql ;
|
||||||
|
|
||||||
create or replace function raise_bad_pt() returns void as $$
|
|
||||||
begin
|
|
||||||
raise sqlstate 'PT40A' using message = 'Wrong';
|
|
||||||
end;
|
|
||||||
$$ language plpgsql;
|
|
||||||
|
|||||||
+6
-2
@@ -847,6 +847,10 @@ def test_log_level(level, has_output, defaultenv):
|
|||||||
|
|
||||||
env = {**defaultenv, "PGRST_LOG_LEVEL": level}
|
env = {**defaultenv, "PGRST_LOG_LEVEL": level}
|
||||||
|
|
||||||
|
# expired token to test 500 response for "JWT expired"
|
||||||
|
claim = {"role": "postgrest_test_author", "exp": 0}
|
||||||
|
headers = jwtauthheader(claim, SECRET)
|
||||||
|
|
||||||
with run(env=env) as postgrest:
|
with run(env=env) as postgrest:
|
||||||
response = postgrest.session.get("/")
|
response = postgrest.session.get("/")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@@ -864,10 +868,10 @@ def test_log_level(level, has_output, defaultenv):
|
|||||||
postgrest.process.stdout.readline().decode(),
|
postgrest.process.stdout.readline().decode(),
|
||||||
)
|
)
|
||||||
|
|
||||||
response = postgrest.session.get("/rpc/raise_bad_pt")
|
response = postgrest.session.get("/", headers=headers)
|
||||||
assert response.status_code == 500
|
assert response.status_code == 500
|
||||||
if has_output[2]:
|
if has_output[2]:
|
||||||
assert re.match(
|
assert re.match(
|
||||||
r'unknownSocket - - \[.+\] "GET /rpc/raise_bad_pt HTTP/1.1" 500 - "" "python-requests/.+"',
|
r'unknownSocket - - \[.+\] "GET / HTTP/1.1" 500 - "" "python-requests/.+"',
|
||||||
postgrest.process.stdout.readline().decode(),
|
postgrest.process.stdout.readline().decode(),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user