This is an automated email from the ASF dual-hosted git repository.

kunwp1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git


The following commit(s) were added to refs/heads/main by this push:
     new 62d4489600 fix: Revert "fix(auth): JwtAuthFilter eager-401 with 
@PermitAll opt-out" (#5025)
62d4489600 is described below

commit 62d448960084019ef2be068b88f67d6b224e69bb
Author: Kunwoo (Chris) <[email protected]>
AuthorDate: Mon May 11 14:06:39 2026 -0700

    fix: Revert "fix(auth): JwtAuthFilter eager-401 with @PermitAll opt-out" 
(#5025)
    
    ### What changes were proposed in this PR?
    
    Reverts apache/texera#4903 because it's causing a side-effect on the JWT
    authentication in ConfigService.
    
    ### Any related issues, documentation, discussions?
    
    Closes #5026
    
    ### How was this PR tested?
    
    Run texera frontend in incognito mode (local cache cleared).
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    No.
---
 .../texera/service/AccessControlService.scala      |   8 +-
 .../auth/UnauthorizedExceptionMapperSpec.scala     |  59 ----
 amber/LICENSE-binary-java                          |   1 -
 common/auth/build.sbt                              |   1 -
 .../org/apache/texera/auth/JwtAuthFilter.scala     |  96 ++-----
 .../apache/texera/auth/UnauthorizedException.scala |  57 ----
 .../org/apache/texera/auth/JwtAuthFilterSpec.scala | 313 ---------------------
 .../service/ComputingUnitManagingService.scala     |   8 +-
 .../org/apache/texera/service/ConfigService.scala  |   8 +-
 .../org/apache/texera/service/FileService.scala    |   8 +-
 .../texera/service/resource/DatasetResource.scala  |   7 +-
 11 files changed, 25 insertions(+), 541 deletions(-)

diff --git 
a/access-control-service/src/main/scala/org/apache/texera/service/AccessControlService.scala
 
b/access-control-service/src/main/scala/org/apache/texera/service/AccessControlService.scala
index 0cb5738419..21d367e2bb 100644
--- 
a/access-control-service/src/main/scala/org/apache/texera/service/AccessControlService.scala
+++ 
b/access-control-service/src/main/scala/org/apache/texera/service/AccessControlService.scala
@@ -24,12 +24,7 @@ import 
io.dropwizard.configuration.{EnvironmentVariableSubstitutor, Substituting
 import io.dropwizard.core.Application
 import io.dropwizard.core.setup.{Bootstrap, Environment}
 import org.apache.texera.amber.config.StorageConfig
-import org.apache.texera.auth.{
-  JwtAuthFilter,
-  RequestLoggingFilter,
-  SessionUser,
-  UnauthorizedExceptionMapper
-}
+import org.apache.texera.auth.{JwtAuthFilter, RequestLoggingFilter, 
SessionUser}
 import org.apache.texera.dao.SqlServer
 import org.apache.texera.service.activity.UserActivityEventListener
 import org.apache.texera.service.resource.{
@@ -77,7 +72,6 @@ class AccessControlService extends 
Application[AccessControlServiceConfiguration
 
     // Register JWT authentication filter
     environment.jersey.register(new AuthDynamicFeature(classOf[JwtAuthFilter]))
-    environment.jersey.register(classOf[UnauthorizedExceptionMapper])
 
     // Enable @Auth annotation for injecting SessionUser
     environment.jersey.register(
diff --git 
a/access-control-service/src/test/scala/org/apache/texera/auth/UnauthorizedExceptionMapperSpec.scala
 
b/access-control-service/src/test/scala/org/apache/texera/auth/UnauthorizedExceptionMapperSpec.scala
deleted file mode 100644
index cd897c896b..0000000000
--- 
a/access-control-service/src/test/scala/org/apache/texera/auth/UnauthorizedExceptionMapperSpec.scala
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.texera.auth
-
-import jakarta.ws.rs.core.HttpHeaders
-import org.scalatest.flatspec.AnyFlatSpec
-import org.scalatest.matchers.should.Matchers
-
-class UnauthorizedExceptionMapperSpec extends AnyFlatSpec with Matchers {
-
-  // The mapper sits behind every microservice's `environment.jersey.register(
-  // classOf[UnauthorizedExceptionMapper])` wiring. JwtAuthFilter throws
-  // `UnauthorizedException(challenge)` (covered by JwtAuthFilterSpec); this
-  // spec pins what the mapper turns that exception into when JAX-RS calls
-  // `toResponse` at the edge.
-
-  private val mapper = new UnauthorizedExceptionMapper
-
-  "UnauthorizedExceptionMapper" should "map any UnauthorizedException to HTTP 
401" in {
-    val response = mapper.toResponse(new UnauthorizedException("Bearer 
realm=\"texera\""))
-    response.getStatus shouldBe 401
-  }
-
-  it should "carry the exception's challenge string verbatim in the 
WWW-Authenticate header" in {
-    // The challenge is RFC 6750 §3 syntax. The mapper must not rewrite it —
-    // JwtAuthFilter is the single source of truth for which challenge fires
-    // (Bearer vs. Bearer + invalid_token), and any rewrite here would mask
-    // a regression in the filter.
-    val challenge =
-      """Bearer realm="texera", error="invalid_token", error_description="JWT 
verification failed""""
-    val response = mapper.toResponse(new UnauthorizedException(challenge))
-    response.getHeaderString(HttpHeaders.WWW_AUTHENTICATE) shouldBe challenge
-  }
-
-  it should "produce no entity body — only status + challenge header" in {
-    // Browsers and curl expect `WWW-Authenticate` on a body-less 401; an
-    // accidental JSON entity (e.g. via Dropwizard's default error mapper)
-    // would suppress the auth challenge prompt in some clients.
-    val response = mapper.toResponse(new UnauthorizedException("Bearer 
realm=\"texera\""))
-    response.hasEntity shouldBe false
-  }
-}
diff --git a/amber/LICENSE-binary-java b/amber/LICENSE-binary-java
index d413b9f8d2..8fbd626ca4 100644
--- a/amber/LICENSE-binary-java
+++ b/amber/LICENSE-binary-java
@@ -630,7 +630,6 @@ licensed with GPL-2.0 with Classpath Exception)
 
--------------------------------------------------------------------------------
 
 Scala/Java jars:
-  - jakarta.annotation.jakarta.annotation-api-2.1.1.jar
   - jakarta.ws.rs.jakarta.ws.rs-api-3.0.0.jar
   - javax.ws.rs.javax.ws.rs-api-2.1.1.jar
   - org.jgrapht.jgrapht-core-1.4.0.jar
diff --git a/common/auth/build.sbt b/common/auth/build.sbt
index 24feb1984f..a33da64fea 100644
--- a/common/auth/build.sbt
+++ b/common/auth/build.sbt
@@ -57,7 +57,6 @@ libraryDependencies ++= Seq(
   "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5",            // for 
LazyLogging
   "org.bitbucket.b_c" % "jose4j" % "0.9.6",                             // for 
jwt parser
   "jakarta.ws.rs" % "jakarta.ws.rs-api" % "3.0.0",                      // for 
JwtAuthFilter
-  "jakarta.annotation" % "jakarta.annotation-api" % "2.1.1",            // for 
@PermitAll opt-out in JwtAuthFilter
   "jakarta.servlet" % "jakarta.servlet-api" % "5.0.0" % "provided",    // for 
RequestLoggingFilter
   "org.eclipse.jetty" % "jetty-servlet" % "11.0.24" % "provided",      // for 
FilterHolder
   "org.scalatest" %% "scalatest" % "3.2.17" % Test
diff --git 
a/common/auth/src/main/scala/org/apache/texera/auth/JwtAuthFilter.scala 
b/common/auth/src/main/scala/org/apache/texera/auth/JwtAuthFilter.scala
index 779618726c..5698515630 100644
--- a/common/auth/src/main/scala/org/apache/texera/auth/JwtAuthFilter.scala
+++ b/common/auth/src/main/scala/org/apache/texera/auth/JwtAuthFilter.scala
@@ -20,91 +20,35 @@
 package org.apache.texera.auth
 
 import com.typesafe.scalalogging.LazyLogging
-import jakarta.annotation.security.PermitAll
-import jakarta.ws.rs.container.{ContainerRequestContext, 
ContainerRequestFilter, ResourceInfo}
-import jakarta.ws.rs.core.{Context, HttpHeaders, SecurityContext}
+import jakarta.ws.rs.container.{ContainerRequestContext, 
ContainerRequestFilter}
+import jakarta.ws.rs.core.{HttpHeaders, SecurityContext}
 import jakarta.ws.rs.ext.Provider
 import org.apache.texera.dao.jooq.generated.enums.UserRoleEnum
 
 import java.security.Principal
 
-/** JAX-RS request filter that authenticates a Bearer JWT and installs a
-  * [[SessionUser]] security context.
-  *
-  * Failure semantics (RFC 6750 §3):
-  *   - No `Authorization: Bearer …` header: throw [[UnauthorizedException]]
-  *     carrying a bare `Bearer realm="texera"` challenge — unless the
-  *     resource method or class is annotated with `@PermitAll`, in which
-  *     case the request continues with no security context. This supports
-  *     the `@Auth Optional[SessionUser]` pattern for endpoints that need
-  *     to serve anonymous users.
-  *   - Header present but token verification / claim extraction fails:
-  *     throw [[UnauthorizedException]] with `error="invalid_token"`
-  *     always, even on `@PermitAll` endpoints — a tampered or stale token
-  *     is never silently treated as anonymous.
-  *   - Header present and valid: install a `SecurityContext` whose
-  *     principal is the parsed [[SessionUser]].
-  *
-  * HTTP translation (status 401, `WWW-Authenticate` header) is done by
-  * [[UnauthorizedExceptionMapper]], registered alongside this filter in
-  * each service.
-  */
 @Provider
 class JwtAuthFilter extends ContainerRequestFilter with LazyLogging {
 
-  @Context
-  private var resourceInfo: ResourceInfo = _
-
   override def filter(requestContext: ContainerRequestContext): Unit = {
-    val tokenOpt = 
extractBearerToken(requestContext.getHeaderString(HttpHeaders.AUTHORIZATION))
-
-    if (tokenOpt.isEmpty) {
-      if (isPermitAll) return
-      throw new UnauthorizedException(JwtAuthFilter.BearerChallenge)
-    }
-
-    val userOpt = JwtParser.parseToken(tokenOpt.get)
-    if (!userOpt.isPresent) {
-      logger.warn("Invalid JWT: Unable to parse token")
-      throw new UnauthorizedException(JwtAuthFilter.InvalidTokenChallenge)
+    val authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION)
+
+    if (authHeader != null && authHeader.startsWith("Bearer ")) {
+      val token = authHeader.substring(7) // Remove "Bearer " prefix
+      val userOpt = JwtParser.parseToken(token)
+
+      if (userOpt.isPresent) {
+        val user = userOpt.get()
+        requestContext.setSecurityContext(new SecurityContext {
+          override def getUserPrincipal: Principal = user
+          override def isUserInRole(role: String): Boolean =
+            user.isRoleOf(UserRoleEnum.valueOf(role))
+          override def isSecure: Boolean = false
+          override def getAuthenticationScheme: String = "Bearer"
+        })
+      } else {
+        logger.warn("Invalid JWT: Unable to parse token")
+      }
     }
-
-    val user = userOpt.get()
-    requestContext.setSecurityContext(new SecurityContext {
-      override def getUserPrincipal: Principal = user
-      override def isUserInRole(role: String): Boolean =
-        user.isRoleOf(UserRoleEnum.valueOf(role))
-      override def isSecure: Boolean = false
-      override def getAuthenticationScheme: String = "Bearer"
-    })
   }
-
-  private def isPermitAll: Boolean = {
-    if (resourceInfo == null) return false
-    val m = resourceInfo.getResourceMethod
-    val c = resourceInfo.getResourceClass
-    (m != null && m.isAnnotationPresent(classOf[PermitAll])) ||
-    (c != null && c.isAnnotationPresent(classOf[PermitAll]))
-  }
-
-  // RFC 7235 §2.1: auth-scheme is case-insensitive and the credentials
-  // follow after 1*SP. Tolerate surrounding whitespace and any
-  // capitalization of "Bearer" so that e.g. `authorization: bearer <jwt>`
-  // is accepted instead of being rejected as a malformed header.
-  private def extractBearerToken(authHeader: String): Option[String] = {
-    if (authHeader == null) return None
-    val parts = authHeader.trim.split("\\s+", 2)
-    if (parts.length != 2 || !parts(0).equalsIgnoreCase("Bearer")) return None
-    val token = parts(1).trim
-    if (token.isEmpty) None else Some(token)
-  }
-}
-
-object JwtAuthFilter {
-  // RFC 6750 §3: bare challenge = "please authenticate". The
-  // `error="invalid_token"` parameter signals "the token you sent is
-  // malformed / expired / signature failed" so a well-behaved client can
-  // discard it instead of retrying.
-  val BearerChallenge: String = "Bearer realm=\"texera\""
-  val InvalidTokenChallenge: String = "Bearer realm=\"texera\", 
error=\"invalid_token\""
 }
diff --git 
a/common/auth/src/main/scala/org/apache/texera/auth/UnauthorizedException.scala 
b/common/auth/src/main/scala/org/apache/texera/auth/UnauthorizedException.scala
deleted file mode 100644
index 646f8a1101..0000000000
--- 
a/common/auth/src/main/scala/org/apache/texera/auth/UnauthorizedException.scala
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.texera.auth
-
-import jakarta.ws.rs.core.{HttpHeaders, Response}
-import jakarta.ws.rs.ext.{ExceptionMapper, Provider}
-
-/** Carries an RFC 6750 §3 `WWW-Authenticate: Bearer …` challenge to be
-  * returned alongside a `401 Unauthorized` response.
-  *
-  * Extends `RuntimeException` (not `WebApplicationException`) so it can be
-  * constructed without a JAX-RS `RuntimeDelegate` on the classpath, which
-  * keeps unit tests for [[JwtAuthFilter]] independent of any Jersey
-  * implementation. The companion [[UnauthorizedExceptionMapper]] converts
-  * the exception to the actual HTTP response at the JAX-RS edge.
-  *
-  * Constructed with `writableStackTrace = false` because this exception is
-  * thrown on every unauthenticated request and the stack trace is never
-  * inspected — skipping `fillInStackTrace` avoids a per-request CPU hit on
-  * the auth hot path.
-  */
-class UnauthorizedException(val challenge: String)
-    extends RuntimeException(
-      challenge,
-      /* cause = */ null,
-      /* enableSuppression = */ false,
-      /* writableStackTrace = */ false
-    )
-
-/** Maps [[UnauthorizedException]] to a `401` response with the carried
-  * `WWW-Authenticate` challenge header.
-  */
-@Provider
-class UnauthorizedExceptionMapper extends 
ExceptionMapper[UnauthorizedException] {
-  override def toResponse(e: UnauthorizedException): Response =
-    Response
-      .status(Response.Status.UNAUTHORIZED)
-      .header(HttpHeaders.WWW_AUTHENTICATE, e.challenge)
-      .build()
-}
diff --git 
a/common/auth/src/test/scala/org/apache/texera/auth/JwtAuthFilterSpec.scala 
b/common/auth/src/test/scala/org/apache/texera/auth/JwtAuthFilterSpec.scala
deleted file mode 100644
index dfab579d48..0000000000
--- a/common/auth/src/test/scala/org/apache/texera/auth/JwtAuthFilterSpec.scala
+++ /dev/null
@@ -1,313 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.texera.auth
-
-import jakarta.annotation.security.PermitAll
-import jakarta.ws.rs.container.{ContainerRequestContext, ResourceInfo}
-import jakarta.ws.rs.core.{HttpHeaders, Response, SecurityContext}
-import org.apache.texera.dao.jooq.generated.enums.UserRoleEnum
-import org.jose4j.jwt.JwtClaims
-import org.scalatest.flatspec.AnyFlatSpec
-import org.scalatest.matchers.should.Matchers
-
-import java.lang.reflect.{Field, Method}
-import java.util.concurrent.atomic.AtomicReference
-
-class JwtAuthFilterSpec extends AnyFlatSpec with Matchers {
-
-  // Minimal stand-in for ContainerRequestContext. The filter only reads the
-  // Authorization header and writes a SecurityContext; everything else is
-  // unimplemented.
-  private class StubRequestContext(authHeader: String) extends 
ContainerRequestContext {
-    val securityContext = new AtomicReference[SecurityContext](null)
-
-    override def getHeaderString(name: String): String =
-      if (name == HttpHeaders.AUTHORIZATION) authHeader else null
-    override def setSecurityContext(context: SecurityContext): Unit = 
securityContext.set(context)
-    override def getSecurityContext: SecurityContext = securityContext.get()
-
-    // unused
-    override def abortWith(response: Response): Unit = ()
-    override def getProperty(x$1: String): Object = null
-    override def getPropertyNames: java.util.Collection[String] =
-      java.util.Collections.emptyList()
-    override def setProperty(x$1: String, x$2: Object): Unit = ()
-    override def removeProperty(x$1: String): Unit = ()
-    override def getRequest: jakarta.ws.rs.core.Request = null
-    override def getMethod: String = null
-    override def setMethod(x$1: String): Unit = ()
-    override def getUriInfo: jakarta.ws.rs.core.UriInfo = null
-    override def setRequestUri(x$1: java.net.URI): Unit = ()
-    override def setRequestUri(x$1: java.net.URI, x$2: java.net.URI): Unit = ()
-    override def getHeaders: jakarta.ws.rs.core.MultivaluedMap[String, String] 
= null
-    override def getCookies: java.util.Map[String, jakarta.ws.rs.core.Cookie] 
= null
-    override def getDate: java.util.Date = null
-    override def getLanguage: java.util.Locale = null
-    override def getLength: Int = 0
-    override def getMediaType: jakarta.ws.rs.core.MediaType = null
-    override def getAcceptableMediaTypes: 
java.util.List[jakarta.ws.rs.core.MediaType] = null
-    override def getAcceptableLanguages: java.util.List[java.util.Locale] = 
null
-    override def hasEntity: Boolean = false
-    override def getEntityStream: java.io.InputStream = null
-    override def setEntityStream(x$1: java.io.InputStream): Unit = ()
-  }
-
-  // Inject @Context ResourceInfo via reflection so tests can flip annotation
-  // states per-case without spinning up Jersey.
-  private def withResourceInfo(filter: JwtAuthFilter, info: ResourceInfo): 
Unit = {
-    val f: Field = classOf[JwtAuthFilter].getDeclaredField("resourceInfo")
-    f.setAccessible(true)
-    f.set(filter, info)
-  }
-
-  private class StubResourceInfo(method: Method, cls: Class[_]) extends 
ResourceInfo {
-    override def getResourceMethod: Method = method
-    override def getResourceClass: Class[_] = cls
-  }
-
-  private def methodOf(cls: Class[_], name: String): Method =
-    cls.getDeclaredMethods.find(_.getName == name).get
-
-  private class RequiredAuthResource { def secured(): Unit = () }
-  private class OptionalAuthResource { @PermitAll def cover(): Unit = () }
-  @PermitAll private class OpenResource { def anything(): Unit = () }
-
-  private def buildClaims(): JwtClaims = {
-    val c = new JwtClaims
-    c.setSubject("alice")
-    c.setClaim("userId", 42)
-    c.setClaim("googleId", "g-123")
-    c.setClaim("email", "[email protected]")
-    c.setClaim("role", UserRoleEnum.ADMIN.name)
-    c.setClaim("googleAvatar", "avatar")
-    c.setExpirationTimeMinutesInTheFuture(10f)
-    c
-  }
-
-  // -------------------- challenge constants --------------------
-
-  "JwtAuthFilter constants" should "match RFC 6750 §3 challenge syntax" in {
-    JwtAuthFilter.BearerChallenge shouldBe "Bearer realm=\"texera\""
-    JwtAuthFilter.InvalidTokenChallenge shouldBe "Bearer realm=\"texera\", 
error=\"invalid_token\""
-  }
-
-  // -------------------- required-auth method --------------------
-
-  "JwtAuthFilter on a required-auth method" should "throw 
UnauthorizedException(BearerChallenge) when no Authorization header is present" 
in {
-    val filter = new JwtAuthFilter
-    withResourceInfo(
-      filter,
-      new StubResourceInfo(
-        methodOf(classOf[RequiredAuthResource], "secured"),
-        classOf[RequiredAuthResource]
-      )
-    )
-    val ctx = new StubRequestContext(null)
-    val thrown = the[UnauthorizedException] thrownBy filter.filter(ctx)
-    thrown.challenge shouldBe JwtAuthFilter.BearerChallenge
-    ctx.getSecurityContext shouldBe null
-  }
-
-  it should "throw UnauthorizedException(BearerChallenge) when the header is 
not a Bearer token" in {
-    val filter = new JwtAuthFilter
-    withResourceInfo(
-      filter,
-      new StubResourceInfo(
-        methodOf(classOf[RequiredAuthResource], "secured"),
-        classOf[RequiredAuthResource]
-      )
-    )
-    val ctx = new StubRequestContext("Basic abc")
-    val thrown = the[UnauthorizedException] thrownBy filter.filter(ctx)
-    thrown.challenge shouldBe JwtAuthFilter.BearerChallenge
-  }
-
-  it should "throw UnauthorizedException(InvalidTokenChallenge) when the 
Bearer token cannot be verified" in {
-    val filter = new JwtAuthFilter
-    withResourceInfo(
-      filter,
-      new StubResourceInfo(
-        methodOf(classOf[RequiredAuthResource], "secured"),
-        classOf[RequiredAuthResource]
-      )
-    )
-    val ctx = new StubRequestContext("Bearer not-a-real-jwt")
-    val thrown = the[UnauthorizedException] thrownBy filter.filter(ctx)
-    thrown.challenge shouldBe JwtAuthFilter.InvalidTokenChallenge
-  }
-
-  it should "install a SecurityContext with the parsed SessionUser when the 
token is valid" in {
-    val filter = new JwtAuthFilter
-    withResourceInfo(
-      filter,
-      new StubResourceInfo(
-        methodOf(classOf[RequiredAuthResource], "secured"),
-        classOf[RequiredAuthResource]
-      )
-    )
-    val ctx = new StubRequestContext(s"Bearer 
${JwtAuth.jwtToken(buildClaims())}")
-
-    filter.filter(ctx)
-
-    val sc = ctx.getSecurityContext
-    sc should not be null
-    sc.getUserPrincipal.asInstanceOf[SessionUser].getUid shouldBe 42
-    sc.getAuthenticationScheme shouldBe "Bearer"
-    sc.isUserInRole(UserRoleEnum.ADMIN.name) shouldBe true
-    sc.isUserInRole(UserRoleEnum.REGULAR.name) shouldBe false
-  }
-
-  // -------------------- @PermitAll opt-out --------------------
-
-  "JwtAuthFilter on a @PermitAll method" should "let an unauthenticated 
request pass through with no SecurityContext" in {
-    val filter = new JwtAuthFilter
-    withResourceInfo(
-      filter,
-      new StubResourceInfo(
-        methodOf(classOf[OptionalAuthResource], "cover"),
-        classOf[OptionalAuthResource]
-      )
-    )
-    val ctx = new StubRequestContext(null)
-    filter.filter(ctx) // must NOT throw
-    ctx.getSecurityContext shouldBe null
-  }
-
-  it should "still throw UnauthorizedException(InvalidTokenChallenge) when a 
token is supplied but invalid" in {
-    val filter = new JwtAuthFilter
-    withResourceInfo(
-      filter,
-      new StubResourceInfo(
-        methodOf(classOf[OptionalAuthResource], "cover"),
-        classOf[OptionalAuthResource]
-      )
-    )
-    val ctx = new StubRequestContext("Bearer not-a-real-jwt")
-    val thrown = the[UnauthorizedException] thrownBy filter.filter(ctx)
-    thrown.challenge shouldBe JwtAuthFilter.InvalidTokenChallenge
-  }
-
-  it should "install a SecurityContext when a valid token is supplied" in {
-    val filter = new JwtAuthFilter
-    withResourceInfo(
-      filter,
-      new StubResourceInfo(
-        methodOf(classOf[OptionalAuthResource], "cover"),
-        classOf[OptionalAuthResource]
-      )
-    )
-    val ctx = new StubRequestContext(s"Bearer 
${JwtAuth.jwtToken(buildClaims())}")
-    filter.filter(ctx)
-    ctx.getSecurityContext.getUserPrincipal.asInstanceOf[SessionUser].getUid 
shouldBe 42
-  }
-
-  "JwtAuthFilter on a class-level @PermitAll" should "honor the class 
annotation when the method has none" in {
-    val filter = new JwtAuthFilter
-    withResourceInfo(
-      filter,
-      new StubResourceInfo(methodOf(classOf[OpenResource], "anything"), 
classOf[OpenResource])
-    )
-    val ctx = new StubRequestContext(null)
-    filter.filter(ctx) // must NOT throw
-    ctx.getSecurityContext shouldBe null
-  }
-
-  "JwtAuthFilter without resource info" should "default to required-auth 
(eager 401)" in {
-    val filter = new JwtAuthFilter
-    // resourceInfo left as null — pre-matching path or test scenario
-    val ctx = new StubRequestContext(null)
-    val thrown = the[UnauthorizedException] thrownBy filter.filter(ctx)
-    thrown.challenge shouldBe JwtAuthFilter.BearerChallenge
-  }
-
-  // -------------------- case-insensitive Bearer scheme --------------------
-
-  // RFC 7235 §2.1: auth-scheme is case-insensitive. The header parser must
-  // accept any capitalization of "Bearer" and tolerate surrounding /
-  // intra-header whitespace.
-  private def filterFor(authHeader: String): StubRequestContext = {
-    val filter = new JwtAuthFilter
-    withResourceInfo(
-      filter,
-      new StubResourceInfo(
-        methodOf(classOf[RequiredAuthResource], "secured"),
-        classOf[RequiredAuthResource]
-      )
-    )
-    val ctx = new StubRequestContext(authHeader)
-    filter.filter(ctx)
-    ctx
-  }
-
-  "JwtAuthFilter Bearer scheme parsing" should "accept lowercase 'bearer'" in {
-    val ctx = filterFor(s"bearer ${JwtAuth.jwtToken(buildClaims())}")
-    ctx.getSecurityContext.getUserPrincipal.asInstanceOf[SessionUser].getUid 
shouldBe 42
-  }
-
-  it should "accept uppercase 'BEARER'" in {
-    val ctx = filterFor(s"BEARER ${JwtAuth.jwtToken(buildClaims())}")
-    ctx.getSecurityContext.getUserPrincipal.asInstanceOf[SessionUser].getUid 
shouldBe 42
-  }
-
-  it should "accept mixed-case 'BeArEr'" in {
-    val ctx = filterFor(s"BeArEr ${JwtAuth.jwtToken(buildClaims())}")
-    ctx.getSecurityContext.getUserPrincipal.asInstanceOf[SessionUser].getUid 
shouldBe 42
-  }
-
-  it should "tolerate leading whitespace before the scheme" in {
-    val ctx = filterFor(s"   Bearer ${JwtAuth.jwtToken(buildClaims())}")
-    ctx.getSecurityContext.getUserPrincipal.asInstanceOf[SessionUser].getUid 
shouldBe 42
-  }
-
-  it should "tolerate multiple spaces between scheme and token" in {
-    val ctx = filterFor(s"Bearer   ${JwtAuth.jwtToken(buildClaims())}")
-    ctx.getSecurityContext.getUserPrincipal.asInstanceOf[SessionUser].getUid 
shouldBe 42
-  }
-
-  it should "tolerate trailing whitespace after the token" in {
-    val ctx = filterFor(s"Bearer ${JwtAuth.jwtToken(buildClaims())}   ")
-    ctx.getSecurityContext.getUserPrincipal.asInstanceOf[SessionUser].getUid 
shouldBe 42
-  }
-
-  it should "reject a Bearer header with no token" in {
-    val filter = new JwtAuthFilter
-    withResourceInfo(
-      filter,
-      new StubResourceInfo(
-        methodOf(classOf[RequiredAuthResource], "secured"),
-        classOf[RequiredAuthResource]
-      )
-    )
-    val ctx = new StubRequestContext("Bearer   ")
-    val thrown = the[UnauthorizedException] thrownBy filter.filter(ctx)
-    thrown.challenge shouldBe JwtAuthFilter.BearerChallenge
-  }
-
-  // -------------------- exception is stack-trace-less --------------------
-
-  // UnauthorizedException is thrown on every unauthenticated request and the
-  // stack is never inspected. Ensure fillInStackTrace was suppressed so the
-  // auth hot path does not pay for stack capture.
-  "UnauthorizedException" should "carry no stack trace" in {
-    val e = new UnauthorizedException(JwtAuthFilter.BearerChallenge)
-    e.getStackTrace.length shouldBe 0
-    e.getMessage shouldBe JwtAuthFilter.BearerChallenge
-  }
-}
diff --git 
a/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala
 
b/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala
index ec5169eee3..a15ced30a2 100644
--- 
a/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala
+++ 
b/computing-unit-managing-service/src/main/scala/org/apache/texera/service/ComputingUnitManagingService.scala
@@ -25,12 +25,7 @@ import 
io.dropwizard.configuration.{EnvironmentVariableSubstitutor, Substituting
 import io.dropwizard.core.Application
 import io.dropwizard.core.setup.{Bootstrap, Environment}
 import org.apache.texera.amber.config.StorageConfig
-import org.apache.texera.auth.{
-  JwtAuthFilter,
-  RequestLoggingFilter,
-  SessionUser,
-  UnauthorizedExceptionMapper
-}
+import org.apache.texera.auth.{JwtAuthFilter, RequestLoggingFilter, 
SessionUser}
 import org.apache.texera.dao.SqlServer
 import org.apache.texera.service.resource.{
   ComputingUnitAccessResource,
@@ -69,7 +64,6 @@ class ComputingUnitManagingService extends 
Application[ComputingUnitManagingServ
 
     // Register JWT authentication filter
     environment.jersey.register(new AuthDynamicFeature(classOf[JwtAuthFilter]))
-    environment.jersey.register(classOf[UnauthorizedExceptionMapper])
 
     // Enable @Auth annotation for injecting SessionUser
     environment.jersey.register(
diff --git 
a/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala 
b/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala
index ae69560781..c787016c27 100644
--- 
a/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala
+++ 
b/config-service/src/main/scala/org/apache/texera/service/ConfigService.scala
@@ -26,12 +26,7 @@ import 
io.dropwizard.configuration.{EnvironmentVariableSubstitutor, Substituting
 import io.dropwizard.core.Application
 import io.dropwizard.core.setup.{Bootstrap, Environment}
 import org.apache.texera.amber.config.StorageConfig
-import org.apache.texera.auth.{
-  JwtAuthFilter,
-  RequestLoggingFilter,
-  SessionUser,
-  UnauthorizedExceptionMapper
-}
+import org.apache.texera.auth.{JwtAuthFilter, RequestLoggingFilter, 
SessionUser}
 import org.apache.texera.config.DefaultsConfig
 import org.apache.texera.dao.SqlServer
 import org.apache.texera.service.resource.{ConfigResource, HealthCheckResource}
@@ -70,7 +65,6 @@ class ConfigService extends 
Application[ConfigServiceConfiguration] with LazyLog
 
     // Register JWT authentication filter
     environment.jersey.register(new AuthDynamicFeature(classOf[JwtAuthFilter]))
-    environment.jersey.register(classOf[UnauthorizedExceptionMapper])
 
     // Enable @Auth annotation for injecting SessionUser
     environment.jersey.register(
diff --git 
a/file-service/src/main/scala/org/apache/texera/service/FileService.scala 
b/file-service/src/main/scala/org/apache/texera/service/FileService.scala
index 64a2f64eba..cc4174682f 100644
--- a/file-service/src/main/scala/org/apache/texera/service/FileService.scala
+++ b/file-service/src/main/scala/org/apache/texera/service/FileService.scala
@@ -28,12 +28,7 @@ import io.dropwizard.core.Application
 import io.dropwizard.core.setup.{Bootstrap, Environment}
 import org.apache.texera.amber.config.StorageConfig
 import org.apache.texera.amber.core.storage.util.LakeFSStorageClient
-import org.apache.texera.auth.{
-  JwtAuthFilter,
-  RequestLoggingFilter,
-  SessionUser,
-  UnauthorizedExceptionMapper
-}
+import org.apache.texera.auth.{JwtAuthFilter, RequestLoggingFilter, 
SessionUser}
 import org.apache.texera.dao.SqlServer
 import org.apache.texera.service.`type`.DatasetFileNode
 import org.apache.texera.service.`type`.serde.DatasetFileNodeSerializer
@@ -88,7 +83,6 @@ class FileService extends 
Application[FileServiceConfiguration] with LazyLogging
 
     // Register JWT authentication filter
     environment.jersey.register(new AuthDynamicFeature(classOf[JwtAuthFilter]))
-    environment.jersey.register(classOf[UnauthorizedExceptionMapper])
 
     // Enable @Auth annotation for injecting SessionUser
     environment.jersey.register(
diff --git 
a/file-service/src/main/scala/org/apache/texera/service/resource/DatasetResource.scala
 
b/file-service/src/main/scala/org/apache/texera/service/resource/DatasetResource.scala
index d9bb85cf4b..46457c9454 100644
--- 
a/file-service/src/main/scala/org/apache/texera/service/resource/DatasetResource.scala
+++ 
b/file-service/src/main/scala/org/apache/texera/service/resource/DatasetResource.scala
@@ -20,7 +20,7 @@
 package org.apache.texera.service.resource
 
 import io.dropwizard.auth.Auth
-import jakarta.annotation.security.{PermitAll, RolesAllowed}
+import jakarta.annotation.security.RolesAllowed
 import jakarta.ws.rs._
 import jakarta.ws.rs.core._
 import org.apache.texera.amber.config.StorageConfig
@@ -2142,11 +2142,6 @@ class DatasetResource {
     */
   @GET
   @Path("/{did}/cover")
-  // Anonymous callers may read covers of public datasets; access checks
-  // below still gate everything else. JwtAuthFilter inspects @PermitAll
-  // to skip its eager 401 when no Bearer header is present, so the
-  // @Auth Optional[SessionUser] parameter is injected as empty.
-  @PermitAll
   def getDatasetCover(
       @PathParam("did") did: Integer,
       @Auth sessionUser: Optional[SessionUser]

Reply via email to