mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-16 16:20:04 +00:00
Implemented field-level encryption for the database
This commit is contained in:
@@ -2,6 +2,9 @@
|
|||||||
<configuration default="false" name="GameyfinApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
<configuration default="false" name="GameyfinApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||||
<option name="ACTIVE_PROFILES" value="dev" />
|
<option name="ACTIVE_PROFILES" value="dev" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH" value="BUNDLED" />
|
<option name="ALTERNATIVE_JRE_PATH" value="BUNDLED" />
|
||||||
|
<envs>
|
||||||
|
<env name="APP_KEY" value="8ODYedBBEA6qTd2Z/dZiWA==" />
|
||||||
|
</envs>
|
||||||
<module name="gameyfin.main" />
|
<module name="gameyfin.main" />
|
||||||
<option name="SHORTEN_COMMAND_LINE" value="ARGS_FILE" />
|
<option name="SHORTEN_COMMAND_LINE" value="ARGS_FILE" />
|
||||||
<option name="SPRING_BOOT_MAIN_CLASS" value="de.grimsi.gameyfin.GameyfinApplication" />
|
<option name="SPRING_BOOT_MAIN_CLASS" value="de.grimsi.gameyfin.GameyfinApplication" />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.grimsi.gameyfin.config.entities
|
package de.grimsi.gameyfin.config.entities
|
||||||
|
|
||||||
|
import de.grimsi.gameyfin.core.security.EncryptionConverter
|
||||||
import jakarta.persistence.*
|
import jakarta.persistence.*
|
||||||
import jakarta.validation.constraints.NotNull
|
import jakarta.validation.constraints.NotNull
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ class ConfigEntry(
|
|||||||
val key: String,
|
val key: String,
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Lob
|
|
||||||
@Column(name = "`value`")
|
@Column(name = "`value`")
|
||||||
|
@Convert(converter = EncryptionConverter::class)
|
||||||
var value: String
|
var value: String
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package de.grimsi.gameyfin.core.security
|
||||||
|
|
||||||
|
import jakarta.persistence.AttributeConverter
|
||||||
|
import jakarta.persistence.Converter
|
||||||
|
import java.util.*
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
@Converter
|
||||||
|
class EncryptionConverter : AttributeConverter<String, String> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ALGORITHM = "AES"
|
||||||
|
private val SECRET_KEY: SecretKeySpec
|
||||||
|
|
||||||
|
init {
|
||||||
|
val base64Key = System.getenv("APP_KEY")
|
||||||
|
?: throw IllegalStateException("APP_KEY environment variable is not set or empty")
|
||||||
|
|
||||||
|
val decodedKey = Base64.getDecoder().decode(base64Key)
|
||||||
|
|
||||||
|
// Ensure the key length is valid for AES (128, 192, or 256 bits)
|
||||||
|
if (decodedKey.size !in listOf(16, 24, 32)) {
|
||||||
|
throw IllegalArgumentException("Invalid AES key length. Key must be 128, 192, or 256 bits.")
|
||||||
|
}
|
||||||
|
|
||||||
|
SECRET_KEY = SecretKeySpec(decodedKey, ALGORITHM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun convertToDatabaseColumn(attribute: String?): String? {
|
||||||
|
return attribute?.let {
|
||||||
|
try {
|
||||||
|
val cipher = Cipher.getInstance(ALGORITHM).apply {
|
||||||
|
init(Cipher.ENCRYPT_MODE, SECRET_KEY)
|
||||||
|
}
|
||||||
|
val encryptedBytes = cipher.doFinal(it.toByteArray())
|
||||||
|
Base64.getEncoder().encodeToString(encryptedBytes)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException("Error during encryption", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun convertToEntityAttribute(dbData: String?): String? {
|
||||||
|
return dbData?.let {
|
||||||
|
try {
|
||||||
|
val cipher = Cipher.getInstance(ALGORITHM).apply {
|
||||||
|
init(Cipher.DECRYPT_MODE, SECRET_KEY)
|
||||||
|
}
|
||||||
|
val decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(it))
|
||||||
|
String(decryptedBytes)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw RuntimeException("Error during decryption", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
package de.grimsi.gameyfin.users.entities
|
package de.grimsi.gameyfin.users.entities
|
||||||
|
|
||||||
import jakarta.persistence.Entity
|
import de.grimsi.gameyfin.core.security.EncryptionConverter
|
||||||
import jakarta.persistence.FetchType
|
import jakarta.persistence.*
|
||||||
import jakarta.persistence.Id
|
|
||||||
import jakarta.persistence.OneToOne
|
|
||||||
import org.hibernate.annotations.CreationTimestamp
|
import org.hibernate.annotations.CreationTimestamp
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
class PasswordResetToken(
|
class PasswordResetToken(
|
||||||
@Id
|
@Id
|
||||||
|
@Convert(converter = EncryptionConverter::class)
|
||||||
val token: String,
|
val token: String,
|
||||||
|
|
||||||
@OneToOne(targetEntity = User::class, fetch = FetchType.EAGER)
|
@OneToOne(targetEntity = User::class, fetch = FetchType.EAGER)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package de.grimsi.gameyfin.users.entities
|
package de.grimsi.gameyfin.users.entities
|
||||||
|
|
||||||
|
import de.grimsi.gameyfin.core.security.EncryptionConverter
|
||||||
import jakarta.annotation.Nullable
|
import jakarta.annotation.Nullable
|
||||||
import jakarta.persistence.*
|
import jakarta.persistence.*
|
||||||
import jakarta.validation.constraints.NotNull
|
import jakarta.validation.constraints.NotNull
|
||||||
@@ -23,6 +24,7 @@ class User(
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Column(unique = true)
|
@Column(unique = true)
|
||||||
|
@Convert(converter = EncryptionConverter::class)
|
||||||
var email: String,
|
var email: String,
|
||||||
|
|
||||||
var email_confirmed: Boolean = false,
|
var email_confirmed: Boolean = false,
|
||||||
|
|||||||
Reference in New Issue
Block a user