feat(user-sessions): implement user login, logout, and session management

This commit is contained in:
2025-10-28 18:32:57 +01:00
parent e2c4da68ca
commit 93b0766138
9 changed files with 186 additions and 4 deletions

View File

@@ -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}

View 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"))
}
}
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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 = {
}
}

View 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")
}
}

View 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>
}