From b604e6935f461a2c4d06b2a5da4f45cfcabfd982 Mon Sep 17 00:00:00 2001 From: Janis Date: Sat, 1 Nov 2025 11:44:55 +0100 Subject: [PATCH] chore: rebase --- knockoutwhistweb/app/auth/Auth.scala | 39 ++++++++ .../app/controllers/HomeController.scala | 89 ------------------- .../app/controllers/IngameController.scala | 75 ++++++++++++++++ .../app/controllers/MainMenuController.scala | 35 ++++++++ .../app/controllers/UserController.scala | 56 ++++-------- .../app/logic/game/GameLobby.scala | 4 +- .../logic/user/impl/BaseSessionManager.scala | 2 +- .../app/services/JwtKeyProvider.scala | 10 +-- .../app/views/sessions.scala.html | 13 --- knockoutwhistweb/conf/application.conf | 4 +- knockoutwhistweb/conf/routes | 18 ++-- 11 files changed, 185 insertions(+), 160 deletions(-) create mode 100644 knockoutwhistweb/app/auth/Auth.scala delete mode 100644 knockoutwhistweb/app/controllers/HomeController.scala create mode 100644 knockoutwhistweb/app/controllers/IngameController.scala create mode 100644 knockoutwhistweb/app/controllers/MainMenuController.scala delete mode 100644 knockoutwhistweb/app/views/sessions.scala.html diff --git a/knockoutwhistweb/app/auth/Auth.scala b/knockoutwhistweb/app/auth/Auth.scala new file mode 100644 index 0000000..8ec161e --- /dev/null +++ b/knockoutwhistweb/app/auth/Auth.scala @@ -0,0 +1,39 @@ +package auth + +import controllers.routes +import logic.user.SessionManager +import model.users.User +import play.api.mvc.* + +import javax.inject.Inject +import scala.concurrent.{ExecutionContext, Future} + +class AuthenticatedRequest[A](val user: User, request: Request[A]) extends WrappedRequest[A](request) + +class AuthAction @Inject()(val sessionManager: SessionManager, val parser: BodyParsers.Default)(implicit ec: ExecutionContext) + extends ActionBuilder[AuthenticatedRequest, AnyContent] { + + override def executionContext: ExecutionContext = ec + + // This simulates checking if a user is logged in (e.g. via session) + private def getUserFromSession(request: RequestHeader): Option[User] = { + val session = request.cookies.get("sessionId") + if (session.isDefined) + return sessionManager.getUserBySession(session.get.value) + None + } + + // Transform a normal request into an AuthenticatedRequest + override def invokeBlock[A]( + request: Request[A], + block: AuthenticatedRequest[A] => Future[Result] + ): Future[Result] = { + getUserFromSession(request) match { + case Some(user) => + block(new AuthenticatedRequest(user, request)) + case None => + Future.successful(Results.Redirect(routes.UserController.login())) + } + } +} + diff --git a/knockoutwhistweb/app/controllers/HomeController.scala b/knockoutwhistweb/app/controllers/HomeController.scala deleted file mode 100644 index a84e529..0000000 --- a/knockoutwhistweb/app/controllers/HomeController.scala +++ /dev/null @@ -1,89 +0,0 @@ -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.PodManager -import model.sessions.SimpleSession -import play.api.mvc.* -import play.api.* -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 HomeController @Inject()(val controllerComponents: ControllerComponents) extends BaseController { - - private var initial = false - private val injector: Injector = Guice.createInjector(KnockOutWebConfigurationModule()) - - /** - * Create an Action to render an HTML page. - * - * The configuration in the `routes` file means that this method - * will be called when the application receives a `GET` request with - * a path of `/`. - */ - def index(): Action[AnyContent] = { - if (!initial) { - initial = true - KnockOutWhist.entry(injector.getInstance(classOf[Configuration])) - } - Action { implicit request => - Redirect("/sessions") - } - } - def rules(): Action[AnyContent] = { - Action { implicit request => - Ok(views.html.rules()) - } - } - def sessions(): Action[AnyContent] = { - Action { implicit request => - Ok(views.html.rules()) - } - } - - def ingame(id: String): Action[AnyContent] = { - val uuid: UUID = UUID.fromString(id) - Action { implicit request => - NotFound(views.html.tui.apply(List(Html(s"

Session with id $id not found!

")))) - } -// if (PodGameManager.identify(uuid).isEmpty) { -// return Action { implicit request => -// NotFound(views.html.tui.apply(List(Html(s"

Session with id $id not found!

")))) -// } -// } else { -// val session = PodGameManager.identify(uuid).get -// val player = session.asInstanceOf[SimpleSession].player -// val logic = BaseGameLogic(null) -// if (logic.getCurrentState == Lobby) { -// -// } else if (logic.getCurrentState == InGame) { -// return Action { implicit request => -// Ok(views.html.ingame.apply(player, logic)) -// } -// } else if (logic.getCurrentState == SelectTrump) { -// return Action { implicit request => -// Ok(views.html.selecttrump.apply(player, logic)) -// } -// } else if (logic.getCurrentState == TieBreak) { -// return Action { implicit request => -// Ok(views.html.tie.apply(player, logic)) -// } -// } -// } -// Action { implicit request => -// InternalServerError("Oops") -// } - } -} \ No newline at end of file diff --git a/knockoutwhistweb/app/controllers/IngameController.scala b/knockoutwhistweb/app/controllers/IngameController.scala new file mode 100644 index 0000000..ccf12c2 --- /dev/null +++ b/knockoutwhistweb/app/controllers/IngameController.scala @@ -0,0 +1,75 @@ +package controllers + +import auth.{AuthAction, AuthenticatedRequest} +import logic.user.{SessionManager, UserManager} +import play.api.* +import play.api.mvc.* + +import javax.inject.* + + +/** + * This controller creates an `Action` to handle HTTP requests to the + * application's home page. + */ +@Singleton +class IngameController @Inject()( + val controllerComponents: ControllerComponents, + val sessionManager: SessionManager, + val userManager: UserManager, + val authAction: AuthAction + ) extends BaseController { + + // Pass the request-handling function directly to authAction (no nested Action) + def mainMenu(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => + Ok("Main Menu for user: " + request.user.name) + } + + 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("/mainmenu") + } 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 { + println("Failed login attempt for user: " + username) + Unauthorized("Invalid username or password") + } + } else { + BadRequest("Invalid form submission") + } + } + } + + // Pass the request-handling function directly to authAction (no nested Action) + def logout(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => + 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/controllers/MainMenuController.scala b/knockoutwhistweb/app/controllers/MainMenuController.scala new file mode 100644 index 0000000..5d85e17 --- /dev/null +++ b/knockoutwhistweb/app/controllers/MainMenuController.scala @@ -0,0 +1,35 @@ +package controllers + +import auth.{AuthAction, AuthenticatedRequest} +import play.api.* +import play.api.mvc.* + +import javax.inject.* + + +/** + * This controller creates an `Action` to handle HTTP requests to the + * application's home page. + */ +@Singleton +class MainMenuController @Inject()( + val controllerComponents: ControllerComponents, + val authAction: AuthAction + ) extends BaseController { + + // Pass the request-handling function directly to authAction (no nested Action) + def mainMenu(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => + Ok("Main Menu for user: " + request.user.name) + } + + def index(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => + Redirect("/mainmenu") + } + + def rules(): Action[AnyContent] = { + Action { implicit request => + Ok(views.html.rules()) + } + } + +} \ No newline at end of file diff --git a/knockoutwhistweb/app/controllers/UserController.scala b/knockoutwhistweb/app/controllers/UserController.scala index 1c7352c..e10bffd 100644 --- a/knockoutwhistweb/app/controllers/UserController.scala +++ b/knockoutwhistweb/app/controllers/UserController.scala @@ -1,19 +1,10 @@ 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.PodManager +import auth.{AuthAction, AuthenticatedRequest} import logic.user.{SessionManager, UserManager} -import model.sessions.SimpleSession import play.api.* import play.api.mvc.* -import play.twirl.api.Html -import java.util.UUID import javax.inject.* @@ -22,25 +13,12 @@ import javax.inject.* * application's home page. */ @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") - } - } - } +class UserController @Inject()( + val controllerComponents: ControllerComponents, + val sessionManager: SessionManager, + val userManager: UserManager, + val authAction: AuthAction + ) extends BaseController { def login(): Action[AnyContent] = { Action { implicit request => @@ -48,9 +26,8 @@ class UserController @Inject()(val controllerComponents: ControllerComponents, v if (session.isDefined) { val possibleUser = sessionManager.getUserBySession(session.get.value) if (possibleUser.isDefined) { - Redirect("/mainmenu") - } else - { + Redirect(routes.MainMenuController.mainMenu()) + } else { Ok(views.html.login()) } } else { @@ -68,7 +45,7 @@ class UserController @Inject()(val controllerComponents: ControllerComponents, v val password = postData.get.get("password").flatMap(_.headOption).getOrElse("") val possibleUser = userManager.authenticate(username, password) if (possibleUser.isDefined) { - Redirect("/mainmenu").withCookies( + Redirect(routes.MainMenuController.mainMenu()).withCookies( Cookie("sessionId", sessionManager.createSession(possibleUser.get)) ) } else { @@ -81,14 +58,13 @@ class UserController @Inject()(val controllerComponents: ControllerComponents, v } } - 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")) + // Pass the request-handling function directly to authAction (no nested Action) + def logout(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => + 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/game/GameLobby.scala b/knockoutwhistweb/app/logic/game/GameLobby.scala index 1549f8c..53e5560 100644 --- a/knockoutwhistweb/app/logic/game/GameLobby.scala +++ b/knockoutwhistweb/app/logic/game/GameLobby.scala @@ -3,14 +3,14 @@ package logic.game import de.knockoutwhist.cards.{Hand, Suit} import de.knockoutwhist.control.GameLogic import de.knockoutwhist.control.GameState.Lobby -import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.{MatchUtil, PlayerUtil, RoundUtil} +import de.knockoutwhist.control.controllerBaseImpl.sublogic.util.{MatchUtil, PlayerUtil} import de.knockoutwhist.events.global.SessionClosed import de.knockoutwhist.events.player.PlayerEvent import de.knockoutwhist.player.Playertype.HUMAN import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory} import de.knockoutwhist.rounds.{Match, Round, Trick} import de.knockoutwhist.utils.events.{EventListener, SimpleEvent} -import exceptions.{CantPlayCardException, GameFullException, NotHostException, NotInThisGameException, NotInteractableException} +import exceptions.* import model.sessions.{InteractionType, UserSession} import model.users.User diff --git a/knockoutwhistweb/app/logic/user/impl/BaseSessionManager.scala b/knockoutwhistweb/app/logic/user/impl/BaseSessionManager.scala index 88e046c..1efe06c 100644 --- a/knockoutwhistweb/app/logic/user/impl/BaseSessionManager.scala +++ b/knockoutwhistweb/app/logic/user/impl/BaseSessionManager.scala @@ -1,7 +1,7 @@ package logic.user.impl -import com.auth0.jwt.{JWT, JWTVerifier} import com.auth0.jwt.algorithms.Algorithm +import com.auth0.jwt.{JWT, JWTVerifier} import com.github.benmanes.caffeine.cache.{Cache, Caffeine} import com.typesafe.config.Config import logic.user.SessionManager diff --git a/knockoutwhistweb/app/services/JwtKeyProvider.scala b/knockoutwhistweb/app/services/JwtKeyProvider.scala index 7e87c0d..f1fb46f 100644 --- a/knockoutwhistweb/app/services/JwtKeyProvider.scala +++ b/knockoutwhistweb/app/services/JwtKeyProvider.scala @@ -1,13 +1,13 @@ package services -import java.nio.file.{Files, Paths} -import java.security.{KeyFactory, KeyPair, PrivateKey, PublicKey} -import java.security.spec.{PKCS8EncodedKeySpec, RSAPublicKeySpec, X509EncodedKeySpec} -import java.util.Base64 -import javax.inject.* import play.api.Configuration +import java.nio.file.{Files, Paths} import java.security.interfaces.{RSAPrivateKey, RSAPublicKey} +import java.security.spec.{PKCS8EncodedKeySpec, RSAPublicKeySpec, X509EncodedKeySpec} +import java.security.{KeyFactory, KeyPair, PrivateKey, PublicKey} +import java.util.Base64 +import javax.inject.* @Singleton class JwtKeyProvider @Inject()(config: Configuration) { diff --git a/knockoutwhistweb/app/views/sessions.scala.html b/knockoutwhistweb/app/views/sessions.scala.html deleted file mode 100644 index d400832..0000000 --- a/knockoutwhistweb/app/views/sessions.scala.html +++ /dev/null @@ -1,13 +0,0 @@ -@import model.sessions.PlayerSession -@(sessions: List[PlayerSession]) - -@main("Sessions") { -
-

Knockout Whist sessions

-

Please select your session to jump inside the game!

- @for(session <- sessions) { - @session.name
- } -
-} - diff --git a/knockoutwhistweb/conf/application.conf b/knockoutwhistweb/conf/application.conf index c9d8a3d..d6d372a 100644 --- a/knockoutwhistweb/conf/application.conf +++ b/knockoutwhistweb/conf/application.conf @@ -6,9 +6,9 @@ auth { issuer = "knockoutwhistweb" audience = "ui" # ${?PUBLIC_KEY_FILE} - privateKeyFile = "/home/janis/Workspaces/IntelliJ/KnockOutWhist/Gitops/rsa512-private.pem" + privateKeyFile = "D:\\Workspaces\\Gitops\\rsa512-private.pem" privateKeyPem = ${?PUBLIC_KEY_PEM} #${?PUBLIC_KEY_FILE} - publicKeyFile = "/home/janis/Workspaces/IntelliJ/KnockOutWhist/Gitops/rsa512-public.pem" + publicKeyFile = "D:\\Workspaces\\Gitops\\rsa512-public.pem" publicKeyPem = ${?PUBLIC_KEY_PEM} } diff --git a/knockoutwhistweb/conf/routes b/knockoutwhistweb/conf/routes index d0b13eb..fa535ee 100644 --- a/knockoutwhistweb/conf/routes +++ b/knockoutwhistweb/conf/routes @@ -3,17 +3,19 @@ # https://www.playframework.com/documentation/latest/ScalaRouting # ~~~~ -# An example controller showing a sample home page -GET / controllers.HomeController.index() -GET /sessions controllers.HomeController.sessions() -GET /ingame/:id controllers.HomeController.ingame(id: String) -# Map static resources from the /public folder to the /assets URL path +# Primary routes +GET / controllers.MainMenuController.index() GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) -GET /rules controllers.HomeController.rules() +# Main menu routes +GET /mainmenu controllers.MainMenuController.mainMenu() +GET /rules controllers.MainMenuController.rules() -GET /mainmenu controllers.UserController.mainMenu() +# User authentication routes GET /login controllers.UserController.login() POST /login controllers.UserController.login_Post() -GET /logout controllers.UserController.logout() \ No newline at end of file +GET /logout controllers.UserController.logout() + +# In-game routes +# GET /ingame/:id controllers.MainMenuController.ingame(id: String) \ No newline at end of file