keep track of auth token in db
This commit is contained in:
parent
182ad004fb
commit
ef181bce1a
@ -2,8 +2,10 @@ package com.restapi.config
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.restapi.config.AppConfig.Companion.appConfig
|
||||
import com.restapi.domain.AuthTokenCache
|
||||
import com.restapi.domain.Plant
|
||||
import com.restapi.domain.Session
|
||||
import com.restapi.domain.Session.database
|
||||
import com.restapi.domain.Session.objectMapper
|
||||
import io.javalin.http.BadRequestResponse
|
||||
import io.javalin.http.ContentType
|
||||
@ -45,7 +47,7 @@ object Auth {
|
||||
private val logger = LoggerFactory.getLogger("Auth")
|
||||
private val authCache = ConcurrentHashMap<String, AuthEndpoint>()
|
||||
|
||||
fun getAuthEndpoint(): AuthEndpoint {
|
||||
private fun getAuthEndpoint(): AuthEndpoint {
|
||||
return authCache.computeIfAbsent("AUTH") {
|
||||
val wellKnown = "${appConfig.iamUrl()}/realms/${appConfig.iamRealm()}/.well-known/openid-configuration"
|
||||
val client = HttpClient.newHttpClient()
|
||||
@ -180,14 +182,14 @@ object Auth {
|
||||
val atResponse = objectMapper.readValue<AuthTokenResponse>(message)
|
||||
val parsed = validateAuthToken(atResponse.accessToken)
|
||||
|
||||
//keep track of this for renewal when asked by client
|
||||
Session.redis.lpush(
|
||||
"$AUTH_TOKEN${parsed.userName}", objectMapper.writeValueAsString(
|
||||
atResponse.copy(
|
||||
createdAt = LocalDateTime.now()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
database.save(AuthTokenCache().apply {
|
||||
this.userId = parsed.userName
|
||||
this.authToken = atResponse.accessToken
|
||||
this.expiresAt = LocalDateTime.now().plusSeconds(atResponse.expiresIn.toLong())
|
||||
this.refreshToken = atResponse.refreshToken
|
||||
this.refreshExpiresAt = LocalDateTime.now().plusSeconds(atResponse.refreshExpiresIn.toLong())
|
||||
})
|
||||
ctx.result(atResponse.accessToken).contentType(ContentType.TEXT_PLAIN)
|
||||
}
|
||||
|
||||
@ -200,22 +202,36 @@ object Auth {
|
||||
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 found = Session.redis.llen(key)
|
||||
logger.warn("for user ${authUser.userName}, found from redis, $key => $found entries")
|
||||
val foundOldAt = (0..found).mapNotNull { Session.redis.lindex(key, it) }
|
||||
.map { objectMapper.readValue<AuthTokenResponse>(it) }.firstOrNull { it.accessToken == authToken }
|
||||
?: throw BadRequestResponse("authToken not found in cache")
|
||||
val foundOldAt = database.find(AuthTokenCache::class.java)
|
||||
.where()
|
||||
.eq("userId", authUser.userName)
|
||||
.eq("expired", false)
|
||||
.eq("loggedOut", false)
|
||||
.gt("refreshExpiresAt", LocalDateTime.now())
|
||||
.findList()
|
||||
.onEach {
|
||||
logger.warn("valid authToken for ${authUser.userName} is ${it.authToken}")
|
||||
}
|
||||
.firstOrNull {
|
||||
it.authToken.equals(authToken, ignoreCase = true)
|
||||
} ?: throw BadRequestResponse("we did not find an entry for this auth token $authToken")
|
||||
|
||||
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 createdAt = foundOldAt.createdAt
|
||||
val expiresAt = foundOldAt.expiresAt
|
||||
val rtExpiresAt = foundOldAt.refreshExpiresAt
|
||||
|
||||
val now = LocalDateTime.now()
|
||||
logger.warn("can we refresh the token for ${authUser.userName}, created = $createdAt expires = $expiresAt, refresh Till = $rtExpiresAt")
|
||||
logger.warn("can we refresh the token for ${authUser.userName}, created = $createdAt expires = $expiresAt, refresh Till = $rtExpiresAt")
|
||||
|
||||
val authTokenValid = expiresAt.isAfter(now)
|
||||
if (authTokenValid) {
|
||||
ctx.result(authToken).contentType(ContentType.TEXT_PLAIN)
|
||||
return
|
||||
}
|
||||
|
||||
//we can refresh if at is expired, but we still have time for refresh
|
||||
if (expiresAt.isBefore(now) && now.isBefore(rtExpiresAt)) {
|
||||
val refreshTokenValid = rtExpiresAt.isAfter(now)
|
||||
if (refreshTokenValid) {
|
||||
logger.warn("We can refresh the token for ${authUser.userName}, expires = $expiresAt, refresh Till = $rtExpiresAt")
|
||||
val ep = getAuthEndpoint().tokenEndpoint
|
||||
val httpClient = HttpClient.newHttpClient()
|
||||
@ -244,14 +260,9 @@ object Auth {
|
||||
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}, 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 [$rtExpiresAt] is expired")
|
||||
throw UnauthorizedResponse()
|
||||
}
|
||||
//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 [$rtExpiresAt] is expired")
|
||||
throw UnauthorizedResponse()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import io.ebean.annotation.*
|
||||
import io.ebean.annotation.Index
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.util.UUID
|
||||
import java.util.*
|
||||
import javax.persistence.*
|
||||
|
||||
data class Comments(val text: String = "", val by: String = "", val at: LocalDateTime = LocalDateTime.now())
|
||||
@ -257,10 +257,12 @@ enum class AddressType {
|
||||
}
|
||||
|
||||
data class ContactPerson(
|
||||
val id : String = UUID.randomUUID().toString(),
|
||||
val name: String = "", val email: String = "", val mobile: String = "")
|
||||
val id: String = UUID.randomUUID().toString(),
|
||||
val name: String = "", val email: String = "", val mobile: String = ""
|
||||
)
|
||||
|
||||
data class Address(
|
||||
val id : String = UUID.randomUUID().toString(),
|
||||
val id: String = UUID.randomUUID().toString(),
|
||||
val type: AddressType = AddressType.BILLING,
|
||||
val address: String = "",
|
||||
val pincode: String = ""
|
||||
@ -683,4 +685,19 @@ open class Plant : BaseModel() {
|
||||
|
||||
@DbJsonB
|
||||
var prefixes: MutableMap<String, String>? = mutableMapOf()
|
||||
}
|
||||
|
||||
@Entity
|
||||
open class AuthTokenCache : BaseModel() {
|
||||
@Column(columnDefinition = "text")
|
||||
var authToken: String = ""
|
||||
var issuedAt: LocalDateTime = LocalDateTime.now()
|
||||
var expiresAt: LocalDateTime = LocalDateTime.now()
|
||||
var refreshExpiresAt: LocalDateTime = LocalDateTime.now()
|
||||
|
||||
@Column(columnDefinition = "text")
|
||||
var refreshToken: String = ""
|
||||
var userId: String = ""
|
||||
var expired: Boolean = false
|
||||
var loggedOut: Boolean = false
|
||||
}
|
||||
28
src/main/resources/dbmigration/1.30.sql
Normal file
28
src/main/resources/dbmigration/1.30.sql
Normal file
@ -0,0 +1,28 @@
|
||||
-- apply changes
|
||||
create table auth_token_cache (
|
||||
sys_pk bigint generated by default as identity not null,
|
||||
deleted_on timestamp,
|
||||
current_approval_level integer default 0 not null,
|
||||
required_approval_levels integer default 0 not null,
|
||||
auth_token text not null,
|
||||
issued_at timestamp not null,
|
||||
expires_at timestamp not null,
|
||||
refresh_expires_at timestamp not null,
|
||||
refresh_token text not null,
|
||||
expired boolean default false not null,
|
||||
logged_out boolean default false not null,
|
||||
deleted boolean default false not null,
|
||||
version integer default 1 not null,
|
||||
created_at timestamp default 'now()' not null,
|
||||
modified_at timestamp default 'now()' not null,
|
||||
deleted_by varchar(255),
|
||||
approval_status varchar(8) default 'APPROVED' not null,
|
||||
tags varchar[] default '{}' not null,
|
||||
comments jsonb default '[]' not null,
|
||||
user_id varchar(255) not null,
|
||||
created_by varchar(255) not null,
|
||||
modified_by varchar(255) not null,
|
||||
constraint ck_auth_token_cache_approval_status check ( approval_status in ('PENDING','APPROVED','REJECTED')),
|
||||
constraint pk_auth_token_cache primary key (sys_pk)
|
||||
);
|
||||
|
||||
29
src/main/resources/dbmigration/model/1.30.model.xml
Normal file
29
src/main/resources/dbmigration/model/1.30.model.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<migration xmlns="http://ebean-orm.github.io/xml/ns/dbmigration">
|
||||
<changeSet type="apply">
|
||||
<createTable name="auth_token_cache" pkName="pk_auth_token_cache">
|
||||
<column name="sys_pk" type="bigint" primaryKey="true"/>
|
||||
<column name="deleted_on" type="localdatetime"/>
|
||||
<column name="deleted_by" type="varchar"/>
|
||||
<column name="current_approval_level" type="integer" defaultValue="0" notnull="true"/>
|
||||
<column name="required_approval_levels" type="integer" defaultValue="0" notnull="true"/>
|
||||
<column name="approval_status" type="varchar(8)" defaultValue="'APPROVED'" notnull="true" checkConstraint="check ( approval_status in ('PENDING','APPROVED','REJECTED'))" checkConstraintName="ck_auth_token_cache_approval_status"/>
|
||||
<column name="tags" type="varchar[]" defaultValue="'{}'" notnull="true"/>
|
||||
<column name="comments" type="jsonb" defaultValue="'[]'" notnull="true"/>
|
||||
<column name="auth_token" type="text" notnull="true"/>
|
||||
<column name="issued_at" type="localdatetime" notnull="true"/>
|
||||
<column name="expires_at" type="localdatetime" notnull="true"/>
|
||||
<column name="refresh_expires_at" type="localdatetime" notnull="true"/>
|
||||
<column name="refresh_token" type="text" notnull="true"/>
|
||||
<column name="user_id" type="varchar" notnull="true"/>
|
||||
<column name="expired" type="boolean" defaultValue="false" notnull="true"/>
|
||||
<column name="logged_out" type="boolean" defaultValue="false" notnull="true"/>
|
||||
<column name="deleted" type="boolean" defaultValue="false" notnull="true"/>
|
||||
<column name="version" type="integer" defaultValue="1" notnull="true"/>
|
||||
<column name="created_at" type="localdatetime" defaultValue="'now()'" notnull="true"/>
|
||||
<column name="modified_at" type="localdatetime" defaultValue="'now()'" notnull="true"/>
|
||||
<column name="created_by" type="varchar" notnull="true"/>
|
||||
<column name="modified_by" type="varchar" notnull="true"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
</migration>
|
||||
Loading…
x
Reference in New Issue
Block a user