@@ -54,33 +54,44 @@ import Protolude
54
54
55
55
-- | Receives the JWT secret and audience (from config) and a JWT and returns a
56
56
-- JSON object of JWT claims.
57
- parseToken :: AppConfig -> ByteString -> UTCTime -> ExceptT Error IO JSON. Value
58
- parseToken _ " " _ = return JSON. emptyObject
59
- parseToken AppConfig {.. } token time = do
60
- secret <- liftEither . maybeToRight (JwtErr JwtTokenMissing ) $ configJWKS
57
+ parseToken :: AppConfig -> Maybe ByteString -> UTCTime -> ExceptT Error IO JSON. Value
58
+ parseToken _ Nothing _ = return JSON. emptyObject
59
+ parseToken _ (Just " " ) _ = throwE . JwtErr $ JwtDecodeError " Empty JWT is sent in Authorization header" -- should we validate JWT ourselves before decoding or let the library (not very user friendly with errors) handle it?
60
+ parseToken AppConfig {.. } (Just token) time = do
61
+ secret <- liftEither . maybeToRight (JwtErr JwtSecretMissing ) $ configJWKS
61
62
eitherContent <- liftIO $ JWT. decode (JWT. keys secret) Nothing token
62
63
content <- liftEither . mapLeft (JwtErr . jwtDecodeError) $ eitherContent
63
64
liftEither $ mapLeft JwtErr $ verifyClaims content
64
65
where
65
- -- TODO: Improve errors, those were just taken as-is from hs-jose to avoid
66
- -- breaking changes.
67
66
jwtDecodeError :: JWT. JwtError -> JwtError
68
- jwtDecodeError (JWT. KeyError _) = JwtTokenInvalid " JWSError JWSInvalidSignature"
69
- jwtDecodeError JWT. BadCrypto = JwtTokenInvalid " JWSError (CompactDecodeError Invalid number of parts: Expected 3 parts; got 2)"
70
- jwtDecodeError (JWT. BadAlgorithm _) = JwtTokenInvalid " JWSError JWSNoSignatures"
71
- jwtDecodeError e = JwtTokenInvalid $ show e
67
+ -- The only errors we can get from JWT.decode function are:
68
+ -- BadAlgorithm
69
+ -- KeyError
70
+ -- BadCrypto
71
+ -- So others should have a generic message because they can't
72
+ -- be covered by tests?
73
+ jwtDecodeError (JWT. KeyError _) = JwtDecodeError " No suitable key or wrong key type"
74
+ jwtDecodeError (JWT. BadAlgorithm _) = JwtDecodeError " Wrong or unsupported encoding algorithm"
75
+ jwtDecodeError JWT. BadCrypto = JwtDecodeError " JWT parsing failed"
76
+ -- We can't test below cases with current implementation
77
+ -- TODO: Remove them or replace with a single generic message?
78
+ jwtDecodeError (JWT. BadDots dots) = JwtDecodeError (" Wrong number of '.' periods in JWT: Expected 3, got " <> show dots)
79
+ jwtDecodeError (JWT. BadHeader _) = JwtDecodeError " Header couldn't be decoded or contains bad data"
80
+ jwtDecodeError JWT. BadClaims = JwtClaimsError " JWT claims couldn't be decoded or contains bad data"
81
+ jwtDecodeError JWT. BadSignature = JwtDecodeError " The JWT signature couldn't be decoded or is invalid"
82
+ jwtDecodeError (JWT. Base64Error _) = JwtDecodeError " A base64 decoding error has occured"
72
83
73
84
verifyClaims :: JWT. JwtContent -> Either JwtError JSON. Value
74
85
verifyClaims (JWT. Jws (_, claims)) = case JSON. decodeStrict claims of
75
- Nothing -> Left $ JwtTokenInvalid " Parsing claims failed"
86
+ Nothing -> Left $ JwtClaimsError " Parsing claims failed"
76
87
Just (JSON. Object mclaims)
77
- | failedExpClaim mclaims -> Left $ JwtTokenInvalid " JWT expired"
78
- | failedNbfClaim mclaims -> Left $ JwtTokenInvalid " JWTNotYetValid "
79
- | failedIatClaim mclaims -> Left $ JwtTokenInvalid " JWTIssuedAtFuture "
80
- | failedAudClaim mclaims -> Left $ JwtTokenInvalid " JWTNotInAudience "
88
+ | failedExpClaim mclaims -> Left $ JwtClaimsError " JWT expired"
89
+ | failedNbfClaim mclaims -> Left $ JwtClaimsError " JWT not yet valid "
90
+ | failedIatClaim mclaims -> Left $ JwtClaimsError " JWT issued at future "
91
+ | failedAudClaim mclaims -> Left $ JwtClaimsError " JWT not in audience "
81
92
Just jclaims -> Right jclaims
82
93
-- TODO: We could enable JWE support here (encrypted tokens)
83
- verifyClaims _ = Left $ JwtTokenInvalid " Unsupported token type"
94
+ verifyClaims _ = Left $ JwtDecodeError " Unsupported token type"
84
95
85
96
allowedSkewSeconds = 30 :: Int64
86
97
now = floor . nominalDiffTimeToSeconds $ utcTimeToPOSIXSeconds time
@@ -148,7 +159,7 @@ middleware appState app req respond = do
148
159
conf <- getConfig appState
149
160
time <- getTime appState
150
161
151
- let token = fromMaybe " " $ Wai. extractBearerAuth =<< lookup HTTP. hAuthorization (Wai. requestHeaders req)
162
+ let token = Wai. extractBearerAuth =<< lookup HTTP. hAuthorization (Wai. requestHeaders req)
152
163
parseJwt = runExceptT $ parseToken conf token time >>= parseClaims conf
153
164
jwtCacheState = getJwtCacheState appState
154
165
@@ -160,15 +171,19 @@ middleware appState app req respond = do
160
171
return $ req { Wai. vault = Wai. vault req & Vault. insert authResultKey authResult & Vault. insert jwtDurKey dur }
161
172
162
173
(True , maxLifetime) -> do
163
- (dur, authResult) <- timeItT $ lookupJwtCache jwtCacheState token maxLifetime parseJwt time
174
+ (dur, authResult) <- timeItT $ case token of
175
+ Just tkn -> lookupJwtCache jwtCacheState tkn maxLifetime parseJwt time
176
+ Nothing -> parseJwt
164
177
return $ req { Wai. vault = Wai. vault req & Vault. insert authResultKey authResult & Vault. insert jwtDurKey dur }
165
178
166
179
(False , 0 ) -> do
167
180
authResult <- parseJwt
168
181
return $ req { Wai. vault = Wai. vault req & Vault. insert authResultKey authResult }
169
182
170
183
(False , maxLifetime) -> do
171
- authResult <- lookupJwtCache jwtCacheState token maxLifetime parseJwt time
184
+ authResult <- case token of
185
+ Just tkn -> lookupJwtCache jwtCacheState tkn maxLifetime parseJwt time
186
+ Nothing -> parseJwt
172
187
return $ req { Wai. vault = Wai. vault req & Vault. insert authResultKey authResult }
173
188
174
189
app req' respond
0 commit comments