Package Exports
- exguard-cached
- exguard-cached/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (exguard-cached) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
exguard-cached
NestJS backend package that caches user RBAC data (roles, groups, permissions) in Redis and provides guards/decorators for permission checks. Designed to work with the exguard-client frontend package.
Quick Start
npm install exguard-cachedAuto-setup (recommended)
npx exguard-cached setupThis generates:
src/redis/redis.module.ts— Redis connection (readsREDIS_HOST,REDIS_PORT,REDIS_PASSWORD,REDIS_DBfrom.env)src/guard/guard.module.ts— ImportsExGuardCacheModule.forRoot()with your Redis clientsrc/guard/guard.controller.ts— Redis proxy endpoints (/guard/redis/get,/guard/redis/set,/guard/redis/del, etc.)- Updates
src/app.module.tsto import both modules - Updates
.envwith Redis defaults
Manual setup
1. Redis module
// src/redis/redis.module.ts
import { Module, Global } from '@nestjs/common';
import { createClient } from 'redis';
@Global()
@Module({
providers: [
{
provide: 'REDIS_CLIENT',
useFactory: async () => {
const client = createClient({
socket: {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379', 10),
},
password: process.env.REDIS_PASSWORD || undefined,
database: parseInt(process.env.REDIS_DB || '0', 10),
});
await client.connect();
return client;
},
},
],
exports: ['REDIS_CLIENT'],
})
export class RedisModule {}2. Guard module
// src/guard/guard.module.ts
import { Module } from '@nestjs/common';
import { ExGuardCacheModule } from 'exguard-cached';
import { GuardController } from './guard.controller';
@Module({
imports: [
ExGuardCacheModule.forRoot({
redisClient: 'REDIS_CLIENT', // NestJS DI token from your Redis module
keyPrefix: 'guard:',
defaultTtl: 300,
}),
],
controllers: [GuardController],
})
export class GuardModule {}3. Guard controller (Redis proxy)
Create a controller at src/guard/guard.controller.ts — or use the auto-generated one from npx exguard-cached setup:
import { Controller, Get, Post, Body, Query } from '@nestjs/common';
import { Inject } from '@nestjs/common';
@Controller('guard')
export class GuardController {
constructor(@Inject('REDIS_CLIENT') private redis: any) {}
@Post('redis/set')
async redisSet(@Body() body: { key: string; value: unknown; ttl?: number }) {
const ttl = body.ttl ?? 300;
await this.redis.setEx(`guard:${body.key}`, ttl, JSON.stringify(body.value));
return { success: true };
}
@Get('redis/get')
async redisGet(@Query('key') key: string) {
const val = await this.redis.get(`guard:${key}`);
if (val === null) return { success: true, value: null };
try {
return { success: true, value: JSON.parse(val) };
} catch {
return { success: true, value: val };
}
}
@Post('redis/del')
async redisDel(@Body() body: { key: string }) {
await this.redis.del(`guard:${body.key}`);
return { success: true };
}
}4. App module
// src/app.module.ts
import { Module } from '@nestjs/common';
import { RedisModule } from './redis/redis.module';
import { GuardModule } from './guard/guard.module';
@Module({
imports: [RedisModule, GuardModule],
})
export class AppModule {}Caching User Data
When a user logs in or their roles/permissions change, store their data in Redis:
import { CachedUserService } from 'exguard-cached';
@Injectable()
export class AuthService {
constructor(private cache: CachedUserService) {}
async onLogin(cognitoSubId: string, userData: CachedUserData) {
await this.cache.setCachedUser(cognitoSubId, userData);
}
async onPermissionsChanged(cognitoSubId: string) {
await this.cache.invalidateCachedUser(cognitoSubId);
}
}Protecting Endpoints
Permission guard
import { RequireCachedUserPermission, CachedUser } from 'exguard-cached';
import type { CachedUserData } from 'exguard-cached';
@Controller('users')
export class UsersController {
@Get()
@RequireCachedUserPermission('exGUARD', 'user_groups:view_group')
async getUsers(@CachedUser() user: CachedUserData) {
return { roles: user.roles, groups: user.groups };
}
}The guard requires request.user.cognitoSubId to be set (e.g. by a JWT auth guard).
CachedUserService API
| Method | Description |
|---|---|
getCachedUser(cognitoSubId) |
Get full CachedUserData from Redis |
setCachedUser(cognitoSubId, data, ttl?) |
Store user data in Redis |
invalidateCachedUser(cognitoSubId) |
Delete user from cache |
hasPermission(cognitoSubId, module, action) |
Check if user has a specific permission |
getUserPermissions(cognitoSubId) |
Return all permission strings |
getUserRoles(cognitoSubId) |
Return all role names |
getUserGroups(cognitoSubId) |
Return all group names |
getUserModules(cognitoSubId) |
Return all module objects |
getRaw(key) |
Read any key from Redis (prefixed) |
setRaw(key, value, ttl?) |
Write any key to Redis (prefixed) |
delRaw(key) |
Delete any key from Redis (prefixed) |
CachedUserData Structure
interface CachedUserData {
user: {
id: string;
cognitoSubId: string;
username: string;
email: string;
givenName: string;
familyName: string;
employeeNumber: string;
regionId: string | null;
fieldOffice: { id: string; code: string; name: string } | null;
} | null;
groups: string[];
roles: string[];
modules: Array<{ key: string; name: string; permissions: string[] }>;
fieldOffices: string[];
permissions: string[]; // flattened: ["exGUARD:user_groups:view_group", ...]
cacheInfo: { userId: string; cognitoSubId: string; cacheKeys: { access: string; permissions: string } };
}CacheUtils (static helpers)
| Method | Description |
|---|---|
hasPermission(user, module, action) |
Check permission against cached data |
hasAnyPermission(user, pairs[]) |
Check if user has any of the given permissions |
hasAllPermissions(user, pairs[]) |
Check if user has all given permissions |
getPermissionsByModule(user, moduleKey) |
Filter permissions by module prefix |
hasRole(user, role) |
Check if user has a role |
isInGroup(user, group) |
Check if user is in a group |
getFieldOfficeId(user) |
Get field office ID |
validate(data) |
Type guard for CachedUserData |
Environment Variables
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=0Dependencies
redis^4.6.0@nestjs/common^10.0.0 (peer)@nestjs/core^10.0.0 (peer)