@Override
    protected Optional<ErrorResponse> filter(DiscFilterRequest req) {
        var now = clock.instant();
        var bearerToken = requestBearerToken(req).orElse(null);
        if (bearerToken == null) {
            log.fine("Missing bearer token");
            return Optional.of(new ErrorResponse(Response.Status.UNAUTHORIZED, "Unauthorized"));
        }
        var permission = Permission.getRequiredPermission(req).orElse(null);
        if (permission == null) return Optional.of(new ErrorResponse(Response.Status.FORBIDDEN, "Forbidden"));
        var requestTokenHash = requestTokenHash(bearerToken);
        var clientIds = new TreeSet<String>();
        var permissions = EnumSet.noneOf(Permission.class);
        var matchedTokens = new HashSet<TokenVersion>();
        for (Client c : allowedClients) {
            if (!c.permissions().contains(permission)) continue;
            var matchedToken  = c.tokens().get(requestTokenHash);
            if (matchedToken == null) continue;
            var expiration = matchedToken.expiration().orElse(null);
            if (expiration != null && now.isAfter(expiration)) continue;
            matchedTokens.add(matchedToken);
            clientIds.add(c.id());
            permissions.addAll(c.permissions());
        }
        if (clientIds.isEmpty()) return Optional.of(new ErrorResponse(Response.Status.FORBIDDEN, "Forbidden"));
        if (matchedTokens.size() > 1) {
            log.warning("Multiple tokens matched for request %s"
                                .formatted(matchedTokens.stream().map(TokenVersion::id).toList()));
            return Optional.of(new ErrorResponse(Response.Status.FORBIDDEN, "Forbidden"));
        }
        var matchedToken = matchedTokens.stream().findAny().get();
        addAccessLogEntry(req, "token.id", matchedToken.id());
        addAccessLogEntry(req, "token.hash", matchedToken.fingerprint().toDelimitedHexString());
        addAccessLogEntry(req, "token.exp", matchedToken.expiration().map(Instant::toString).orElse("<none>"));
        ClientPrincipal.attachToRequest(req, clientIds, permissions);
        return Optional.empty();
    }
@Test
    void fails_for_missing_token() {
        var req = FilterTestUtils.newRequestBuilder()
                .withMethod(Method.GET)
                .build();
        var responseHandler = new MockResponseHandler();
        newFilterWithClientsConfig().filter(req, responseHandler);
        assertNotNull(responseHandler.getResponse());
        assertEquals(UNAUTHORIZED, responseHandler.getResponse().getStatus());
    }