@nipigev2/rbac-client
v0.4.4
Published
Shared RBAC authorization client for Nipige services
Maintainers
Readme
@nipigev2/rbac-client
Shared RBAC authorization client for Nipige services.
Replaces per-service authorize middleware with a single package that handles caching, retries, circuit breaking, and live cache invalidation via Redis pub/sub. Built on top of ioredis, the standard Redis client across Nipige services.
Install
npm install @nipigev2/rbac-clientRequires Node 18+ and ioredis (already a dependency of every Nipige
service). The lib reuses whichever ioredis client you've already
created — no second TCP connection.
Quick start
import Koa from 'koa';
import Router from 'koa-joi-router';
import Redis from 'ioredis';
import config from 'config';
import { rbacClient, fromIoredis } from '@nipigev2/rbac-client';
const ioredisClient = new Redis(config.get('redis_config'));
const rbac = rbacClient({
rbacUrl: process.env.RBAC_URL!,
serviceName: 'order-service',
redis: fromIoredis(ioredisClient), // ← reuses your existing client
cacheTtlSeconds: 60,
});
await rbac.start(); // subscribes to invalidation channels
const app = new Koa();
const router = Router();
router.post('/orders', rbac.assureRole(), createOrder);
router.delete('/orders/:id', rbac.assureNipigeAdmin(), deleteOrder);
router.get('/public/catalog', rbac.assureTenant(), listCatalog);
app.use(router.middleware());
app.listen(3000);
process.on('SIGTERM', async () => {
await rbac.stop();
await ioredisClient.quit();
});Middleware reference
| Method | Attaches to ctx | Use when |
|---|---|---|
| rbac.assureRole() | _userId, tenant, category, user | Full auth + route permission check |
| rbac.assureNipigeAdmin() | same | Route is for Nipige Admin only |
| rbac.assureTenantAdmin() | same | Route is for Tenant Admin only |
| rbac.assureTenant() | tenant (only) | Public or tenant-scoped route where you just need the tenant id, no role check. Reads authorization header first; falls back to x-encrypted-key |
Cache backend vs invalidation transport
Two independent concerns:
| Concern | Default | Notes |
|---|---|---|
| Where the cache lives | in-memory (per-process LRU+TTL) | fast, recommended |
| How invalidation propagates | TTL-only (60s) | flip on by passing redis |
For most services: keep the cache in memory and pass a redis adapter
to enable live pub/sub invalidation. Cache reads stay in microseconds,
revocation propagates in milliseconds.
If you need to share the cache itself across replicas (very high replica counts, 50+), opt in:
rbacClient({
rbacUrl,
serviceName,
redis: fromIoredis(ioredisClient),
cacheBackend: 'redis', // shared cache, slower reads
});Options
| Option | Default | Notes |
|---|---|---|
| rbacUrl | required | e.g. http://rbac:3000 |
| serviceName | required | shows up in logs and x-service-name header |
| redis | undefined | RedisAdapter from fromIoredis / fromNodeRedis |
| cacheBackend | 'memory' | 'memory' or 'redis' (requires redis) |
| cacheTtlSeconds | 60 | safety net even with pub/sub |
| cacheMaxEntries | 5000 | in-memory LRU bound |
| requestTimeoutMs | 3000 | per attempt |
| maxRetries | 2 | exponential backoff |
| circuitBreakerThreshold | 5 | consecutive failures → open |
| circuitBreakerCooldownMs | 10000 | how long to stay open |
| interServiceToken | undefined | sets x-inter-service-token |
| logger | console | pino-compatible interface |
Live invalidation
When you pass a redis adapter and call rbac.start(), the lib
subscribes to rbac:invalidate:user|role|group|all channels published
by the RBAC service. On any message, the local cache is flushed for
this service and the next request re-fetches fresh data.
If Redis is unavailable, cache TTL is the safety net.
Errors
RbacUnauthorizedError(status 401) — token missing or deniedRbacUnavailableError(status 503) — RBAC unreachable beyond the retry budget, or circuit breaker open
Migration from 0.2.x
0.3.0 is a breaking change. The redisUrl option is removed; pass a
redis: fromIoredis(client) adapter instead. This stops the lib from
opening a second TCP connection to Redis when your service already has
one.
-const rbac = rbacClient({
- rbacUrl,
- serviceName: 'order-service',
- redisUrl: process.env.REDIS_URL,
-});
+import Redis from 'ioredis';
+import { rbacClient, fromIoredis } from '@nipigev2/rbac-client';
+const ioredisClient = new Redis(process.env.REDIS_URL);
+const rbac = rbacClient({
+ rbacUrl,
+ serviceName: 'order-service',
+ redis: fromIoredis(ioredisClient),
+});