feat(user-sessions): implement user login, logout, and session management
This commit is contained in:
@@ -38,7 +38,9 @@ lazy val knockoutwhistweb = project.in(file("knockoutwhistweb"))
|
|||||||
.settings(
|
.settings(
|
||||||
commonSettings,
|
commonSettings,
|
||||||
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "7.0.2" % Test,
|
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "7.0.2" % Test,
|
||||||
libraryDependencies += "de.mkammerer" % "argon2-jvm" % "2.12"
|
libraryDependencies += "de.mkammerer" % "argon2-jvm" % "2.12",
|
||||||
|
// libraryDependencies += "com.auth0" % "java-jwt" % "4.5.0",
|
||||||
|
libraryDependencies += "com.github.ben-manes.caffeine" % "caffeine" % "3.2.2"
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val root = (project in file("."))
|
lazy val root = (project in file("."))
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import com.google.inject.{Guice, Injector}
|
import com.google.inject.{Guice, Injector}
|
||||||
import controllers.sessions.AdvancedSession
|
|
||||||
import de.knockoutwhist.KnockOutWhist
|
import de.knockoutwhist.KnockOutWhist
|
||||||
import de.knockoutwhist.components.Configuration
|
import de.knockoutwhist.components.Configuration
|
||||||
import de.knockoutwhist.control.GameState.{InGame, Lobby, SelectTrump, TieBreak}
|
import de.knockoutwhist.control.GameState.{InGame, Lobby, SelectTrump, TieBreak}
|
||||||
|
|||||||
75
knockoutwhistweb/app/controllers/UserController.scala
Normal file
75
knockoutwhistweb/app/controllers/UserController.scala
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import com.google.inject.{Guice, Injector}
|
||||||
|
import de.knockoutwhist.KnockOutWhist
|
||||||
|
import de.knockoutwhist.components.Configuration
|
||||||
|
import de.knockoutwhist.control.GameState.{InGame, Lobby, SelectTrump, TieBreak}
|
||||||
|
import de.knockoutwhist.control.controllerBaseImpl.BaseGameLogic
|
||||||
|
import di.KnockOutWebConfigurationModule
|
||||||
|
import logic.PodGameManager
|
||||||
|
import logic.user.{SessionManager, UserManager}
|
||||||
|
import model.sessions.AdvancedSession
|
||||||
|
import play.api.*
|
||||||
|
import play.api.mvc.*
|
||||||
|
import play.twirl.api.Html
|
||||||
|
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.inject.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This controller creates an `Action` to handle HTTP requests to the
|
||||||
|
* application's home page.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class UserController @Inject()(val controllerComponents: ControllerComponents, val sessionManager: SessionManager, val userManager: UserManager) extends BaseController {
|
||||||
|
|
||||||
|
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")
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
Ok(views.html.login())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(views.html.login())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def login_Post(): Action[AnyContent] = {
|
||||||
|
Action { implicit request =>
|
||||||
|
val postData = request.body.asFormUrlEncoded
|
||||||
|
if (postData.isDefined) {
|
||||||
|
// Extract username and password from form data
|
||||||
|
val username = postData.get.get("username").flatMap(_.headOption).getOrElse("")
|
||||||
|
val password = postData.get.get("password").flatMap(_.headOption).getOrElse("")
|
||||||
|
val possibleUser = userManager.authenticate(username, password)
|
||||||
|
if (possibleUser.isDefined) {
|
||||||
|
Redirect("/mainmenu").withCookies(
|
||||||
|
Cookie("sessionId", sessionManager.createSession(possibleUser.get))
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Unauthorized("Invalid username or password")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
BadRequest("Invalid form submission")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def logout(): Action[AnyContent] = {
|
||||||
|
Action { implicit request =>
|
||||||
|
val sessionCookie = request.cookies.get("sessionId")
|
||||||
|
if (sessionCookie.isDefined) {
|
||||||
|
sessionManager.invalidateSession(sessionCookie.get.value)
|
||||||
|
}
|
||||||
|
NoContent.discardingCookies(DiscardingCookie("sessionId"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package logic.user
|
package logic.user
|
||||||
|
|
||||||
|
import com.google.inject.ImplementedBy
|
||||||
|
import logic.user.impl.BaseSessionManager
|
||||||
import model.users.User
|
import model.users.User
|
||||||
|
|
||||||
|
@ImplementedBy(classOf[BaseSessionManager])
|
||||||
trait SessionManager {
|
trait SessionManager {
|
||||||
|
|
||||||
def createSession(user: User): String
|
def createSession(user: User): String
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
package logic.user
|
package logic.user
|
||||||
|
|
||||||
|
import com.google.inject.ImplementedBy
|
||||||
|
import logic.user.impl.StubUserManager
|
||||||
|
import model.users.User
|
||||||
|
|
||||||
|
@ImplementedBy(classOf[StubUserManager])
|
||||||
trait UserManager {
|
trait UserManager {
|
||||||
|
|
||||||
def addUser(name: String, password: String): Boolean
|
def addUser(name: String, password: String): Boolean
|
||||||
def authenticate(name: String, password: String): Boolean
|
def authenticate(name: String, password: String): Option[User]
|
||||||
def userExists(name: String): Boolean
|
def userExists(name: String): Option[User]
|
||||||
def removeUser(name: String): Boolean
|
def removeUser(name: String): Boolean
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package logic.user.impl
|
||||||
|
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import logic.user.SessionManager
|
||||||
|
import model.users.User
|
||||||
|
|
||||||
|
import javax.inject.{Inject, Singleton}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class BaseSessionManager @Inject()(val config: Config) extends SessionManager {
|
||||||
|
|
||||||
|
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
|
||||||
|
sessionId
|
||||||
|
}
|
||||||
|
|
||||||
|
override def getUserBySession(sessionId: String): Option[User] = {
|
||||||
|
//TODO verify JWT token instead of looking up in cache
|
||||||
|
//Read session identifier from cache and DB
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
override def invalidateSession(sessionId: String): Unit = {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
47
knockoutwhistweb/app/logic/user/impl/StubUserManager.scala
Normal file
47
knockoutwhistweb/app/logic/user/impl/StubUserManager.scala
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package logic.user.impl
|
||||||
|
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import logic.user.UserManager
|
||||||
|
import model.users.User
|
||||||
|
import util.UserHash
|
||||||
|
|
||||||
|
import javax.inject.{Inject, Singleton}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class StubUserManager @Inject()(val config: Config) extends UserManager {
|
||||||
|
|
||||||
|
private val user: Map[String, User] = Map(
|
||||||
|
"Janis" -> User(
|
||||||
|
internalId = 1L,
|
||||||
|
id = java.util.UUID.fromString("123e4567-e89b-12d3-a456-426614174000"),
|
||||||
|
name = "Janis",
|
||||||
|
passwordHash = UserHash.hashPW("password123")
|
||||||
|
),
|
||||||
|
"Leon" -> User(
|
||||||
|
internalId = 2L,
|
||||||
|
id = java.util.UUID.fromString("223e4567-e89b-12d3-a456-426614174000"),
|
||||||
|
name = "Leon",
|
||||||
|
passwordHash = UserHash.hashPW("password123")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
override def addUser(name: String, password: String): Boolean = {
|
||||||
|
throw new NotImplementedError("StubUserManager.addUser is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override def authenticate(name: String, password: String): Option[User] = {
|
||||||
|
user.get(name) match {
|
||||||
|
case Some(u) if UserHash.verifyUser(password, u) => Some(u)
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def userExists(name: String): Option[User] = {
|
||||||
|
user.get(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def removeUser(name: String): Boolean = {
|
||||||
|
throw new NotImplementedError("StubUserManager.removeUser is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
18
knockoutwhistweb/app/views/login.scala.html
Normal file
18
knockoutwhistweb/app/views/login.scala.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
@()
|
||||||
|
|
||||||
|
@main("Login") {
|
||||||
|
<div class="container">
|
||||||
|
<h2>Login</h2>
|
||||||
|
<form action="@routes.UserController.login_Post()" method="post">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">Username:</label>
|
||||||
|
<input type="text" class="form-control" id="username" name="username" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Password:</label>
|
||||||
|
<input type="password" class="form-control" id="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -12,3 +12,8 @@ GET /ingame/:id controllers.HomeController.ingame(id: String
|
|||||||
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
|
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
|
||||||
|
|
||||||
GET /rules controllers.HomeController.rules()
|
GET /rules controllers.HomeController.rules()
|
||||||
|
|
||||||
|
|
||||||
|
GET /login controllers.UserController.login()
|
||||||
|
POST /login controllers.UserController.login_Post()
|
||||||
|
GET /logout controllers.UserController.logout()
|
||||||
Reference in New Issue
Block a user