All Downloads are FREE. Search and download functionalities are using the official Maven repository.

no.kodeworks.kvarg.actor.SessionService.scala Maven / Gradle / Ivy

There is a newer version: 0.7
Show newest version
package no.kodeworks.kvarg.actor

import akka.actor.{ActorRef, ActorRefFactory, Cancellable}
import akka.event.LoggingAdapter
import akka.http.scaladsl.server.Directive1
import akka.http.scaladsl.server.Directives._
import akka.util.Timeout
import no.kodeworks.kvarg.actor.DbService._
import no.kodeworks.kvarg.message.{InitFailure, InitSuccess}
import no.kodeworks.kvarg.model.Session
import no.kodeworks.kvarg.util.AtLeastOnceDelivery.pattern
import no.kodeworks.kvarg.util.{AtLeastOnceDeliveryDefault, IdGen, _}
import no.kodeworks.kvarg.actor.SessionService.{Scavenge, SessionTimeout}
import com.softwaremill.session.SessionOptions._
import com.softwaremill.session._
import shapeless.{::, HNil}

import scala.collection.mutable
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._

class SessionService
(
  bootService: ActorRef,
  dbService: ActorRef,
  listeners: List[ActorRef],
  sessionConfig: SessionConfig,
  timeout: Timeout = 5 seconds
)
  extends AtLeastOnceDeliveryDefault {
  val sessionMaxAgeMillis = sessionConfig.sessionMaxAgeSeconds.map(1000L *).getOrElse(Long.MaxValue)

  implicit def ec = context.dispatcher

  val sessions = mutable.Map[String, Long]()
  var scavenger: Cancellable = emptyCancellable

  override def preStart {
    log.info("Born")
    if (sessionConfig.sessionMaxAgeSeconds.isEmpty) log.warning("No session max age set")
    log.debug(s"Listeners: ${listeners.map(_.path.name).mkString(", ")}")
    bootService ! InitSuccess
  }

  override def receive = (super.receive orElse {
    case session: String =>
      log.debug("Set or touch session {}", session)
      sessions(session) = System.currentTimeMillis
      if (1 == sessions.size) scavenge()
    case Scavenge =>
      scavenge()
    case x =>
      log.error("Unknown " + x)
  })

  def scavenge() {
    log.debug("Scavenge")
    val now = System.currentTimeMillis
    val expires = now - sessionMaxAgeMillis
    val (tooOld, stillGoingStrong) = sessions.partition(_._2 <= expires)
    sessions.retain { case (s, _) => stillGoingStrong.contains(s) }
    for {
      s <- tooOld.keys
      l <- listeners
    } l ! SessionTimeout(s)
    scavenger.cancel()
    if (sessions.nonEmpty) {
      val nextTimeout = (sessions.values.min + sessionMaxAgeMillis - now) millis

      log.debug("Next scavenge in {} seconds", nextTimeout.toSeconds)
      scavenger = context.system.scheduler.scheduleOnce(nextTimeout, self, Scavenge)
    }
  }

  override def postStop {
    log.info("Died")
  }
}

object SessionService {
  type R = Session :: HNil

  case object Scavenge

  case class SessionTimeout(session: String)

  def ensureSession(
                     sessionManager: SessionManager[String],
                     sessionService: ActorRef,
                     ec: ExecutionContext,
                     ac: ActorRefFactory,
                     timeout: Timeout = 5 seconds
                   ): Directive1[String] = {
    import CsrfDirectives._
    import CsrfOptions._
    import SessionDirectives._
    import SessionOptions._
    implicit def _sessionManager = sessionManager

    implicit def _ec = ec

    implicit def _ac = ac

    implicit def to = timeout

    val session = SessionService.createSession
    //TODO after login, remember to call setNewCsrfToken(checkHeader)
    randomTokenCsrfProtection(checkHeader) &
      (SessionService.touchRequiredSession |
        setSession(oneOff[String], usingCookies, session).tflatMap(_ => provide(session))
        ).map { session =>
        sessionService !! session
        session
      }
  }

  def touchRequiredSession(implicit ec: ExecutionContext,
                           sm: SessionManager[String]
                          ): Directive1[String] =
    SessionDirectives.touchRequiredSession(oneOff[String], usingCookies)

  def requiredSession(implicit ec: ExecutionContext,
                      sm: SessionManager[String],
                      log: LoggingAdapter): Directive1[String] = {
    log.debug("Requiring session")
    SessionDirectives.requiredSession(oneOff[String], usingCookies)
  }

  def createSession: String = SessionUtil.randomString(64)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy