Skip to content

Commit ae6ea83

Browse files
committed
Tune the connection between api and auth modules
1 parent 49eb11a commit ae6ea83

2 files changed

Lines changed: 38 additions & 4 deletions

File tree

api/api-app/src/main/kotlin/co/nilin/opex/api/app/proxy/AuthProxy.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ import org.springframework.web.reactive.function.client.bodyToMono
1313
@Component
1414
class AuthProxy(
1515
@Value("\${app.auth.token-url}")
16-
private val tokenUrl: String
16+
private val tokenUrl: String,
17+
private val client: WebClient
1718
) {
1819

1920
private val logger = LoggerFactory.getLogger(AuthProxy::class.java)
20-
private val client = WebClient.create()
2121

2222
suspend fun exchangeToken(clientSecret: String, token: String): AccessTokenResponse {
2323
val body = BodyInserters.fromFormData("client_id", "opex-api-key")

api/api-app/src/main/kotlin/co/nilin/opex/api/app/service/APIKeyServiceImpl.kt

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import kotlinx.coroutines.reactive.awaitFirstOrElse
1212
import kotlinx.coroutines.reactive.awaitFirstOrNull
1313
import kotlinx.coroutines.reactor.awaitSingle
1414
import kotlinx.coroutines.reactor.awaitSingleOrNull
15+
import kotlinx.coroutines.sync.Mutex
16+
import kotlinx.coroutines.sync.withLock
1517
import org.slf4j.LoggerFactory
1618
import org.springframework.beans.factory.annotation.Value
1719
import org.springframework.cache.Cache
@@ -21,6 +23,7 @@ import java.time.Instant
2123
import java.time.LocalDateTime
2224
import java.time.ZoneId
2325
import java.util.*
26+
import java.util.concurrent.ConcurrentHashMap
2427
import java.util.concurrent.TimeUnit
2528
import javax.crypto.Cipher
2629
import javax.crypto.spec.IvParameterSpec
@@ -36,7 +39,7 @@ class APIKeyServiceImpl(
3639
) : APIKeyService {
3740

3841
private val logger = LoggerFactory.getLogger(APIKeyServiceImpl::class.java)
39-
42+
private val refreshLocks = ConcurrentHashMap<String, Mutex>()
4043
override suspend fun createAPIKey(
4144
userId: String,
4245
label: String,
@@ -76,7 +79,7 @@ class APIKeyServiceImpl(
7679

7780
with(apiKey) {
7881
if (this != null) {
79-
launch { checkupAPIKey(this@with, secret) }
82+
refreshIfNeeded(this@with, secret)
8083
APIKey(
8184
userId,
8285
label,
@@ -123,6 +126,37 @@ class APIKeyServiceImpl(
123126
apiKeyRepository.delete(apiKey).awaitFirstOrNull()
124127
}
125128

129+
private suspend fun refreshIfNeeded(apiKey: APIKeyModel, secret: String) {
130+
if (apiKey.isExpired || !apiKey.isEnabled) return
131+
val now = LocalDateTime.now()
132+
133+
if (apiKey.expirationTime?.isBefore(now) == true) {
134+
logger.info("Expiring api key ${apiKey.key}")
135+
apiKey.isExpired = true
136+
apiKeyRepository.save(apiKey).awaitSingle().apply { updateCache(this) }
137+
logger.info("API key ${apiKey.key} is expired")
138+
return
139+
}
140+
141+
if (apiKey.tokenExpirationTime.isAfter(now)) return
142+
143+
val mutex = refreshLocks.computeIfAbsent(apiKey.key) { Mutex() }
144+
mutex.withLock {
145+
// Double-check after acquiring the lock to avoid redundant refresh
146+
if (apiKey.tokenExpirationTime.isAfter(LocalDateTime.now())) return
147+
try {
148+
logger.info("Refreshing api key ${apiKey.key} token")
149+
val response = authProxy.refreshToken(clientSecret, decryptAES(apiKey.refreshToken, secret))
150+
apiKey.accessToken = encryptAES(response.access_token, secret)
151+
apiKey.tokenExpirationTime = tokenExpiration(response.expires_in)
152+
apiKeyRepository.save(apiKey).awaitSingle().apply { updateCache(this) }
153+
logger.info("API key ${apiKey.key} token refreshed")
154+
} catch (e: Exception) {
155+
logger.error("Error refreshing api key ${apiKey.key}", e)
156+
}
157+
}
158+
}
159+
126160
private suspend fun checkupAPIKey(apiKey: APIKeyModel, secret: String) {
127161
if (apiKey.isExpired || !apiKey.isEnabled)
128162
return

0 commit comments

Comments
 (0)