Here's everything I use, which includes the ability to reset the 
password. I'm sure most of this could be done better, but it's working 
fine for me right now.

In User.scala in the perisistence layer:


/**
  This class represents a user with login privileges on the website.
*/
@Entity
@Table{val name = "USERS"}
class User extends BaseEntity {
   @Id
   @GeneratedValue{val strategy = GenerationType.AUTO}
   var id: Long = _

   @Column{val name="EMAIL_ADDRESS", val unique = true,
     val nullable = false}
   var emailAddress: String = ""

   @Column{val name="VALIDATION_CODE"}
   var validationCode: String = ""

   @Column{val name="VALIDATION_EXPIRY"}
   var validationExpiry : Long = _

   @Column{val name="PASSWORD_SALT"}
   var passwordSalt: String = ""

   @Column{val name="PASSWORD_HASH"}
   var passwordHash: String = ""

   @Column{val name="IS_ADMINISTRATOR"}
   var isAdministrator: Boolean = false

   @Column{val name="IS_ACTIVE"}
   var isActive: Boolean = true

   /** create a SHA hash from a String */
   def hash(in: String): String = {
     new String((new Base64) encode
       (MessageDigest.getInstance("SHA")).digest(in.getBytes("UTF-8")))
   }

   def password: String = this.passwordHash

   def password_=(pw: String) = {
     this.passwordSalt = randomString(16)
     this.passwordHash = hash(pw + this.passwordSalt)
   }

   def authenticate(pw: String) = {
     hash(pw + this.passwordSalt) == this.passwordHash && isActive
   }

   def setValidation : String = {
     this.validationCode = randomString(32)
     this.validationExpiry = System.currentTimeMillis + 7400000L
     this.validationCode
   }

   @PrePersist
   def nullValidation = {
     if (this.validationCode.length > 0 && this.validationExpiry <
       System.currentTimeMillis()) {
         this.validationCode = null
         this.validationExpiry = 0
     }
   }

   /** random numbers generator */
   private val random = new java.security.SecureRandom

   /**
    * Create a random string of a given size
    */
   def randomString(size: Int): String = {
     def addChar(pos: Int, lastRand: Int,
         sb: StringBuilder): StringBuilder = {
       if (pos >= size) sb
       else {
         val randNum = if ((pos % 6) == 0) random.nextInt else lastRand
         sb.append((randNum & 0x1f) match {
           case n if n < 26 => ('A' + n).toChar
           case n if n < 52 => ('a' + (n - 26)).toChar
           case n => ('0' + (n - 26)).toChar
         })
         addChar(pos + 1, randNum >> 5, sb)
       }
     }
     addChar(0, 0, new StringBuilder(size)).toString
   }
}



In Boot:

LiftRules.addDispatchBefore {
   case RequestState(List("logout"), "", _) => AccessControl.logout
}

val validation_rewriter: LiftRules.RewritePf = {
   case RewriteRequest(path @ ParsePath("validation" :: page :: _,
       _, _,_), _, _) =>
     RewriteResponse(
       ParsePath("credentials" :: "index" :: Nil, "", true, false),
       Map("code" -> page :: path.wholePath.drop(2).zipWithIndex.map(
         p => ("param"+(p._2 + 1)) -> p._1) :_*)
     )
}

LiftRules.addRewriteBefore(validation_rewriter)

LiftRules.addDispatchBefore {
   case RequestState("admin" :: page , "", _)
     if !AccessControl.isAuthenticated_? =>
       S.error("Please log in to view the page you requested.")
       RequestedURL(Full(S.uri))
       () => Full(RedirectResponse("/login"))
}



In LoginOps:

object LoginOps extends LoginOps

class LoginOps {

   def login (xhtml : NodeSeq) : NodeSeq = {
     def logUserIn () = {
       try {
         val user: User =
           Model.createNamedQuery[User]("findUserByEmailAddress",
           "emailAddress" ->
             S.param("emailAddress").openOr("")).getSingleResult()

         if (user.authenticate(S.param("password").openOr(""))) {
             CurrentUser(Full(user))
             CurrentUserId(Full(user.id))
         }
         else {
           S.error("Unable to log you in.")
         }
       } catch {
         case x: NoResultException =>
           S.error("Unable to log you in.")
         case e: Exception => S.error(e.getMessage)
       }

       if (AccessControl.isAuthenticated_?) {
         redirectTo(RequestedURL.openOr("/"))
         RequestedURL(Empty)
       }
     }

     bind("login", xhtml,
          "emailAddress" -> (FocusOnLoad(
             <input id="login_form_email_address" type="text" size="24"
               name="emailAddress" value=""/>)),
          "password" -> <input id="login_form_password" type="password"
            name="password" size="16" value=""/>,
          "submit" -> SHtml.submit("Log In", logUserIn))
   }

   def changePassword (xhtml : NodeSeq) : NodeSeq = {
     def updatePassword () = {
       try {
         val user: User =
           Model.createNamedQuery[User]("findUserByValidationCode",
           "code" -> S.param("code").openOr("")).getSingleResult()

         if (user.validationExpiry > System.currentTimeMillis()) {
           if (S.param("password") != S.param("confirmation"))
             S.error("Password and confirmation do not match.")
           else {
             val quality =
               Pattern.compile("^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,}")
             val password = S.param("password").openOr("")

             if (quality.matcher(password).matches) {
               S.param("password").map(user.password = _)
               user.validationCode = null
               user.validationExpiry = 0
               Model.merge(user)
               CurrentUser(Full(user))
               CurrentUserId(Full(user.id))
               S.notice(
                 "You have successfully changed your password and " +
                 "are now logged in.")

               Mailer.sendMail(
                 From("[EMAIL PROTECTED]"),
                 Subject("[XXX] Credentials changed"),
                 List(
                   To(user.emailAddress),
                   xmlToMailBodyType(
                     <html>
                       <head>
                         <title>
                           Your credentials have been changed.
                         </title>
                       </head>
                       <body>
                         <p>
                           Someone, probably you, used a validation email
                           sent to this address to successfully change
                           your credentials. If this wasn't done by you,
                           please contact the [EMAIL PROTECTED]
                         </p>
                       </body>
                    </html>
                    )
                 ): _*
               )

               redirectTo("/")
             } else {
               S.error("The password you entered is invalid.")
             }
           }
         }
         else {
           S.error("That validation code has expired.")
           redirectTo("/password_reset")
         }
       } catch {
         case x: NoResultException =>
           S.error("That validation code has expired.")
           redirectTo("/password_reset")
         case y: Exception => println(y)
           redirectTo("/")
       }
     }

     bind("change", xhtml,
          "password" -> (FocusOnLoad(<input id="password" type="password"
            name="password" size="24" value=""/>)),
          "confirmation" -> <input id="confirmation" type="password"
            name="confirmation" size="24" value=""/>,
          "submit" -> SHtml.submit("Save Changes", updatePassword))
   }

   def passwordReset (xhtml : NodeSeq) : NodeSeq = {
     def resetPassword () = {
       try {
         val user: User =
             Model.createNamedQuery[User]("findUserByEmailAddress",
           "emailAddress" ->
             S.param("emailAddress").openOr("")).getSingleResult()

         val code = "http://localhost:8888/validation/"; +
           user.setValidation
         S.notice("Instructions have been mailed to you.")
         Model.merge(user)

         Mailer.sendMail(
           From("[EMAIL PROTECTED]"),
           Subject("[XXX] Important information"),
           List(
             To(user.emailAddress),
             xmlToMailBodyType(
               <html>
                 <head>
                   <title>Reset Information</title>
                 </head>
                 <body>
                   <p>
                     Someone, probably you, used your email address to
                     request that your credentials for the XXX site be
                     reset. To make changes to your site credentials,
                     follow the link below:
                   </p>

                   <p><a href={code}>{code}</a></p>

                   <p>
                     This validation address will expire at
                     {new  Date(user.validationExpiry)}.
                   </p>
                 </body>
              </html>
              )
           ): _*
         )

       } catch {
         case x: NoResultException =>
           S.error("Unable to find your email address.")
         case _ => S.error("A problem was encountered.")
       }

       redirectTo("/login")
     }

     bind("reset", xhtml,
          "emailAddress" -> (FocusOnLoad(
            <input id="login_form_email_address" type="text" size="24"
              name="emailAddress" value=""/>)),
          "submit" -> SHtml.submit("Reset Password", resetPassword))
   }
}



In LoginHelpers (you could put this in Boot):

object CurrentUserId extends SessionVar[Can[Long]](Empty)
object RequestedURL extends SessionVar[Can[String]](Empty)
object CurrentUser extends RequestVar[Can[User]](Empty)

object AccessControl {

   def logout(): Can[LiftResponse] = {
     CurrentUser(Empty)
     CurrentUserId(Empty)
     Full(RedirectResponse(S.param("path").openOr("/")))
   }

   def isAuthenticated_?() = CurrentUser.is match {
     case Empty => CurrentUserId.is match {
       case Empty => false
       case Full(id) => Model.find(classOf[User], id) match {
         case null => false
         case m => CurrentUser(Full(m))
           true
       }
     }
     case Full(m) => true
   }
}



In /webapp/login/index/html:

<lift:surround with="default" at="content">
   <div class="column span-11 last">
     <h2>Please log in</h2>

     <lift:LoginOps.login form="POST">

       <p class="add_link">
         [ <a href="/password_reset">Lost password</a> ]
       </p>
       <table class="editor" border="0" cellpadding="3" cellspacing="0">
         <tr>
           <th><label for="email_address">Email Address</label></th>
           <td><login:emailAddress/></td>
         </tr>
         <tr>
           <th><label for="password">Password</label></th>
           <td><login:password/></td>
         </tr>
         <tr>
           <th>&nbsp;</th>
           <td><login:submit/></td>
         </tr>
       </table>
     </lift:LoginOps.login>
   </div>
</lift:surround>



In /webapp/password_reset/index.html:
(you could also do this from the login form)

<lift:surround with="default" at="content">
   <div class="column span-11 last">
     <h2>Lost password</h2>

     <p>
       To reset your password, enter your email address in the form below
       and click the reset button. You will receive instructions on how
       to reset your password via that email address. Note: you must have
       previously registered your email address with this site for this
       to work.
     </p>

     <lift:LoginOps.passwordReset form="POST">
       <table class="editor" border="0" cellpadding="3" cellspacing="0">
         <tr>
           <th><label for="email address">Email Address</label></th>
           <td><reset:emailAddress/></td>
         </tr>
         <tr>
           <th>&nbsp;</th>
           <td><reset:submit/></td>
         </tr>
       </table>
     </lift:LoginOps.passwordReset>
   </div>
</lift:surround>



In /webapp/credentials/index.html:

<lift:surround with="default" at="content">
   <div class="column span-11 last">
     <h2>Reset your password</h2>

     <lift:LoginOps.changePassword form="POST">
       <table class="editor" border="0" cellpadding="3" cellspacing="0">
         <tr>
           <th><label for="email address">New Password</label></th>
           <td><change:password/></td>
         </tr>
         <tr>
           <th><label for="password">Type It Again</label></th>
           <td><change:confirmation/></td>
         </tr>
         <tr>
           <th>&nbsp;</th>
           <td><change:submit/></td>
         </tr>
       </table>
     </lift:LoginOps.changePassword>
   </div>
</lift:surround>

Hope that helps. Let me know if I left anything out or you have any 
questions.

Chas.

Tim Perrett wrote:
> Hey guys,
> 
> Has anyone been encrypting passwords through JPA and lift? Im looking
> at implementing something Jasypt but wanted to see what / if anyone
> else has some other pointers / ideas?
> 
> Cheers
> 
> Tim
> > 

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Lift" group.
To post to this group, send email to liftweb@googlegroups.com
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/liftweb?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to