back to synchronous, unthrottled password hashing
This commit is contained in:
parent
0cc0b02e9f
commit
6162060ea4
|
@ -2,7 +2,6 @@ package lila.user
|
|||
|
||||
import play.api.data._
|
||||
import play.api.data.Forms._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
object DataForm {
|
||||
|
||||
|
@ -39,11 +38,7 @@ object DataForm {
|
|||
|
||||
def passwd(u: User) = UserRepo loginCandidate u map { candidate =>
|
||||
Form(mapping(
|
||||
"oldPasswd" -> nonEmptyText.verifying("incorrectPassword", { pass =>
|
||||
// ugly shit, but Play forms are synchronous.
|
||||
// make sure the form is ratelimited by user upstream
|
||||
candidate.check(pass) await 2.seconds
|
||||
}),
|
||||
"oldPasswd" -> nonEmptyText.verifying("incorrectPassword", candidate.check),
|
||||
"newPasswd1" -> nonEmptyText(minLength = 2),
|
||||
"newPasswd2" -> nonEmptyText(minLength = 2)
|
||||
)(Passwd.apply)(Passwd.unapply).verifying("the new passwords don't match", _.samePasswords))
|
||||
|
|
|
@ -91,18 +91,12 @@ final class Env(
|
|||
)
|
||||
|
||||
lazy val passwordAuth = new Authenticator(
|
||||
new AsyncPasswordHasher(
|
||||
new PasswordHasher(
|
||||
secret = PasswordBPassSecret,
|
||||
logRounds = 10,
|
||||
hashTimer = lila.mon.measure(_.user.auth.hashTime)
|
||||
),
|
||||
new lila.hub.SyncMultiSequencer(
|
||||
system = system,
|
||||
parallelismFactor = PasswordParallelism
|
||||
)
|
||||
new PasswordHasher(
|
||||
secret = PasswordBPassSecret,
|
||||
logRounds = 10,
|
||||
hashTimer = lila.mon.measure(_.user.auth.hashTime)
|
||||
),
|
||||
lila.mon.user.auth.shaLogin()
|
||||
onShaLogin = lila.mon.user.auth.shaLogin
|
||||
)
|
||||
|
||||
lazy val upgradeShaPasswords = PasswordUpgradeSha
|
||||
|
|
|
@ -61,17 +61,3 @@ final class PasswordHasher(
|
|||
BCrypt.bytesEqualSecure(hash, bHash(salt, pass))
|
||||
}
|
||||
}
|
||||
|
||||
final class AsyncPasswordHasher(
|
||||
hasher: PasswordHasher,
|
||||
sequencer: lila.hub.SyncMultiSequencer
|
||||
) {
|
||||
|
||||
def hash(pass: String): Fu[Array[Byte]] = sequencer {
|
||||
hasher.hash(pass)
|
||||
}
|
||||
|
||||
def check(bytes: Array[Byte], pass: String): Fu[Boolean] = sequencer {
|
||||
hasher.check(bytes, pass)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,13 +111,13 @@ object User {
|
|||
|
||||
type ID = String
|
||||
|
||||
type CredentialCheck = String => Fu[Boolean]
|
||||
type CredentialCheck = String => Boolean
|
||||
case class LoginCandidate(user: User, check: CredentialCheck) {
|
||||
def apply(password: String): Fu[Option[User]] =
|
||||
check(password) map { res =>
|
||||
lila.mon.user.auth.result(res)()
|
||||
res option user
|
||||
}
|
||||
def apply(password: String): Option[User] = {
|
||||
val res = check(password)
|
||||
lila.mon.user.auth.result(res)()
|
||||
res option user
|
||||
}
|
||||
}
|
||||
|
||||
val anonymous = "Anonymous"
|
||||
|
@ -259,10 +259,10 @@ object User {
|
|||
}
|
||||
}
|
||||
|
||||
final class Authenticator(passHasher: AsyncPasswordHasher, onShaLogin: => Unit) {
|
||||
final class Authenticator(passHasher: PasswordHasher, onShaLogin: () => Unit) {
|
||||
import com.roundeights.hasher.Implicits._
|
||||
|
||||
def passEnc(pass: String): Fu[Array[Byte]] = passHasher.hash(pass)
|
||||
def passEnc(pass: String): Array[Byte] = passHasher.hash(pass)
|
||||
|
||||
case class AuthData(
|
||||
_id: String,
|
||||
|
@ -271,7 +271,7 @@ final class Authenticator(passHasher: AsyncPasswordHasher, onShaLogin: => Unit)
|
|||
salt: Option[String] = None,
|
||||
sha512: Option[Boolean] = None
|
||||
) {
|
||||
def compare(p: String): Fu[Boolean] = {
|
||||
def compare(p: String): Boolean = {
|
||||
val newP = salt.fold(p) { s =>
|
||||
val salted = s"$p{$s}" // BC
|
||||
(~sha512).fold(salted.sha512, salted.sha1).hex
|
||||
|
@ -279,7 +279,7 @@ final class Authenticator(passHasher: AsyncPasswordHasher, onShaLogin: => Unit)
|
|||
|
||||
bpass match {
|
||||
// Deprecated fallback. Log & fail after DB migration.
|
||||
case None => password ?? { p => onShaLogin; fuccess(p == newP) }
|
||||
case None => password ?? { p => onShaLogin(); p == newP }
|
||||
case Some(bHash) => passHasher.check(bHash, newP)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -225,24 +225,21 @@ object UserRepo {
|
|||
def removeAllToints = coll.update($empty, $unset("toints"), multi = true)
|
||||
|
||||
def authenticateById(id: ID, password: String): Fu[Option[User]] =
|
||||
loginCandidateById(id) flatMap { _ ?? { _(password) } }
|
||||
loginCandidateById(id) map { _ flatMap { _(password) } }
|
||||
|
||||
def authenticateByEmail(email: EmailAddress, password: String): Fu[Option[User]] =
|
||||
loginCandidateByEmail(email) flatMap { _ ?? { _(password) } }
|
||||
loginCandidateByEmail(email) map { _ flatMap { _(password) } }
|
||||
|
||||
import Env.current.passwordAuth._
|
||||
|
||||
// This creates a bcrypt hash using the existing sha as input,
|
||||
// allowing us to migrate all users in bulk.
|
||||
def upgradePassword(a: AuthData) = (a.bpass, a.password) match {
|
||||
case (None, Some(pass)) => Some {
|
||||
passEnc(pass) flatMap { hash =>
|
||||
coll.update(
|
||||
$id(a._id),
|
||||
$set(F.bpass -> hash) ++ $unset(F.password)
|
||||
).void >>- lila.mon.user.auth.shaBcUpgrade()
|
||||
}
|
||||
}
|
||||
case (None, Some(pass)) => Some(coll.update(
|
||||
$id(a._id),
|
||||
$set(F.bpass -> passEnc(pass)) ++ $unset(F.password)
|
||||
).void >>- lila.mon.user.auth.shaBcUpgrade())
|
||||
|
||||
case _ => None
|
||||
}
|
||||
|
||||
|
@ -253,19 +250,19 @@ object UserRepo {
|
|||
loginCandidate($doc(F.email -> email))
|
||||
|
||||
def loginCandidate(u: User): Fu[User.LoginCandidate] =
|
||||
loginCandidateById(u.id) map { _ | User.LoginCandidate(u, _ => fuccess(false)) }
|
||||
loginCandidateById(u.id) map { _ | User.LoginCandidate(u, _ => false) }
|
||||
|
||||
def authWithBenefits(auth: AuthData)(p: String): Fu[Boolean] =
|
||||
auth compare p map { res =>
|
||||
if (res && auth.salt.isDefined && Env.current.upgradeShaPasswords)
|
||||
passwd(id = auth._id, pass = p) >>- lila.mon.user.auth.bcFullMigrate()
|
||||
res
|
||||
}
|
||||
def authWithBenefits(auth: AuthData)(p: String) = {
|
||||
val res = auth compare p
|
||||
if (res && auth.salt.isDefined && Env.current.upgradeShaPasswords)
|
||||
passwd(id = auth._id, pass = p) >>- lila.mon.user.auth.bcFullMigrate()
|
||||
res
|
||||
}
|
||||
|
||||
private def loginCandidate(select: Bdoc): Fu[Option[User.LoginCandidate]] =
|
||||
coll.uno[AuthData](select) zip coll.uno[User](select) map {
|
||||
case (Some(authData), Some(user)) if user.enabled =>
|
||||
User.LoginCandidate(user, authWithBenefits(authData) _).some
|
||||
User.LoginCandidate(user, authWithBenefits(authData)).some
|
||||
case _ => none
|
||||
}
|
||||
|
||||
|
@ -283,11 +280,9 @@ object UserRepo {
|
|||
): Fu[Option[User]] =
|
||||
!nameExists(username) flatMap {
|
||||
_ ?? {
|
||||
passEnc(password) flatMap { hash =>
|
||||
val doc = newUser(username, hash, email, blind, mobileApiVersion, mustConfirmEmail) ++
|
||||
("len" -> BSONInteger(username.size))
|
||||
coll.insert(doc) >> named(normalize(username))
|
||||
}
|
||||
val doc = newUser(username, password, email, blind, mobileApiVersion, mustConfirmEmail) ++
|
||||
("len" -> BSONInteger(username.size))
|
||||
coll.insert(doc) >> named(normalize(username))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -352,12 +347,9 @@ object UserRepo {
|
|||
}
|
||||
)
|
||||
|
||||
def passwd(id: ID, pass: String): Funit = passEnc(pass) flatMap { hash =>
|
||||
coll.update(
|
||||
$id(id),
|
||||
$set(F.bpass -> hash) ++ $unset(F.salt, F.password, F.sha512)
|
||||
).void
|
||||
}
|
||||
def passwd(id: ID, pass: String): Funit =
|
||||
coll.update($id(id), $set(F.bpass -> passEnc(pass))
|
||||
++ $unset(F.salt, F.password, F.sha512)).void
|
||||
|
||||
def email(id: ID, email: EmailAddress): Funit =
|
||||
coll.update($id(id), $set(F.email -> email) ++ $unset(F.prevEmail)).void
|
||||
|
@ -452,7 +444,7 @@ object UserRepo {
|
|||
|
||||
private def newUser(
|
||||
username: String,
|
||||
passwordHash: Array[Byte],
|
||||
password: String,
|
||||
email: EmailAddress,
|
||||
blind: Boolean,
|
||||
mobileApiVersion: Option[ApiVersion],
|
||||
|
@ -468,7 +460,7 @@ object UserRepo {
|
|||
F.username -> username,
|
||||
F.email -> email,
|
||||
F.mustConfirmEmail -> mustConfirmEmail.option(DateTime.now),
|
||||
F.bpass -> passwordHash,
|
||||
F.bpass -> passEnc(password),
|
||||
F.perfs -> $empty,
|
||||
F.count -> Count.default,
|
||||
F.enabled -> true,
|
||||
|
|
Loading…
Reference in a new issue