Dev actions controller/router + user registration
This commit is contained in:
+53
-5
@@ -1,5 +1,6 @@
|
||||
package dao
|
||||
|
||||
import dao.UsersDAO.{UnexpectedError, UsernameTaken}
|
||||
import de.mkammerer.argon2.Argon2Factory
|
||||
import execution.CryptoExecutionContext
|
||||
import jakarta.inject.Inject
|
||||
@@ -9,23 +10,70 @@ import slick.jdbc.PostgresProfile.api.*
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import models.User
|
||||
import org.postgresql.util.{PSQLException, PSQLState}
|
||||
import schemas.Users
|
||||
|
||||
class UsersDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider, private val cryptoEC: CryptoExecutionContext)(implicit val ec: ExecutionContext)
|
||||
import java.nio.charset.StandardCharsets.UTF_8
|
||||
import scala.util.Random
|
||||
|
||||
class UsersDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider, private val cryptoEC: CryptoExecutionContext)
|
||||
(using ec: ExecutionContext)
|
||||
extends HasDatabaseConfigProvider[PostgresProfile] {
|
||||
private val argon2 = Argon2Factory.create()
|
||||
|
||||
private object ArgonParameters {
|
||||
val iterations = 3
|
||||
val parallelism = 1
|
||||
val memory = 65536
|
||||
}
|
||||
|
||||
private final val dummyDigest = "$argon2i$v=19$m=65536,t=3,p=1$EB9QibLwzqkO7ne2bJoU/Q$ADkdyweXKylJORvWXbBVekURYaazkW/OplTq2wdN1So"
|
||||
|
||||
def authenticate(username: String, password: String): Future[Option[User]] = {
|
||||
val query = Users.filter(_.username === username).take(1).result.headOption
|
||||
|
||||
val userFuture = db.run(query)
|
||||
|
||||
userFuture.map { userOption =>
|
||||
for {
|
||||
user <- userOption
|
||||
digest <- user.passwordDigest
|
||||
if argon2.verify(digest, password.getBytes("UTF-8"))
|
||||
} yield user
|
||||
}(using cryptoEC)
|
||||
} yield (user, digest)
|
||||
}.map {
|
||||
case Some((user, digest)) => (Some(user), digest)
|
||||
case None => (None, dummyDigest)
|
||||
}.flatMap { (userOpt, digest) =>
|
||||
for {
|
||||
validHash <- Future(argon2.verify(digest, password.getBytes(UTF_8)))(using cryptoEC)
|
||||
opt = userOpt.flatMap(u => if validHash then Some(u) else None)
|
||||
} yield opt
|
||||
}
|
||||
}
|
||||
|
||||
def create(username: String, password: String): Future[Either[UsersDAO.RegistrationError, User]] = {
|
||||
Future(argonHash(password))(using cryptoEC)
|
||||
.flatMap{ passwordDigest =>
|
||||
val gid = Random.alphanumeric.take(12).mkString
|
||||
val user = User(
|
||||
id = None,
|
||||
gid = gid,
|
||||
username = username,
|
||||
passwordDigest = Some(passwordDigest)
|
||||
)
|
||||
|
||||
db.run((Users returning Users) += user).map(Right(_))
|
||||
}.recover {
|
||||
case ex: PSQLException if ex.getSQLState == PSQLState.UNIQUE_VIOLATION.getState =>
|
||||
Left(UsernameTaken)
|
||||
case ex: Throwable =>
|
||||
Left(UnexpectedError(ex))
|
||||
}
|
||||
}
|
||||
|
||||
private def argonHash(data: String) = argon2.hash(ArgonParameters.iterations, ArgonParameters.memory, ArgonParameters.parallelism, data.getBytes(UTF_8))
|
||||
}
|
||||
|
||||
object UsersDAO {
|
||||
sealed trait RegistrationError
|
||||
case object UsernameTaken extends RegistrationError
|
||||
case class UnexpectedError(cause: Throwable) extends RegistrationError
|
||||
}
|
||||
Reference in New Issue
Block a user