packages feed

notion-client-0.10.0.0: src/Notion/V1/TokenSource.hs

-- | Per-request bearer authorization and a single reactive renewal.
module Notion.V1.TokenSource (TokenSource (..), staticTokenSource, withAuthorization) where

import Control.Exception (throwIO, try)
import Data.Text (Text)
import Notion.V1.Error (APIErrorCode (Unauthorized), NotionError (..))

-- | One source per installation. Tokens exclude the @Bearer @ prefix.
data TokenSource = TokenSource
  { currentToken :: IO Text,
    -- | Renew after rejection, or return a token another request obtained.
    -- Nothing means no renewal is available. Exceptions propagate in IO.
    renewToken :: Text -> IO (Maybe Text)
  }

staticTokenSource :: Text -> TokenSource
staticTokenSource token = TokenSource (pure token) (const (pure Nothing))

-- | Replay once only on typed Unauthorized. The action must be replayable.
-- Scope this to request acquisition, never a stream body or user callback.
withAuthorization :: TokenSource -> (Text -> IO a) -> IO a
withAuthorization TokenSource {currentToken, renewToken} action = do
  token <- currentToken
  result <- try (action ("Bearer " <> token))
  case result of
    Right a -> pure a
    Left e@NotionError {code = Unauthorized} -> do
      replacement <- renewToken token
      maybe (throwIO e) (action . ("Bearer " <>)) replacement
    Left e -> throwIO e