feat(security): add internal secret handling and Redis integration for bot events
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
plugins {
|
||||
id("scala")
|
||||
id("org.scoverage") version "8.1"
|
||||
}
|
||||
|
||||
group = "de.nowchess"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val versions = rootProject.extra["VERSIONS"] as Map<String, String>
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
scala {
|
||||
scalaVersion = versions["SCALA3"]!!
|
||||
}
|
||||
|
||||
scoverage {
|
||||
scoverageVersion.set(versions["SCOVERAGE"]!!)
|
||||
}
|
||||
|
||||
tasks.withType<ScalaCompile> {
|
||||
scalaCompileOptions.additionalParameters = listOf("-encoding", "UTF-8")
|
||||
}
|
||||
|
||||
val quarkusPlatformGroupId: String by project
|
||||
val quarkusPlatformArtifactId: String by project
|
||||
val quarkusPlatformVersion: String by project
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.scala-lang:scala3-compiler_3") {
|
||||
version { strictly(versions["SCALA3"]!!) }
|
||||
}
|
||||
implementation("org.scala-lang:scala3-library_3") {
|
||||
version { strictly(versions["SCALA3"]!!) }
|
||||
}
|
||||
|
||||
compileOnly(platform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
|
||||
compileOnly("io.quarkus:quarkus-rest")
|
||||
compileOnly("io.quarkus:quarkus-rest-client")
|
||||
compileOnly("io.quarkus:quarkus-grpc")
|
||||
compileOnly("io.quarkus:quarkus-arc")
|
||||
}
|
||||
|
||||
configurations.matching { !it.name.startsWith("scoverage") }.configureEach {
|
||||
resolutionStrategy.force("org.scala-lang:scala-library:${versions["SCALA_LIBRARY"]!!}")
|
||||
}
|
||||
configurations.scoverage {
|
||||
resolutionStrategy.eachDependency {
|
||||
if (requested.group == "org.scoverage" && requested.name.startsWith("scalac-scoverage-plugin_")) {
|
||||
useTarget("${requested.group}:scalac-scoverage-plugin_2.13.16:2.3.0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs.add("-parameters")
|
||||
}
|
||||
|
||||
tasks.withType<Jar>().configureEach {
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.nowchess.security;
|
||||
|
||||
import jakarta.ws.rs.NameBinding;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@NameBinding
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
public @interface InternalOnly {}
|
||||
@@ -0,0 +1,23 @@
|
||||
package de.nowchess.security
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped
|
||||
import jakarta.ws.rs.container.{ContainerRequestContext, ContainerRequestFilter}
|
||||
import jakarta.ws.rs.core.Response
|
||||
import jakarta.ws.rs.ext.Provider
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty
|
||||
import scala.compiletime.uninitialized
|
||||
|
||||
@Provider
|
||||
@InternalOnly
|
||||
@ApplicationScoped
|
||||
class InternalAuthFilter extends ContainerRequestFilter:
|
||||
|
||||
@ConfigProperty(name = "nowchess.internal.secret")
|
||||
// scalafix:off DisableSyntax.var
|
||||
var secret: String = uninitialized
|
||||
// scalafix:on DisableSyntax.var
|
||||
|
||||
override def filter(ctx: ContainerRequestContext): Unit =
|
||||
val header = ctx.getHeaderString("X-Internal-Secret")
|
||||
if header == null || header != secret then
|
||||
ctx.abortWith(Response.status(Response.Status.UNAUTHORIZED).build())
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package de.nowchess.security
|
||||
|
||||
import io.grpc.{Metadata, ServerCall, ServerCallHandler, ServerInterceptor, Status}
|
||||
import io.quarkus.grpc.GlobalInterceptor
|
||||
import jakarta.enterprise.context.ApplicationScoped
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty
|
||||
import scala.compiletime.uninitialized
|
||||
|
||||
@GlobalInterceptor
|
||||
@ApplicationScoped
|
||||
class InternalGrpcAuthInterceptor extends ServerInterceptor:
|
||||
|
||||
private val secretKey = Metadata.Key.of("x-internal-secret", Metadata.ASCII_STRING_MARSHALLER)
|
||||
|
||||
@ConfigProperty(name = "nowchess.internal.secret")
|
||||
// scalafix:off DisableSyntax.var
|
||||
var secret: String = uninitialized
|
||||
// scalafix:on DisableSyntax.var
|
||||
|
||||
override def interceptCall[Req, Resp](
|
||||
call: ServerCall[Req, Resp],
|
||||
headers: Metadata,
|
||||
next: ServerCallHandler[Req, Resp],
|
||||
): ServerCall.Listener[Req] =
|
||||
val token = Option(headers.get(secretKey)).getOrElse("")
|
||||
if token != secret then
|
||||
call.close(Status.UNAUTHENTICATED.withDescription("Missing or invalid internal secret"), new Metadata())
|
||||
new ServerCall.Listener[Req] {}
|
||||
else
|
||||
next.startCall(call, headers)
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package de.nowchess.security
|
||||
|
||||
import io.grpc.{CallOptions, Channel, ClientCall, ClientInterceptor, ForwardingClientCall, Metadata, MethodDescriptor}
|
||||
import io.quarkus.grpc.GlobalInterceptor
|
||||
import jakarta.enterprise.context.ApplicationScoped
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty
|
||||
import scala.compiletime.uninitialized
|
||||
|
||||
@GlobalInterceptor
|
||||
@ApplicationScoped
|
||||
class InternalGrpcSecretClientInterceptor extends ClientInterceptor:
|
||||
|
||||
private val secretKey = Metadata.Key.of("x-internal-secret", Metadata.ASCII_STRING_MARSHALLER)
|
||||
|
||||
@ConfigProperty(name = "nowchess.internal.secret")
|
||||
// scalafix:off DisableSyntax.var
|
||||
var secret: String = uninitialized
|
||||
// scalafix:on DisableSyntax.var
|
||||
|
||||
override def interceptCall[Req, Resp](
|
||||
method: MethodDescriptor[Req, Resp],
|
||||
callOptions: CallOptions,
|
||||
next: Channel,
|
||||
): ClientCall[Req, Resp] =
|
||||
new ForwardingClientCall.SimpleForwardingClientCall[Req, Resp](next.newCall(method, callOptions)):
|
||||
override def start(responseListener: ClientCall.Listener[Resp], headers: Metadata): Unit =
|
||||
headers.put(secretKey, secret)
|
||||
super.start(responseListener, headers)
|
||||
@@ -0,0 +1,17 @@
|
||||
package de.nowchess.security
|
||||
|
||||
import jakarta.enterprise.context.ApplicationScoped
|
||||
import jakarta.ws.rs.client.{ClientRequestContext, ClientRequestFilter}
|
||||
import org.eclipse.microprofile.config.inject.ConfigProperty
|
||||
import scala.compiletime.uninitialized
|
||||
|
||||
@ApplicationScoped
|
||||
class InternalSecretClientFilter extends ClientRequestFilter:
|
||||
|
||||
@ConfigProperty(name = "nowchess.internal.secret")
|
||||
// scalafix:off DisableSyntax.var
|
||||
var secret: String = uninitialized
|
||||
// scalafix:on DisableSyntax.var
|
||||
|
||||
override def filter(ctx: ClientRequestContext): Unit =
|
||||
ctx.getHeaders.putSingle("X-Internal-Secret", secret)
|
||||
Reference in New Issue
Block a user