If you want your users to be able to have "extended logins" and you're using lift's mapper framework with a RDBMS, it's pretty easy to do using "extended sessions". However, if you're using Mongo, you can't use Mapper. Here's how I got this feature working under Mongo.
DISCLAIMER: This has not been thoroughly tested, and may still have bugs. I may be able to make better use of Scala or Lift features. (If so, please post on how to do so.) The data stored in the cookie is not appropriate, primarily because it contains the user's login. Finally, I don't use the validation data in the cookie yet. (These last two are just things I need to get around to.)
WARNING: I did a lot of refactoring of MetaMegaProtoUser, for reasons I won't go into. As a result, the code I actually have wouldn't work with a "standard" distribution. Below, I've tried to "backdate" my code as if I hadn't hacked MMPU, but probably didn't get everything right. You'll need to experiment.
First, in ProtoUser, add three fields:
/**True if the user wants the system to automatically log them in based
* on cookie data, false otherwise.
*/
object autoLogin extends BooleanField(this) {
override def defaultValue = false
}
/** When autoLogin is true, this field stores a random ID that helps authenticate the user. */
object randomID extends LongField(this)
/** When autoLogin is true, this field is compared with cookie data to help authenticate the user. */
object lastLoggedIn extends LongField(this)
Next, in your Login screen, add a "Remember Me On This Computer" checkbox, and in your login logic, insert the following:
val random = scala.util.Random.nextLong
val now = new Date().getTime
if (rememberme) {
user.randomID(random)
user.lastLoggedIn(now)
user.autoLogin(true)
user.save
val cookieData = user.email.value + ":" + (now ^ random).toString
val cookie = net.liftweb.http.provider.HTTPCookie("RememberMe", cookieData)
cookie.setMaxAge(1000000).setPath("/")
S.addCookie(cookie)
}
"rememberme" is the status of the checkbox in the Login screen, and the user should have been looked up by the time this code runs.
Define the following method somewhere convenient (inside MegaMetaProtoUser may be best). It's what's going to try to login a user using an extended login cookie, before any other processing is done.
private def loginUsingCookie: Box[Req] => Unit = {
import net.liftweb.json.JsonDSL._
ignoredReq => {
S.findCookie("RememberMe") match {
case Full(cookie) =>
val data = cookie.value.getOrElse("?").split(":")
if (data.length == 2) {
val email = data(0)
val validation = data(1)
val userRecords =User.findAll(("email" -> email))
if (userRecords.length == 1) {
val user = userRecords(0)
if (user.autoLogin.value) {
User.logoutCurrentUser
User.currentUserID(Full(user.id.toString))
} else {
S.deleteCookie("RememberMe")
}
}
}
case _ => ;
}
Give the above def first crack at incoming requests by putting the following in your Boot class:
LiftRules.earlyInStateful.append(loginUsingCookie)
Finally, we have to allow the user to disable extended sessions. We do this simply by turning it off if the user explicitly logs out. Put this somewhere in your logout logic (if you put it in logoutUser in the MegaMetaProtoUser trait, it should work fine, I think):
val user = currentUser.get.open_!
user.autoLogin(false)
user.save
S.deleteCookie("RememberMe")
There are probably many better ways to do this (I'd certainly like to hear about them), but this way works at least in the simple testing I've done, it has the advantage of being very easy to understand (aside from the fact that you need to look at code in four different places)
Thanks to all the many people who answered way too many questions from me while I was trying to get this feature working.
At some point (when I've actually started to develop project-specific functionality), this project will be going up on GitHub, but that probably won't be for another two weeks or so. If you'd really like to see it before then, I can put it up sooner, but the code is currently still undergoing a lot of refactoring, so things are pretty ugly.
Hope this helps someone,
Ken