summaryrefslogtreecommitdiff
path: root/fig-monitor-twitch/src/Fig/Monitor/Twitch/Auth/AppToken.hs
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2026-03-07 23:41:50 -0500
committerLLLL Colonq <llll@colonq>2026-03-07 23:41:50 -0500
commitcb85036ca25cd9e13e4e959e105b88bb73dbf9e5 (patch)
tree93bbf019944db71482fd2f6bcd5ffe94442f9bdb /fig-monitor-twitch/src/Fig/Monitor/Twitch/Auth/AppToken.hs
parent8cbf224b0edc8646690ebbc877d6f72507447d7a (diff)
Refactor, use app tokens
Diffstat (limited to 'fig-monitor-twitch/src/Fig/Monitor/Twitch/Auth/AppToken.hs')
-rw-r--r--fig-monitor-twitch/src/Fig/Monitor/Twitch/Auth/AppToken.hs82
1 files changed, 82 insertions, 0 deletions
diff --git a/fig-monitor-twitch/src/Fig/Monitor/Twitch/Auth/AppToken.hs b/fig-monitor-twitch/src/Fig/Monitor/Twitch/Auth/AppToken.hs
new file mode 100644
index 0000000..2796f7e
--- /dev/null
+++ b/fig-monitor-twitch/src/Fig/Monitor/Twitch/Auth/AppToken.hs
@@ -0,0 +1,82 @@
+module Fig.Monitor.Twitch.Auth.AppToken
+ ( Authed
+ , RequestConfig(..)
+ , authedRequest, authedRequestJSON
+ , runAuthed
+ ) where
+
+import Fig.Prelude
+
+import qualified Data.ByteString.Lazy as BS.Lazy
+
+import Control.Monad.Reader (ReaderT, runReaderT)
+
+import Data.Aeson ((.:))
+import qualified Data.Aeson as Aeson
+import qualified Data.Aeson.Types as Aeson
+
+import Network.HTTP.Client as HTTP
+import Network.HTTP.Client.TLS as HTTP
+import Network.HTTP.Client.MultipartFormData as HTTP
+
+import Fig.Monitor.Twitch.Utils
+
+data RequestConfig = RequestConfig
+ { config :: Config
+ , manager :: HTTP.Manager
+ , appToken :: Text
+ }
+
+newtype Authed a = Authed { unAuthed :: ReaderT RequestConfig IO a }
+ deriving (Functor, Applicative, Monad, MonadReader RequestConfig, MonadIO, MonadThrow)
+
+authedRequest :: Text -> Text -> BS.Lazy.ByteString -> Authed BS.Lazy.ByteString
+authedRequest method url body = do
+ rc <- ask
+ initialRequest <- liftIO . HTTP.parseRequest $ unpack url
+ let request = initialRequest
+ { method = encodeUtf8 method
+ , requestBody = RequestBodyLBS body
+ , requestHeaders =
+ [ ("Authorization", encodeUtf8 $ "Bearer " <> rc.appToken)
+ , ("Client-Id", encodeUtf8 rc.config.clientId)
+ , ("Content-Type", "application/json")
+ ]
+ }
+ response <- liftIO $ HTTP.httpLbs request rc.manager
+ pure $ HTTP.responseBody response
+
+authedRequestJSON :: (Aeson.ToJSON a, Aeson.FromJSON b) => Text -> Text -> Maybe a -> Authed b
+authedRequestJSON method url val = do
+ resp <- authedRequest method url $ maybe "" Aeson.encode val
+ case Aeson.eitherDecode resp of
+ Left err -> do
+ throwM . FigMonitorTwitchException $ tshow err
+ Right res -> pure res
+
+getAppToken :: HTTP.Manager -> Config -> IO Text
+getAppToken manager config = do
+ initialRequest <- HTTP.parseRequest "https://id.twitch.tv/oauth2/token"
+ let preRequest = initialRequest
+ { method = "POST"
+ , requestHeaders = [("Content-Type", "application/json")]
+ }
+ request <- HTTP.formDataBody
+ [ partBS "client_id" $ encodeUtf8 config.clientId
+ , partBS "client_secret" $ encodeUtf8 config.clientSecret
+ , partBS "grant_type" "client_credentials"
+ ]
+ preRequest
+ response <- liftIO $ HTTP.httpLbs request manager
+ case Aeson.eitherDecode $ HTTP.responseBody response of
+ Left err -> throwM . FigMonitorTwitchException $ tshow err
+ Right v -> case Aeson.parseMaybe (.: "access_token") v of
+ Nothing -> throwM $ FigMonitorTwitchException "failed to obtain access token"
+ Just t -> pure t
+
+runAuthed :: Config -> Authed a -> IO a
+runAuthed config body = do
+ manager <- HTTP.newManager HTTP.tlsManagerSettings
+ appToken <- getAppToken manager config
+ log $ "got app token! " <> appToken
+ runReaderT body.unAuthed RequestConfig{..}