github-rest 1.1.4 → 1.2.0
raw patch · 5 files changed
+66/−56 lines, 5 filesdep +cryptondep +jose-jwtdep −jwtdep ~aeson-qqdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: crypton, jose-jwt
Dependencies removed: jwt
Dependency ranges changed: aeson-qq, base
API changes (from Hackage documentation)
- GitHub.REST.Auth: loadSigner :: FilePath -> IO EncodeSigner
+ GitHub.REST.Auth: instance GHC.Exception.Type.Exception GitHub.REST.Auth.JwtError
+ GitHub.REST.Auth: instance GHC.Show.Show GitHub.REST.Auth.JwtError
- GitHub.REST: data StdMethod
+ GitHub.REST: data () => StdMethod
- GitHub.REST.Auth: getJWTToken :: EncodeSigner -> AppId -> IO Token
+ GitHub.REST.Auth: getJWTToken :: PrivateKey -> AppId -> IO Token
Files
- CHANGELOG.md +7/−0
- github-rest.cabal +11/−8
- src/GitHub/REST/Auth.hs +31/−48
- test/Auth.hs +15/−0
- test/Main.hs +2/−0
CHANGELOG.md view
@@ -1,3 +1,10 @@+# v1.2.0++* Switch from `jwt` to `jose-jwt` + `crypton`+ * Removes the `loadSigner` helper, use normal `crypton`/`crypton-x509`/`crypton-x509-store` API+* Add support for GHC 9.8 + 9.10+* Drop support for GHC < 9.6+ # v1.1.4 * Fix a test failure due to GitHub changing URLs
github-rest.cabal view
@@ -1,11 +1,11 @@ cabal-version: >= 1.10 --- This file has been generated from package.yaml by hpack version 0.35.2.+-- This file has been generated from package.yaml by hpack version 0.36.0. -- -- see: https://github.com/sol/hpack name: github-rest-version: 1.1.4+version: 1.2.0 synopsis: Query the GitHub REST API programmatically description: Query the GitHub REST API programmatically, which can provide a more flexible and clear interface than if all of the endpoints and their types@@ -45,16 +45,17 @@ ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wnoncanonical-monad-instances -Wunused-packages build-depends: aeson <3- , base >=4.15 && <5- , bytestring <0.12+ , base <5+ , bytestring <0.13+ , crypton <1.1 , http-client >=0.5.13 && <0.8 , http-client-tls <0.4 , http-types >=0.11 && <0.13- , jwt >=0.9 && <0.12+ , jose-jwt >=0.10.0 && <0.11 , mtl <2.4 , scientific <0.4- , text <2.1- , time <1.13+ , text <2.2+ , time <1.15 , transformers <0.7 , unliftio <0.3 , unliftio-core <0.3@@ -64,6 +65,7 @@ type: exitcode-stdio-1.0 main-is: Main.hs other-modules:+ Auth Endpoint Helpers MockQuery@@ -75,9 +77,10 @@ ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wnoncanonical-monad-instances -Wunused-packages build-depends: aeson >=1.5.6.0- , aeson-qq+ , aeson-qq >=0.8.4 , base , bytestring+ , crypton , github-rest , http-types , mtl
src/GitHub/REST/Auth.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} @@ -16,27 +16,21 @@ -- * Helpers for using JWT tokens with the GitHub API getJWTToken,- loadSigner, ) where +import qualified Crypto.PubKey.RSA as Crypto+import Data.Aeson ((.=))+import qualified Data.Aeson as Aeson+import qualified Data.Aeson.Text as Aeson import Data.ByteString (ByteString)-import qualified Data.ByteString as ByteString--#if !MIN_VERSION_base(4,11,0)-import Data.Monoid ((<>))-#endif-import Data.Text (Text)-import qualified Data.Text as Text import qualified Data.Text.Encoding as Text-import Data.Time (addUTCTime, getCurrentTime)+import qualified Data.Text.Lazy as TextL+import Data.Time (getCurrentTime) import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)-import qualified Web.JWT as JWT--#if MIN_VERSION_jwt(0,11,0)-type EncodeSigner = JWT.EncodeSigner-#else-type EncodeSigner = JWT.Signer-#endif+import qualified Jose.Jwa as Jose+import qualified Jose.Jws as Jose+import qualified Jose.Jwt as Jose+import UnliftIO.Exception (Exception, throwIO) -- | The token to use to authenticate with GitHub. data Token@@ -55,37 +49,26 @@ type AppId = Int -- | Create a JWT token that expires in 10 minutes.-getJWTToken :: EncodeSigner -> AppId -> IO Token-getJWTToken signer appId = mkToken <$> getNow- where- mkToken now =- let claims =- mempty- { JWT.iat = JWT.numericDate $ utcTimeToPOSIXSeconds now- , JWT.exp = JWT.numericDate $ utcTimeToPOSIXSeconds now + (10 * 60)- , JWT.iss = JWT.stringOrURI $ Text.pack $ show appId- }- in BearerToken . Text.encodeUtf8 $ signToken signer claims- -- lose a second in the case of rounding- -- https://github.community/t5/GitHub-API-Development-and/quot-Expiration-time-claim-exp-is-too-far-in-the-future-quot/m-p/20457/highlight/true#M1127- getNow = addUTCTime (-1) <$> getCurrentTime--signToken :: EncodeSigner -> JWT.JWTClaimsSet -> Text-#if MIN_VERSION_jwt(0,10,0)-signToken = flip JWT.encodeSigned mempty-#else-signToken = JWT.encodeSigned-#endif+getJWTToken :: Crypto.PrivateKey -> AppId -> IO Token+getJWTToken privKey appId = do+ -- use floor to ensure expiration doesn't go past 10 minutes+ -- https://github.com/orgs/community/discussions/24635#discussioncomment-3244803+ now <- floor . utcTimeToPOSIXSeconds <$> getCurrentTime --- | Load a RSA private key as a Signer from the given file path.-loadSigner :: FilePath -> IO EncodeSigner-loadSigner file = maybe badSigner return . readSigner =<< ByteString.readFile file+ BearerToken . Jose.unJwt <$> signToken (mkClaims now) where- badSigner = fail $ "Not a valid RSA private key file: " ++ file- readSigner = fmap toEncodeRSAPrivateKey . JWT.readRsaSecret+ mkClaims now =+ Text.encodeUtf8 . TextL.toStrict . Aeson.encodeToLazyText $+ Aeson.object+ [ "iat" .= (now :: Integer)+ , "exp" .= (now + 10 * 60)+ , "iss" .= show appId+ ]+ signToken claims =+ Jose.rsaEncode Jose.RS256 privKey claims >>= \case+ Right jwt -> pure jwt+ Left e -> throwIO $ JwtError e -#if MIN_VERSION_jwt(0,11,0)- toEncodeRSAPrivateKey = JWT.EncodeRSAPrivateKey-#else- toEncodeRSAPrivateKey = JWT.RSAPrivateKey-#endif+-- https://github.com/tekul/jose-jwt/issues/30+data JwtError = JwtError Jose.JwtError+ deriving (Show, Exception)
+ test/Auth.hs view
@@ -0,0 +1,15 @@+module Auth (tests) where++import qualified Crypto.PubKey.RSA as Crypto+import Test.Tasty (TestTree)+import Test.Tasty.HUnit++import GitHub.REST.Auth++tests :: [TestTree]+tests =+ [ testCase "getJWTToken works" $ do+ (_, privKey) <- Crypto.generate 256 3+ _ <- getJWTToken privKey 123+ pure ()+ ]
test/Main.hs view
@@ -1,5 +1,6 @@ import Test.Tasty (defaultMain, testGroup) +import qualified Auth import qualified Endpoint import qualified Helpers import qualified MockQuery@@ -15,5 +16,6 @@ , testGroup "GitHub.REST.Endpoint" Endpoint.tests , testGroup "GitHub.REST.Monad.Class" MockQuery.tests , testGroup "GitHub.REST.PageLinks" PageLinks.tests+ , testGroup "GitHub.REST.Auth" Auth.tests , testGroup "GitHub.REST (End-to-End)" Query.tests ]