Do not require jwt secret, but die on auth without it

This commit is contained in:
Joe Nelson
2016-09-24 21:28:08 -07:00
parent 449480bf01
commit 62ed9e2c4d
9 changed files with 69 additions and 23 deletions
+13 -8
View File
@@ -80,7 +80,8 @@ postgrest conf refDbStructure pool =
let schema = toS $ configSchema conf
apiRequest = userApiRequest schema req body
eClaims = jwtClaims (secret $ configJwtSecret conf) (iJWT apiRequest) time
eClaims = jwtClaims
(secret <$> configJwtSecret conf) (iJWT apiRequest) time
authed = containsRole eClaims
handleReq = runWithClaims conf eClaims (app dbStructure conf) apiRequest
txMode = transactionMode $ iAction apiRequest
@@ -212,7 +213,7 @@ app dbStructure conf apiRequest =
Just (PayloadJSON (UniformObjects payload))) -> do
let p = V.head payload
singular = iPreferSingular apiRequest
jwtSecret = secret $ configJwtSecret conf
jwtSecret = secret <$> configJwtSecret conf
returnType = lookup (qiName qi) $ dbProcs dbStructure
returnsJWT = fromMaybe False $
isInfixOf "jwt_claims" . pdReturnType <$> returnType
@@ -220,13 +221,17 @@ app dbStructure conf apiRequest =
Left e -> return $ responseLBS status400 [jsonH] $ toS e
Right (q,cq) -> respondToRange $ do
row <- H.query () (callProc qi p q cq topLevelRange shouldCount singular)
let (tableTotal, queryTotal, body) = fromMaybe (Just 0, 0, emptyArray) row
let (tableTotal, queryTotal, body) =
fromMaybe (Just 0, 0, emptyArray) row
(status, contentRange) = rangeHeader queryTotal tableTotal
in
return $ responseLBS status [jsonH, contentRange]
(if returnsJWT
then "{\"token\":\"" <> toS (tokenJWT jwtSecret body) <> "\"}"
else toS $ encode body)
return $ case (returnsJWT, jwtSecret) of
(True, Nothing) ->
errResponse status500 "Server lacks JWT secret"
(True, Just s) ->
responseLBS status [jsonH, contentRange] $
"{\"token\":\"" <> toS (tokenJWT s body) <> "\"}"
(False, _) ->
responseLBS status [jsonH, contentRange] (toS . encode $ body)
(ActionRead, TargetRoot, Nothing) -> do
let host = configHost conf
+10 -6
View File
@@ -52,6 +52,7 @@ claimsToSQL claims = roleStmts <> varStmts
-}
data JWTAttempt = JWTExpired
| JWTInvalid
| JWTMissingSecret
| JWTClaims (M.HashMap Text Value)
deriving Eq
@@ -59,18 +60,21 @@ data JWTAttempt = JWTExpired
Receives the JWT secret (from config) and a JWT and returns a map
of JWT claims.
-}
jwtClaims :: JWT.Secret -> Text -> NominalDiffTime -> JWTAttempt
jwtClaims :: Maybe JWT.Secret -> Text -> NominalDiffTime -> JWTAttempt
jwtClaims _ "" _ = JWTClaims M.empty
jwtClaims secret jwt time =
case isExpired <$> mClaims of
Just True -> JWTExpired
Nothing -> JWTInvalid
Just False -> JWTClaims $ value2map $ fromJust mClaims
case secret of
Nothing -> JWTMissingSecret
Just s ->
let mClaims = toJSON . JWT.claims <$> JWT.decodeAndVerifySignature s jwt in
case isExpired <$> mClaims of
Just True -> JWTExpired
Nothing -> JWTInvalid
Just False -> JWTClaims $ value2map $ fromJust mClaims
where
isExpired claims =
let mExp = claims ^? key "exp" . _Integer
in fromMaybe False $ (<= time) . fromInteger <$> mExp
mClaims = toJSON . JWT.claims <$> JWT.decodeAndVerifySignature secret jwt
value2map (Object o) = o
value2map _ = M.empty
+2 -2
View File
@@ -40,7 +40,7 @@ data AppConfig = AppConfig {
, configSchema :: Text
, configHost :: Text
, configPort :: Int
, configJwtSecret :: Text
, configJwtSecret :: Maybe Text
, configPool :: Int
, configMaxRows :: Maybe Integer
, configQuiet :: Bool
@@ -54,7 +54,7 @@ argParser = AppConfig
<*> (toS <$> strOption (long "schema" <> short 's' <> help "schema to use for API routes" <> metavar "NAME" <> value "public" <> showDefault))
<*> (toS <$> strOption (long "host" <> short 'l' <> help "hostname or ip on which to run HTTP server" <> metavar "HOST" <> value "*4" <> showDefault))
<*> option auto (long "port" <> short 'p' <> help "port number on which to run HTTP server" <> metavar "PORT" <> value 3000 <> showDefault)
<*> (toS <$> strOption (long "jwt-secret" <> short 'j' <> help "secret used to encrypt and decrypt JWT tokens" <> metavar "SECRET"))
<*> (optional . map toS <$> strOption) (long "jwt-secret" <> short 'j' <> help "secret used to encrypt and decrypt JWT tokens" <> metavar "SECRET")
<*> option auto (long "pool" <> short 'o' <> help "max connections in database pool" <> metavar "COUNT" <> value 10 <> showDefault)
<*> (readMay <$> strOption (long "max-rows" <> short 'm' <> help "max rows in response" <> metavar "COUNT" <> value "infinity" <> showDefault))
<*> pure False
+3 -1
View File
@@ -7,7 +7,7 @@ import Data.Aeson (Value (..))
import qualified Data.HashMap.Strict as M
import qualified Hasql.Transaction as H
import Network.HTTP.Types.Status (unauthorized401)
import Network.HTTP.Types.Status (unauthorized401, status500)
import Network.Wai (Application, Response,
responseLBS)
import Network.Wai.Middleware.Cors (cors)
@@ -18,6 +18,7 @@ import PostgREST.ApiRequest (ApiRequest(..), ContentType(..),
ctToHeader)
import PostgREST.Auth (claimsToSQL, JWTAttempt(..))
import PostgREST.Config (AppConfig (..), corsPolicy)
import PostgREST.Error (errResponse)
import Protolude hiding (concat, null)
@@ -28,6 +29,7 @@ runWithClaims conf eClaims app req =
case eClaims of
JWTExpired -> return $ unauthed "JWT expired"
JWTInvalid -> return $ unauthed "JWT invalid"
JWTMissingSecret -> return $ errResponse status500 "Server lacks JWT secret"
JWTClaims claims -> do
-- role claim defaults to anon if not specified in jwt
H.sql . mconcat . claimsToSQL $ M.union claims (M.singleton "role" anon)