module Feature.Auth.AudienceJwtSecretSpec where

import Network.Wai (Application)

import Network.HTTP.Types
import Protolude           hiding (get)
import SpecHelper
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON

spec :: SpecWith ((), Application)
spec = describe "test handling of aud claims in JWT when the jwt-aud config is set" $ do

  context "when the audience claim is a string" $ do
    -- this test will stop working 9999999999s after the UNIX EPOCH
    it "succeeds when the audience claim matches" $ do
      let jwtPayload =
            [json|{
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": "youraudience"
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith` 200

    it "fails when the audience claim does not match" $ do
      let jwtPayload = [json|
            {
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": "notyouraudience"
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith`
          [json|{"code":"PGRST303","details":null,"hint":null,"message":"JWT not in audience"}|]
          { matchStatus = 401 }

    it "fails when the audience claim is empty" $ do
      let jwtPayload = [json|
            {
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": ""
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith`
          [json|{"code":"PGRST303","details":null,"hint":null,"message":"JWT not in audience"}|]
          { matchStatus = 401 }

  context "when the audience claim is an array of strings" $ do
    it "succeeds when the audience claim has 1 element and it matches" $ do
      let jwtPayload = [json|
            {
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": ["youraudience"]
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith` 200

    it "succeeds when the audience claim has more than 1 element and one matches" $ do
      let jwtPayload = [json|
            {
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": ["notyouraudience", "youraudience", "anotheraudience"]
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith` 200

    it "fails when the audience claim has 1 element and it doesn't match" $ do
      let jwtPayload = [json|
            {
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": ["notyouraudience"]
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith`
          [json|{"code":"PGRST303","details":null,"hint":null,"message":"JWT not in audience"}|]
          { matchStatus = 401 }


    it "fails when the audience claim has more than 1 element and none matches" $ do
      let jwtPayload = [json|
            {
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": ["notyouraudience", "stillnotyouraudience", "anotheraudience"]
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith`
          [json|{"code":"PGRST303","details":null,"hint":null,"message":"JWT not in audience"}|]
          { matchStatus = 401 }

    it "ignores the audience claim and succeeds when it's empty" $ do
      let jwtPayload = [json|
            {
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": []
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith` 200

    it "ignores the audience claim and succeeds when it's null" $ do
      let jwtPayload = [json|
            {
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": null
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith` 200

  context "when the audience claim is not present" $ do
    it "succeeds with a JWT with no audience claim" $ do
      let jwtPayload = [json|
            {
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe"
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith` 200

    it "succeeds without a JWT" $
      get "/has_count_column" `shouldRespondWith` 200

disabledSpec :: SpecWith ((), Application)
disabledSpec = describe "test handling of aud claims in JWT when the jwt-aud config is not set" $ do

  context "when the audience claim is a string" $ do
    it "ignores the audience claim and suceeds" $ do
      let jwtPayload =
            [json|{
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": "youraudience"
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith` 200

    it "ignores the audience claim and suceeds when it's empty" $ do
      let jwtPayload =
            [json|{
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": ""
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith` 200

  context "when the audience is an array of strings" $ do
    it "ignores the audience claim and suceeds when it has 1 element" $ do
      let jwtPayload = [json|
            {
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": ["youraudience"]
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith` 200

    it "ignores the audience claim and suceeds when it has more than 1 element" $ do
      let jwtPayload = [json|
            {
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": ["notyouraudience", "youraudience", "anotheraudience"]
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith` 200

    it "ignores the audience claim and suceeds when it's empty" $ do
      let jwtPayload = [json|
            {
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": []
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith` 200

    it "ignores the audience claim and succeeds when it's null" $ do
      let jwtPayload = [json|
            {
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe",
              "aud": null
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith` 200

  context "when the audience claim is not present" $ do
    it "succeeds with a JWT with no audience claim" $ do
      let jwtPayload = [json|
            {
              "exp": 9999999999,
              "role": "postgrest_test_author",
              "id": "jdoe"
            }|]
          auth = authHeaderJWT $ generateJWT jwtPayload
      request methodGet "/authors_only" [auth] ""
        `shouldRespondWith` 200

    it "succeeds without a JWT" $
      get "/has_count_column" `shouldRespondWith` 200
