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.fasterxml.jackson.module.kotlin.readValue
|
||||||
import com.restapi.config.AppConfig.Companion.appConfig
|
import com.restapi.config.AppConfig.Companion.appConfig
|
||||||
|
import com.restapi.domain.AuthTokenCache
|
||||||
import com.restapi.domain.Plant
|
import com.restapi.domain.Plant
|
||||||
import com.restapi.domain.Session
|
import com.restapi.domain.Session
|
||||||
|
import com.restapi.domain.Session.database
|
||||||
import com.restapi.domain.Session.objectMapper
|
import com.restapi.domain.Session.objectMapper
|
||||||
import io.javalin.http.BadRequestResponse
|
import io.javalin.http.BadRequestResponse
|
||||||
import io.javalin.http.ContentType
|
import io.javalin.http.ContentType
|
||||||
@ -45,7 +47,7 @@ object Auth {
|
|||||||
private val logger = LoggerFactory.getLogger("Auth")
|
private val logger = LoggerFactory.getLogger("Auth")
|
||||||
private val authCache = ConcurrentHashMap<String, AuthEndpoint>()
|
private val authCache = ConcurrentHashMap<String, AuthEndpoint>()
|
||||||
|
|
||||||
fun getAuthEndpoint(): AuthEndpoint {
|
private fun getAuthEndpoint(): AuthEndpoint {
|
||||||
return authCache.computeIfAbsent("AUTH") {
|
return authCache.computeIfAbsent("AUTH") {
|
||||||
val wellKnown = "${appConfig.iamUrl()}/realms/${appConfig.iamRealm()}/.well-known/openid-configuration"
|
val wellKnown = "${appConfig.iamUrl()}/realms/${appConfig.iamRealm()}/.well-known/openid-configuration"
|
||||||
val client = HttpClient.newHttpClient()
|
val client = HttpClient.newHttpClient()
|
||||||
@ -180,14 +182,14 @@ object Auth {
|
|||||||
val atResponse = objectMapper.readValue<AuthTokenResponse>(message)
|
val atResponse = objectMapper.readValue<AuthTokenResponse>(message)
|
||||||
val parsed = validateAuthToken(atResponse.accessToken)
|
val parsed = validateAuthToken(atResponse.accessToken)
|
||||||
|
|
||||||
//keep track of this for renewal when asked by client
|
|
||||||
Session.redis.lpush(
|
database.save(AuthTokenCache().apply {
|
||||||
"$AUTH_TOKEN${parsed.userName}", objectMapper.writeValueAsString(
|
this.userId = parsed.userName
|
||||||
atResponse.copy(
|
this.authToken = atResponse.accessToken
|
||||||
createdAt = LocalDateTime.now()
|
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)
|
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 client = ctx.queryParam("client") ?: throw BadRequestResponse("client not sent")
|
||||||
val redirectUri = ctx.queryParam("redirectUri") ?: throw BadRequestResponse("redirectUri not sent")
|
val redirectUri = ctx.queryParam("redirectUri") ?: throw BadRequestResponse("redirectUri not sent")
|
||||||
|
|
||||||
val key = "$AUTH_TOKEN${authUser.userName}"
|
val foundOldAt = database.find(AuthTokenCache::class.java)
|
||||||
val found = Session.redis.llen(key)
|
.where()
|
||||||
logger.warn("for user ${authUser.userName}, found from redis, $key => $found entries")
|
.eq("userId", authUser.userName)
|
||||||
val foundOldAt = (0..found).mapNotNull { Session.redis.lindex(key, it) }
|
.eq("expired", false)
|
||||||
.map { objectMapper.readValue<AuthTokenResponse>(it) }.firstOrNull { it.accessToken == authToken }
|
.eq("loggedOut", false)
|
||||||
?: throw BadRequestResponse("authToken not found in cache")
|
.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 createdAt = foundOldAt.createdAt
|
||||||
val expiresAt = createdAt.plusSeconds(foundOldAt.expiresIn + 0L)
|
val expiresAt = foundOldAt.expiresAt
|
||||||
val rtExpiresAt = createdAt.plusSeconds(foundOldAt.refreshExpiresIn + 0L)
|
val rtExpiresAt = foundOldAt.refreshExpiresAt
|
||||||
|
|
||||||
val now = LocalDateTime.now()
|
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
|
//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")
|
logger.warn("We can refresh the token for ${authUser.userName}, expires = $expiresAt, refresh Till = $rtExpiresAt")
|
||||||
val ep = getAuthEndpoint().tokenEndpoint
|
val ep = getAuthEndpoint().tokenEndpoint
|
||||||
val httpClient = HttpClient.newHttpClient()
|
val httpClient = HttpClient.newHttpClient()
|
||||||
@ -244,14 +260,9 @@ object Auth {
|
|||||||
ctx.result(atResponse.accessToken).contentType(ContentType.TEXT_PLAIN)
|
ctx.result(atResponse.accessToken).contentType(ContentType.TEXT_PLAIN)
|
||||||
} else {
|
} else {
|
||||||
//at is still valid
|
//at is still valid
|
||||||
if (expiresAt.isAfter(now)) {
|
//we have exceeded the refresh time, so we shall ask the user to login again
|
||||||
logger.warn("Still valid, the token for ${authUser.userName}, will expire at $expiresAt")
|
logger.warn("We can't refresh the token for ${authUser.userName}, as refresh-time [$rtExpiresAt] is expired")
|
||||||
ctx.result(foundOldAt.accessToken).contentType(ContentType.TEXT_PLAIN)
|
throw UnauthorizedResponse()
|
||||||
} 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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import io.ebean.annotation.*
|
|||||||
import io.ebean.annotation.Index
|
import io.ebean.annotation.Index
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.util.UUID
|
import java.util.*
|
||||||
import javax.persistence.*
|
import javax.persistence.*
|
||||||
|
|
||||||
data class Comments(val text: String = "", val by: String = "", val at: LocalDateTime = LocalDateTime.now())
|
data class Comments(val text: String = "", val by: String = "", val at: LocalDateTime = LocalDateTime.now())
|
||||||
@ -257,10 +257,12 @@ enum class AddressType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class ContactPerson(
|
data class ContactPerson(
|
||||||
val id : String = UUID.randomUUID().toString(),
|
val id: String = UUID.randomUUID().toString(),
|
||||||
val name: String = "", val email: String = "", val mobile: String = "")
|
val name: String = "", val email: String = "", val mobile: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
data class Address(
|
data class Address(
|
||||||
val id : String = UUID.randomUUID().toString(),
|
val id: String = UUID.randomUUID().toString(),
|
||||||
val type: AddressType = AddressType.BILLING,
|
val type: AddressType = AddressType.BILLING,
|
||||||
val address: String = "",
|
val address: String = "",
|
||||||
val pincode: String = ""
|
val pincode: String = ""
|
||||||
@ -683,4 +685,19 @@ open class Plant : BaseModel() {
|
|||||||
|
|
||||||
@DbJsonB
|
@DbJsonB
|
||||||
var prefixes: MutableMap<String, String>? = mutableMapOf()
|
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