diff --git a/build.gradle.kts b/build.gradle.kts index dfcdbbf..67e2fc1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,6 +28,7 @@ dependencies { implementation("io.quarkus:quarkus-hibernate-orm-panache-kotlin") implementation("io.quarkus:quarkus-jdbc-mariadb") implementation("io.quarkus:quarkus-kotlin") + implementation("io.quarkus:quarkus-oidc") implementation("io.quarkus:quarkus-smallrye-jwt") implementation("io.quarkus:quarkus-smallrye-health") implementation("io.quarkus:quarkus-rest-client-reactive-jackson") @@ -37,6 +38,8 @@ dependencies { implementation("io.quarkus:quarkus-hibernate-orm") implementation("io.quarkus:quarkus-resteasy-reactive") implementation("io.quarkus:quarkus-flyway") + implementation("io.jsonwebtoken:jjwt-impl:0.11.2") + implementation("io.jsonwebtoken:jjwt-jackson:0.11.2") implementation("org.flywaydb:flyway-mysql") testImplementation("io.quarkus:quarkus-junit5") testImplementation("io.rest-assured:rest-assured") diff --git a/src/main/kotlin/com/faforever/icebreaker/security/CustomTenantResolver.kt b/src/main/kotlin/com/faforever/icebreaker/security/CustomTenantResolver.kt new file mode 100644 index 0000000..e8fa9ea --- /dev/null +++ b/src/main/kotlin/com/faforever/icebreaker/security/CustomTenantResolver.kt @@ -0,0 +1,27 @@ +package com.faforever.icebreaker.security + +import com.fasterxml.jackson.databind.ObjectMapper +import io.quarkus.oidc.TenantResolver +import io.vertx.core.http.HttpHeaders.AUTHORIZATION +import io.vertx.ext.web.RoutingContext +import jakarta.enterprise.context.ApplicationScoped + + +@ApplicationScoped +class CustomTenantResolver( + private val objectMapper: ObjectMapper +): TenantResolver { + + override fun resolve(context: RoutingContext): String? = + context.request().getHeader(AUTHORIZATION) + ?.takeIf { it.startsWith("Bearer ") } + ?.let { + val rawToken = it.substring(7) + val body = java.util.Base64.getDecoder().decode(rawToken.split(".")[1]) + val json = objectMapper.readTree(body) + + json["iss"]?.textValue() + } + ?.takeIf { it == "https://ice.faforever.com" } + ?.let { "self-tenant" } +} \ No newline at end of file diff --git a/src/main/kotlin/com/faforever/icebreaker/web/SessionController.kt b/src/main/kotlin/com/faforever/icebreaker/web/SessionController.kt index cecf61b..0d3cbde 100644 --- a/src/main/kotlin/com/faforever/icebreaker/web/SessionController.kt +++ b/src/main/kotlin/com/faforever/icebreaker/web/SessionController.kt @@ -3,16 +3,44 @@ package com.faforever.icebreaker.web import com.faforever.icebreaker.service.Session import com.faforever.icebreaker.service.SessionService import io.quarkus.security.PermissionsAllowed +import io.quarkus.security.identity.SecurityIdentity +import io.smallrye.jwt.build.Jwt import jakarta.inject.Singleton import jakarta.ws.rs.GET import jakarta.ws.rs.Path import jakarta.ws.rs.Produces import jakarta.ws.rs.core.MediaType +import org.eclipse.microprofile.jwt.JsonWebToken import org.jboss.resteasy.reactive.RestPath +import java.time.Instant +import java.time.temporal.ChronoUnit @Path("/session") @Singleton -class SessionController(private val sessionService: SessionService) { +class SessionController( + private val sessionService: SessionService, + private val securityIdentity: SecurityIdentity?, +) { + + + @GET + @Path("/game/{gameId}/token") + fun buildToken(@RestPath gameId: Long): String { + val userId = when(val principal = securityIdentity?.principal) { + is JsonWebToken -> principal.subject.toInt() + else -> TODO() + } + + return Jwt.subject(userId.toString()) + .claim("gameId", gameId) + .issuer("https://ice.faforever.com") + .audience("https://ice.faforever.com") + .expiresAt(Instant.now().plus(24, ChronoUnit.HOURS)) +// .jws() +// .keyId("banana") + .signWithSecret("bananaPlusAttachmentToReach256BitLength") + } + @GET @Produces(MediaType.APPLICATION_JSON) @Path("/game/{gameId}") diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index dcc416d..89212ad 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -13,13 +13,35 @@ quarkus: physical-naming-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy flyway: migrate-at-start: true -mp: - jwt: - verify: - issuer: ${HYDRA_TOKEN_ISSUER:http://faf-ory-hydra:4444/} - key-format: JWKS - publickey: - location: ${HYDRA_JWKS_URL:http://localhost:4444/.well-known/jwks.json} + oidc: + auth-server-url: ${HYDRA_URL:http://localhost:4444} + # A tenant for our self-signed JWTs + # (also requires the CustomTenantResolver) + self-tenant: + # There is no .well-known/openid-configuration + auth-server-url: "https://ice.faforever.com" + discovery-enabled: false + token: + # Hard coded JWT settings, as there is no JWKS + authorization-scheme: Bearer + signature-algorithm: RS256 + # No Quarkus, there is really no JWKS! Stop looking for it. + jwks-uri: "" + forced-jwk-refresh-interval: 0S + allow-jwt-introspection: "true" + credentials: + jwt: + issuer: https://ice.faforever.com + audience: https://ice.faforever.com + # JWKS related settings part 2 + signature-algorithm: HS256 + secret: bananaPlusAttachmentToReach256BitLength + http: + auth: + permission: + authenticated: + paths: "/*" + policy: authenticated faf: environment: ${ENVIRONMENT:dev} @@ -31,7 +53,38 @@ xirsys: ident: ${XIRSYS_IDENT} secret: ${XIRSYS_SECRET} channel-namespace: "faf" - +#smallrye: +# jwt: +# sign: +# key: |- +# -----BEGIN PRIVATE KEY----- +# MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDXsCsl9W0vnW2k +# 5GaNOVoZ6LPFYu60Y1Cd4ERRXvt8KzKTm2HHZeLKd77OLeIHR4RvJ2Q76SFwfDBM +# 35F5eEx1mjPua2ljxbObsgz/bA9yBwO1RugpNOe+GoGUhPyZvmmZwqRnnQsT/SHV +# ZvRq7ej6k+KkJf09IIOxfWrGUj8SajW3iEpkuKdNpjp1dRnJdZAZ8mV1LgnwHCAf +# osL3t3+PElBSxnRQNW9iYVwB9wQAWK+aivx5warhuCeyKVtDaR0x96bOUTaKL4i/ +# Uihn0CElGt5ZA907wHa6/N4Z8ssXjY+/vizYB2VYxuAG/MVkkbwWUUTGjzEEX6Ww +# h5icvVm1AgMBAAECggEAAZYYGyVc8ja0MbxETNGZKgueFtuNaeI5G5AksHyEWPtw +# WcmQxIipTFfpHVcVDHyoKrEdeZtTVaJ0MHyMc1pBJbRGoYBEvCkeEw0SL2a6Dlqi +# 2lh1KKhs8+b6AP+hY/gUir71upVbGYCJGSqyrX6mcgFYb2CgJizxCwMjH+ZG9Hm0 +# CkGeh4g0VDOWmx4uCChXSyoaPzD4yTJts/EOpSD61KqS+cNcnRD8PVUxwwSH+4DY +# ZSuaAUC/kFvD4qQq2lY/eia2CQi2R1Ff2TCxcbNZ34yW8IR1UBdrOPo3orQK5vSf +# iT5++MYJmTZJ8/QxY5M1nZqiyJEjTvaBQNGv8abKWQKBgQDxd/5lJkc13x8jPFJm +# EnmPvxrJaYk3MLW3dtxz1HtjHDQAvCmjXy7Ss13WhLJv9nHJDtQlSRr+l+7eNPTP +# QtiwDsqv9COfbPbvH2qcNJuNoINQ2YSKYvR0j+QlMz2dHroWEyXL4oyOfXAJ3ZrU +# lyWn/a2BD3uiJAj4p8YzJgfgnwKBgQDkqwGC6AMLPbVmhCMnUd+cxFMkYymdi8R4 +# ZXMkjJiMLAOt8tkp8T0nqxC/zMfD0jnPKw1R9MP7XlM/tonLeAM/P8GUMwJnTCTc +# PvP1JxkvMG3do+7y9AbLyJsNZDkbYj1wLzvZYUrXQV/HKU4balDj3QVI6yr+W6ha +# idlsMDYBKwKBgBeuF9GdlmAvGGOhN8dwymERcbQM2HsEGN38FxR44vzOOD9WNJMj +# 83iQRISUENewCGqaPK3HZJFRHwjFkrh8qrlhSflFbPTmf7TllNPqyNJzykz0d+4G +# VEjWD56iTsmIyOD/UbaT6grTPFiLVfLBO90koI5GkW5OMF8KPQKpGR6rAoGAU5NQ +# 1RiZbDVcpKBs/MUG1pRG0wjPP/7Ci0KBB/2/D5RSr/QPfS3nrSTv1ToyVRbz/Az/ +# LFIqgyghgyrjSBOQFEDoLpNKMJj66+iyX4qvwLiRny14eyHHjhm+2fEkkiagz+zj +# kfrmULBbIj6thoWgFPhGIzWYnCjB6n1xkwI36ssCgYACiZNHvqld4Om2IChCjIV6 +# UPNLUDOvr7V1qsEy+y0dp2RQH9Es121n/v30GYfsUUYmH35CQYR0aOEqU17Qm2V7 +# 1auC1ZD9UeE9dy2LpW635uYf16D5FejAcmxyf/MRSBBnvFauGdS2vZ7Pf05u9Zpw +# i8UgZE7+lTYKv7+4ujmgHw== +# -----END PRIVATE KEY----- "%dev": quarkus: log: @@ -39,4 +92,6 @@ xirsys: "org.hibernate.SQL": level: DEBUG "com.faforever": + level: DEBUG + "io.quarkus.oidc": level: DEBUG \ No newline at end of file