Test that the JWT's iss field meets our expectations
The JWT should match "accounts.google.com" or "https://accounts.google.com". If it doesn't, we produce a validation error. TL;DR: - Group all failed stringOrURI function calls as StringOrURIParseFailure errors
This commit is contained in:
parent
526728eb89
commit
f1883b2790
3 changed files with 42 additions and 9 deletions
|
@ -16,6 +16,7 @@ import qualified TestUtils
|
||||||
data JWTFields = JWTFields
|
data JWTFields = JWTFields
|
||||||
{ overwriteSigner :: Signer
|
{ overwriteSigner :: Signer
|
||||||
, overwriteAuds :: [StringOrURI]
|
, overwriteAuds :: [StringOrURI]
|
||||||
|
, overwriteIss :: StringOrURI
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultJWTFields :: JWTFields
|
defaultJWTFields :: JWTFields
|
||||||
|
@ -23,6 +24,7 @@ defaultJWTFields = JWTFields
|
||||||
{ overwriteSigner = hmacSecret "secret"
|
{ overwriteSigner = hmacSecret "secret"
|
||||||
, overwriteAuds = ["771151720060-buofllhed98fgt0j22locma05e7rpngl.apps.googleusercontent.com"]
|
, overwriteAuds = ["771151720060-buofllhed98fgt0j22locma05e7rpngl.apps.googleusercontent.com"]
|
||||||
|> fmap TestUtils.unsafeStringOrURI
|
|> fmap TestUtils.unsafeStringOrURI
|
||||||
|
, overwriteIss = TestUtils.unsafeStringOrURI "accounts.google.com"
|
||||||
}
|
}
|
||||||
|
|
||||||
googleJWT :: JWTFields -> GoogleSignIn.EncodedJWT
|
googleJWT :: JWTFields -> GoogleSignIn.EncodedJWT
|
||||||
|
@ -43,7 +45,7 @@ googleJWT JWTFields{..} =
|
||||||
|
|
||||||
claimSet :: JWTClaimsSet
|
claimSet :: JWTClaimsSet
|
||||||
claimSet = JWTClaimsSet
|
claimSet = JWTClaimsSet
|
||||||
{ iss = stringOrURI "accounts.google.com"
|
{ iss = Just overwriteIss
|
||||||
, sub = stringOrURI "114079822315085727057"
|
, sub = stringOrURI "114079822315085727057"
|
||||||
, aud = overwriteAuds |> Right |> Just
|
, aud = overwriteAuds |> Right |> Just
|
||||||
-- TODO: Replace date creation with a human-readable date constructor.
|
-- TODO: Replace date creation with a human-readable date constructor.
|
||||||
|
|
|
@ -8,6 +8,7 @@ import Web.JWT
|
||||||
import Utils
|
import Utils
|
||||||
|
|
||||||
import qualified Network.HTTP.Simple as HTTP
|
import qualified Network.HTTP.Simple as HTTP
|
||||||
|
import qualified Data.Text as Text
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
newtype EncodedJWT = EncodedJWT Text
|
newtype EncodedJWT = EncodedJWT Text
|
||||||
|
@ -18,7 +19,9 @@ data ValidationResult
|
||||||
| DecodeError
|
| DecodeError
|
||||||
| GoogleSaysInvalid Text
|
| GoogleSaysInvalid Text
|
||||||
| NoMatchingClientIDs [StringOrURI]
|
| NoMatchingClientIDs [StringOrURI]
|
||||||
| ClientIDParseFailure Text
|
| WrongIssuer StringOrURI
|
||||||
|
| StringOrURIParseFailure Text
|
||||||
|
| MissingIssuer
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
-- | Returns True when the supplied `jwt` meets the following criteria:
|
-- | Returns True when the supplied `jwt` meets the following criteria:
|
||||||
|
@ -49,15 +52,31 @@ jwtIsValid skipHTTP (EncodedJWT encodedJWT) = do
|
||||||
where
|
where
|
||||||
continue :: JWT UnverifiedJWT -> IO ValidationResult
|
continue :: JWT UnverifiedJWT -> IO ValidationResult
|
||||||
continue jwt = do
|
continue jwt = do
|
||||||
let audValues = jwt |> claims |> auds
|
let audValues :: [StringOrURI]
|
||||||
mClientID = stringOrURI "771151720060-buofllhed98fgt0j22locma05e7rpngl.apps.googleusercontent.com"
|
audValues = jwt |> claims |> auds
|
||||||
case mClientID of
|
expectedClientID :: Text
|
||||||
Nothing ->
|
expectedClientID = "771151720060-buofllhed98fgt0j22locma05e7rpngl.apps.googleusercontent.com"
|
||||||
pure $ ClientIDParseFailure "771151720060-buofllhed98fgt0j22locma05e7rpngl.apps.googleusercontent.com"
|
expectedIssuers :: [Text]
|
||||||
Just clientID ->
|
expectedIssuers = [ "accounts.google.com"
|
||||||
|
, "https://accounts.google.com"
|
||||||
|
]
|
||||||
|
mExpectedClientID :: Maybe StringOrURI
|
||||||
|
mExpectedClientID = stringOrURI expectedClientID
|
||||||
|
mExpectedIssuers :: Maybe [StringOrURI]
|
||||||
|
mExpectedIssuers = expectedIssuers |> traverse stringOrURI
|
||||||
|
case (mExpectedClientID, mExpectedIssuers) of
|
||||||
|
(Nothing, _) -> pure $ StringOrURIParseFailure expectedClientID
|
||||||
|
(_, Nothing) -> pure $ StringOrURIParseFailure (Text.unwords expectedIssuers)
|
||||||
|
(Just clientID, Just parsedIssuers) ->
|
||||||
-- TODO: Prefer reading clientID from a config. I'm thinking of the
|
-- TODO: Prefer reading clientID from a config. I'm thinking of the
|
||||||
-- AppContext type having my Configuration
|
-- AppContext type having my Configuration
|
||||||
if not $ clientID `elem` audValues then
|
if not $ clientID `elem` audValues then
|
||||||
pure $ NoMatchingClientIDs audValues
|
pure $ NoMatchingClientIDs audValues
|
||||||
else
|
else
|
||||||
pure Valid
|
case jwt |> claims |> iss of
|
||||||
|
Nothing -> pure MissingIssuer
|
||||||
|
Just jwtIssuer ->
|
||||||
|
if not $ jwtIssuer `elem` parsedIssuers then
|
||||||
|
pure $ WrongIssuer jwtIssuer
|
||||||
|
else
|
||||||
|
pure Valid
|
||||||
|
|
|
@ -32,3 +32,15 @@ main = hspec $ do
|
||||||
encodedJWT = F.defaultJWTFields { F.overwriteAuds = auds }
|
encodedJWT = F.defaultJWTFields { F.overwriteAuds = auds }
|
||||||
|> F.googleJWT
|
|> F.googleJWT
|
||||||
jwtIsValid' encodedJWT `shouldReturn` Valid
|
jwtIsValid' encodedJWT `shouldReturn` Valid
|
||||||
|
|
||||||
|
it "returns validation error when one of the iss field doesn't match accounts.google.com or https://accounts.google.com" $ do
|
||||||
|
let erroneousIssuer = TestUtils.unsafeStringOrURI "not-accounts.google.com"
|
||||||
|
encodedJWT = F.defaultJWTFields { F.overwriteIss = erroneousIssuer }
|
||||||
|
|> F.googleJWT
|
||||||
|
jwtIsValid' encodedJWT `shouldReturn` WrongIssuer erroneousIssuer
|
||||||
|
|
||||||
|
it "returns validation success when the iss field matches accounts.google.com or https://accounts.google.com" $ do
|
||||||
|
let erroneousIssuer = TestUtils.unsafeStringOrURI "https://accounts.google.com"
|
||||||
|
encodedJWT = F.defaultJWTFields { F.overwriteIss = erroneousIssuer }
|
||||||
|
|> F.googleJWT
|
||||||
|
jwtIsValid' encodedJWT `shouldReturn` Valid
|
||||||
|
|
Loading…
Reference in a new issue