Add getCurrentTime cache for jwt validation
This commit is contained in:
committed by
Steve Chávez
parent
58f4b4bc33
commit
5c87fe2704
+9
-1
@@ -14,6 +14,9 @@ import PostgREST.OpenAPI (isMalformedProxyUri)
|
|||||||
import PostgREST.Types (DbStructure, Schema, PgVersion(..))
|
import PostgREST.Types (DbStructure, Schema, PgVersion(..))
|
||||||
import Protolude hiding (hPutStrLn, replace)
|
import Protolude hiding (hPutStrLn, replace)
|
||||||
|
|
||||||
|
|
||||||
|
import Control.AutoUpdate (defaultUpdateSettings,
|
||||||
|
mkAutoUpdate, updateAction)
|
||||||
import Control.Retry (RetryStatus, capDelay,
|
import Control.Retry (RetryStatus, capDelay,
|
||||||
exponentialBackoff,
|
exponentialBackoff,
|
||||||
retrying, rsPreviousDelay)
|
retrying, rsPreviousDelay)
|
||||||
@@ -25,6 +28,7 @@ import Data.String (IsString (..))
|
|||||||
import Data.Text (pack, replace, stripPrefix, strip)
|
import Data.Text (pack, replace, stripPrefix, strip)
|
||||||
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
|
import Data.Text.Encoding (decodeUtf8, encodeUtf8)
|
||||||
import Data.Text.IO (hPutStrLn)
|
import Data.Text.IO (hPutStrLn)
|
||||||
|
import Data.Time.Clock (getCurrentTime)
|
||||||
import qualified Hasql.Pool as P
|
import qualified Hasql.Pool as P
|
||||||
import qualified Hasql.Session as H
|
import qualified Hasql.Session as H
|
||||||
import Network.Wai.Handler.Warp (defaultSettings,
|
import Network.Wai.Handler.Warp (defaultSettings,
|
||||||
@@ -206,13 +210,17 @@ main = do
|
|||||||
) Nothing
|
) Nothing
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
--
|
|
||||||
|
-- ask for the OS time at most once per second
|
||||||
|
getTime <- mkAutoUpdate defaultUpdateSettings {updateAction = getCurrentTime}
|
||||||
|
|
||||||
-- run the postgrest application
|
-- run the postgrest application
|
||||||
runSettings appSettings $
|
runSettings appSettings $
|
||||||
postgrest
|
postgrest
|
||||||
conf
|
conf
|
||||||
refDbStructure
|
refDbStructure
|
||||||
pool
|
pool
|
||||||
|
getTime
|
||||||
(connectionWorker
|
(connectionWorker
|
||||||
mainTid
|
mainTid
|
||||||
pool
|
pool
|
||||||
|
|||||||
+7
-2
@@ -29,12 +29,14 @@ executable postgrest
|
|||||||
-rtsopts
|
-rtsopts
|
||||||
"-with-rtsopts=-N -I2"
|
"-with-rtsopts=-N -I2"
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
build-depends: base
|
build-depends: auto-update
|
||||||
|
, base
|
||||||
, hasql
|
, hasql
|
||||||
, hasql-pool
|
, hasql-pool
|
||||||
, postgrest
|
, postgrest
|
||||||
, protolude
|
, protolude
|
||||||
, text
|
, text
|
||||||
|
, time
|
||||||
, warp
|
, warp
|
||||||
, bytestring
|
, bytestring
|
||||||
, base64-bytestring
|
, base64-bytestring
|
||||||
@@ -68,7 +70,7 @@ library
|
|||||||
, http-types
|
, http-types
|
||||||
, insert-ordered-containers
|
, insert-ordered-containers
|
||||||
, interpolatedstring-perl6
|
, interpolatedstring-perl6
|
||||||
, jose >= 0.6
|
, jose
|
||||||
, lens
|
, lens
|
||||||
, lens-aeson
|
, lens-aeson
|
||||||
, network-uri
|
, network-uri
|
||||||
@@ -81,6 +83,7 @@ library
|
|||||||
, scientific
|
, scientific
|
||||||
, swagger2
|
, swagger2
|
||||||
, text
|
, text
|
||||||
|
, time
|
||||||
, unordered-containers
|
, unordered-containers
|
||||||
, vector
|
, vector
|
||||||
, wai
|
, wai
|
||||||
@@ -138,6 +141,7 @@ Test-Suite spec
|
|||||||
Build-Depends: aeson
|
Build-Depends: aeson
|
||||||
, aeson-qq
|
, aeson-qq
|
||||||
, async
|
, async
|
||||||
|
, auto-update
|
||||||
, base
|
, base
|
||||||
, bytestring
|
, bytestring
|
||||||
, base64-bytestring
|
, base64-bytestring
|
||||||
@@ -160,6 +164,7 @@ Test-Suite spec
|
|||||||
, process
|
, process
|
||||||
, protolude
|
, protolude
|
||||||
, regex-tdfa
|
, regex-tdfa
|
||||||
|
, time
|
||||||
, transformers-base
|
, transformers-base
|
||||||
, wai
|
, wai
|
||||||
, wai-extra
|
, wai-extra
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import qualified Data.ByteString.Char8 as BS
|
|||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import Data.IORef (IORef, readIORef)
|
import Data.IORef (IORef, readIORef)
|
||||||
import Data.Text (intercalate)
|
import Data.Text (intercalate)
|
||||||
|
import Data.Time.Clock (UTCTime)
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
|
|
||||||
import qualified Hasql.Pool as P
|
import qualified Hasql.Pool as P
|
||||||
@@ -62,12 +63,13 @@ import Data.Function (id)
|
|||||||
import Protolude hiding (intercalate, Proxy)
|
import Protolude hiding (intercalate, Proxy)
|
||||||
import Safe (headMay)
|
import Safe (headMay)
|
||||||
|
|
||||||
postgrest :: AppConfig -> IORef (Maybe DbStructure) -> P.Pool -> IO () -> Application
|
postgrest :: AppConfig -> IORef (Maybe DbStructure) -> P.Pool -> IO UTCTime -> IO () -> Application
|
||||||
postgrest conf refDbStructure pool worker =
|
postgrest conf refDbStructure pool getTime worker =
|
||||||
let middle = (if configQuiet conf then id else logStdout) . defaultMiddle
|
let middle = (if configQuiet conf then id else logStdout) . defaultMiddle
|
||||||
jwtSecret = parseJWK <$> configJwtSecret conf in
|
jwtSecret = parseJWK <$> configJwtSecret conf in
|
||||||
|
|
||||||
middle $ \ req respond -> do
|
middle $ \ req respond -> do
|
||||||
|
time <- getTime
|
||||||
body <- strictRequestBody req
|
body <- strictRequestBody req
|
||||||
maybeDbStructure <- readIORef refDbStructure
|
maybeDbStructure <- readIORef refDbStructure
|
||||||
case maybeDbStructure of
|
case maybeDbStructure of
|
||||||
@@ -76,7 +78,7 @@ postgrest conf refDbStructure pool worker =
|
|||||||
response <- case userApiRequest (configSchema conf) req body of
|
response <- case userApiRequest (configSchema conf) req body of
|
||||||
Left err -> return $ apiRequestError err
|
Left err -> return $ apiRequestError err
|
||||||
Right apiRequest -> do
|
Right apiRequest -> do
|
||||||
eClaims <- jwtClaims jwtSecret (configJwtAudience conf) (toS $ iJWT apiRequest)
|
eClaims <- jwtClaims jwtSecret (configJwtAudience conf) (toS $ iJWT apiRequest) time
|
||||||
|
|
||||||
let authed = containsRole eClaims
|
let authed = containsRole eClaims
|
||||||
proc = case (iTarget apiRequest, iPayload apiRequest, iPreferSingleObjectParameter apiRequest) of
|
proc = case (iTarget apiRequest, iPayload apiRequest, iPreferSingleObjectParameter apiRequest) of
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ module PostgREST.Auth (
|
|||||||
import Control.Lens.Operators
|
import Control.Lens.Operators
|
||||||
import Data.Aeson (Value (..), decode, toJSON)
|
import Data.Aeson (Value (..), decode, toJSON)
|
||||||
import qualified Data.HashMap.Strict as M
|
import qualified Data.HashMap.Strict as M
|
||||||
|
import Data.Time.Clock (UTCTime)
|
||||||
import Protolude
|
import Protolude
|
||||||
|
|
||||||
import qualified Crypto.JOSE.Types as JOSE.Types
|
import qualified Crypto.JOSE.Types as JOSE.Types
|
||||||
@@ -38,16 +39,16 @@ data JWTAttempt = JWTInvalid JWTError
|
|||||||
Receives the JWT secret and audience (from config) and a JWT and returns a map
|
Receives the JWT secret and audience (from config) and a JWT and returns a map
|
||||||
of JWT claims.
|
of JWT claims.
|
||||||
-}
|
-}
|
||||||
jwtClaims :: Maybe JWK -> Maybe StringOrURI -> LByteString -> IO JWTAttempt
|
jwtClaims :: Maybe JWK -> Maybe StringOrURI -> LByteString -> UTCTime -> IO JWTAttempt
|
||||||
jwtClaims _ _ "" = return $ JWTClaims M.empty
|
jwtClaims _ _ "" _ = return $ JWTClaims M.empty
|
||||||
jwtClaims secret audience payload =
|
jwtClaims secret audience payload time =
|
||||||
case secret of
|
case secret of
|
||||||
Nothing -> return JWTMissingSecret
|
Nothing -> return JWTMissingSecret
|
||||||
Just s -> do
|
Just s -> do
|
||||||
let validation = defaultJWTValidationSettings (maybe (const True) (==) audience)
|
let validation = defaultJWTValidationSettings (maybe (const True) (==) audience)
|
||||||
eJwt <- runExceptT $ do
|
eJwt <- runExceptT $ do
|
||||||
jwt <- decodeCompact payload
|
jwt <- decodeCompact payload
|
||||||
verifyClaims validation s jwt
|
verifyClaimsAt validation s time jwt
|
||||||
return $ case eJwt of
|
return $ case eJwt of
|
||||||
Left e -> JWTInvalid e
|
Left e -> JWTInvalid e
|
||||||
Right jwt -> JWTClaims . claims2map $ jwt
|
Right jwt -> JWTClaims . claims2map $ jwt
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ extra-deps:
|
|||||||
- hasql-1.1
|
- hasql-1.1
|
||||||
- hasql-pool-0.4.3
|
- hasql-pool-0.4.3
|
||||||
- hasql-transaction-0.5.2
|
- hasql-transaction-0.5.2
|
||||||
|
- jose-0.7.0.0
|
||||||
ghc-options:
|
ghc-options:
|
||||||
postgrest: -O2 -Werror -Wall -fwarn-identities -fno-warn-redundant-constraints
|
postgrest: -O2 -Werror -Wall -fwarn-identities -fno-warn-redundant-constraints
|
||||||
nix:
|
nix:
|
||||||
|
|||||||
+13
-9
@@ -9,8 +9,10 @@ import PostgREST.App (postgrest)
|
|||||||
import PostgREST.Config (pgVersion95, pgVersion96, configSettings)
|
import PostgREST.Config (pgVersion95, pgVersion96, configSettings)
|
||||||
import PostgREST.DbStructure (getDbStructure, getPgVersion, fillSessionWithSettings)
|
import PostgREST.DbStructure (getDbStructure, getPgVersion, fillSessionWithSettings)
|
||||||
import PostgREST.Types (DbStructure(..))
|
import PostgREST.Types (DbStructure(..))
|
||||||
|
import Control.AutoUpdate (defaultUpdateSettings, mkAutoUpdate, updateAction)
|
||||||
import Data.Function (id)
|
import Data.Function (id)
|
||||||
import Data.IORef
|
import Data.IORef
|
||||||
|
import Data.Time.Clock (getCurrentTime)
|
||||||
|
|
||||||
import qualified Feature.AuthSpec
|
import qualified Feature.AuthSpec
|
||||||
import qualified Feature.AsymmetricJwtSpec
|
import qualified Feature.AsymmetricJwtSpec
|
||||||
@@ -47,17 +49,19 @@ main = do
|
|||||||
|
|
||||||
dbStructure <- pure $ either (panic.show) id result
|
dbStructure <- pure $ either (panic.show) id result
|
||||||
|
|
||||||
|
getTime <- mkAutoUpdate defaultUpdateSettings { updateAction = getCurrentTime }
|
||||||
|
|
||||||
refDbStructure <- newIORef $ Just dbStructure
|
refDbStructure <- newIORef $ Just dbStructure
|
||||||
|
|
||||||
let withApp = return $ postgrest (testCfg testDbConn) refDbStructure pool $ pure ()
|
let withApp = return $ postgrest (testCfg testDbConn) refDbStructure pool getTime $ pure ()
|
||||||
ltdApp = return $ postgrest (testLtdRowsCfg testDbConn) refDbStructure pool $ pure ()
|
ltdApp = return $ postgrest (testLtdRowsCfg testDbConn) refDbStructure pool getTime $ pure ()
|
||||||
unicodeApp = return $ postgrest (testUnicodeCfg testDbConn) refDbStructure pool $ pure ()
|
unicodeApp = return $ postgrest (testUnicodeCfg testDbConn) refDbStructure pool getTime $ pure ()
|
||||||
proxyApp = return $ postgrest (testProxyCfg testDbConn) refDbStructure pool $ pure ()
|
proxyApp = return $ postgrest (testProxyCfg testDbConn) refDbStructure pool getTime $ pure ()
|
||||||
noJwtApp = return $ postgrest (testCfgNoJWT testDbConn) refDbStructure pool $ pure ()
|
noJwtApp = return $ postgrest (testCfgNoJWT testDbConn) refDbStructure pool getTime $ pure ()
|
||||||
binaryJwtApp = return $ postgrest (testCfgBinaryJWT testDbConn) refDbStructure pool $ pure ()
|
binaryJwtApp = return $ postgrest (testCfgBinaryJWT testDbConn) refDbStructure pool getTime $ pure ()
|
||||||
audJwtApp = return $ postgrest (testCfgAudienceJWT testDbConn) refDbStructure pool $ pure ()
|
audJwtApp = return $ postgrest (testCfgAudienceJWT testDbConn) refDbStructure pool getTime $ pure ()
|
||||||
asymJwkApp = return $ postgrest (testCfgAsymJWK testDbConn) refDbStructure pool $ pure ()
|
asymJwkApp = return $ postgrest (testCfgAsymJWK testDbConn) refDbStructure pool getTime $ pure ()
|
||||||
nonexistentSchemaApp = return $ postgrest (testNonexistentSchemaCfg testDbConn) refDbStructure pool $ pure ()
|
nonexistentSchemaApp = return $ postgrest (testNonexistentSchemaCfg testDbConn) refDbStructure pool getTime $ pure ()
|
||||||
|
|
||||||
let reset :: IO ()
|
let reset :: IO ()
|
||||||
reset = P.use pool (fillSessionWithSettings (configSettings $ testCfg testDbConn)) >> resetDb testDbConn
|
reset = P.use pool (fillSessionWithSettings (configSettings $ testCfg testDbConn)) >> resetDb testDbConn
|
||||||
|
|||||||
Reference in New Issue
Block a user