npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sla-wizard-plugin-auth-request-ratelimit

v1.0.2

Published

Plugin that replaces nginx limit_req with auth_request-based rate limiting, enabling dynamic X-RateLimit headers via alma-telemeter

Downloads

422

Readme

sla-wizard-plugin-auth-request-ratelimit

A sla-wizard plugin that augments nginx's static limit_req rate limiting with a dynamic auth_request-based delegation to alma-telemeter.

When alma-telemeter is reachable, every request passes through a subrequest that enforces the rate limit and returns live X-RateLimit-* headers. When alma-telemeter is unreachable, nginx falls back to its own limit_req token-bucket limiter so SLA limits are always enforced.


How it fits in the plugin chain

configNginxAuthRequest
  └─ sla-wizard-plugin-custom-baseurl  (configNginxBaseUrl)
       └─ sla-wizard-plugin-nginx-strip  (configNginxStrip)
            └─ sla-wizard-nginx-confd  (configNginxConfd)

Architecture: dual-layer rate limiting

The plugin deliberately keeps both limiters active at all times:

Client request
  │
  ├─ [nginx preaccess] limit_req zone=… burst=N nodelay
  │     token-bucket guard; burst = SLA_max − 1
  │     → 429 @rate_limited_static (if burst exhausted)
  │
  ├─ [nginx rewrite]  set $rl_limit_static N+1
  │
  └─ [nginx access]   auth_request /internal/rate-limit-check
        │
        ├─ Telemeter UP → 200 (allowed) or 403 (exceeded)
        │     200 → forward upstream + dynamic X-RateLimit-* headers
        │     403 → error_page 403 =429 @rate_limited (dynamic headers)
        │
        └─ Telemeter DOWN (5xx / timeout)
              → /_telemeter_down returns 200
              → request allowed through; limit_req already ran above
              → static X-RateLimit-* headers from map fallback

Path summary

| Scenario | Enforcer | Rate-limit headers | |---|---|---| | Telemeter UP, within limit | telemeter (fixed-window Redis) | Dynamic from telemeter | | Telemeter UP, limit exceeded | telemeter → @rate_limited | Dynamic from telemeter | | Telemeter DOWN, within burst | limit_req (token-bucket) | Static ($rl_limit_static) | | Telemeter DOWN, burst exceeded | limit_req@rate_limited_static | Static, partial | | Redis DOWN | telemeter fail-open → limit_req | Static ($rl_limit_static) |


Installation

npm install sla-wizard-plugin-auth-request-ratelimit

Runtime dependency

alma-telemeter must be reachable from nginx. It receives subrequests at /internal/rate-limit and responds with X-RateLimit-* headers.

Default endpoint: http://127.0.0.1:2047/internal/rate-limit. Override with --telemeterUrl CLI option or TELEMETER_URL environment variable.


CLI usage

// cli.js
const slaWizard = require('sla-wizard');
const authRequestPlugin = require('sla-wizard-plugin-auth-request-ratelimit');

slaWizard.use(authRequestPlugin);
slaWizard.program.parse(process.argv);
node cli.js --help

config-nginx-auth-request

Generates a full nginx configuration (nginx.conf + conf.d/) with auth_request-based rate limiting applied.

node cli.js config-nginx-auth-request \
  -o <outputDirectory> \
  --oas <pathToOAS>                   \  # default: ./specs/oas.yaml
  --sla <slaFileOrDirectory>          \  # default: ./specs/sla.yaml
  --authLocation <header|query|url>   \  # default: header
  --authName <paramName>              \  # default: apikey
  --proxyPort <port>                  \  # default: 80
  --telemeterUrl <url>                \  # default: http://127.0.0.1:2047/internal/rate-limit
  [--customTemplate <file>]

add-to-auth-request-confd

Generates only conf.d/ files for a new user without touching nginx.conf. Use when nginx.conf was already generated by a previous config-nginx-auth-request run.

node cli.js add-to-auth-request-confd \
  -o <outputDirectory> \
  --oas <pathToOAS> \
  --sla <slaFileOrDirectory>

Programmatic usage

const slaWizard = require('sla-wizard');
const authRequestPlugin = require('sla-wizard-plugin-auth-request-ratelimit');

slaWizard.use(authRequestPlugin);

// Full config (nginx.conf + conf.d/)
slaWizard.configNginxAuthRequest({
  outDir:       './nginx-output',
  oas:          './specs/oas.yaml',
  sla:          './specs/slas',
  telemeterUrl: 'http://telemeter-host:2047/internal/rate-limit', // optional
});

// conf.d only (nginx.conf already exists)
slaWizard.addToAuthRequestConfd({
  outDir: './nginx-output',
  oas:    './specs/oas.yaml',
  sla:    './specs/slas/new-user.yaml',
});

Direct import

const {
  DEFAULT_TELEMETER_URL,
  applyAuthRequestToConfd,                   // (content: string) → string
  applyAuthRequestToNginxConf,               // (content: string, telemeterUrl?: string) → string
  applyAuthRequestTransformations,           // (outDir: string,  telemeterUrl?: string) → void
  applyAuthRequestTransformationsConfdOnly,  // (outDir: string) → void
} = require('sla-wizard-plugin-auth-request-ratelimit');

const transformed = applyAuthRequestToConfd(fs.readFileSync('user.conf', 'utf8'));
applyAuthRequestTransformations('./nginx-output', 'http://telemeter-host:2047/internal/rate-limit');

nginx transformations applied

nginx.conf changes

| Element | After transformation | |---|---| | limit_req_zone … | (kept — nginx-native fallback when telemeter is down) | | limit_req_status 429; | (kept — fallback) | | Before server { | Two map directives injected (header fallback logic) | | Before include conf.d/*.conf; | location /internal/rate-limit-check injected | | Before include conf.d/*.conf; | location = /_telemeter_down injected | | Before include conf.d/*.conf; | location @rate_limited injected | | Before include conf.d/*.conf; | location @rate_limited_static injected |

Injected map directives (http context, before server {):

map $rl_limit $rl_limit_header {
    ""      $rl_limit_static;   # telemeter down or Redis fail-open → static value
    default $rl_limit;          # telemeter up → dynamic value
}

map $rl_remaining $rl_remaining_header {
    ""      -1;                 # telemeter down → -1 signals unavailable
    default $rl_remaining;      # telemeter up → dynamic value
}

Injected /internal/rate-limit-check block:

location /internal/rate-limit-check {
    internal;
    proxy_pass              http://127.0.0.1:2047/internal/rate-limit;
    proxy_pass_request_body off;
    proxy_set_header        X-API-Key  $http_apikey;
    proxy_set_header        X-Endpoint $uri_original;
    proxy_set_header        X-Method   $request_method;
    proxy_connect_timeout   500ms;
    proxy_read_timeout      1s;
    proxy_intercept_errors  on;
    error_page 500 502 503 504 =200 /_telemeter_down;
}

Injected /_telemeter_down block (telemeter unreachable → allow through, fall back to limit_req):

location = /_telemeter_down {
    internal;
    return 200;
}

Injected @rate_limited block (telemeter rejected the request → dynamic 429):

location @rate_limited {
    default_type application/json;
    add_header X-RateLimit-Limit     $rl_limit always;
    add_header X-RateLimit-Remaining 0         always;
    add_header X-RateLimit-Reset     $rl_reset always;
    add_header Retry-After           $rl_retry always;
    return 429 '{"error":"TooManyRequests","message":"Rate limit exceeded","status":429}';
}

Injected @rate_limited_static block (limit_req rejected the request → static 429):

location @rate_limited_static {
    default_type application/json;
    add_header X-RateLimit-Remaining 0   always;
    add_header Retry-After           60  always;
    return 429 '{"error":"TooManyRequests","message":"Rate limit exceeded","status":429}';
}

conf.d/*.conf changes (per rate-limited location block)

The original limit_req line is kept as the first directive (nginx-native fallback). The auth_request block is injected immediately after it.

Before:

location /sla-user1_endpoint_POST {
    rewrite … break;
    proxy_pass http://localhost:8000;
    limit_req zone=sla-user1_endpoint_POST burst=4 nodelay;
}

After:

location /sla-user1_endpoint_POST {
    rewrite … break;
    proxy_pass http://localhost:8000;
    limit_req zone=sla-user1_endpoint_POST burst=4 nodelay;   # kept as fallback

    set $rl_limit_static          5;   # burst+1 = SLA max; static header fallback
    auth_request      /internal/rate-limit-check;
    auth_request_set  $rl_limit     $upstream_http_x_ratelimit_limit;
    auth_request_set  $rl_remaining $upstream_http_x_ratelimit_remaining;
    auth_request_set  $rl_reset     $upstream_http_x_ratelimit_reset;
    auth_request_set  $rl_retry     $upstream_http_retry_after;

    add_header X-RateLimit-Limit     $rl_limit_header     always;
    add_header X-RateLimit-Remaining $rl_remaining_header always;
    add_header X-RateLimit-Reset     $rl_reset            always;

    error_page 403 =429 @rate_limited;
    error_page 429      @rate_limited_static;
}

$rl_limit_header and $rl_remaining_header resolve via the map directives in nginx.conf:

  • Telemeter UP: take dynamic values from telemeter response headers.
  • Telemeter DOWN: $rl_limit_header$rl_limit_static; $rl_remaining_header-1.

Response headers

On successful (2xx) responses

| Telemeter state | Header | Value | |---|---|---| | UP | X-RateLimit-Limit | Dynamic — SLA max from telemeter | | UP | X-RateLimit-Remaining | Dynamic — requests left in current window | | UP | X-RateLimit-Reset | Dynamic — Unix timestamp of window reset | | DOWN | X-RateLimit-Limit | Static — $rl_limit_static (SLA burst + 1) | | DOWN | X-RateLimit-Remaining | -1 (sentinel: telemeter unavailable) | | DOWN | X-RateLimit-Reset | (absent — limit_req does not expose window time) |

On rate-limited (429) responses

Via @rate_limited (telemeter rejected the request):

| Header | Value | |---|---| | X-RateLimit-Limit | Dynamic — SLA max | | X-RateLimit-Remaining | 0 | | X-RateLimit-Reset | Dynamic — Unix timestamp of window reset | | Retry-After | Dynamic — seconds until window resets |

Via @rate_limited_static (limit_req burst exhausted before auth_request ran):

| Header | Value | |---|---| | X-RateLimit-Remaining | 0 | | Retry-After | 60 (conservative hardcoded fallback) | | X-RateLimit-Limit | (absent — set $rl_limit_static runs in rewrite phase, after limit_req preaccess rejection) | | X-RateLimit-Reset | (absent — limit_req does not expose window time) |

Response body (both 429 paths):

{"error":"TooManyRequests","message":"Rate limit exceeded","status":429}

Headers not added

Responses rejected before any location block runs — 401 Unauthorized (missing API key) and 403 Forbidden (unknown API key) — do not carry X-RateLimit-* headers because the auth_request subrequest never fires for those requests.


Exported API reference

| Export | Signature | Description | |---|---|---| | apply | (program, ctx) → void | sla-wizard plugin entry point | | DEFAULT_TELEMETER_URL | string | http://127.0.0.1:2047/internal/rate-limit | | configNginxAuthRequest | (options, ctx) → void | Full config generation (nginx.conf + conf.d/) | | addToAuthRequestConfd | (options, ctx) → void | conf.d-only generation | | applyAuthRequestToConfd | (content: string) → string | Transforms one conf.d file content | | applyAuthRequestToNginxConf | (content: string, telemeterUrl?: string) → string | Transforms nginx.conf content | | applyAuthRequestTransformations | (outDir: string, telemeterUrl?: string) → void | Transforms all files in outDir | | applyAuthRequestTransformationsConfdOnly | (outDir: string) → void | Transforms only conf.d/ files |


Testing

# Unit tests + container integration tests (requires Docker)
npm test

# Unit tests only (no Docker required)
npx mocha ./tests/tests.js

Test suite overview

| Suite | Tests | Type | |---|---|---| | applyAuthRequestToConfd | 17 | Unit (pure string) | | applyAuthRequestToNginxConf | 17 | Unit (pure string) | | applyAuthRequestTransformations | 6 | Unit (filesystem) | | applyAuthRequestTransformationsConfdOnly | 4 | Unit (filesystem) | | Module shape + plugin registration | 9 | Unit | | configNginxAuthRequest programmatic | 23 | Integration | | addToAuthRequestConfd programmatic | 6 | Integration | | CLI usage | 10 | CLI (execSync) | | Container: X-RateLimit headers under limit | 8 | Container (Docker) | | Container: 429 with correct headers | 6 | Container (Docker) | | Container: independent counters per key | 3 | Container (Docker) | | Container: authentication checks | 2 | Container (Docker) | | Total | 111 | |

The container tests spin up three Docker containers (echo backend, mock alma-telemeter, nginx with the generated config) and verify the full request cycle end-to-end, including live header values and rate-limit enforcement.