@drvalue-oss/iam-nestjs
v0.1.1
Published
NestJS module for drvalue IAM: gateway signature verification, X-User-* parsing, decorators, proxy helpers
Maintainers
Readme
@drvalue-oss/iam-nestjs
NestJS module for the drvalue IAM platform. Handles:
- HMAC-SHA256 gateway signature verification — rejects requests that did not transit IAM Gateway
X-User-*header parsing — populates a typedreq.user: IamUserPayload@CurrentUser(),@CurrentGroup(),@Roles(),@Public(),@SkipGatewaySignature()decorators- Proxy helpers —
stripClientUserHeaders()andinjectIamUserHeaders()for services that further proxy to downstream APIs
Install
pnpm add @drvalue-oss/iam-nestjs @drvalue-oss/iam-corePeer dependencies: @nestjs/common, @nestjs/core, reflect-metadata, rxjs.
Setup
// app.module.ts
import { Module } from '@nestjs/common';
import { IamModule } from '@drvalue-oss/iam-nestjs';
@Module({
imports: [
IamModule.forRoot({
// Required when enforceGatewayOnly=true. Same secret IAM Gateway uses to sign.
gatewaySharedSecret: process.env.GATEWAY_SHARED_SECRET!,
// PRODUCTION: must be true. Rejects requests without a valid signature.
// DEV: false lets you `curl localhost:3000` directly.
enforceGatewayOnly: process.env.NODE_ENV === 'production',
// Optional. Default ±30,000 ms.
signatureTimestampSkewMs: 30_000,
}),
],
})
export class AppModule {}IamModule.forRoot() installs both guards as APP_GUARD-scoped globals by default, so every controller is protected without @UseGuards(). Pass global: false if you want to apply them selectively.
Using decorators
import { Controller, Get } from '@nestjs/common';
import {
CurrentUser,
CurrentGroup,
Roles,
Public,
SkipGatewaySignature,
type IamUserPayload,
type GroupMembership,
} from '@drvalue-oss/iam-nestjs';
@Controller('orders')
export class OrdersController {
@Get('mine')
@Roles('USER') // PLATFORM_ADMIN bypasses
list(@CurrentUser() user: IamUserPayload, @CurrentGroup() group: GroupMembership) {
return { userId: user.sub, groupId: group.id, role: group.role };
}
@Get('public-stats')
@Public() // Skip IamUserGuard (signature still required)
stats() {
return { ok: true };
}
}
@Controller('health')
export class HealthController {
@Get()
@SkipGatewaySignature()
@Public()
health() {
return { status: 'ok' };
}
}Acting as a further proxy
When your NestJS app proxies to downstream microservices, sanitize and re-inject the user headers:
import { stripClientUserHeaders, injectIamUserHeaders } from '@drvalue-oss/iam-nestjs';
import { createProxyMiddleware } from 'http-proxy-middleware';
createProxyMiddleware({
target: 'http://api-user:3001',
on: {
proxyReq: (proxyReq, req) => {
stripClientUserHeaders(proxyReq, req.headers);
const user = (req as { user?: IamUserPayload }).user;
if (user) injectIamUserHeaders(proxyReq, user);
},
},
});Security notes
IamUserGuarddoes NOT verify the JWT. Trust is established byGatewaySignatureGuard+ a network policy that limits ingress to IAM Gateway only. Without the network policy, an attacker who can reach your service directly can forgeX-User-Id: 1and impersonate any user —enforceGatewayOnly: trueis your only line of defense.PLATFORM_ADMINbypasses all@Roles()checks. Encode group-scoped role checks in a separate guard againstuser.activeGroup.role.
