MigrationAuthenticationService.kt

package de.pflugradts.passbird.application.process.migration
import de.pflugradts.kotlinextensions.TryResult
import de.pflugradts.kotlinextensions.TryResult.Companion.failure
import de.pflugradts.kotlinextensions.TryResult.Companion.success
import de.pflugradts.passbird.application.KeyStoreAdapterPort
import de.pflugradts.passbird.application.SecureInputUnavailableException
import de.pflugradts.passbird.application.UserInterfaceAdapterPort
import de.pflugradts.passbird.application.configuration.ReadableConfiguration
import de.pflugradts.passbird.application.toDirectory
import de.pflugradts.passbird.application.toFileName
import de.pflugradts.passbird.application.util.SystemOperation
import de.pflugradts.passbird.domain.model.shell.PlainShell
import de.pflugradts.passbird.domain.model.shell.PlainShell.Companion.plainShellOf
import de.pflugradts.passbird.domain.model.shell.Shell
import de.pflugradts.passbird.domain.model.shell.Shell.Companion.shellOf
import de.pflugradts.passbird.domain.model.transfer.Output.Companion.outputOf
private const val DEFAULT_PROMPT = "Enter key: "
class MigrationCredentials private constructor(
    private val key: Shell,
    private val password: PlainShell,
) {
    fun keyCopy() = key.copy()
    fun passwordCopy() = plainShellOf(password.toCharArray())
    fun invalidate() {
        key.scramble()
        password.scramble()
    }
    companion object {
        fun migrationCredentialsOf(key: Shell, password: PlainShell) = MigrationCredentials(key, password)
    }
}
class MigrationAuthenticationService constructor(
    private val configuration: ReadableConfiguration,
    private val keyStoreAdapterPort: KeyStoreAdapterPort,
    private val userInterfaceAdapterPort: UserInterfaceAdapterPort,
    private val systemOperation: SystemOperation,
) {
    private var migrationCredentials: MigrationCredentials? = null
    fun authenticate(maxAttempts: Int = 3, prompt: String = DEFAULT_PROMPT): TryResult<MigrationCredentials> {
        migrationCredentials?.let { return success(it) }
        var result = authenticate(prompt)
        repeat(maxAttempts - 1) {
            if (result.failure) {
                result = authenticate(prompt)
            }
        }
        return result
    }
    fun invalidate() {
        migrationCredentials?.invalidate()
        migrationCredentials = null
    }
    private fun authenticate(prompt: String): TryResult<MigrationCredentials> {
        migrationCredentials?.let { return success(it) }
        return try {
            val input = userInterfaceAdapterPort.receiveSecurely(outputOf(shellOf(prompt)))
            val loadPassword = input.shell.copy().toPlainShell()
            val password = input.toPlainShell()
            keyStoreAdapterPort.loadKey(loadPassword, keyStorePath())
                .map { key ->
                    MigrationCredentials.migrationCredentialsOf(key, password).also { migrationCredentials = it }
                }
                .onFailure { password.scramble() }
        } catch (ex: SecureInputUnavailableException) {
            failure(ex)
        }
    }
    private fun keyStorePath() = systemOperation.resolvePath(
        configuration.adapter.keyStore.location.toDirectory(),
        ReadableConfiguration.KEYSTORE_FILENAME.toFileName(),
    )
}