feat: Create authorization

This commit is contained in:
2026-01-20 11:32:06 +01:00
parent 709a833b4b
commit f8c979ab3d
8 changed files with 73 additions and 27 deletions

View File

@@ -1,7 +1,5 @@
package controllers
import auth.AuthAction
import com.typesafe.config.Config
import logic.user.{SessionManager, UserManager}
import model.users.User
import play.api.Configuration
@@ -22,7 +20,7 @@ class OpenIDController @Inject()(
val config: Configuration
)(implicit ec: ExecutionContext) extends BaseController {
def loginWithProvider(provider: String) = Action.async { implicit request =>
def loginWithProvider(provider: String): Action[AnyContent] = Action.async { implicit request =>
val state = openIDService.generateState()
val nonce = openIDService.generateNonce()
@@ -40,7 +38,7 @@ class OpenIDController @Inject()(
}
}
def callback(provider: String) = Action.async { implicit request =>
def callback(provider: String): Action[AnyContent] = Action.async { implicit request =>
val sessionState = request.session.get("oauth_state")
val sessionNonce = request.session.get("oauth_nonce")
val sessionProvider = request.session.get("oauth_provider")
@@ -63,7 +61,7 @@ class OpenIDController @Inject()(
openIDService.getUserInfo(provider, tokenResponse.accessToken).map {
case Some(userInfo) =>
// Store user info in session for username selection
Redirect(config.get[String]("app.url") + "/select-username")
Redirect(config.get[String]("openid.selectUserRoute"))
.withSession(
"oauth_user_info" -> Json.toJson(userInfo).toString(),
"oauth_provider" -> provider,
@@ -81,7 +79,7 @@ class OpenIDController @Inject()(
}
}
def selectUsername() = Action.async { implicit request =>
def selectUsername(): Action[AnyContent] = Action.async { implicit request =>
request.session.get("oauth_user_info") match {
case Some(userInfoJson) =>
val userInfo = Json.parse(userInfoJson).as[OpenIDUserInfo]
@@ -90,14 +88,15 @@ class OpenIDController @Inject()(
"email" -> userInfo.email,
"name" -> userInfo.name,
"picture" -> userInfo.picture,
"provider" -> userInfo.provider
"provider" -> userInfo.provider,
"providerName" -> userInfo.providerName
)))
case None =>
Future.successful(Redirect("/login").flashing("error" -> "No authentication information found"))
}
}
def submitUsername() = Action.async { implicit request =>
def submitUsername(): Action[AnyContent] = Action.async { implicit request =>
val username = request.body.asJson.flatMap(json => (json \ "username").asOpt[String])
.orElse(request.body.asFormUrlEncoded.flatMap(_.get("username").flatMap(_.headOption)))
val userInfoJson = request.session.get("oauth_user_info")

View File

@@ -19,6 +19,12 @@ class StubUserManager @Inject()(config: Config) extends UserManager {
name = "Janis",
passwordHash = UserHash.hashPW("password123")
),
"Leon" -> User(
internalId = 2L,
id = java.util.UUID.randomUUID(),
name = "Jakob",
passwordHash = UserHash.hashPW("password123")
),
"Jakob" -> User(
internalId = 2L,
id = java.util.UUID.fromString("323e4567-e89b-12d3-a456-426614174000"),

View File

@@ -26,8 +26,11 @@ class UserSession(val user: User, val host: Boolean, val gameLobby: GameLobby) e
else canInteract = Some(InteractionType.Card)
case _ =>
}
lock.lock()
websocketActor.foreach(_.solveRequests())
websocketActor.foreach(_.transmitEventToClient(event))
lock.unlock()
}
override def id: UUID = user.id

View File

@@ -19,7 +19,8 @@ case class OpenIDUserInfo(
email: Option[String],
name: Option[String],
picture: Option[String],
provider: String
provider: String,
providerName: String
)
object OpenIDUserInfo {
@@ -51,7 +52,7 @@ class OpenIDConnectService@Inject(ws: WSClient, config: Configuration)(implicit
private val providers = Map(
"discord" -> OpenIDProvider(
name = "discord",
name = "Discord",
clientId = config.get[String]("openid.discord.clientId"),
clientSecret = config.get[String]("openid.discord.clientSecret"),
redirectUri = config.get[String]("openid.discord.redirectUri"),
@@ -61,7 +62,7 @@ class OpenIDConnectService@Inject(ws: WSClient, config: Configuration)(implicit
scopes = Set("identify", "email")
),
"keycloak" -> OpenIDProvider(
name = "keycloak",
name = "Identity",
clientId = config.get[String]("openid.keycloak.clientId"),
clientSecret = config.get[String]("openid.keycloak.clientSecret"),
redirectUri = config.get[String]("openid.keycloak.redirectUri"),
@@ -74,16 +75,30 @@ class OpenIDConnectService@Inject(ws: WSClient, config: Configuration)(implicit
def getAuthorizationUrl(providerName: String, state: String, nonce: String): Option[String] = {
providers.get(providerName).map { provider =>
val authRequest = new AuthenticationRequest.Builder(
new ResponseType(ResponseType.Value.CODE),
new com.nimbusds.oauth2.sdk.Scope(provider.scopes.mkString(" ")),
new com.nimbusds.oauth2.sdk.id.ClientID(provider.clientId),
URI.create(provider.redirectUri)
)
.state(new com.nimbusds.oauth2.sdk.id.State(state))
.nonce(new Nonce(nonce))
.endpointURI(URI.create(provider.authorizationEndpoint))
.build()
val authRequest = if (provider.scopes.contains("openid")) {
// Use OpenID Connect AuthenticationRequest for OpenID providers
new AuthenticationRequest.Builder(
new ResponseType(ResponseType.Value.CODE),
new com.nimbusds.oauth2.sdk.Scope(provider.scopes.mkString(" ")),
new com.nimbusds.oauth2.sdk.id.ClientID(provider.clientId),
URI.create(provider.redirectUri)
)
.state(new com.nimbusds.oauth2.sdk.id.State(state))
.nonce(new Nonce(nonce))
.endpointURI(URI.create(provider.authorizationEndpoint))
.build()
} else {
// Use standard OAuth2 AuthorizationRequest for non-OpenID providers (like Discord)
new AuthorizationRequest.Builder(
new ResponseType(ResponseType.Value.CODE),
new com.nimbusds.oauth2.sdk.id.ClientID(provider.clientId)
)
.scope(new com.nimbusds.oauth2.sdk.Scope(provider.scopes.mkString(" ")))
.state(new com.nimbusds.oauth2.sdk.id.State(state))
.redirectionURI(URI.create(provider.redirectUri))
.endpointURI(URI.create(provider.authorizationEndpoint))
.build()
}
authRequest.toURI.toString
}
@@ -139,7 +154,8 @@ class OpenIDConnectService@Inject(ws: WSClient, config: Configuration)(implicit
email = (json \ "email").asOpt[String],
name = (json \ "name").asOpt[String].orElse((json \ "login").asOpt[String]),
picture = (json \ "picture").asOpt[String].orElse((json \ "avatar_url").asOpt[String]),
provider = providerName
provider = providerName,
providerName = provider.name
))
} else {
None