@@ -12,6 +12,8 @@ import kotlinx.coroutines.reactive.awaitFirstOrElse
1212import kotlinx.coroutines.reactive.awaitFirstOrNull
1313import kotlinx.coroutines.reactor.awaitSingle
1414import kotlinx.coroutines.reactor.awaitSingleOrNull
15+ import kotlinx.coroutines.sync.Mutex
16+ import kotlinx.coroutines.sync.withLock
1517import org.slf4j.LoggerFactory
1618import org.springframework.beans.factory.annotation.Value
1719import org.springframework.cache.Cache
@@ -21,6 +23,7 @@ import java.time.Instant
2123import java.time.LocalDateTime
2224import java.time.ZoneId
2325import java.util.*
26+ import java.util.concurrent.ConcurrentHashMap
2427import java.util.concurrent.TimeUnit
2528import javax.crypto.Cipher
2629import 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