Add ClearPassword wrapper
This commit is contained in:
parent
14f5a341a3
commit
81754ca72b
|
@ -6,6 +6,7 @@ import play.api.data.validation.Constraints
|
|||
|
||||
import lila.common.{ LameName, EmailAddress }
|
||||
import lila.user.{ User, UserRepo }
|
||||
import User.ClearPassword
|
||||
|
||||
final class DataForm(
|
||||
val captcher: akka.actor.ActorSelection,
|
||||
|
@ -84,7 +85,7 @@ final class DataForm(
|
|||
|
||||
def changeEmail(u: User, old: Option[EmailAddress]) = authenticator loginCandidate u map { candidate =>
|
||||
Form(mapping(
|
||||
"passwd" -> nonEmptyText.verifying("incorrectPassword", candidate.check),
|
||||
"passwd" -> nonEmptyText.verifying("incorrectPassword", p => candidate.check(ClearPassword(p))),
|
||||
"email" -> acceptableUniqueEmail(candidate.user.some).verifying(emailValidator differentConstraint old)
|
||||
)(ChangeEmail.apply)(ChangeEmail.unapply)).fill(ChangeEmail(
|
||||
passwd = "",
|
||||
|
|
|
@ -47,7 +47,7 @@ final class SecurityApi(
|
|||
} map loadedLoginForm _
|
||||
|
||||
private def authenticateCandidate(candidate: Option[User.LoginCandidate])(username: String, password: String): Option[User] =
|
||||
candidate ?? { _(password) }
|
||||
candidate ?? { _(User.ClearPassword(password)) }
|
||||
|
||||
def saveAuthentication(userId: User.ID, apiVersion: Option[ApiVersion])(implicit req: RequestHeader): Fu[String] =
|
||||
UserRepo mustConfirmEmail userId flatMap {
|
||||
|
|
|
@ -5,7 +5,7 @@ import reactivemongo.bson._
|
|||
|
||||
import lila.common.EmailAddress
|
||||
import lila.db.dsl._
|
||||
import lila.user.User.{ BSONFields => F }
|
||||
import lila.user.User.{ ClearPassword, BSONFields => F }
|
||||
|
||||
final class Authenticator(
|
||||
passHasher: PasswordHasher,
|
||||
|
@ -15,24 +15,24 @@ final class Authenticator(
|
|||
) {
|
||||
import Authenticator._
|
||||
|
||||
def passEnc(pass: String): HashedPassword = passHasher.hash(pass)
|
||||
def passEnc(p: ClearPassword): HashedPassword = passHasher.hash(p)
|
||||
|
||||
def compare(auth: AuthData, p: String): Boolean = {
|
||||
def compare(auth: AuthData, p: ClearPassword): Boolean = {
|
||||
val newP = auth.salt.fold(p) { s =>
|
||||
val salted = s"$p{$s}" // BC
|
||||
(~auth.sha512).fold(salted.sha512, salted.sha1).hex
|
||||
val salted = s"${p.value}{$s}" // BC
|
||||
ClearPassword((~auth.sha512).fold(salted.sha512, salted.sha1))
|
||||
}
|
||||
auth.bpass match {
|
||||
// Deprecated fallback. Log & fail after DB migration.
|
||||
case None => auth.password ?? { p => onShaLogin(); p == newP }
|
||||
case None => auth.password ?? { sHash => onShaLogin(); sHash == newP.value }
|
||||
case Some(bpass) => passHasher.check(bpass, newP)
|
||||
}
|
||||
}
|
||||
|
||||
def authenticateById(id: User.ID, password: String): Fu[Option[User]] =
|
||||
def authenticateById(id: User.ID, password: ClearPassword): Fu[Option[User]] =
|
||||
loginCandidateById(id) map { _ flatMap { _(password) } }
|
||||
|
||||
def authenticateByEmail(email: EmailAddress, password: String): Fu[Option[User]] =
|
||||
def authenticateByEmail(email: EmailAddress, password: ClearPassword): Fu[Option[User]] =
|
||||
loginCandidateByEmail(email) map { _ flatMap { _(password) } }
|
||||
|
||||
// This creates a bcrypt hash using the existing sha as input,
|
||||
|
@ -40,7 +40,7 @@ final class Authenticator(
|
|||
def upgradePassword(a: AuthData) = (a.bpass, a.password) match {
|
||||
case (None, Some(shaHash)) => Some(userRepo.coll.update(
|
||||
$id(a._id),
|
||||
$set(F.bpass -> passEnc(shaHash).bytes) ++ $unset(F.password)
|
||||
$set(F.bpass -> passEnc(ClearPassword(shaHash)).bytes) ++ $unset(F.password)
|
||||
).void >>- lila.mon.user.auth.shaBcUpgrade())
|
||||
|
||||
case _ => None
|
||||
|
@ -55,16 +55,16 @@ final class Authenticator(
|
|||
def loginCandidateByEmail(email: EmailAddress): Fu[Option[User.LoginCandidate]] =
|
||||
loginCandidate($doc(F.email -> email))
|
||||
|
||||
def setPassword(id: User.ID, pass: String): Funit =
|
||||
def setPassword(id: User.ID, p: ClearPassword): Funit =
|
||||
userRepo.coll.update(
|
||||
$id(id),
|
||||
$set(F.bpass -> passEnc(pass).bytes) ++ $unset(F.salt, F.password, F.sha512)
|
||||
$set(F.bpass -> passEnc(p).bytes) ++ $unset(F.salt, F.password, F.sha512)
|
||||
).void
|
||||
|
||||
private def authWithBenefits(auth: AuthData)(p: String): Boolean = {
|
||||
private def authWithBenefits(auth: AuthData)(p: ClearPassword): Boolean = {
|
||||
val res = compare(auth, p)
|
||||
if (res && auth.salt.isDefined && upgradeShaPasswords)
|
||||
setPassword(id = auth._id, pass = p) >>- lila.mon.user.auth.bcFullMigrate()
|
||||
setPassword(id = auth._id, p) >>- lila.mon.user.auth.bcFullMigrate()
|
||||
res
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@ package lila.user
|
|||
import play.api.data._
|
||||
import play.api.data.Forms._
|
||||
|
||||
import User.ClearPassword
|
||||
|
||||
final class DataForm(authenticator: Authenticator) {
|
||||
|
||||
val note = Form(mapping(
|
||||
|
@ -38,7 +40,7 @@ final class DataForm(authenticator: Authenticator) {
|
|||
|
||||
def passwd(u: User) = authenticator loginCandidate u map { candidate =>
|
||||
Form(mapping(
|
||||
"oldPasswd" -> nonEmptyText.verifying("incorrectPassword", candidate.check),
|
||||
"oldPasswd" -> nonEmptyText.verifying("incorrectPassword", p => candidate.check(ClearPassword(p))),
|
||||
"newPasswd1" -> nonEmptyText(minLength = 2),
|
||||
"newPasswd2" -> nonEmptyText(minLength = 2)
|
||||
)(Passwd.apply)(Passwd.unapply).verifying("the new passwords don't match", _.samePasswords))
|
||||
|
|
|
@ -52,19 +52,20 @@ final class PasswordHasher(
|
|||
hashTimer: (=> Array[Byte]) => Array[Byte] = x => x
|
||||
) {
|
||||
import org.mindrot.BCrypt
|
||||
import User.ClearPassword
|
||||
|
||||
private val aes = new Aes(secret)
|
||||
private def bHash(salt: Array[Byte], p: String) =
|
||||
hashTimer(BCrypt.hashpwRaw(p.sha512, 'a', logRounds, salt))
|
||||
private def bHash(salt: Array[Byte], p: ClearPassword) =
|
||||
hashTimer(BCrypt.hashpwRaw(p.value.sha512, 'a', logRounds, salt))
|
||||
|
||||
def hash(p: String): HashedPassword = {
|
||||
def hash(p: ClearPassword): HashedPassword = {
|
||||
val salt = new Array[Byte](16)
|
||||
new SecureRandom().nextBytes(salt)
|
||||
|
||||
HashedPassword(salt ++ aes.encrypt(Aes.iv(salt), bHash(salt, p)))
|
||||
}
|
||||
|
||||
def check(bytes: HashedPassword, p: String): Boolean = bytes.parse ?? {
|
||||
def check(bytes: HashedPassword, p: ClearPassword): Boolean = bytes.parse ?? {
|
||||
case (salt, encHash) =>
|
||||
val hash = aes.decrypt(Aes.iv(salt), encHash)
|
||||
BCrypt.bytesEqualSecure(hash, bHash(salt, p))
|
||||
|
|
|
@ -111,10 +111,10 @@ object User {
|
|||
|
||||
type ID = String
|
||||
|
||||
type CredentialCheck = String => Boolean
|
||||
type CredentialCheck = ClearPassword => Boolean
|
||||
case class LoginCandidate(user: User, check: CredentialCheck) {
|
||||
def apply(password: String): Option[User] = {
|
||||
val res = check(password)
|
||||
def apply(p: ClearPassword): Option[User] = {
|
||||
val res = check(p)
|
||||
lila.mon.user.auth.result(res)()
|
||||
res option user
|
||||
}
|
||||
|
@ -131,6 +131,10 @@ object User {
|
|||
|
||||
case class Emails(current: Option[EmailAddress], previous: Option[EmailAddress])
|
||||
|
||||
case class ClearPassword(value: String) extends AnyVal {
|
||||
override def toString = "ClearPassword(****)"
|
||||
}
|
||||
|
||||
case class PlayTime(total: Int, tv: Int) {
|
||||
import org.joda.time.Period
|
||||
def totalPeriod = new Period(total * 1000l)
|
||||
|
|
Loading…
Reference in a new issue