feat(user-sessions): add JWT-based session management and main menu route
This commit is contained in:
@@ -24,13 +24,31 @@ import javax.inject.*
|
||||
@Singleton
|
||||
class UserController @Inject()(val controllerComponents: ControllerComponents, val sessionManager: SessionManager, val userManager: UserManager) extends BaseController {
|
||||
|
||||
def mainMenu() : Action[AnyContent] = {
|
||||
Action { implicit request =>
|
||||
val session = request.cookies.get("sessionId")
|
||||
if (session.isDefined) {
|
||||
val possibleUser = sessionManager.getUserBySession(session.get.value)
|
||||
if (possibleUser.isDefined) {
|
||||
Ok("Main Menu for user: " + possibleUser.get.name)
|
||||
} else
|
||||
{
|
||||
println("Invalid session, redirecting to login")
|
||||
Redirect("/login")
|
||||
}
|
||||
} else {
|
||||
Redirect("/login")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def login(): Action[AnyContent] = {
|
||||
Action { implicit request =>
|
||||
val session = request.cookies.get("sessionId")
|
||||
if (session.isDefined) {
|
||||
val possibleUser = sessionManager.getUserBySession(session.get.value)
|
||||
if (possibleUser.isDefined) {
|
||||
Redirect("/main-menu")
|
||||
Redirect("/mainmenu")
|
||||
} else
|
||||
{
|
||||
Ok(views.html.login())
|
||||
@@ -54,6 +72,7 @@ class UserController @Inject()(val controllerComponents: ControllerComponents, v
|
||||
Cookie("sessionId", sessionManager.createSession(possibleUser.get))
|
||||
)
|
||||
} else {
|
||||
println("Failed login attempt for user: " + username)
|
||||
Unauthorized("Invalid username or password")
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
package logic.user.impl
|
||||
|
||||
import com.auth0.jwt.JWT
|
||||
import com.auth0.jwt.algorithms.Algorithm
|
||||
import com.typesafe.config.Config
|
||||
import logic.user.SessionManager
|
||||
import model.users.User
|
||||
import services.JwtKeyProvider
|
||||
|
||||
import javax.inject.{Inject, Singleton}
|
||||
|
||||
@Singleton
|
||||
class BaseSessionManager @Inject()(val config: Config) extends SessionManager {
|
||||
class BaseSessionManager @Inject()(val keyProvider: JwtKeyProvider, val config: Config) extends SessionManager {
|
||||
|
||||
private val algorithm = Algorithm.RSA512(keyProvider.publicKey, keyProvider.privateKey)
|
||||
|
||||
override def createSession(user: User): String = {
|
||||
//TODO create JWT token instead of random string
|
||||
//Write session identifier to cache and DB
|
||||
val sessionId = java.util.UUID.randomUUID().toString
|
||||
val sessionId = JWT.create()
|
||||
.withIssuer(config.getString("auth.issuer"))
|
||||
.withAudience(config.getString("auth.audience"))
|
||||
.withSubject(user.internalId.toString)
|
||||
.sign(algorithm)
|
||||
//TODO write to DB
|
||||
sessionId
|
||||
}
|
||||
|
||||
|
||||
56
knockoutwhistweb/app/services/JwtKeyProvider.scala
Normal file
56
knockoutwhistweb/app/services/JwtKeyProvider.scala
Normal file
@@ -0,0 +1,56 @@
|
||||
package services
|
||||
|
||||
import java.nio.file.{Files, Paths}
|
||||
import java.security.{KeyFactory, PrivateKey, PublicKey}
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import java.util.Base64
|
||||
import javax.inject.*
|
||||
import play.api.Configuration
|
||||
|
||||
import java.security.interfaces.{RSAPrivateKey, RSAPublicKey}
|
||||
|
||||
@Singleton
|
||||
class JwtKeyProvider @Inject()(config: Configuration) {
|
||||
|
||||
private def cleanPem(pem: String): String =
|
||||
pem.replaceAll("-----BEGIN (.*)-----", "")
|
||||
.replaceAll("-----END (.*)-----", "")
|
||||
.replaceAll("\\s", "")
|
||||
|
||||
private def loadPublicKeyFromPem(pem: String): RSAPublicKey = {
|
||||
val decoded = Base64.getDecoder.decode(cleanPem(pem))
|
||||
val spec = new X509EncodedKeySpec(decoded)
|
||||
KeyFactory.getInstance("RSA").generatePublic(spec).asInstanceOf[RSAPublicKey]
|
||||
}
|
||||
|
||||
private def loadPrivateKeyFromPem(pem: String): RSAPrivateKey = {
|
||||
val decoded = Base64.getDecoder.decode(cleanPem(pem))
|
||||
val spec = new X509EncodedKeySpec(decoded)
|
||||
KeyFactory.getInstance("RSA").generatePrivate(spec).asInstanceOf[RSAPrivateKey]
|
||||
}
|
||||
|
||||
val publicKey: RSAPublicKey = {
|
||||
val pemOpt = config.getOptional[String]("auth.publicKeyPem")
|
||||
val fileOpt = config.getOptional[String]("auth.publicKeyFile")
|
||||
|
||||
pemOpt.orElse(fileOpt.map { path =>
|
||||
new String(Files.readAllBytes(Paths.get(path)))
|
||||
}) match {
|
||||
case Some(pem) => loadPublicKeyFromPem(pem)
|
||||
case None => throw new RuntimeException("No RSA public key configured.")
|
||||
}
|
||||
}
|
||||
|
||||
val privateKey: RSAPrivateKey = {
|
||||
val pemOpt = config.getOptional[String]("auth.privateKeyPem")
|
||||
val fileOpt = config.getOptional[String]("auth.privateKeyFile")
|
||||
|
||||
pemOpt.orElse(fileOpt.map { path =>
|
||||
new String(Files.readAllBytes(Paths.get(path)))
|
||||
}) match {
|
||||
case Some(pem) => loadPrivateKeyFromPem(pem)
|
||||
case None => throw new RuntimeException("No RSA private key configured.")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +1,12 @@
|
||||
# https://www.playframework.com/documentation/latest/Configuration
|
||||
play.filters.disabled += play.filters.csrf.CSRFFilter
|
||||
|
||||
|
||||
auth {
|
||||
issuer = "knockoutwhistweb"
|
||||
audience = "ui"
|
||||
privateKeyFile = D:\Workspaces\Gitops\rsa512-private.pem # ${?PUBLIC_KEY_FILE}
|
||||
privateKeyPem = ${?PUBLIC_KEY_PEM}
|
||||
publicKeyFile = D:\Workspaces\Gitops\rsa512-public.pem #${?PUBLIC_KEY_FILE}
|
||||
publicKeyPem = ${?PUBLIC_KEY_PEM}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ GET /assets/*file controllers.Assets.versioned(path="/public",
|
||||
|
||||
GET /rules controllers.HomeController.rules()
|
||||
|
||||
|
||||
GET /mainmenu controllers.UserController.mainMenu()
|
||||
GET /login controllers.UserController.login()
|
||||
POST /login controllers.UserController.login_Post()
|
||||
GET /logout controllers.UserController.logout()
|
||||
Reference in New Issue
Block a user