SimpleNotes/domain/src/UserService.kt

81 lines
3.0 KiB
Kotlin

package be.simplenotes.domain
import arrow.core.Either
import arrow.core.raise.either
import arrow.core.raise.ensure
import arrow.core.raise.ensureNotNull
import be.simplenotes.domain.security.PasswordHash
import be.simplenotes.domain.security.SimpleJwt
import be.simplenotes.domain.validation.UserValidations
import be.simplenotes.persistence.repositories.UserRepository
import be.simplenotes.search.NoteSearcher
import be.simplenotes.types.LoggedInUser
import be.simplenotes.types.PersistedUser
import io.konform.validation.ValidationErrors
import jakarta.inject.Singleton
import kotlinx.serialization.Serializable
interface UserService {
fun register(form: RegisterForm): Either<RegisterError, PersistedUser>
fun login(form: LoginForm): Either<LoginError, Token>
fun delete(form: DeleteForm): Either<DeleteError, Unit>
}
@Singleton
internal class UserServiceImpl(
private val userRepository: UserRepository,
private val passwordHash: PasswordHash,
private val jwt: SimpleJwt<LoggedInUser>,
private val searcher: NoteSearcher,
) : UserService {
override fun register(form: RegisterForm) = either {
val user = UserValidations.validateRegister(form).bind()
ensure(!userRepository.exists(user.username)) { RegisterError.UserExists }
ensureNotNull(userRepository.create(user.copy(password = passwordHash.crypt(user.password)))) {
RegisterError.UserExists
}
}
override fun login(form: LoginForm) = either {
val user = UserValidations.validateLogin(form).bind()
val persistedUser = ensureNotNull(userRepository.find(user.username)) { LoginError.Unregistered }
ensure(passwordHash.verify(form.password!!, persistedUser.password)) { LoginError.WrongPassword }
jwt.sign(LoggedInUser(persistedUser))
}
override fun delete(form: DeleteForm) = either {
val user = UserValidations.validateDelete(form).bind()
val persistedUser = ensureNotNull(userRepository.find(user.username)) { DeleteError.Unregistered }
ensure(passwordHash.verify(user.password, persistedUser.password)) { DeleteError.WrongPassword }
ensure(userRepository.delete(persistedUser.id)) { DeleteError.Unregistered }
searcher.dropIndex(persistedUser.id)
}
}
sealed class DeleteError {
object Unregistered : DeleteError()
object WrongPassword : DeleteError()
class InvalidForm(val validationErrors: ValidationErrors) : DeleteError()
}
data class DeleteForm(val username: String?, val password: String?, val checked: Boolean)
sealed class LoginError {
object Unregistered : LoginError()
object WrongPassword : LoginError()
class InvalidLoginForm(val validationErrors: ValidationErrors) : LoginError()
}
typealias Token = String
sealed class RegisterError {
object UserExists : RegisterError()
class InvalidRegisterForm(val validationErrors: ValidationErrors) : RegisterError()
}
typealias RegisterForm = LoginForm
@Serializable
data class LoginForm(val username: String?, val password: String?)