Skip to content

Instantly share code, notes, and snippets.

@marvelm
Last active December 12, 2015 00:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marvelm/fda4aec1d25a518fb2f5 to your computer and use it in GitHub Desktop.
Save marvelm/fda4aec1d25a518fb2f5 to your computer and use it in GitHub Desktop.
Redis-backed extended sessions for Lift (liftweb)
trait RedisExtendedSession[U <: ProtoUser[_]] extends Loggable {
implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global
def redisClient: RedisClient
def expiration: Duration
def cookieName = "redis_ext_session_key"
def logUserIdIn(uid: String): Unit
def generateKey() = Random.alphanumeric.take(8).mkString
def uidFieldName = "uid"
/**
* ProtoUser calls this on logout
* @param user
*/
def userDidLogout(user: Box[U]): Unit = {
for {
cookie <- S.findCookie(cookieName)
key <- cookie.value
} {
S.deleteCookie(cookie)
redisClient.hdel(key)
}
}
/**
* ProtoUser calls this on login.
* @param user
*/
def userDidLogin(user: U): Unit = {
val uid = user.userIdAsString
userDidLogout(Full(user))
userDidLogin(uid)
}
/**
* Login user with just a uid
* Adds a hashkey to Redis
* @param uid
*/
def userDidLogin(uid: String): Unit = {
val key = generateKey()
val keyExists = Await.result(redisClient.hexists(key, uidFieldName), 3.seconds)
if (keyExists) userDidLogin(uid)
else {
redisClient.hset(key, uidFieldName, uid)
redisClient.expire(key, expiration.toSeconds)
val cookie = HTTPCookie(cookieName, key)
.setMaxAge(expiration.toSeconds.toInt)
.setPath("/")
S.addCookie(cookie)
}
}
/**
* Defines where to get the uid from if the user is logged in through ProtoUser
* @return
*/
def recoverUserId: Box[String]
/**
* Sets cookie/login if necessary
* @param ignoredReq This isn't used
*/
def testCookieEarlyInStateful(ignoredReq: Box[Req]): Unit = {
(recoverUserId, S.findCookie(cookieName)) match {
// Cookie exists but unable to retrieve session in User object
case (Empty, Full(c)) =>
val key = c.value openOr ""
val u = Await.ready(redisClient.hget(key, uidFieldName), 3.seconds).value.get
u match {
case Success(Some(uid)) => logUserIdIn(uid.decodeString("UTF-8"))
case Success(None) | Failure => S.deleteCookie(c)
}
// Logged in through User object, but no cookie
case (Full(uid), Empty) =>
userDidLogin(uid)
// Logged in through User object and cookie exists
case (Full(_), Full(_)) =>
// Logged out
case (Empty, Empty) =>
// Something horrible has happened
case a: Any => println(a)
}
}
}
// Assumes you've got some sort of ProtoUser singleton called User
object RedisExtSession extends RedisExtendedSession[User] with Logger {
// All the overriden fields are necessary.
override val redisClient: RedisClient = {
implicit val system = ActorSystem("redis")
RedisClient()
}
override def recoverUserId: Box[String] = User.currentUserId
override val expiration: Duration = 1.day
override def logUserIdIn(uid: String): Unit = User.logUserIdIn(uid)
}
// Do this in your application's User singleton
object User extends User with MetaProtoUser[User] {
onLogIn = List(RedisExtSession.userDidLogin)
onLogOut = List(RedisExtSession.userDidLogout)
// ...
}
// In your Boot.scala file
class Boot {
def boot() {
LiftRules.earlyInStateful.append(RedisExtSession.testCookieEarlyInStateful)
// ...
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment