@nobrainerlabs/nest-identity v0.4.2
nest-identity
Description
Standard NestJS user management, roles, authentication, and ACL handling.
Requirements
Make sure to have the following installed
NodeJS / NPMfor application
Installation
To use the nest-identity node module:
$ npm login #make sure you request access to @nobrainerlabs
$ npm install --save @nobrainerlabs/nest-identityConfiguration
The following are configuration parameters to be used as environment variables or in config/config.json or environment specific config json (config/config.[NODE_ENV].json)
jwt.expiresIn(Environment:JWT_EXPIRES_IN) - expressed in seconds or a string describing a time span zeit/ms. Eg:60,"2 days","10h","7d". A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), otherwise milliseconds unit is used by default ("120"is equal to"120ms").jwt.secret(Environment:JWT_SECRET) - is a string, buffer, or object containing either the secret for HMAC algorithms or the PEM encoded private key for RSA and ECDSA.pagination.maxPageSize- when querying a list of records limit the number of records returned (defaults to 200).pagination.defaultPageSize- when querying a list of records limit the number of records returned by default (defaults to 100).password.rounds- Number of bcrypt rounds to use, defaults to 10 if omitted.superadmin.autoCreate- Set to true if you want a superadmin user created during module initialization. Super admin role and user will only be created if both Roles and User tables are empty. Default isfalse.superadmin.defaultEmail- Override the default email address of the superadmin account. Defaults tosuper@admin.com.superadmin.defaultPassword- Override the default super admin password. Defaults tosuperadmin.superadmin.defaultRole- Override the super admin role name. Defaults tosuperadmin.
Setup
Follow the instructions below to get started using the nest-identitiy module in your NestJS application.
Folder Structure
The following is the recommended folder structure for your source files:
entities/- place your NestJS entities hereuser.entity.ts- extends nest-identity's user.entity.tsuser-address.entity.ts- extends nest-identity's user-address.entity.tsuser-phone.entity.ts- extends nest-identity's user-phone.entity.tsroles.entity.ts- extends nest-identity's role.entity.tsstate.entity.ts- US address state code referenceorganization.ts- ...
user/user.module.ts- your custom user module overrideuser.service.ts- user service that extends the nest-identity user.serviceuser.controller.ts- user controller that extends the nest-identity user.controlleruser-validate.strategy.ts- JWT validate strategy that extends the nest-identity validate.strategy
roles/roles.module.ts- your custom roles module overrideroles.controller.ts- roles controller that extends the nest-identity roles.controller
User and Role Entities
The first step is to make sure you have your User and Role entities models defined and have them extend nest-identity built in entities where applicable. You can then extend and add additional properties as needed.
// entities/user.entity.ts
import { Entity, Column } from 'typeorm';
import { ApiProperty } from '@nestjs/swagger';
import { ReplaceRelationType } from '@nobrainerlabs/nest-utils';
import { User as BaseUser } from '@nobrainerlabs/nest-identity';
import { Role } from './role.entity';
@Entity()
export class User extends BaseUser {
/**
* Patient medical ID number.
*/
@ApiProperty()
@Column('text', { name: 'patient_number', nullable: true })
public patientNumber: string;
/**
* Need to redeclare roles relationship to replace base class reference
*/
@ReplaceRelationType(type => Role)
public roles: Role[];
}// entities/role.entity.ts
import { Entity } from 'typeorm';
import { User } from './user.entity';
import { Role as BaseRole } from '@nobrainerlabs/nest-identity';
import { ReplaceRelationType } from '@nobrainerlabs/nest-utils';
@Entity()
export class Role extends BaseRole {
@ReplaceRelationType(type => User)
public users: User[];
}UserModule, UserService, UserController, and Validate Strategy
Next, setup your UserService, UserController, and ValidateStrategy overrides of base classes in nest-identity:
user.service.ts- service class to interface between the NestJS repository and your controller classes.user.controller.ts- NestJS controller for exposing API endpoints, role permissions, and authentication.user-validate.strategy.ts- Used by the JWT authentication strategy for validating a decrypted JWT payload.user.module.ts- The user module containing references to the above classes. Also, make sure you reference the AuthModule (see below for example)
// user/user.service.ts
import { Injectable, HttpException, OnModuleInit } from '@nestjs/common';
import { UserService as BaseUserService } from '@nobrainerlabs/nest-identity';
@Injectable()
export class UserService extends BaseUserService implements OnModuleInit {
constructor(
@InjectRepository(User)
protected readonly userRepository: Repository<User>,
protected readonly configService: ConfigService,
protected readonly rolesService: RolesService,
protected readonly moduleRef: ModuleRef,
@Optional()
protected readonly mailerProvider?: MailerService,
) {
super(userRepository, configService, moduleRef, rolesService);
}
// only necessary if you're user entity has additional required fields
onModuleInit() {
const user: User = this.userRepository.create({
mobileNumber: '8004561111',
});
super.initialize(user);
}
}
// user/user.controller.ts
import { UserController as BaseUserController } from '@nobrainerlabs/nest-identity';
@Controller('users')
export class UserController extends BaseUserController {
constructor(
protected readonly userService: UserService,
protected readonly configService: ConfigService
) {
super(userService, configService);
}
}
// user/user-validate.strategy.ts
import { ValidateStrategy, JwtPayload, User } from '@nobrainerlabs/nest-identity';
export class UserValidateStrategy extends ValidateStrategy implements ValidateStrategy {
constructor(@Inject(UserService) private readonly userService: UserService) {
super();
}
async validate(payload: JwtPayload): Promise<User> {
// Add your custom validation logic here
return this.userService.findById(payload.userId);
}
}
// user/user.module.ts
import { Module } from '@nestjs/common';
import { User } from '../entities/user.entity';
import { UserService } from './user.service';
import { RolesModule } from './../roles/roles.module';
import { UserController } from './user.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserValidateStrategy } from './user-validate.strategy';
import {
AuthModule,
RolesGuard,
AuthService,
ValidateStrategy
} from '@nobrainerlabs/nest-identity';
@Module({
imports: [
TypeOrmModule.forFeature([User]),
AuthModule.forRoot(UserValidateStrategy), // Important for handling ACLs on REST APIs
RolesModule
],
providers: [AuthService, UserService, RolesGuard], // RolesGuard needed for @Roles decorator for ACLs
controllers: [UserController],
exports: [UserService]
})
export class UserModule {}AppModule
Modify your app.module.ts to include references to the AuthModule and RolesGuard if you want to use @Roles() decorator for ACL permissions in your own custom NestJS controllers.
Example AppModule:
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forRoot({
type: config.type,
host: config.host,
port: config.port,
username: config.username,
password: config.password,
database: config.database,
entities: ['src/**/**.entity{.ts,.js}'],
extra: {
idleTimeoutMillis: config.poolIdleTimeout || undefined,
max: config.poolMax,
ssl: config.ssl,
},
synchronize: false,
logging: 'all',
namingStrategy: new PostgresNamingStrategy(),
}),
MailerModule.forRootAsync({
useClass: MailerConfigService,
}),
AuthModule.forRoot(UserValidateStrategy),
UserModule,
],
controllers: [AppController],
providers: [AppService, RolesGuard],
})
export class AppModule {}RolesGuard
The roles guard decorator @Roles(...string) bundled in nest-identiity allows you to define ACL permissions on exposed REST API endpoints. You can pass a list of role names into the decorator for validation.
The following are special keywords:
$everyone- same as not using@Roles(); allows everyone to call the endpoint.$authenticated- only allows an authenticated user; a user supplying a valid JWT access token.$userOwner- When using this keyword make sure to define an:idin the path that represents theuser_id. They keyword will validate the user id in the JWT access token matches the:idpassed into the URL path.
@Roles('Admin', '$userOwner')
@Get(':id([0-9]+|me)')
public async getOne(
@Param('id', new ParseIntPipe())
id: number
) {
const user = await this.userService.findById(id);
if (!user) {
throw new NotFoundException();
}
return user;
}OwnerInterceptor
The OwnerInterceptor is used to assign the requester's user id to any property in a model usually when used with a POST or PUT API request. For example, if you wanted to set the createdBy and/or modifiedBy fields when saving the user profile.
@Roles('Admin')
@Post()
@UseInterceptors(new OwnerInterceptor(['createdBy', 'modifiedBy']))
public async create(
@Body(new RequiredPipe())
user: User,
): Promise<User> {
return await this.userService.create(user);
}
QwnerInterceptorwill throw an error ifrequest.useris empty.
Registration and Password Recovery
If you would like to enable email sending for new user registration and password recovery features, You will need to import the MailerModule and provide a MailerConfigService as shown in example below. You can either provide a custom MailerConfigService or use the default provided by this module.
...
import { MailerModule } from '@nest-modules/mailer';
import { AuthModule, MailerConfigService } from '@nobrainerlabs/nest-identity';
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forRoot({...}),
MailerModule.forRootAsync({
useClass: MailerConfigService,
}),
AuthModule.forRoot(UserValidateStrategy),
UserModule,
],
controllers: [AppController],
providers: [AppService, RolesGuard],
})
export class AppModule {}If you are using a subclassed UserService, make sure to inject a MailerService in your constructor, see UserService.ts in this module.
This module provides default configurations for email sending that you can override in your own config, please config schema and samples in the config directory.
Finally, you also need create the email templates in the directory you specified in your config file. The default template directory is public/templates. You can copy the default templates provided by this module and customize it according to your needs.
User Controller
TODO: Document all REST API endpoints as part of the UserController base class.
Roles Controller
TODO: Document all REST API endpoints as part of the RolesController base class.
Contributing
To contribute to the nest-identity project please make sure to have the following configured in your development environment. Submit pull request to master after making sure all unit tests run successful.
dockerfor postgres databasetslintfor TypeScript linting (tslint in VSCode to automate linting)jestfor unit testing
$ npm installDevelopment environment setup
Make sure to have the following installed:
Node 8.10+ / NPM 6.1+for applicationdockerfor postgres databasejestfor unit testingtslintfor TypeScript linting (tslint in VSCode to automate linting)prettierfor auto formatting in VSCode- Make sure you setup an npmjs.com account and request access to the
@nobrainerlabsprivate repos for the NPM dependencies.
Install all node module dependencies:
$ npm installIf you would like to develop/debug nest-identity in your source project you can npm link to symlink cross-dependency:
# Build the project first
$ npm run build
# Run npm link on this project
$ npm link
# Run npm link on your project
$ npm link @nobrainerlabs/nest-identityImportant Note: Ensure that the version of
nest-utilsinsidenest-identityand in your host application are the same to avoid incompatible types error.
Database Setup
Setup the Postgres database instance.
# Rebuilds the database with a new Docker container.
$ npm run db
# Load initial mock data via tests.
$ npm run mocksRecreating Database Schema
When making changes to the database schema you can create a sql dump to replace db/initial-db-schema.sql by running:
docker exec -t nest-identity_db_1 pg_dump -U root identity > db/initial-db-schema.sqlDevelopment:
nest-identity_db_1- is the name of the docker container on your machine (development).
Running the app
# development
$ npm run start
# watch mode
$ npm run start:devTest
All code changes should have supported unit tests.
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov4 years ago