module Ecluse.Core.Credential.Refresh.Internal (
RefreshConfig (..),
defaultRefreshConfig,
refreshingProvider,
refreshingProviderWith,
RefreshReporter (..),
noRefreshReporter,
CredentialReporters (..),
noCredentialReporters,
CredentialError (..),
CacheState (..),
ServeAction (..),
decide,
refreshDueAt,
onMintSuccess,
onMintFailure,
releaseSingleFlight,
) where
import Control.Concurrent.STM (retry)
import Data.Ord (clamp)
import Data.Time (NominalDiffTime, UTCTime, addUTCTime, diffUTCTime)
import UnliftIO (asyncWithUnmask, throwIO, try)
import UnliftIO.Exception (mask)
import Ecluse.Core.Breaker (
Breaker,
BreakerReporter,
admit,
initialBreaker,
noBreakerReporter,
recordFailure,
recordSuccess,
reportBreakerChange,
)
import Ecluse.Core.Credential (AuthToken (..), CredentialProvider (..))
import Ecluse.Core.InFlight (guardInFlight)
data CredentialError
=
BreakerOpen
|
Unconfigured Text
|
MintedTokenAlreadyExpired
deriving stock (CredentialError -> CredentialError -> Bool
(CredentialError -> CredentialError -> Bool)
-> (CredentialError -> CredentialError -> Bool)
-> Eq CredentialError
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: CredentialError -> CredentialError -> Bool
== :: CredentialError -> CredentialError -> Bool
$c/= :: CredentialError -> CredentialError -> Bool
/= :: CredentialError -> CredentialError -> Bool
Eq, Int -> CredentialError -> ShowS
[CredentialError] -> ShowS
CredentialError -> String
(Int -> CredentialError -> ShowS)
-> (CredentialError -> String)
-> ([CredentialError] -> ShowS)
-> Show CredentialError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> CredentialError -> ShowS
showsPrec :: Int -> CredentialError -> ShowS
$cshow :: CredentialError -> String
show :: CredentialError -> String
$cshowList :: [CredentialError] -> ShowS
showList :: [CredentialError] -> ShowS
Show)
instance Exception CredentialError
data RefreshReporter = RefreshReporter
{ RefreshReporter -> Maybe UTCTime -> IO ()
onRefreshSucceeded :: Maybe UTCTime -> IO ()
, RefreshReporter -> Maybe UTCTime -> IO ()
onRefreshFailed :: Maybe UTCTime -> IO ()
}
noRefreshReporter :: RefreshReporter
noRefreshReporter :: RefreshReporter
noRefreshReporter = (Maybe UTCTime -> IO ())
-> (Maybe UTCTime -> IO ()) -> RefreshReporter
RefreshReporter (IO () -> Maybe UTCTime -> IO ()
forall a b. a -> b -> a
const IO ()
forall (f :: * -> *). Applicative f => f ()
pass) (IO () -> Maybe UTCTime -> IO ()
forall a b. a -> b -> a
const IO ()
forall (f :: * -> *). Applicative f => f ()
pass)
data CredentialReporters = CredentialReporters
{ CredentialReporters -> BreakerReporter
crBreakerReporter :: BreakerReporter
, CredentialReporters -> RefreshReporter
crRefreshReporter :: RefreshReporter
}
noCredentialReporters :: CredentialReporters
noCredentialReporters :: CredentialReporters
noCredentialReporters = BreakerReporter -> RefreshReporter -> CredentialReporters
CredentialReporters BreakerReporter
noBreakerReporter RefreshReporter
noRefreshReporter
data RefreshConfig = RefreshConfig
{ RefreshConfig -> IO AuthToken
rcMint :: IO AuthToken
, RefreshConfig -> IO UTCTime
rcClock :: IO UTCTime
, RefreshConfig -> IO Double
rcJitter :: IO Double
, RefreshConfig -> Double
rcRefreshAt :: Double
, RefreshConfig -> NominalDiffTime
rcRefreshFloor :: NominalDiffTime
, RefreshConfig -> Int
rcBreakerThreshold :: Int
, RefreshConfig -> NominalDiffTime
rcBreakerCooldown :: NominalDiffTime
, RefreshConfig -> CredentialReporters
rcReporters :: CredentialReporters
}
defaultRefreshConfig :: RefreshConfig
defaultRefreshConfig :: RefreshConfig
defaultRefreshConfig =
RefreshConfig
{ rcMint :: IO AuthToken
rcMint = Text -> IO AuthToken
forall a. Text -> IO a
unconfigured Text
"rcMint"
, rcClock :: IO UTCTime
rcClock = Text -> IO UTCTime
forall a. Text -> IO a
unconfigured Text
"rcClock"
, rcJitter :: IO Double
rcJitter = Double -> IO Double
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Double
0
, rcRefreshAt :: Double
rcRefreshAt = Double
0.8
, rcRefreshFloor :: NominalDiffTime
rcRefreshFloor = NominalDiffTime
30
, rcBreakerThreshold :: Int
rcBreakerThreshold = Int
5
, rcBreakerCooldown :: NominalDiffTime
rcBreakerCooldown = NominalDiffTime
60
, rcReporters :: CredentialReporters
rcReporters = CredentialReporters
noCredentialReporters
}
where
unconfigured :: Text -> IO a
unconfigured :: forall a. Text -> IO a
unconfigured Text
field = CredentialError -> IO a
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO (Text -> CredentialError
Unconfigured Text
field)
data CacheState = CacheState
{ CacheState -> AuthToken
csToken :: AuthToken
, CacheState -> Maybe UTCTime
csRefreshDue :: Maybe UTCTime
, CacheState -> Bool
csRefreshing :: Bool
, CacheState -> Breaker
csBreaker :: Breaker
}
refreshingProvider :: RefreshConfig -> IO CredentialProvider
refreshingProvider :: RefreshConfig -> IO CredentialProvider
refreshingProvider = IO () -> RefreshConfig -> IO CredentialProvider
refreshingProviderWith (() -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ())
refreshingProviderWith :: IO () -> RefreshConfig -> IO CredentialProvider
refreshingProviderWith :: IO () -> RefreshConfig -> IO CredentialProvider
refreshingProviderWith IO ()
afterClaim RefreshConfig
cfg = do
now <- RefreshConfig -> IO UTCTime
rcClock RefreshConfig
cfg
token <- rcMint cfg
due <- refreshDueAt cfg now token
stateVar <- newTVarIO (CacheState token due False initialBreaker)
pure CredentialProvider{currentToken = serve afterClaim cfg stateVar}
data ServeAction
=
ServeCached AuthToken
|
ServeAndRefresh AuthToken
|
MintNow
deriving stock (ServeAction -> ServeAction -> Bool
(ServeAction -> ServeAction -> Bool)
-> (ServeAction -> ServeAction -> Bool) -> Eq ServeAction
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: ServeAction -> ServeAction -> Bool
== :: ServeAction -> ServeAction -> Bool
$c/= :: ServeAction -> ServeAction -> Bool
/= :: ServeAction -> ServeAction -> Bool
Eq, Int -> ServeAction -> ShowS
[ServeAction] -> ShowS
ServeAction -> String
(Int -> ServeAction -> ShowS)
-> (ServeAction -> String)
-> ([ServeAction] -> ShowS)
-> Show ServeAction
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> ServeAction -> ShowS
showsPrec :: Int -> ServeAction -> ShowS
$cshow :: ServeAction -> String
show :: ServeAction -> String
$cshowList :: [ServeAction] -> ShowS
showList :: [ServeAction] -> ShowS
Show)
serve :: IO () -> RefreshConfig -> TVar CacheState -> IO AuthToken
serve :: IO () -> RefreshConfig -> TVar CacheState -> IO AuthToken
serve IO ()
afterClaim RefreshConfig
cfg TVar CacheState
stateVar = ((forall a. IO a -> IO a) -> IO AuthToken) -> IO AuthToken
forall (m :: * -> *) b.
MonadUnliftIO m =>
((forall a. m a -> m a) -> m b) -> m b
mask (((forall a. IO a -> IO a) -> IO AuthToken) -> IO AuthToken)
-> ((forall a. IO a -> IO a) -> IO AuthToken) -> IO AuthToken
forall a b. (a -> b) -> a -> b
$ \forall a. IO a -> IO a
restore -> do
now <- RefreshConfig -> IO UTCTime
rcClock RefreshConfig
cfg
atomically (decide stateVar now) >>= \case
ServeCached AuthToken
token -> AuthToken -> IO AuthToken
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure AuthToken
token
ServeAndRefresh AuthToken
token -> AuthToken
token AuthToken -> IO () -> IO AuthToken
forall a b. a -> IO b -> IO a
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ IO () -> RefreshConfig -> TVar CacheState -> IO ()
forkRefresh IO ()
afterClaim RefreshConfig
cfg TVar CacheState
stateVar
ServeAction
MintNow ->
(IO AuthToken -> IO AuthToken)
-> (SomeException -> IO ())
-> IO ()
-> IO AuthToken
-> IO AuthToken
forall a.
(IO a -> IO a) -> (SomeException -> IO ()) -> IO () -> IO a -> IO a
guardInFlight IO AuthToken -> IO AuthToken
forall a. IO a -> IO a
restore SomeException -> IO ()
noWaiter (TVar CacheState -> IO ()
releaseSingleFlight TVar CacheState
stateVar) (IO ()
afterClaim IO () -> IO AuthToken -> IO AuthToken
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> RefreshConfig -> TVar CacheState -> IO AuthToken
mintSynchronously RefreshConfig
cfg TVar CacheState
stateVar)
forkRefresh :: IO () -> RefreshConfig -> TVar CacheState -> IO ()
forkRefresh :: IO () -> RefreshConfig -> TVar CacheState -> IO ()
forkRefresh IO ()
afterClaim RefreshConfig
cfg TVar CacheState
stateVar =
IO (Async ()) -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO (Async ()) -> IO ()) -> IO (Async ()) -> IO ()
forall a b. (a -> b) -> a -> b
$
((forall a. IO a -> IO a) -> IO ()) -> IO (Async ())
forall (m :: * -> *) a.
MonadUnliftIO m =>
((forall b. m b -> m b) -> m a) -> m (Async a)
asyncWithUnmask (((forall a. IO a -> IO a) -> IO ()) -> IO (Async ()))
-> ((forall a. IO a -> IO a) -> IO ()) -> IO (Async ())
forall a b. (a -> b) -> a -> b
$ \forall a. IO a -> IO a
unmask ->
(IO () -> IO ())
-> (SomeException -> IO ()) -> IO () -> IO () -> IO ()
forall a.
(IO a -> IO a) -> (SomeException -> IO ()) -> IO () -> IO a -> IO a
guardInFlight IO () -> IO ()
forall a. IO a -> IO a
unmask SomeException -> IO ()
noWaiter (TVar CacheState -> IO ()
releaseSingleFlight TVar CacheState
stateVar) (IO ()
afterClaim IO () -> IO () -> IO ()
forall a b. IO a -> IO b -> IO b
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> RefreshConfig -> TVar CacheState -> IO ()
backgroundRefresh RefreshConfig
cfg TVar CacheState
stateVar)
noWaiter :: SomeException -> IO ()
noWaiter :: SomeException -> IO ()
noWaiter = IO () -> SomeException -> IO ()
forall a b. a -> b -> a
const IO ()
forall (f :: * -> *). Applicative f => f ()
pass
decide :: TVar CacheState -> UTCTime -> STM ServeAction
decide :: TVar CacheState -> UTCTime -> STM ServeAction
decide TVar CacheState
stateVar UTCTime
now = TVar CacheState -> STM CacheState
forall a. TVar a -> STM a
readTVar TVar CacheState
stateVar STM CacheState
-> (CacheState -> STM ServeAction) -> STM ServeAction
forall a b. STM a -> (a -> STM b) -> STM b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= TVar CacheState -> UTCTime -> CacheState -> STM ServeAction
decideFrom TVar CacheState
stateVar UTCTime
now
decideFrom :: TVar CacheState -> UTCTime -> CacheState -> STM ServeAction
decideFrom :: TVar CacheState -> UTCTime -> CacheState -> STM ServeAction
decideFrom TVar CacheState
stateVar UTCTime
now CacheState
st
| Bool -> Bool
not (UTCTime -> AuthToken -> Bool
tokenValid UTCTime
now (CacheState -> AuthToken
csToken CacheState
st)) =
if CacheState -> Bool
csRefreshing CacheState
st then STM ServeAction
forall a. STM a
retry else TVar CacheState -> CacheState -> ServeAction -> STM ServeAction
claimSingleFlight TVar CacheState
stateVar CacheState
st ServeAction
MintNow
| UTCTime -> CacheState -> Bool
refreshNeeded UTCTime
now CacheState
st Bool -> Bool -> Bool
&& Bool -> Bool
not (CacheState -> Bool
csRefreshing CacheState
st) =
TVar CacheState -> CacheState -> ServeAction -> STM ServeAction
claimSingleFlight TVar CacheState
stateVar CacheState
st (AuthToken -> ServeAction
ServeAndRefresh (CacheState -> AuthToken
csToken CacheState
st))
| Bool
otherwise = ServeAction -> STM ServeAction
forall a. a -> STM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (AuthToken -> ServeAction
ServeCached (CacheState -> AuthToken
csToken CacheState
st))
claimSingleFlight :: TVar CacheState -> CacheState -> ServeAction -> STM ServeAction
claimSingleFlight :: TVar CacheState -> CacheState -> ServeAction -> STM ServeAction
claimSingleFlight TVar CacheState
stateVar CacheState
st ServeAction
action = ServeAction
action ServeAction -> STM () -> STM ServeAction
forall a b. a -> STM b -> STM a
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ TVar CacheState -> CacheState -> STM ()
forall a. TVar a -> a -> STM ()
writeTVar TVar CacheState
stateVar CacheState
st{csRefreshing = True}
data MintOutcome
= BreakerRefused
| Minted AuthToken
| MintedExpired
| MintThrew SomeException
attemptMint :: RefreshConfig -> TVar CacheState -> IO MintOutcome
attemptMint :: RefreshConfig -> TVar CacheState -> IO MintOutcome
attemptMint RefreshConfig
cfg TVar CacheState
stateVar = do
now <- RefreshConfig -> IO UTCTime
rcClock RefreshConfig
cfg
permitted <- gatedMint cfg stateVar now
if permitted
then do
result <- try (rcMint cfg)
now' <- rcClock cfg
case result of
Right AuthToken
token | UTCTime -> AuthToken -> Bool
tokenValid UTCTime
now' AuthToken
token -> do
RefreshConfig -> TVar CacheState -> UTCTime -> AuthToken -> IO ()
recordMintSuccess RefreshConfig
cfg TVar CacheState
stateVar UTCTime
now' AuthToken
token
MintOutcome -> IO MintOutcome
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (AuthToken -> MintOutcome
Minted AuthToken
token)
Right AuthToken
_ -> do
RefreshConfig -> TVar CacheState -> UTCTime -> IO ()
recordMintFailure RefreshConfig
cfg TVar CacheState
stateVar UTCTime
now'
MintOutcome -> IO MintOutcome
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure MintOutcome
MintedExpired
Left (SomeException
e :: SomeException) -> do
RefreshConfig -> TVar CacheState -> UTCTime -> IO ()
recordMintFailure RefreshConfig
cfg TVar CacheState
stateVar UTCTime
now'
MintOutcome -> IO MintOutcome
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (SomeException -> MintOutcome
MintThrew SomeException
e)
else pure BreakerRefused
backgroundRefresh :: RefreshConfig -> TVar CacheState -> IO ()
backgroundRefresh :: RefreshConfig -> TVar CacheState -> IO ()
backgroundRefresh RefreshConfig
cfg TVar CacheState
stateVar = IO MintOutcome -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (RefreshConfig -> TVar CacheState -> IO MintOutcome
attemptMint RefreshConfig
cfg TVar CacheState
stateVar)
mintSynchronously :: RefreshConfig -> TVar CacheState -> IO AuthToken
mintSynchronously :: RefreshConfig -> TVar CacheState -> IO AuthToken
mintSynchronously RefreshConfig
cfg TVar CacheState
stateVar =
RefreshConfig -> TVar CacheState -> IO MintOutcome
attemptMint RefreshConfig
cfg TVar CacheState
stateVar IO MintOutcome -> (MintOutcome -> IO AuthToken) -> IO AuthToken
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Minted AuthToken
token -> AuthToken -> IO AuthToken
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure AuthToken
token
MintOutcome
BreakerRefused -> CredentialError -> IO AuthToken
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO CredentialError
BreakerOpen
MintOutcome
MintedExpired -> CredentialError -> IO AuthToken
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO CredentialError
MintedTokenAlreadyExpired
MintThrew SomeException
e -> SomeException -> IO AuthToken
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO SomeException
e
releaseSingleFlight :: TVar CacheState -> IO ()
releaseSingleFlight :: TVar CacheState -> IO ()
releaseSingleFlight TVar CacheState
stateVar =
STM () -> IO ()
forall (m :: * -> *) a. MonadIO m => STM a -> m a
atomically (TVar CacheState -> (CacheState -> CacheState) -> STM ()
forall a. TVar a -> (a -> a) -> STM ()
modifyTVar' TVar CacheState
stateVar (\CacheState
st -> CacheState
st{csRefreshing = False}))
admitMintTxn :: TVar CacheState -> UTCTime -> STM (Bool, Breaker, Breaker)
admitMintTxn :: TVar CacheState -> UTCTime -> STM (Bool, Breaker, Breaker)
admitMintTxn TVar CacheState
stateVar UTCTime
now = do
st <- TVar CacheState -> STM CacheState
forall a. TVar a -> STM a
readTVar TVar CacheState
stateVar
let old = CacheState -> Breaker
csBreaker CacheState
st
(permitted, new) = admit now old
writeTVar stateVar st{csBreaker = new}
pure (permitted, old, new)
gatedMint :: RefreshConfig -> TVar CacheState -> UTCTime -> IO Bool
gatedMint :: RefreshConfig -> TVar CacheState -> UTCTime -> IO Bool
gatedMint RefreshConfig
cfg TVar CacheState
stateVar UTCTime
now = do
(permitted, old, new) <- STM (Bool, Breaker, Breaker) -> IO (Bool, Breaker, Breaker)
forall (m :: * -> *) a. MonadIO m => STM a -> m a
atomically (TVar CacheState -> UTCTime -> STM (Bool, Breaker, Breaker)
admitMintTxn TVar CacheState
stateVar UTCTime
now)
reportBreakerChange (crBreakerReporter (rcReporters cfg)) old new
pure permitted
recordMintSuccess :: RefreshConfig -> TVar CacheState -> UTCTime -> AuthToken -> IO ()
recordMintSuccess :: RefreshConfig -> TVar CacheState -> UTCTime -> AuthToken -> IO ()
recordMintSuccess RefreshConfig
cfg TVar CacheState
stateVar UTCTime
now' AuthToken
token = do
due <- RefreshConfig -> UTCTime -> AuthToken -> IO (Maybe UTCTime)
refreshDueAt RefreshConfig
cfg UTCTime
now' AuthToken
token
commitBreakerFold cfg stateVar (onMintSuccess token due)
onRefreshSucceeded (crRefreshReporter (rcReporters cfg)) (authExpiresAt token)
recordMintFailure :: RefreshConfig -> TVar CacheState -> UTCTime -> IO ()
recordMintFailure :: RefreshConfig -> TVar CacheState -> UTCTime -> IO ()
recordMintFailure RefreshConfig
cfg TVar CacheState
stateVar UTCTime
now' = do
cached <- CacheState -> AuthToken
csToken (CacheState -> AuthToken) -> IO CacheState -> IO AuthToken
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> TVar CacheState -> IO CacheState
forall (m :: * -> *) a. MonadIO m => TVar a -> m a
readTVarIO TVar CacheState
stateVar
commitBreakerFold cfg stateVar (onMintFailure cfg now')
onRefreshFailed (crRefreshReporter (rcReporters cfg)) (authExpiresAt cached)
commitBreakerFold :: RefreshConfig -> TVar CacheState -> (CacheState -> CacheState) -> IO ()
commitBreakerFold :: RefreshConfig
-> TVar CacheState -> (CacheState -> CacheState) -> IO ()
commitBreakerFold RefreshConfig
cfg TVar CacheState
stateVar CacheState -> CacheState
step = do
(old, new) <- STM (Breaker, Breaker) -> IO (Breaker, Breaker)
forall (m :: * -> *) a. MonadIO m => STM a -> m a
atomically (STM (Breaker, Breaker) -> IO (Breaker, Breaker))
-> STM (Breaker, Breaker) -> IO (Breaker, Breaker)
forall a b. (a -> b) -> a -> b
$ do
st <- TVar CacheState -> STM CacheState
forall a. TVar a -> STM a
readTVar TVar CacheState
stateVar
let st' = CacheState -> CacheState
step CacheState
st
writeTVar stateVar st'
pure (csBreaker st, csBreaker st')
reportBreakerChange (crBreakerReporter (rcReporters cfg)) old new
onMintSuccess :: AuthToken -> Maybe UTCTime -> CacheState -> CacheState
onMintSuccess :: AuthToken -> Maybe UTCTime -> CacheState -> CacheState
onMintSuccess AuthToken
token Maybe UTCTime
due CacheState
st =
CacheState
st
{ csToken = token
, csRefreshDue = due
, csBreaker = recordSuccess (csBreaker st)
}
onMintFailure :: RefreshConfig -> UTCTime -> CacheState -> CacheState
onMintFailure :: RefreshConfig -> UTCTime -> CacheState -> CacheState
onMintFailure RefreshConfig
cfg UTCTime
now CacheState
st =
CacheState
st{csBreaker = recordFailure (rcBreakerThreshold cfg) (rcBreakerCooldown cfg) now (csBreaker st)}
tokenValid :: UTCTime -> AuthToken -> Bool
tokenValid :: UTCTime -> AuthToken -> Bool
tokenValid UTCTime
now AuthToken
token = case AuthToken -> Maybe UTCTime
authExpiresAt AuthToken
token of
Maybe UTCTime
Nothing -> Bool
True
Just UTCTime
expiry -> UTCTime
now UTCTime -> UTCTime -> Bool
forall a. Ord a => a -> a -> Bool
< UTCTime
expiry
refreshNeeded :: UTCTime -> CacheState -> Bool
refreshNeeded :: UTCTime -> CacheState -> Bool
refreshNeeded UTCTime
now CacheState
st = case CacheState -> Maybe UTCTime
csRefreshDue CacheState
st of
Maybe UTCTime
Nothing -> Bool
False
Just UTCTime
due -> UTCTime
now UTCTime -> UTCTime -> Bool
forall a. Ord a => a -> a -> Bool
>= UTCTime
due
refreshDueAt :: RefreshConfig -> UTCTime -> AuthToken -> IO (Maybe UTCTime)
refreshDueAt :: RefreshConfig -> UTCTime -> AuthToken -> IO (Maybe UTCTime)
refreshDueAt RefreshConfig
cfg UTCTime
issuedAt AuthToken
token = case AuthToken -> Maybe UTCTime
authExpiresAt AuthToken
token of
Maybe UTCTime
Nothing -> Maybe UTCTime -> IO (Maybe UTCTime)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe UTCTime
forall a. Maybe a
Nothing
Just UTCTime
expiry -> do
jitter <- RefreshConfig -> IO Double
rcJitter RefreshConfig
cfg
let lifetime = NominalDiffTime -> Double
forall a b. (Real a, Fractional b) => a -> b
realToFrac (UTCTime -> UTCTime -> NominalDiffTime
diffUTCTime UTCTime
expiry UTCTime
issuedAt) :: Double
frac = Double -> Double
clamp01 (RefreshConfig -> Double
rcRefreshAt RefreshConfig
cfg Double -> Double -> Double
forall a. Num a => a -> a -> a
- Double -> Double
clamp01 Double
jitter)
byFraction = NominalDiffTime -> UTCTime -> UTCTime
addUTCTime (Double -> NominalDiffTime
forall a b. (Real a, Fractional b) => a -> b
realToFrac (Double
frac Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
lifetime)) UTCTime
issuedAt
floorInstant = NominalDiffTime -> UTCTime -> UTCTime
addUTCTime (NominalDiffTime -> NominalDiffTime
forall a. Num a => a -> a
negate (RefreshConfig -> NominalDiffTime
rcRefreshFloor RefreshConfig
cfg)) UTCTime
expiry
due = UTCTime -> UTCTime -> UTCTime
forall a. Ord a => a -> a -> a
max UTCTime
issuedAt (UTCTime -> UTCTime -> UTCTime
forall a. Ord a => a -> a -> a
min UTCTime
byFraction UTCTime
floorInstant)
pure (Just due)
where
clamp01 :: Double -> Double
clamp01 :: Double -> Double
clamp01 = (Double, Double) -> Double -> Double
forall a. Ord a => (a, a) -> a -> a
clamp (Double
0, Double
1)