unifyedx-common-lib is a Node.js module that provides authentication functionality for UnifyedX microservices. Check package.json for nestjs dependency.
npm install unifyedx-common-lib
unifyedx-common-lib provides certain utility functions and components which can be used across all unifyed products.
A nestjs guard named UnifyedAuthCheckGuard that can be used to check if a user is authenticated before allowing access to API routes.
To enable the guard, import and add it to the APP_GUARD providers in your main module (app.module.ts):
import { UnifyedAuthCheckGuard } from 'unifyedx-common-lib';
{
provide: APP_GUARD,
useClass: UnifyedAuthCheckGuard,
},
This will check for a valid user in the following ways:
Check request header for Bearer token. Use that token to look up for an authenticated user in redis cache If no user found, throw Unauthorized error To skip authentication for specific routes, decorate the class or method with the @Public() decorator. This decorator is also provided in the common library.
Usage of the pubilc decorator is shown below.
import { Public } from 'unifyedx-common-lib';
@Controller()
export class PublicController {
@Public()
@Get()
publicMethod() {
}
}
unifyedx-common-lib provides an IP inject middleare and and IP Check guard which can be included in your project as shown below. Both should be used in conjuction. UnifyedIpInjectMiddleware will set the client ip in the request which can later be used by the UnifyedIPGuard to permit of deny access to API routes.
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { UnifyedIPGuard, UnifyedIpInjectMiddleware } from 'unifyedx-common-lib';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from './modules/config/config.module';
import { TenantModule } from './modules/tenant/tenant.module';
import { APP_GUARD } from '@nestjs/core';
@Module({
imports: [ConfigModule, TenantModule],
controllers: [AppController],
providers: [
AppService,
{
provide: APP_GUARD,
useClass: UnifyedIPGuard,
},
],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(UnifyedIpInjectMiddleware).forRoutes('*');
}
}
If a class or method to be protected using the IP guard then set the metadata and pass the allowed IPs as shown below. (comma separated and without spaces)
import { Controller, Get, SetMetadata } from '@nestjs/common';
@Controller()
export class AppController {
@Get()
@SetMetadata('allowed-ips', '127.0.0.1,192.168.0.1') // Specify allowed IPs
findAll() {
return 'This action returns all items';
}
}
OR
import { Controller, Get, SetMetadata } from '@nestjs/common';
@Controller()
export class AppController {
@Get()
@AllowedIPs('127.0.0.1,192.168.0.1') // Specify allowed IPs
findAll() {
return 'This action returns all items';
}
}
unifyedx-common-lib provides an service class for publishing and subscribing to redis channels. This can be included in your project as shown below
import { Controller, Get, Param } from '@nestjs/common';
import { AppService } from './app.service';
import { RedisPubSubService } from 'unifyed-common-lib';
import { Public } from './decorators';
@Controller()
@Public()
export class AppController {
constructor(
private readonly pubSubService: RedisPubSubService,
) {}
@Get('/publish/:channel/:message')
publish(
@Param('channel') channel: string,
@Param('message') message: string,
): string {
this.pubSubService.publish(channel, message);
return `published message: ${message} to channel ${channel}`;
}
@Get('/subscribe/:channel')
subscribe(@Param('channel') channel: string): string {
this.pubSubService.subscribe(channel, (message) => {
console.log(`Received message from channel ${channel}: ${message}`);
});
return `subscribed to channel ${channel}`;
}
}
Dynamic logger should be used in all the unifyed products . This allows the logger to be configured at runtime and can be used to log messages with different log levels.
UnifyedLogger is exported as a provider and can be injected into the constructor of any class just like any other provider.
UnifyedLogger listener should also be registered in the main module as shown below. This will ensure that the logger changes are consumed by the logger listener.
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UnifyedAuthCheckGuard } from './guards/auth.check.guard';
import { ConfigModule } from '@nestjs/config';
import { UnifyedLoggerListener } from 'unifyed-common-lib';
@Module({
imports: [ConfigModule.forRoot()],
controllers: [AppController],
providers: [
AppService,
UnifyedLoggerListener
],
})
export class AppModule {}
Example usage of UnifyedLogger
import { Controller, Get, Param } from '@nestjs/common';
import { UnifyedLogger } from 'unifyed-common-lib';
@Controller()
@Public()
export class AppController {
constructor(
private readonly logger: UnifyedLogger
) {}
@Get('/publish/:channel/:message')
publish(
@Param('channel') channel: string,
@Param('message') message: string,
): string {
this.logger.verbose(
`Publishing message: ${message} to channel: ${channel}`,
AppController.name,
);
this.logger.debug(
`Publishing message: ${message} to channel: ${channel}`,
AppController.name,
);
this.logger.log(
`Publishing message: ${message} to channel: ${channel}`,
AppController.name,
);
this.logger.warn(
`Publishing message: ${message} to channel: ${channel}`,
AppController.name,
);
this.logger.error(
`Publishing message: ${message} to channel: ${channel}`,
AppController.name,
);
return `published message: ${message} to channel ${channel}`;
}
}
A middleware that injects the appropriate database connection for a multi-tenant application.
- It retrieves the tenant identifier from the request hostname, fetches the tenant configuration from an API,
- and then sets the appropriate database connection on the request object.
Example usage is shown below.
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { UnifyedAuthCheckGuard } from 'unifyed-common-lib';
import { RedisPubSubService } from 'unifyed-common-lib';
import { UnifyedLogger } from 'unifyed-common-lib';
import { UnifyedLoggerListener } from 'unifyed-common-lib';
import { UnifyedTenantConnectionInjectMiddleware } from 'unifyed-common-lib';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [
MongooseModule.forRootAsync({
useFactory: async () => ({
uri: `mongodb://root:admin2008@localhost:27017/UNIFYEX_MASTER?authSource=admin&readPreference=primary&ssl=false&minPoolSize=50&maxPoolSize=500`
})
})
],
controllers: [],
providers: [
RedisPubSubService,
UnifyedLogger,
UnifyedLoggerListener,
{
provide: APP_GUARD,
useClass: UnifyedAuthCheckGuard
}
]
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(UnifyedTenantConnectionInjectMiddleware).forRoutes('*');
}
}
Provides a factory function that creates a Mongoose model provider dynamically based on the given model name and schema.The provider is registered with the tenant connection and pagination is enabled on the model schema before returning the model.
Example usage is shown below.
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { UnifyedTenantConnectionInjectMiddleware } from 'unifyed-common-lib';
import { MongooseModule } from '@nestjs/mongoose';
import { UnifyedModelProvider } from 'unifyed-common-lib';
@Module({
imports: [
MongooseModule.forRootAsync({
useFactory: async () => ({
uri: `mongodb://root:admin2008@localhost:27017/UNIFYEX_MASTER?authSource=admin&readPreference=primary&ssl=false&minPoolSize=50&maxPoolSize=500`
})
})
],
controllers: [AppController],
providers: [
AppService,
UnifyedModelProvider.forFeature(Test.name, TestSchema),
]
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(UnifyedTenantConnectionInjectMiddleware).forRoutes('*');
}
}
Exposes a DTO which is used to store person record data.
/**
* Represents a person record in the Unifyed system.
*
* @param userId - The unique identifier for the person.
* @param firstName - The person's first name.
* @param lastName - The person's last name.
* @param phone - The person's phone number.
* @param address - The person's address.
* @param preferredName - The person's preferred name (optional).
* @param preferredPronouns - The person's preferred pronouns (optional).
* @param personalEmail - The person's personal email address (optional).
* @param officialEmail - The person's official email address (optional).
* @param dob - The person's date of birth (optional).
* @param unifyedRoles - The person's Unifyed roles (optional).
* @param identityRoles - The person's identity roles (optional).
* @param additionalAttributes - Additional attributes for the person (optional).
* @param backgroundInfo - Background information about the person (optional).
* @param education - Information about the person's education (optional).
*
* @author Ranjith Madhavan
* @since 11 June 2024
*/
export class UnifyedPersonRecord {
constructor(
public userId: string,
public firstName: string = '',
public lastName: string = '',
public phone: string = '',
public address: string = '',
public preferredName: string = '',
public preferredPronouns: string = '',
public tenantDomain: string = '',
public coverImageUrl: string = '',
public profileImageUrl: string = '',
public personalEmail: string = '',
public officialEmail: string = '',
public dob: Date = null,
public unifyedRoles: string[] = [],
public identityRoles: string[] = [],
public additionalAttributes: Record<string, any> = {},
public backgroundInfo: {
homeTownOrCity: string;
highSchool: string;
} = {
homeTownOrCity: '',
highSchool: ''
},
public education: {
classOf: string;
housingStatus: 'On Campus' | 'Off Campus' | 'None';
pgOccupationConsiderations: string[];
} = {
classOf: '',
housingStatus: 'None',
pgOccupationConsiderations: []
}
) {}
}
Usage: In main.ts use this as global filter
async function bootstrap() {
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0';
dotenv.config();
const app = await NestFactory.create(AppModule, {
logger:
configuration().app.NODE_ENV === 'development'
? ['log', 'debug', 'error', 'verbose', 'warn']
: ['error', 'warn', 'log'],
});
app.enableCors();
app.useGlobalPipes(new ValidationPipe());
app.setGlobalPrefix(configuration().app.apiGlobalPrefix);
//app.useGlobalInterceptors(new CustomCacheInterceptor(app.get(Reflector)));
app.useGlobalFilters(new UnifyedGlobalExceptionHandlerFilter());
await setupSwagger(app);
app.use(cookieParser());
await app.listen(configuration().app.port);
logger.log(
`Server running on http://localhost:${configuration().app.port}/api`,
);
}
A middleware that injects the appropriate database connection for a multi-tenant application.
- It retrieves the tenant identifier from the request header (x-tenant-domain), fetches the tenant configuration from an API,
- and then sets the appropriate database connection on the request object.
Example usage is shown below.
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { UnifyedAuthCheckGuard } from 'unifyed-common-lib';
import { RedisPubSubService } from 'unifyed-common-lib';
import { UnifyedLogger } from 'unifyed-common-lib';
import { UnifyedLoggerListener } from 'unifyed-common-lib';
import { UnifyedInterServiceConnectionMiddleware } from 'unifyed-common-lib';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [
MongooseModule.forRootAsync({
useFactory: async () => ({
uri: `mongodb://root:admin2008@localhost:27017/UNIFYEX_MASTER?authSource=admin&readPreference=primary&ssl=false&minPoolSize=50&maxPoolSize=500`
})
})
],
controllers: [],
providers: [
RedisPubSubService,
UnifyedLogger,
UnifyedLoggerListener,
{
provide: APP_GUARD,
useClass: UnifyedAuthCheckGuard
}
]
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(UnifyedInterServiceConnectionMiddleware).forRoutes('*');
}
}
The UnifyedTenantDomainCheckGuard is a NestJS guard that checks if the incoming request has a valid x-tenant-domain header. This guard is useful in a multi-tenant application where each tenant has a unique domain, and you want to ensure that the incoming request is associated with a valid tenant domain.
Here's how the guard works:
The guard implements the CanActivate interface from NestJS, which means it can be used to control access to routes or methods.
- The canActivate method checks if the method or class is annotated with @CheckDomainInRequestHeader(). If it is, the guard will look for the x-tenant-domain header in the incoming request.
- If the x-tenant-domain header is missing, the guard throws an UnauthorizedException.
- If the x-tenant-domain header is present, the guard sets the tenantDomain property on the request object with the value of the header.
- If the method or class is not annotated with @CheckDomainInRequestHeader(), the guard allows access without checking for the x-tenant-domain header.
To use this guard, you can apply it to a controller or a specific route using the @UseGuards decorator from NestJS. Here's an example:
import { Controller, Get, UseGuards } from '@nestjs/common';
import { CheckDomainInRequestHeader } from 'unifyedx-common-lib';
@Controller('example')
@CheckDomainInRequestHeader() // Apply the guard to the entire controller
export class ExampleController {
@Get('public')
public() {
return 'This route is public and does not require the x-tenant-domain header';
}
@Get('private')
@CheckDomainInRequestHeader() // Apply the guard to a specific route
private() {
return 'This route requires the x-tenant-domain header';
}
}
The UnifyedAuditInterceptor
is a NestJS interceptor that logs/injects audit information for incoming requests. It's part of the unifyedx-common-lib
package. It will add logged in user to updatedBy and createdBy fields.
Controller Specific Usage
import { Controller, UseInterceptors } from '@nestjs/common';
import { UnifyedAuditInterceptor } from 'unifyedx-common-lib';
@Controller('example')
@UseInterceptors(UnifyedAuditInterceptor)
export class ExampleController {
// Your controller methods
}
Method specific usage
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { UnifyedAuditInterceptor } from 'unifyedx-common-lib';
@Controller('example')
export class ExampleController {
@Get('audit-this')
@UseInterceptors(UnifyedAuditInterceptor)
auditedMethod() {
return 'This method is audited';
}
@Get('not-audited')
notAuditedMethod() {
return 'This method is not audited';
}
}
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { UnifyedAuditInterceptor } from 'unifyedx-common-lib';
@Controller('example')
export class ExampleController {
@Get('audit-this')
@UseInterceptors(UnifyedAuditInterceptor)
auditedMethod() {
return 'This method is audited';
}
@Get('not-audited')
notAuditedMethod() {
return 'This method is not audited';
}
}