mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-16 10:22:58 +08:00
chore: improve database secret
This commit is contained in:
@@ -1,29 +0,0 @@
|
|||||||
package app.termora
|
|
||||||
|
|
||||||
import org.apache.commons.codec.digest.DigestUtils
|
|
||||||
import org.apache.commons.lang3.StringUtils
|
|
||||||
import org.apache.commons.lang3.SystemUtils
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用户需要保证自己的电脑是可信环境
|
|
||||||
*/
|
|
||||||
internal class LocalSecret private constructor() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun getInstance(): LocalSecret {
|
|
||||||
return ApplicationScope.forApplicationScope()
|
|
||||||
.getOrCreate(LocalSecret::class) { LocalSecret() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 一个 16 长度的密码
|
|
||||||
*/
|
|
||||||
val password: String = StringUtils.substring(DigestUtils.sha256Hex(SystemUtils.USER_NAME), 0, 16)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 一个 12 长度的盐
|
|
||||||
*/
|
|
||||||
val salt: String = StringUtils.substring(password, 0, 16)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -16,12 +16,13 @@ import org.jdesktop.swingx.JXHyperlink
|
|||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
import java.awt.Dimension
|
import java.awt.Dimension
|
||||||
import java.awt.Window
|
import java.awt.Window
|
||||||
import java.awt.event.ItemEvent
|
|
||||||
import java.awt.event.WindowAdapter
|
import java.awt.event.WindowAdapter
|
||||||
import java.awt.event.WindowEvent
|
import java.awt.event.WindowEvent
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.swing.*
|
import javax.swing.*
|
||||||
|
import javax.swing.event.ListDataEvent
|
||||||
|
import javax.swing.event.ListDataListener
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
@@ -33,6 +34,10 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
private val cancelAction = super.createCancelAction()
|
private val cancelAction = super.createCancelAction()
|
||||||
private val cancelButton = super.createJButtonForAction(cancelAction)
|
private val cancelButton = super.createJButtonForAction(cancelAction)
|
||||||
private val isLoggingIn = AtomicBoolean(false)
|
private val isLoggingIn = AtomicBoolean(false)
|
||||||
|
private val singaporeServer =
|
||||||
|
Server(I18n.getString("termora.settings.account.server-singapore"), "https://account.termora.app")
|
||||||
|
private val chinaServer =
|
||||||
|
Server(I18n.getString("termora.settings.account.server-china"), "https://account.termora.cn")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
isModal = true
|
isModal = true
|
||||||
@@ -63,16 +68,12 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
val step = 2
|
val step = 2
|
||||||
|
|
||||||
|
|
||||||
val singaporeServer =
|
|
||||||
Server(I18n.getString("termora.settings.account.server-singapore"), "https://account.termora.app")
|
|
||||||
val chinaServer = Server(I18n.getString("termora.settings.account.server-china"), "https://account.termora.cn")
|
|
||||||
|
|
||||||
if (Application.isUnknownVersion()) {
|
if (Application.isUnknownVersion()) {
|
||||||
serverComboBox.addItem(Server("Localhost", "http://127.0.0.1:8080"))
|
serverComboBox.addItem(Server("Localhost", "http://127.0.0.1:8080"))
|
||||||
}
|
}
|
||||||
|
|
||||||
serverComboBox.addItem(singaporeServer)
|
// serverComboBox.addItem(singaporeServer)
|
||||||
serverComboBox.addItem(chinaServer)
|
// serverComboBox.addItem(chinaServer)
|
||||||
|
|
||||||
val properties = DatabaseManager.getInstance().properties
|
val properties = DatabaseManager.getInstance().properties
|
||||||
val servers = (runCatching {
|
val servers = (runCatching {
|
||||||
@@ -117,7 +118,7 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
val dialog = this
|
val dialog = this
|
||||||
val newAction = object : AnAction(I18n.getString("termora.welcome.contextmenu.new")) {
|
val newAction = object : AnAction(I18n.getString("termora.welcome.contextmenu.new")) {
|
||||||
override fun actionPerformed(evt: AnActionEvent) {
|
override fun actionPerformed(evt: AnActionEvent) {
|
||||||
if (serverComboBox.selectedItem == singaporeServer || serverComboBox.selectedItem == chinaServer) {
|
if (serverComboBox.itemCount < 1 || serverComboBox.selectedItem == singaporeServer || serverComboBox.selectedItem == chinaServer) {
|
||||||
val c = NewServerDialog(dialog)
|
val c = NewServerDialog(dialog)
|
||||||
c.isVisible = true
|
c.isVisible = true
|
||||||
val server = c.server ?: return
|
val server = c.server ?: return
|
||||||
@@ -142,18 +143,35 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val newServer = JXHyperlink(newAction)
|
|
||||||
newServer.isFocusable = false
|
|
||||||
serverComboBox.addItemListener {
|
fun refreshButton() {
|
||||||
if (it.stateChange == ItemEvent.SELECTED) {
|
if (serverComboBox.selectedItem == singaporeServer || serverComboBox.selectedItem == chinaServer || serverComboBox.itemCount < 1) {
|
||||||
if (serverComboBox.selectedItem == singaporeServer || serverComboBox.selectedItem == chinaServer) {
|
newAction.name = I18n.getString("termora.welcome.contextmenu.new")
|
||||||
newAction.name = I18n.getString("termora.welcome.contextmenu.new")
|
} else {
|
||||||
} else {
|
newAction.name = I18n.getString("termora.remove")
|
||||||
newAction.name = I18n.getString("termora.remove")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val newServer = JXHyperlink(newAction)
|
||||||
|
newServer.isFocusable = false
|
||||||
|
serverComboBox.addItemListener { refreshButton() }
|
||||||
|
|
||||||
|
serverComboBox.model.addListDataListener(object : ListDataListener {
|
||||||
|
override fun intervalAdded(e: ListDataEvent?) {
|
||||||
|
refreshButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun intervalRemoved(e: ListDataEvent?) {
|
||||||
|
refreshButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun contentsChanged(e: ListDataEvent?) {
|
||||||
|
refreshButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
return FormBuilder.create().layout(layout).debug(false).padding("0dlu, $FORM_MARGIN, 0dlu, $FORM_MARGIN")
|
return FormBuilder.create().layout(layout).debug(false).padding("0dlu, $FORM_MARGIN, 0dlu, $FORM_MARGIN")
|
||||||
.add("${I18n.getString("termora.settings.account.server")}:").xy(1, rows)
|
.add("${I18n.getString("termora.settings.account.server")}:").xy(1, rows)
|
||||||
@@ -166,6 +184,7 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun createOkAction(): AbstractAction {
|
override fun createOkAction(): AbstractAction {
|
||||||
return okAction
|
return okAction
|
||||||
}
|
}
|
||||||
@@ -250,7 +269,12 @@ class LoginServerDialog(owner: Window) : DialogWrapper(owner) {
|
|||||||
override fun doOKAction() {
|
override fun doOKAction() {
|
||||||
if (isLoggingIn.get()) return
|
if (isLoggingIn.get()) return
|
||||||
|
|
||||||
val server = serverComboBox.selectedItem as? Server ?: return
|
val server = serverComboBox.selectedItem as? Server
|
||||||
|
if (server == null) {
|
||||||
|
serverComboBox.outline = FlatClientProperties.OUTLINE_ERROR
|
||||||
|
serverComboBox.requestFocusInWindow()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (usernameTextField.text.isBlank()) {
|
if (usernameTextField.text.isBlank()) {
|
||||||
usernameTextField.outline = FlatClientProperties.OUTLINE_ERROR
|
usernameTextField.outline = FlatClientProperties.OUTLINE_ERROR
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
package app.termora.database
|
package app.termora.database
|
||||||
|
|
||||||
import app.termora.LocalSecret
|
|
||||||
import app.termora.randomUUID
|
import app.termora.randomUUID
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.jetbrains.exposed.v1.core.Column
|
import org.jetbrains.exposed.v1.core.Column
|
||||||
import org.jetbrains.exposed.v1.core.Table
|
import org.jetbrains.exposed.v1.core.Table
|
||||||
import org.jetbrains.exposed.v1.crypt.Algorithms
|
import org.jetbrains.exposed.v1.crypt.Algorithms
|
||||||
|
|
||||||
object DataEntity : Table() {
|
internal object DataEntity : Table() {
|
||||||
val id: Column<String> = char("id", length = 32).clientDefault { randomUUID() }
|
val id: Column<String> = char("id", length = 32).clientDefault { randomUUID() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,8 +44,8 @@ object DataEntity : Table() {
|
|||||||
*/
|
*/
|
||||||
val data: Column<String> = encryptedText(
|
val data: Column<String> = encryptedText(
|
||||||
"data", Algorithms.AES_256_PBE_GCM(
|
"data", Algorithms.AES_256_PBE_GCM(
|
||||||
LocalSecret.getInstance().password,
|
DatabaseSecret.getInstance().password,
|
||||||
LocalSecret.getInstance().salt
|
DatabaseSecret.getInstance().salt
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ import kotlin.reflect.KProperty
|
|||||||
|
|
||||||
class DatabaseManager private constructor() : Disposable {
|
class DatabaseManager private constructor() : Disposable {
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
private const val DB_PASSWORD = "DB_PASSWORD"
|
||||||
|
private const val DB_SALT = "DB_SALT"
|
||||||
|
|
||||||
val log = LoggerFactory.getLogger(DatabaseManager::class.java)!!
|
val log = LoggerFactory.getLogger(DatabaseManager::class.java)!!
|
||||||
fun getInstance(): DatabaseManager {
|
fun getInstance(): DatabaseManager {
|
||||||
return ApplicationScope.forApplicationScope()
|
return ApplicationScope.forApplicationScope()
|
||||||
@@ -42,6 +46,15 @@ class DatabaseManager private constructor() : Disposable {
|
|||||||
val appearance by lazy { Appearance(this) }
|
val appearance by lazy { Appearance(this) }
|
||||||
val sftp by lazy { SFTP(this) }
|
val sftp by lazy { SFTP(this) }
|
||||||
|
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
internal var dbPassword = StringUtils.EMPTY
|
||||||
|
private set
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
internal var dbSalt = StringUtils.EMPTY
|
||||||
|
private set
|
||||||
|
|
||||||
private val map = Collections.synchronizedMap<String, String?>(mutableMapOf())
|
private val map = Collections.synchronizedMap<String, String?>(mutableMapOf())
|
||||||
private val accountManager get() = AccountManager.getInstance()
|
private val accountManager get() = AccountManager.getInstance()
|
||||||
|
|
||||||
@@ -59,6 +72,14 @@ class DatabaseManager private constructor() : Disposable {
|
|||||||
driver = "org.sqlite.JDBC", user = "sa"
|
driver = "org.sqlite.JDBC", user = "sa"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 没有加密的表优先创建
|
||||||
|
if (isExists.not()) {
|
||||||
|
transaction(database) { SchemaUtils.create(UnsafeSettingEntity) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取密钥信息
|
||||||
|
transaction(database) { DatabaseSecret.getInstance(database) }
|
||||||
|
|
||||||
// 设置数据库版本号,便于后续升级
|
// 设置数据库版本号,便于后续升级
|
||||||
if (isExists.not()) {
|
if (isExists.not()) {
|
||||||
transaction(database) {
|
transaction(database) {
|
||||||
|
|||||||
65
src/main/kotlin/app/termora/database/DatabaseSecret.kt
Normal file
65
src/main/kotlin/app/termora/database/DatabaseSecret.kt
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package app.termora.database
|
||||||
|
|
||||||
|
import app.termora.ApplicationScope
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils
|
||||||
|
import org.apache.commons.lang3.RandomUtils
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.Database
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.insert
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||||
|
|
||||||
|
internal class DatabaseSecret(database: Database) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val PASSWORD = "__DB_PASSWORD"
|
||||||
|
private const val SALT = "__DB_SALT"
|
||||||
|
|
||||||
|
fun getInstance(database: Database): DatabaseSecret {
|
||||||
|
return ApplicationScope.forApplicationScope()
|
||||||
|
.getOrCreate(DatabaseSecret::class) { DatabaseSecret(database) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInstance(): DatabaseSecret {
|
||||||
|
return ApplicationScope.forApplicationScope().get(DatabaseSecret::class)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
var password: String = StringUtils.EMPTY
|
||||||
|
private set
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
var salt: String = StringUtils.EMPTY
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
transaction(database) {
|
||||||
|
val unsafeSettings = UnsafeSettingEntity.selectAll()
|
||||||
|
.map { it[UnsafeSettingEntity.name] to it[UnsafeSettingEntity.value] }
|
||||||
|
.associateBy { it.first }
|
||||||
|
if (unsafeSettings.containsKey(PASSWORD)) {
|
||||||
|
password = unsafeSettings.getValue(PASSWORD).second
|
||||||
|
} else {
|
||||||
|
password =
|
||||||
|
StringUtils.substring(DigestUtils.sha256Hex(RandomUtils.secureStrong().randomBytes(128)), 0, 16)
|
||||||
|
UnsafeSettingEntity.insert {
|
||||||
|
it[UnsafeSettingEntity.name] = PASSWORD
|
||||||
|
it[UnsafeSettingEntity.value] = password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unsafeSettings.containsKey(SALT)) {
|
||||||
|
salt = unsafeSettings.getValue(SALT).second
|
||||||
|
} else {
|
||||||
|
salt =
|
||||||
|
StringUtils.substring(DigestUtils.sha256Hex(RandomUtils.secureStrong().randomBytes(128)), 0, 12)
|
||||||
|
UnsafeSettingEntity.insert {
|
||||||
|
it[UnsafeSettingEntity.name] = SALT
|
||||||
|
it[UnsafeSettingEntity.value] = salt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,19 +1,18 @@
|
|||||||
package app.termora.database
|
package app.termora.database
|
||||||
|
|
||||||
import app.termora.LocalSecret
|
|
||||||
import app.termora.randomUUID
|
import app.termora.randomUUID
|
||||||
import org.apache.commons.lang3.StringUtils
|
import org.apache.commons.lang3.StringUtils
|
||||||
import org.jetbrains.exposed.v1.core.Column
|
import org.jetbrains.exposed.v1.core.Column
|
||||||
import org.jetbrains.exposed.v1.core.Table
|
import org.jetbrains.exposed.v1.core.Table
|
||||||
import org.jetbrains.exposed.v1.crypt.Algorithms
|
import org.jetbrains.exposed.v1.crypt.Algorithms
|
||||||
|
|
||||||
object SettingEntity : Table() {
|
internal object SettingEntity : Table() {
|
||||||
val id: Column<String> = char("id", length = 32).clientDefault { randomUUID() }
|
val id: Column<String> = char("id", length = 32).clientDefault { randomUUID() }
|
||||||
val name: Column<String> = varchar("name", length = 128).index()
|
val name: Column<String> = varchar("name", length = 128).index()
|
||||||
val value: Column<String> = encryptedText(
|
val value: Column<String> = encryptedText(
|
||||||
"value", Algorithms.AES_256_PBE_GCM(
|
"value", Algorithms.AES_256_PBE_GCM(
|
||||||
LocalSecret.getInstance().password,
|
DatabaseSecret.getInstance().password,
|
||||||
LocalSecret.getInstance().salt
|
DatabaseSecret.getInstance().salt
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
23
src/main/kotlin/app/termora/database/UnsafeSettingEntity.kt
Normal file
23
src/main/kotlin/app/termora/database/UnsafeSettingEntity.kt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package app.termora.database
|
||||||
|
|
||||||
|
import app.termora.randomUUID
|
||||||
|
import org.apache.commons.lang3.StringUtils
|
||||||
|
import org.jetbrains.exposed.v1.core.Column
|
||||||
|
import org.jetbrains.exposed.v1.core.Table
|
||||||
|
|
||||||
|
internal object UnsafeSettingEntity : Table() {
|
||||||
|
val id: Column<String> = char("id", length = 32).clientDefault { randomUUID() }
|
||||||
|
val name: Column<String> = varchar("name", length = 128).index()
|
||||||
|
val value: Column<String> = text("value")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 备用字段1-3
|
||||||
|
*/
|
||||||
|
val extra1: Column<String> = text("extra1").clientDefault { StringUtils.EMPTY }
|
||||||
|
val extra2: Column<String> = text("extra2").clientDefault { StringUtils.EMPTY }
|
||||||
|
val extra3: Column<String> = text("extra3").clientDefault { StringUtils.EMPTY }
|
||||||
|
|
||||||
|
override val primaryKey: PrimaryKey get() = PrimaryKey(id)
|
||||||
|
override val tableName: String
|
||||||
|
get() = "tb_unsafe_setting"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user