PassbirdSetup.kt

package de.pflugradts.passbird.application.boot.setup
import de.pflugradts.passbird.application.Directory
import de.pflugradts.passbird.application.KeyStoreAdapterPort
import de.pflugradts.passbird.application.SecureInputUnavailableException
import de.pflugradts.passbird.application.UserInterfaceAdapterPort
import de.pflugradts.passbird.application.boot.Bootable
import de.pflugradts.passbird.application.configuration.ConfigurationSync
import de.pflugradts.passbird.application.configuration.ReadableConfiguration
import de.pflugradts.passbird.application.util.SystemOperation
import de.pflugradts.passbird.domain.model.shell.PlainShell
import de.pflugradts.passbird.domain.model.shell.Shell.Companion.shellOf
import de.pflugradts.passbird.domain.model.transfer.Input
import de.pflugradts.passbird.domain.model.transfer.Output.Companion.outputOf
import java.nio.file.Paths
class PassbirdSetup constructor(
    private val setupGuide: SetupGuide,
    private val configurationSync: ConfigurationSync,
    private val configurationDirectory: Directory,
    private val configuration: ReadableConfiguration,
    private val keyStoreAdapterPort: KeyStoreAdapterPort,
    private val userInterfaceAdapterPort: UserInterfaceAdapterPort,
    private val systemOperation: SystemOperation,
) : Bootable {
    override fun boot() {
        setupGuide.sendWelcome()
        try {
            if (configuration.template) {
                setupGuide.sendConfigTemplateRouteInformation()
                if (continueRoute()) {
                    configTemplateRoute()
                }
            } else {
                setupGuide.sendConfigKeyStoreRouteInformation(configuration.adapter.keyStore.location)
                if (continueRoute()) {
                    configKeyStoreRoute()
                }
            }
        } catch (_: SecureInputUnavailableException) {
        }
        setupGuide.sendGoodbye()
        systemOperation.exit()
    }
    private fun continueRoute() = userInterfaceAdapterPort.receiveConfirmation(outputOf(shellOf("Your input: ")))
    private fun configTemplateRoute() {
        setupGuide.sendInputPath("configuration")
        val directory = receiveValidDirectory()
        if (createConfiguration(directory).failure) {
            return
        }
        setupGuide.sendCreateKeyStoreInformation()
        createKeyStore(directory, receiveMasterPassword())
        setupGuide.sendCreateKeyStoreSucceeded()
        setupGuide.sendRestart()
    }
    private fun configKeyStoreRoute() {
        setupGuide.sendInputPath("keystore")
        setupGuide.sendCreateKeyStoreInformation()
        val directory = receiveValidDirectory()
        createKeyStore(directory, receiveMasterPassword())
        if (configurationSync.syncKeyStoreLocation(configurationDirectory, directory).failure) {
            return
        }
        setupGuide.sendCreateKeyStoreSucceeded()
        setupGuide.sendRestart()
    }
    private fun createConfiguration(directory: Directory) = configurationSync.sync(directory)
    private fun receiveMasterPassword(): PlainShell {
        var input: Input? = null
        var inputRepeated: Input? = null
        while (true) {
            try {
                input = userInterfaceAdapterPort.receiveSecurely(outputOf(shellOf("first input: ")))
                inputRepeated = userInterfaceAdapterPort.receiveSecurely(outputOf(shellOf("second input: ")))
            } catch (ex: SecureInputUnavailableException) {
                input?.invalidate()
                inputRepeated?.invalidate()
                throw ex
            }
            if (input.isNotEmpty && input == inputRepeated) {
                val password = input.toPlainShell()
                inputRepeated.invalidate()
                userInterfaceAdapterPort.sendLineBreak()
                return password
            }
            input.invalidate()
            inputRepeated.invalidate()
            setupGuide.sendNonMatchingInputs()
        }
    }
    private fun createKeyStore(directory: Directory, password: PlainShell) {
        keyStoreAdapterPort.storeKey(
            password,
            Paths.get(directory.value).resolve(ReadableConfiguration.KEYSTORE_FILENAME),
        )
    }
    private fun receiveValidDirectory(): Directory {
        var directory = Directory(userInterfaceAdapterPort.receive(outputOf(shellOf("your input: "))).shell.asString())
        while (!isValidDirectory(directory)) {
            directory = Directory(userInterfaceAdapterPort.receive(outputOf(shellOf("your input: "))).shell.asString())
        }
        return directory
    }
    private fun isValidDirectory(directory: Directory) = systemOperation.exists(directory) && systemOperation.isDirectory(directory)
}