diff --git a/build.sbt b/build.sbt index 490def8..9485c6f 100644 --- a/build.sbt +++ b/build.sbt @@ -38,7 +38,9 @@ lazy val knockoutwhistweb = project.in(file("knockoutwhistweb")) .settings( commonSettings, 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(".")) diff --git a/knockoutwhistweb/app/controllers/HomeController.scala b/knockoutwhistweb/app/controllers/HomeController.scala index 4db8838..ff7ae53 100644 --- a/knockoutwhistweb/app/controllers/HomeController.scala +++ b/knockoutwhistweb/app/controllers/HomeController.scala @@ -1,7 +1,6 @@ package controllers import com.google.inject.{Guice, Injector} -import controllers.sessions.AdvancedSession import de.knockoutwhist.KnockOutWhist import de.knockoutwhist.components.Configuration import de.knockoutwhist.control.GameState.{InGame, Lobby, SelectTrump, TieBreak} diff --git a/knockoutwhistweb/app/controllers/UserController.scala b/knockoutwhistweb/app/controllers/UserController.scala new file mode 100644 index 0000000..af4831c --- /dev/null +++ b/knockoutwhistweb/app/controllers/UserController.scala @@ -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")) + } + } + +} \ No newline at end of file diff --git a/knockoutwhistweb/app/logic/user/SessionManager.scala b/knockoutwhistweb/app/logic/user/SessionManager.scala index c573b23..aad7472 100644 --- a/knockoutwhistweb/app/logic/user/SessionManager.scala +++ b/knockoutwhistweb/app/logic/user/SessionManager.scala @@ -1,7 +1,10 @@ package logic.user +import com.google.inject.ImplementedBy +import logic.user.impl.BaseSessionManager import model.users.User +@ImplementedBy(classOf[BaseSessionManager]) trait SessionManager { def createSession(user: User): String diff --git a/knockoutwhistweb/app/logic/user/UserManager.scala b/knockoutwhistweb/app/logic/user/UserManager.scala index 69e4481..b6a4f47 100644 --- a/knockoutwhistweb/app/logic/user/UserManager.scala +++ b/knockoutwhistweb/app/logic/user/UserManager.scala @@ -1,10 +1,15 @@ package logic.user +import com.google.inject.ImplementedBy +import logic.user.impl.StubUserManager +import model.users.User + +@ImplementedBy(classOf[StubUserManager]) trait UserManager { def addUser(name: String, password: String): Boolean - def authenticate(name: String, password: String): Boolean - def userExists(name: String): Boolean + def authenticate(name: String, password: String): Option[User] + def userExists(name: String): Option[User] def removeUser(name: String): Boolean } diff --git a/knockoutwhistweb/app/logic/user/impl/BaseSessionManager.scala b/knockoutwhistweb/app/logic/user/impl/BaseSessionManager.scala new file mode 100644 index 0000000..70d7d9d --- /dev/null +++ b/knockoutwhistweb/app/logic/user/impl/BaseSessionManager.scala @@ -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 = { + + } +} diff --git a/knockoutwhistweb/app/logic/user/impl/StubUserManager.scala b/knockoutwhistweb/app/logic/user/impl/StubUserManager.scala new file mode 100644 index 0000000..a367358 --- /dev/null +++ b/knockoutwhistweb/app/logic/user/impl/StubUserManager.scala @@ -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") + } + +} diff --git a/knockoutwhistweb/app/views/login.scala.html b/knockoutwhistweb/app/views/login.scala.html new file mode 100644 index 0000000..e254fa4 --- /dev/null +++ b/knockoutwhistweb/app/views/login.scala.html @@ -0,0 +1,18 @@ +@() + +@main("Login") { +
+

Login

+
+
+ + +
+
+ + +
+ +
+
+} \ No newline at end of file diff --git a/knockoutwhistweb/conf/routes b/knockoutwhistweb/conf/routes index 3d2cb6d..565ed7d 100644 --- a/knockoutwhistweb/conf/routes +++ b/knockoutwhistweb/conf/routes @@ -12,3 +12,8 @@ GET /ingame/:id controllers.HomeController.ingame(id: String GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) GET /rules controllers.HomeController.rules() + + +GET /login controllers.UserController.login() +POST /login controllers.UserController.login_Post() +GET /logout controllers.UserController.logout() \ No newline at end of file