2023-11-12 09:59:14 +05:30

190 lines
6.9 KiB
Kotlin

package com.restapi.domain
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.restapi.config.AppConfig.Companion.appConfig
import com.restapi.config.AuthUser
import io.ebean.Database
import io.ebean.DatabaseFactory
import io.ebean.config.CurrentTenantProvider
import io.ebean.config.CurrentUserProvider
import io.ebean.config.DatabaseConfig
import io.ebean.config.TenantMode
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.util.io.pem.PemReader
import org.jose4j.jwk.JsonWebKey
import org.jose4j.jwk.PublicJsonWebKey
import org.jose4j.jwk.RsaJsonWebKey
import org.jose4j.jwk.RsaJwkGenerator
import org.jose4j.jws.AlgorithmIdentifiers
import org.jose4j.jws.JsonWebSignature
import org.slf4j.LoggerFactory
import redis.clients.jedis.JedisPooled
import java.io.StringReader
import java.io.StringWriter
import java.security.KeyFactory
import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import java.util.*
import kotlin.jvm.optionals.getOrDefault
object Session {
private val KEY_ID = "${appConfig.appName()}-KEY"
private val logger = LoggerFactory.getLogger("session")
private val currentUser = object : ThreadLocal<AuthUser>() {
override fun initialValue(): AuthUser {
return AuthUser("", "", emptyList<String>())
}
}
fun setAuthorizedUser(a: AuthUser) = currentUser.set(a)
//if not passed in ENV, then we shall generate and print
private fun makeRsaJsonWebKey(publicKey: String, privateKey: String): RsaJsonWebKey {
logger.warn("making KeyPair from Config \n$publicKey\n\n$privateKey")
val newPublicKey = readPublicKey(publicKey)
val newPrivateKey = readPrivateKey(privateKey)
val rsa = PublicJsonWebKey.Factory.newPublicJwk(newPublicKey) as RsaJsonWebKey
rsa.privateKey = newPrivateKey
rsa.keyId = KEY_ID
return rsa
}
private val keyFactory = KeyFactory.getInstance("RSA")
private fun readPublicKey(file: String): RSAPublicKey {
StringReader(file).use { keyReader ->
PemReader(keyReader).use { pemReader ->
val pemObject = pemReader.readPemObject()
val content = pemObject.content
val pubKeySpec = X509EncodedKeySpec(content)
return keyFactory.generatePublic(pubKeySpec) as RSAPublicKey
}
}
}
private fun readPrivateKey(file: String): RSAPrivateKey {
StringReader(file).use { keyReader ->
PemReader(keyReader).use { pemReader ->
val pemObject = pemReader.readPemObject()
val content = pemObject.content
val privKeySpec = PKCS8EncodedKeySpec(content)
return keyFactory.generatePrivate(privKeySpec) as RSAPrivateKey
}
}
}
private val keypair: RsaJsonWebKey by lazy {
if (appConfig.privateKey().isPresent && appConfig.publicKey().isPresent) {
makeRsaJsonWebKey(
appConfig.publicKey().get(),
appConfig.privateKey().get()
)
} else {
RsaJwkGenerator.generateJwk(2048).apply {
keyId = KEY_ID
logger.warn("Save this PrivateKey/PublicKey and pass it in the Config File from next time")
StringWriter().use { s ->
JcaPEMWriter(s).use { p ->
p.writeObject(privateKey)
}
logger.warn("Private Key\n${s.toString()}")
}
StringWriter().use { s ->
JcaPEMWriter(s).use { p ->
p.writeObject(publicKey)
}
logger.warn("Public Key\n${s.toString()}")
}
}
}
}
fun signPayload(payload: String): String {
// Create a new JsonWebSignature
val jws = JsonWebSignature()
// Set the payload, or signed content, on the JWS object
jws.setPayload(payload)
// Set the signature algorithm on the JWS that will integrity protect the payload
jws.algorithmHeaderValue = AlgorithmIdentifiers.RSA_PSS_USING_SHA512
// Set the signing key on the JWS
// Note that your application will need to determine where/how to get the key
// and here we just use an example from the JWS spec
jws.setKey(keypair.privateKey)
// Sign the JWS and produce the compact serialization or complete JWS representation, which
// is a string consisting of three dot ('.') separated base64url-encoded
// parts in the form Header.Payload.Signature
return jws.getCompactSerialization()
}
private val sc = DatabaseConfig().apply {
loadFromProperties(Properties().apply {
setProperty("datasource.db.username", appConfig.dbUser())
setProperty("datasource.db.password", appConfig.dbPass())
setProperty("datasource.db.url", appConfig.dbUrl())
setProperty("ebean.migration.run", appConfig.dbRunMigration().toString())
})
tenantMode = TenantMode.PARTITION
currentTenantProvider = CurrentTenantProvider { currentUser.get().tenant }
currentUserProvider = CurrentUserProvider { currentUser.get().userName }
}
val database: Database = DatabaseFactory.create(sc)
val objectMapper = jacksonObjectMapper().apply {
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
findAndRegisterModules()
}
fun currentUser() = currentUser.get().userName
fun currentTenant() = currentUser.get().tenant
fun currentRoles() = currentUser.get().roles
fun jwk() = keypair.toParams(JsonWebKey.OutputControlLevel.PUBLIC_ONLY)
fun Database.findByEntityAndId(entity: String, id: String): DataModel {
return find(DataModel::class.java)
.where()
.eq("uniqueIdentifier", id)
.eq("entityName", entity)
.findOne() ?: throw DataNotFoundException
}
private fun seqName(entity: String) = "sequence_$entity"
fun creatSeq(entity: String): Int {
return database.sqlUpdate("CREATE SEQUENCE IF NOT EXISTS ${seqName(entity)} START 1;").execute()
}
fun nextUniqId(entity: String): String {
val s = database
.sqlQuery("SELECT nextval('${seqName(entity)}');")
.findOne()?.getLong("nextval") ?: throw DataNotFoundException
return String.format("%s-%s", entity, "$s".padStart(10, '0'))
}
val redis = JedisPooled(appConfig.redisUri().getOrDefault("redis://localhost:6739/0"))
}
object DataNotFoundException : Exception() {
private fun readResolve(): Any = DataNotFoundException
}