Introduce authentication framework with AuthConfig, AuthGate, and Authenticator classes, alongside comprehensive tests for rules, modes, and schemes.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
package dev.coph.nextusweb.server.ratelimit;
|
||||
|
||||
import dev.coph.nextusweb.server.router.Request;
|
||||
import dev.coph.nextusweb.server.router.Response;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
@@ -22,19 +22,38 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
public final class RateLimitGate {
|
||||
|
||||
/** Default idle age after which per-key limiter state is eligible for eviction. */
|
||||
private static final long DEFAULT_STALE_AFTER_NANOS = 10L * 60 * 1_000_000_000L;
|
||||
|
||||
/** The rule set this gate enforces. */
|
||||
private final RateLimitConfig config;
|
||||
/** Idle age (nanoseconds) after which a limiter's per-key state may be evicted. */
|
||||
private final long staleAfterNanos;
|
||||
/** Single-threaded scheduler driving periodic cleanup of stale buckets. */
|
||||
private final ScheduledExecutorService cleanup;
|
||||
|
||||
/**
|
||||
* Creates a gate for the given configuration and starts a background cleanup task that runs
|
||||
* every five minutes on a daemon thread.
|
||||
* every five minutes on a daemon thread, evicting per-key state idle for more than ten
|
||||
* minutes.
|
||||
*
|
||||
* @param config the rate-limit rules to enforce
|
||||
*/
|
||||
public RateLimitGate(RateLimitConfig config) {
|
||||
this(config, DEFAULT_STALE_AFTER_NANOS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a gate with an explicit idle age before per-key limiter state is evicted.
|
||||
*
|
||||
* @param config the rate-limit rules to enforce
|
||||
* @param staleAfterNanos idle age in nanoseconds after which per-key state is evicted; must
|
||||
* be positive
|
||||
*/
|
||||
public RateLimitGate(RateLimitConfig config, long staleAfterNanos) {
|
||||
if (staleAfterNanos <= 0) throw new IllegalArgumentException("staleAfterNanos must be > 0");
|
||||
this.config = config;
|
||||
this.staleAfterNanos = staleAfterNanos;
|
||||
this.cleanup = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r, "ratelimit-cleanup");
|
||||
t.setDaemon(true);
|
||||
@@ -52,12 +71,13 @@ public final class RateLimitGate {
|
||||
* independent. The first denial short-circuits and is returned immediately; if every rule
|
||||
* allows the request, the result with the least remaining quota is returned.</p>
|
||||
*
|
||||
* @param req the incoming request, used by key resolvers
|
||||
* @param path the request path used to select rules
|
||||
* @param remoteAddress the client's remote address, used as a key-resolver fallback
|
||||
* @param req the incoming request, used by key resolvers
|
||||
* @param path the request path used to select rules
|
||||
* @param clientIp the resolved client IP (honouring trusted proxies), used as a key-resolver
|
||||
* fallback
|
||||
* @return the limiting result, or {@code null} if no rule applies to the path
|
||||
*/
|
||||
public RateLimiter.Result check(HttpRequest req, String path, String remoteAddress) {
|
||||
public RateLimiter.Result check(Request req, String path, String clientIp) {
|
||||
List<RateLimitConfig.Rule> rules = config.rulesFor(path);
|
||||
if (rules.isEmpty()) return null;
|
||||
|
||||
@@ -65,7 +85,7 @@ public final class RateLimitGate {
|
||||
RateLimiter.Result strictest = null;
|
||||
|
||||
for (var rule : rules) {
|
||||
String key = rule.name() + ":" + rule.keyResolver().resolve(req, remoteAddress);
|
||||
String key = rule.name() + ":" + rule.keyResolver().resolve(req, clientIp);
|
||||
RateLimiter.Result result = rule.limiter().tryAcquire(key, now);
|
||||
|
||||
if (!result.allowed()) return result;
|
||||
@@ -97,11 +117,18 @@ public final class RateLimitGate {
|
||||
}
|
||||
|
||||
/**
|
||||
* Periodic cleanup hook invoked by the background scheduler to evict limiter state that has
|
||||
* not been touched recently (older than roughly ten minutes).
|
||||
* Periodic cleanup hook invoked by the background scheduler. Asks every configured limiter to
|
||||
* evict per-key state idle for longer than {@link #staleAfterNanos}. A failure cleaning one
|
||||
* limiter must not abort the others or kill the scheduler, so each call is guarded.
|
||||
*/
|
||||
private void doCleanup() {
|
||||
long threshold = 10L * 60 * 1_000_000_000L;
|
||||
for (RateLimiter limiter : config.allLimiters()) {
|
||||
try {
|
||||
limiter.cleanup(staleAfterNanos);
|
||||
} catch (RuntimeException ignored) {
|
||||
// Best-effort eviction; never let one limiter break the cleanup cycle.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user