Created
February 8, 2011 13:33
-
-
Save eltimn/816439 to your computer and use it in GitHub Desktop.
Lift-Record Password Field
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.regex._ | |
import scala.xml._ | |
import org.mindrot.jbcrypt.BCrypt | |
import net.liftweb._ | |
import common._ | |
import http.S | |
import http.js._ | |
import json.JsonAST.{JNothing, JNull, JString, JValue} | |
import mongodb.record.field._ | |
import mongodb.record._ | |
import net.liftweb.record._ | |
import net.liftweb.record.field._ | |
import util._ | |
import Helpers._ | |
import S._ | |
import JE._ | |
object PasswordField { | |
@volatile var blankPw = "*******" | |
@volatile var minPasswordLength = 6 | |
@volatile var logRounds = 10 | |
private[lib] def hashpw(in: String): Box[String] = | |
tryo(BCrypt.hashpw(in, BCrypt.gensalt(logRounds))) | |
} | |
trait PasswordTypedField extends TypedField[String] { | |
def maxLength: Int | |
def confirmField: Box[TypedField[String]] // | |
/* | |
* Call this after validation and before it is saved to the db to hash | |
* the password. Eg. in the finish method of a screen. | |
*/ | |
def hashIt { | |
valueBox foreach { v => | |
setBox(PasswordField.hashpw(v)) | |
} | |
} | |
/* | |
* jBCrypt throws "String index out of range" exception | |
* if password is an empty String | |
*/ | |
def match_?(toTest: String): Boolean = valueBox | |
.filter(_.length > 0) | |
.flatMap(p => tryo(BCrypt.checkpw(toTest, p))) | |
.openOr(false) | |
private def elem = S.fmapFunc(SFuncHolder(this.setFromAny(_))){ | |
funcName => <input type="password" maxlength={maxLength.toString} | |
name={funcName} | |
value={valueBox openOr ""} | |
tabindex={tabIndex toString}/>} | |
override def toForm: Box[NodeSeq] = | |
uniqueFieldId match { | |
case Full(id) => Full(elem % ("id" -> (id + "_field"))) | |
case _ => Full(elem) | |
} | |
} | |
class PasswordField[OwnerType <: Record[OwnerType]](rec: OwnerType, maxLength: Int, _confirmField: Box[TypedField[String]]) | |
extends StringField[OwnerType](rec, maxLength) with PasswordTypedField { | |
val confirmField = _confirmField | |
/* | |
* If confirmField is Full, check it against the inputted value. | |
*/ | |
private def valMatch(msg: => String)(value: String): List[FieldError] = { | |
confirmField.filterNot(_.is == value).map(p => | |
FieldError(this, Text(msg)) | |
).toList | |
} | |
override def validations = | |
valMatch("Passwords must match.") _ :: | |
valMinLen(PasswordField.minPasswordLength, "Password must be at least "+PasswordField.minPasswordLength+" characters.") _ :: | |
valMaxLen(maxLength, "Password must be "+maxLength+" characters or less.") _ :: | |
super.validations | |
override def setFilter = trim _ :: super.setFilter | |
/* | |
* Use this when creating users programatically. It allows chaining: | |
* User.createRecord.email("[email protected]").password("pass1", true).save | |
* This will automatically hash the plain text password if isPlain is | |
* true. | |
*/ | |
def apply(in: String, isPlain: Boolean): OwnerType = { | |
val hashed = | |
if (isPlain) | |
PasswordField.hashpw(in) openOr "" | |
else | |
in | |
if (owner.meta.mutable_?) { | |
this.setBox(Full(hashed)) | |
owner | |
} else { | |
owner.meta.createWithMutableField(owner, this, Full(hashed)) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment