org.http4k.security.CredentialsProvider.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of http4k-core Show documentation
Show all versions of http4k-core Show documentation
Dependency-lite Server as a Function in pure Kotlin
package org.http4k.security
import java.time.Clock
import java.time.Duration
import java.time.Duration.ZERO
import java.time.Instant
import java.util.concurrent.atomic.AtomicReference
fun interface CredentialsProvider : () -> T? {
companion object
}
fun interface RefreshCredentials : (T?) -> ExpiringCredentials?
data class ExpiringCredentials(val credentials: T, val expiry: Instant)
fun CredentialsProvider.Companion.Refreshing(
gracePeriod: Duration = Duration.ofSeconds(10),
clock: Clock = Clock.systemUTC(),
refreshFn: RefreshCredentials
) = Refreshing(gracePeriod, clock::instant, refreshFn)
fun CredentialsProvider.Companion.Refreshing(
gracePeriod: Duration = Duration.ofSeconds(10),
timeSource: () -> Instant,
refreshFn: RefreshCredentials
) = object : CredentialsProvider {
private val stored = AtomicReference>(null)
private val isRefreshing = AtomicReference(false)
override fun invoke() = (stored.get()
?.takeIf {
val expiresSoon = it.expiresWithin(gracePeriod)
val expiresReallySoon = it.expiresWithin(gracePeriod.dividedBy(2))
val isAlreadyRefreshing = isRefreshing.get()
(!expiresSoon || isAlreadyRefreshing) && !expiresReallySoon
} ?: refresh())?.credentials
private fun refresh() = synchronized(this) {
isRefreshing.set(true)
try {
val current = stored.get()
when {
current != null && !current.expiresWithin(gracePeriod) -> current
else -> try {
refreshFn(current?.credentials).also(stored::set)
} catch (e: Exception) {
if (current == null || current.expiresWithin(ZERO)) throw e
current
}
}
} finally {
isRefreshing.set(false)
}
}
private fun ExpiringCredentials.expiresWithin(duration: Duration): Boolean =
expiry
.minus(duration)
.isBefore(timeSource())
}