From 5c13e0efdd262b8f9f18bff16f11a7c7b01bc92a Mon Sep 17 00:00:00 2001 From: "gowthaman.b" Date: Mon, 13 Nov 2023 08:15:19 +0530 Subject: [PATCH] fix authtoken expiry check --- src/main/kotlin/com/restapi/Main.kt | 40 +++++++++++++------ src/main/kotlin/com/restapi/config/Auth.kt | 10 +++-- .../com/restapi/config/AuthTokenResponse.kt | 2 +- src/main/kotlin/com/restapi/domain/db.kt | 3 +- 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/com/restapi/Main.kt b/src/main/kotlin/com/restapi/Main.kt index 98b385d..6aa1fa3 100644 --- a/src/main/kotlin/com/restapi/Main.kt +++ b/src/main/kotlin/com/restapi/Main.kt @@ -50,6 +50,7 @@ fun main(args: Array) { val createRole = Role.Standard(Action.CREATE) val updateRole = Role.Standard(Action.UPDATE) val approveOrRejectRole = Role.Standard(Action.APPROVE) + val AUTH_TOKEN = "AUTH_TOKEN_V2" //todo, create roles in keycloak based on entity and actions @@ -111,12 +112,20 @@ fun main(args: Array) { ) .header("Content-Type", "application/x-www-form-urlencoded") .build() + val message = httpClient.send(req, BodyHandlers.ofString()).body() val atResponse = objectMapper.readValue(message) val parsed = validateAuthToken(atResponse.accessToken) - //keep track of this - redis.rpush("AUTH_TOKEN_${parsed.userName}", message) + //keep track of this for renewal when asked by client + redis.lpush( + "$AUTH_TOKEN${parsed.userName}", + objectMapper.writeValueAsString( + atResponse.copy( + createdAt = LocalDateTime.now() + ) + ) + ) it.result(atResponse.accessToken).contentType(ContentType.TEXT_PLAIN) } @@ -138,17 +147,20 @@ fun main(args: Array) { val client = ctx.queryParam("client") ?: throw BadRequestResponse("client not sent") val redirectUri = ctx.queryParam("redirectUri") ?: throw BadRequestResponse("redirectUri not sent") - val key = "AUTH_TOKEN_${authUser.userName}" + val key = "$AUTH_TOKEN${authUser.userName}" val found = redis.llen(key) val foundOldAt = (0..found) .mapNotNull { redis.lindex(key, it) } .map { objectMapper.readValue(it) } - .firstOrNull { it.accessToken == authToken } ?: throw BadRequestResponse("authToken not found in cache") + .firstOrNull { it.accessToken == authToken } + ?: throw BadRequestResponse("authToken not found in cache") - val expiresAt = foundOldAt.createdAt.plusSeconds(foundOldAt.expiresIn + 0L) - val rtExpiresAt = foundOldAt.createdAt.plusSeconds(foundOldAt.refreshExpiresIn + 0L) + val createdAt = foundOldAt.createdAt ?: throw BadRequestResponse("created at is missing") + val expiresAt = createdAt.plusSeconds(foundOldAt.expiresIn + 0L) + val rtExpiresAt = createdAt.plusSeconds(foundOldAt.refreshExpiresIn + 0L) val now = LocalDateTime.now() + logger.warn("can we refresh the token for ${authUser.userName}, created = $createdAt expires = $expiresAt, refresh Till = $rtExpiresAt") //we can refresh if at is expired, but we still have time for refresh if (expiresAt.isBefore(now) && now.isBefore(rtExpiresAt)) { @@ -175,18 +187,22 @@ fun main(args: Array) { val atResponse = objectMapper.readValue(message) val parsed = validateAuthToken(atResponse.accessToken) - //keep track of this - redis.rpush("AUTH_TOKEN_${parsed.userName}", message) + redis.lpush( + "AUTH_TOKEN_${parsed.userName}", + objectMapper.writeValueAsString( + atResponse.copy(createdAt = LocalDateTime.now()) + ) + ) - ctx.json(atResponse) + ctx.result(atResponse.accessToken).contentType(ContentType.TEXT_PLAIN) } else { //at is still valid if (expiresAt.isAfter(now)) { - logger.warn("Still valid, the token for ${authUser.userName}") - ctx.json(foundOldAt) + logger.warn("Still valid, the token for ${authUser.userName}, will expire at $expiresAt") + ctx.result(foundOldAt.accessToken).contentType(ContentType.TEXT_PLAIN) } else { //we have exceeded the refresh time, so we shall ask the user to login again - logger.warn("We can't refresh the token for ${authUser.userName}, as refresh-time is expired") + logger.warn("We can't refresh the token for ${authUser.userName}, as refresh-time [$rtExpiresAt] is expired") throw UnauthorizedResponse() } } diff --git a/src/main/kotlin/com/restapi/config/Auth.kt b/src/main/kotlin/com/restapi/config/Auth.kt index 66db38b..9149de3 100644 --- a/src/main/kotlin/com/restapi/config/Auth.kt +++ b/src/main/kotlin/com/restapi/config/Auth.kt @@ -11,6 +11,9 @@ import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest import java.net.http.HttpResponse +import java.time.LocalDateTime +import java.time.ZoneId +import java.util.* import java.util.concurrent.ConcurrentHashMap object Auth { @@ -56,18 +59,19 @@ object Auth { val userId = jwtClaims.jwtClaims.claimsMap["preferred_username"] as String val tenant = jwtClaims.jwtClaims.claimsMap["tenant"] as String val roles = ((jwtClaims.jwtClaims.claimsMap["realm_access"] as Map)["roles"]) as List - + val date = Date(jwtClaims.jwtClaims.expirationTime.valueInMillis) return AuthUser( userName = userId, tenant = tenant, roles = roles, - token = authToken + token = authToken, + expiry = LocalDateTime.from(date.toInstant().atZone(ZoneId.systemDefault())) ) } } -data class AuthUser(val userName: String, val tenant: String, val roles: List, val token: String) +data class AuthUser(val userName: String, val tenant: String, val roles: List, val token: String, val expiry: LocalDateTime) enum class Action { CREATE, VIEW, UPDATE, DELETE, APPROVE, ADMIN } diff --git a/src/main/kotlin/com/restapi/config/AuthTokenResponse.kt b/src/main/kotlin/com/restapi/config/AuthTokenResponse.kt index 7d6ad98..ca8c410 100644 --- a/src/main/kotlin/com/restapi/config/AuthTokenResponse.kt +++ b/src/main/kotlin/com/restapi/config/AuthTokenResponse.kt @@ -19,5 +19,5 @@ data class AuthTokenResponse( val sessionState: String, @JsonProperty("token_type") val tokenType: String, - val createdAt: LocalDateTime = LocalDateTime.now() + val createdAt: LocalDateTime? = null ) \ No newline at end of file diff --git a/src/main/kotlin/com/restapi/domain/db.kt b/src/main/kotlin/com/restapi/domain/db.kt index 4e28306..a15f737 100644 --- a/src/main/kotlin/com/restapi/domain/db.kt +++ b/src/main/kotlin/com/restapi/domain/db.kt @@ -28,6 +28,7 @@ import java.security.interfaces.RSAPrivateKey import java.security.interfaces.RSAPublicKey import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec +import java.time.LocalDateTime import java.util.* import kotlin.jvm.optionals.getOrDefault @@ -37,7 +38,7 @@ object Session { private val logger = LoggerFactory.getLogger("session") private val currentUser = object : ThreadLocal() { override fun initialValue(): AuthUser { - return AuthUser("", "", emptyList(), "") + return AuthUser("", "", emptyList(), "", LocalDateTime.now()) } }