-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Servant combinators to facilitate writing GitHub webhooks. -- -- This package provides servant combinators that make writing safe -- GitHub webhooks very simple. -- -- It features automatic verification of the digital signatures provided -- by GitHub in the webhook HTTP requests as well as route dispatching -- based on repository event type. @package servant-github-webhook @version 0.4.1.0 -- | The GitHub webhook machinery will attach three headers to the HTTP -- requests that it fires: X-Github-Event, -- X-Hub-Signature, and X-Github-Delivery. The former -- two headers correspond with the GitHubEvent and -- GitHubSignedReqBody'' routing combinators. This library ignores -- the X-Github-Delivery header; if you would like to access its -- value, then use the builtin Header combinator from Servant. -- -- Usage of the library is straightforward: protect routes with the -- GitHubEvent combinator to ensure that the route is only reached -- for specific RepoWebhookEvents, and replace any ReqBody -- combinators you would write under that route with -- GitHubSignedReqBody. It is advised to always include a -- GitHubSignedReqBody'', as this is the only way you can be sure -- that it is GitHub who is sending the request, and not a malicious -- user. If you don't care about the request body, then simply use -- Aeson's Object type as the deserialization target -- -- GitHubSignedReqBody' key '[JSON] Object -- and ignore the -- Object in the handler. -- -- The GitHubSignedReqBody'' combinator makes use of the Servant -- Context in order to extract the signing key. This is the same -- key that must be entered in the configuration of the webhook on -- GitHub. See GitHubKey' for more details. -- -- In order to support multiple keys on a per-route basis, the basic -- combinator GitHubSignedReqBody'' takes as a type parameter as -- a key index. To use this, create a datatype, e.g. KeyIndex -- whose constructors identify the different keys you will be using. -- Generally, this means one constructor per repository. Use the -- DataKinds extension to promote this datatype to a kind, and -- write an instance of Reflect for each promoted constructor of -- your datatype. Finally, create a Context containing -- GitHubKey' whose wrapped function's domain is the datatype -- you've built up. Thus, your function can determine which key to -- retrieve. module Servant.GitHub.Webhook -- | A clone of Servant's ReqBody combinator, except that it will -- also verify the signature provided by GitHub in the -- X-Hub-Signature header by computing the SHA1 HMAC of the -- request body and comparing. -- -- The use of this combinator will require that the router context -- contain an appropriate GitHubKey' entry. Specifically, the type -- parameter of GitHubKey' must correspond with Demote k -- where k is the kind of the index key used here. -- Consequently, it will be necessary to use serveWithContext -- instead of serve. -- -- Other routes are not tried upon the failure of this combinator, and a -- 401 response is generated. -- -- Use of this datatype directly is discouraged, since the choice of the -- index key determines its kind k and hence -- proxy, which is . Instead, use GitHubSignedReqBody', -- which computes the proxy argument given just key. -- The proxy argument is necessary to avoid UndecidableInstances -- for the implementation of the HasServer instance for the -- datatype. data GitHubSignedReqBody'' (proxy :: KProxy k) (key :: k) (list :: [*]) (result :: *) -- | Convenient synonym for GitHubSignedReqBody'' that computes its -- first type argument given just the second one. -- -- Use this type synonym if you are creating a webhook server to handle -- webhooks from multiple repositories, with different secret keys. type GitHubSignedReqBody' (key :: k) = GitHubSignedReqBody'' ( 'KProxy :: KProxy k) key -- | A convenient alias for a trivial key index. -- -- USe this type synonym if you are creating a webhook server to handle -- only webhooks from a single repository, or for mutliple repositories -- using the same secret key. type GitHubSignedReqBody = GitHubSignedReqBody' '() -- | A routing combinator that succeeds only for a webhook request that -- matches one of the given RepoWebhookEvent given in the -- type-level list events. -- -- If the list contains WebhookWildcardEvent, then all events will -- be matched. -- -- The combinator will require that its associated handler take a -- RepoWebhookEvent parameter, and the matched event will be -- passed to the handler. This allows the handler to determine which -- event triggered it from the list. -- -- Other routes are tried if there is a mismatch. data GitHubEvent (events :: [RepoWebhookEvent]) -- | A wrapper for an IO strategy to obtain the signing key for the webhook -- as configured in GitHub. The strategy is executed each time the -- GitHubSignedReqBody's routing logic is executed. -- -- We allow the use of IO here so that you can fetch the key -- from a cache or a database. If the key is a constant or read only -- once, just use pure. -- -- The type key used here must correspond with Demote -- k where k is the kind whose types are used as indices in -- GitHubSignedReqBody'. -- -- If you don't care about indices and just want to write a webhook using -- a global key, see GitHubKey which fixes key to -- () and use gitHubKey, which fills the newtype with a -- constant function. newtype GitHubKey' key result GitHubKey :: (key -> result -> IO (Maybe ByteString)) -> GitHubKey' key result [unGitHubKey] :: GitHubKey' key result -> key -> result -> IO (Maybe ByteString) -- | A synonym for strategies producing so-called global keys, in -- which the key index is simply (). type GitHubKey result = GitHubKey' () result -- | Smart constructor for GitHubKey, for a so-called global -- key. gitHubKey :: IO ByteString -> GitHubKey result -- | dynamicKey keyLookup keyIdLookup acquires the key identifier, -- such as repository or user name, from the result then uses -- keyLookup to acquire the key (or Nothing). -- -- Dynamic keys allow servers to specify per-user repository keys. This -- limits the impact of compromized keys and allows the server to acquire -- the key from external sources, such as a live configuration or -- per-user rows in a database. dynamicKey :: (Text -> IO (Maybe ByteString)) -> (result -> Maybe Text) -> GitHubKey result repositoryKey :: HasRepository result => (Text -> IO (Maybe ByteString)) -> GitHubKey result -- | The HasRepository class helps extract the full (unique) "name/repo" of -- a repository, allowing easy lookup of a per-repository key or, using -- takeWhile (=''), lookup of per user keys. class HasRepository r -- | For use with 'github-webhooks' package types. Routes would look like: -- --
--   api = "github-webevent" :> 
--       :> GitHubSignedReqBody '[JSON] (EventWithHookRepo IssuesEvent)
--       :> Post '[JSON] ()
--   
-- -- And the handler would unwrap the event: -- --
--   handler :: EventWithHookRepo IssuesEvent -> Handler ()
--   handler (eventOf -> e) = -- ... expr handling e :: IssuesEvent ...
--   
newtype EventWithHookRepo e EventWithHookRepo :: e -> EventWithHookRepo e [eventOf] :: EventWithHookRepo e -> e data RepoWebhookEvent :: * WebhookWildcardEvent :: RepoWebhookEvent WebhookCommitCommentEvent :: RepoWebhookEvent WebhookCreateEvent :: RepoWebhookEvent WebhookDeleteEvent :: RepoWebhookEvent WebhookDeploymentEvent :: RepoWebhookEvent WebhookDeploymentStatusEvent :: RepoWebhookEvent WebhookForkEvent :: RepoWebhookEvent WebhookGollumEvent :: RepoWebhookEvent WebhookIssueCommentEvent :: RepoWebhookEvent WebhookIssuesEvent :: RepoWebhookEvent WebhookMemberEvent :: RepoWebhookEvent WebhookPageBuildEvent :: RepoWebhookEvent WebhookPingEvent :: RepoWebhookEvent WebhookPublicEvent :: RepoWebhookEvent WebhookPullRequestReviewCommentEvent :: RepoWebhookEvent WebhookPullRequestEvent :: RepoWebhookEvent WebhookPushEvent :: RepoWebhookEvent WebhookReleaseEvent :: RepoWebhookEvent WebhookStatusEvent :: RepoWebhookEvent WebhookTeamAddEvent :: RepoWebhookEvent WebhookWatchEvent :: RepoWebhookEvent -- | A concrete, promotable proxy type, for use at the kind level There are -- no instances for this because it is intended at the kind level only data KProxy t :: * -> * KProxy :: KProxy t -- | Convient alias for Demote' that allows us to avoid using -- KProxy explicitly. type Demote (a :: k) = Demote' ( 'KProxy :: KProxy k) -- | Type function that reflects a kind to a type. -- | Class of types that can be reflected to values. class Reflect (a :: k) reflect :: Reflect a => Proxy (a :: k) -> Demote a -- | Helper that parses a header using a FromHttpApiData instance -- and discards the parse error message if any. parseHeaderMaybe :: FromHttpApiData a => ByteString -> Maybe a -- | Determines whether a given webhook event matches a given raw -- representation of one. The result is Nothing if there is no -- match. This function accounts for the WebhookWildcardEvent -- matching everything, so it returns the result of parsing the raw -- representation when trying to match against the wildcard. matchEvent :: RepoWebhookEvent -> ByteString -> Maybe RepoWebhookEvent instance forall k sublayout (context :: [*]) (key :: k) result (list :: [*]). (Servant.Server.Internal.HasServer sublayout context, Servant.Server.Internal.Context.HasContextEntry context (Servant.GitHub.Webhook.GitHubKey' (Servant.GitHub.Webhook.Demote key) result), Servant.GitHub.Webhook.Reflect key, Servant.API.ContentTypes.AllCTUnrender list result) => Servant.Server.Internal.HasServer (Servant.GitHub.Webhook.GitHubSignedReqBody'' 'Data.Proxy.KProxy key list result Servant.API.Sub.:> sublayout) context instance (Servant.GitHub.Webhook.Reflect events, Servant.Server.Internal.HasServer sublayout context) => Servant.Server.Internal.HasServer (Servant.GitHub.Webhook.GitHubEvent events Servant.API.Sub.:> sublayout) context instance GHC.TypeLits.KnownSymbol s => Servant.GitHub.Webhook.Reflect s instance Servant.GitHub.Webhook.Reflect '() instance Servant.GitHub.Webhook.Reflect '[] instance forall a (x :: a) (xs :: [a]). (Servant.GitHub.Webhook.Reflect x, Servant.GitHub.Webhook.Reflect xs) => Servant.GitHub.Webhook.Reflect (x : xs) instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookWildcardEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookCommitCommentEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookCreateEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookDeleteEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookDeploymentEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookDeploymentStatusEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookForkEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookGollumEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookIssueCommentEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookIssuesEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookMemberEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookPageBuildEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookPingEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookPublicEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookPullRequestReviewCommentEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookPullRequestEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookPushEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookReleaseEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookStatusEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookTeamAddEvent instance Servant.GitHub.Webhook.Reflect 'GitHub.Data.Webhooks.WebhookWatchEvent instance Data.Aeson.Types.FromJSON.FromJSON e => Data.Aeson.Types.FromJSON.FromJSON (Servant.GitHub.Webhook.EventWithHookRepo e) instance GitHub.Data.Webhooks.Events.EventHasRepo e => Servant.GitHub.Webhook.HasRepository (Servant.GitHub.Webhook.EventWithHookRepo e) instance Servant.GitHub.Webhook.HasRepository Data.Aeson.Types.Internal.Value instance Servant.GitHub.Webhook.HasRepository Data.Aeson.Types.Internal.Object