Created HashPassword bson serializer

This commit is contained in:
Isaac Levy 2017-09-28 14:28:53 -04:00
parent 88e271f9da
commit 14f5a341a3
3 changed files with 21 additions and 17 deletions

View file

@ -1,7 +1,7 @@
package lila.user
import com.roundeights.hasher.Implicits._
import reactivemongo.bson.Macros
import reactivemongo.bson._
import lila.common.EmailAddress
import lila.db.dsl._
@ -15,17 +15,17 @@ final class Authenticator(
) {
import Authenticator._
def passEnc(pass: String): HashedPass = passHasher.hash(pass)
def passEnc(pass: String): HashedPassword = passHasher.hash(pass)
def compare(auth: AuthData, p: String): Boolean = {
val newP = auth.salt.fold(p) { s =>
val salted = s"$p{$s}" // BC
(~auth.sha512).fold(salted.sha512, salted.sha1).hex
}
auth.bcryptHash match {
auth.bpass match {
// Deprecated fallback. Log & fail after DB migration.
case None => auth.password ?? { p => onShaLogin(); p == newP }
case Some(bHash) => passHasher.check(bHash, newP)
case Some(bpass) => passHasher.check(bpass, newP)
}
}
@ -80,15 +80,19 @@ object Authenticator {
case class AuthData(
_id: User.ID,
bpass: Option[Array[Byte]] = None,
bpass: Option[HashedPassword] = None,
password: Option[String] = None,
salt: Option[String] = None,
sha512: Option[Boolean] = None
) {
def bcryptHash = bpass map HashedPass
def hashToken: String = bpass.fold(~password) { _.sha512.hex }
def hashToken: String = bpass.fold(~password) { _.bytes.sha512.hex }
}
implicit val HashedPasswordBsonHandler = new BSONHandler[BSONBinary, HashedPassword] {
def read(b: BSONBinary) = HashedPassword(b.byteArray)
def write(hash: HashedPassword) = BSONBinary(hash.bytes, Subtype.GenericBinarySubtype)
}
implicit val AuthDataBSONHandler = Macros.handler[AuthData]
}

View file

@ -42,8 +42,8 @@ private[user] object Aes {
def iv(bytes: Array[Byte]): InitVector = new IvParameterSpec(bytes)
}
case class HashedPass(bytes: Array[Byte]) extends AnyVal {
def parse = bytes.size == 39 option bytes.splitAt(16)
case class HashedPassword(bytes: Array[Byte]) extends AnyVal {
def parse = bytes.length == 39 option bytes.splitAt(16)
}
final class PasswordHasher(
@ -57,14 +57,14 @@ final class PasswordHasher(
private def bHash(salt: Array[Byte], p: String) =
hashTimer(BCrypt.hashpwRaw(p.sha512, 'a', logRounds, salt))
def hash(p: String): HashedPass = {
def hash(p: String): HashedPassword = {
val salt = new Array[Byte](16)
new SecureRandom().nextBytes(salt)
HashedPass(salt ++ aes.encrypt(Aes.iv(salt), bHash(salt, p)))
HashedPassword(salt ++ aes.encrypt(Aes.iv(salt), bHash(salt, p)))
}
def check(bytes: HashedPass, p: String): Boolean = bytes.parse ?? {
def check(bytes: HashedPassword, p: String): Boolean = bytes.parse ?? {
case (salt, encHash) =>
val hash = aes.decrypt(Aes.iv(salt), encHash)
BCrypt.bytesEqualSecure(hash, bHash(salt, p))

View file

@ -226,7 +226,7 @@ object UserRepo {
def create(
username: String,
passwordHash: HashedPass,
passwordHash: HashedPassword,
email: EmailAddress,
blind: Boolean,
mobileApiVersion: Option[ApiVersion],
@ -301,7 +301,7 @@ object UserRepo {
}
)
import Authenticator.AuthDataBSONHandler
import Authenticator._
def getPasswordHash(id: User.ID): Fu[Option[String]] =
coll.byId[Authenticator.AuthData](id) map {
_.map { _.hashToken }
@ -400,7 +400,7 @@ object UserRepo {
private def newUser(
username: String,
passwordHash: HashedPass,
passwordHash: HashedPassword,
email: EmailAddress,
blind: Boolean,
mobileApiVersion: Option[ApiVersion],
@ -416,7 +416,7 @@ object UserRepo {
F.username -> username,
F.email -> email,
F.mustConfirmEmail -> mustConfirmEmail.option(DateTime.now),
F.bpass -> passwordHash.bytes,
F.bpass -> passwordHash,
F.perfs -> $empty,
F.count -> Count.default,
F.enabled -> true,