NestJS authentication: how to globally pass UserId to CRUD endpoints? - nestjs

I've followed the NestJS Authentication Guide and everything is working as expected:
A global JWT guard:
#Module({
imports: [
// ...
BoardsModule,
// ...
],
controllers: [AppController],
providers: [
// ...
{
provide: 'APP_GUARD',
useClass: JwtAuthGuard,
},
],
})
A controller:
export class BoardsController {
// ...
#Get()
findAll(#Req() req: any) {
const userId = req.user.userId;
return this.boardsService.findAll(userId);
}
// ...
}
A service that uses prisma to fetch the boards for an authenticated user:
export class BoardsService {
// ...
findAll(userId: string) {
return prisma.board.findMany({
where: {
userId,
},
});
}
// ...
}
As most of my endpoints are user scoped I wonder how can one avoid passing the userId in CRUD endpoint again and again?
// ...
const userId = req.user.userId;
return this.boardsService.findAll(userId);
}
// ...

Related

how to inject my services layer into Auth Guard? NestJS - AuthGuards

I am creating an authentication system via UseGuards, but how can I inject dependencies into my guards?
I'd like to do it in a global way, to avoid repeating code and every controller importing the injections.
I am using the mode of dependency inversion and injecting the classes, I am also making the classes only depend on implementations and interface rules...
My AuthServices
export class AuthServices implements IMiddlewareAuth {
constructor(
private readonly jwt: IJWTDecrypt,
private readonly authUserRepo: IAuthUserContract,
) {}
public async intercept(
req: IMiddlewareAuth.Attrs,
): Promise<IMiddlewareAuth.Return> {
const token = req.headers['Authorization'];
if (!token) {
new Fails('Access denied', 403);
}
const parts = token.split(' ');
if (parts.length !== 2) {
return new Fails('Anauthorized', 401);
}
const [scheme, access] = parts;
if (!/^Bearer$/i.test(scheme)) {
return new Fails('Anauthorized', 400);
}
const id = await this.jwt.decrypt(access);
if (!id) {
return new Fails('Anauthorized', 400);
}
const user = await this.authUserRepo.findById(id);
if (!user) {
return new Fails('Anauthorized', 400);
}
return user;
}
}
my Auth.Guards
#Injectable()
export class AuthorizationGuard implements CanActivate {
constructor(
#Inject('AuthServices')
private authServices: IMiddlewareAuth,
) {}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const user = await this.authServices.intercept(request);
if (user instanceof Fails) {
throw new HttpException(
{
statusCode: user.statusCode,
error: user.message,
},
user.statusCode,
);
}
request.user = {
email: user.email,
id: user.id,
name: user.name,
} as ISessionLogin;
return true;
}
}
my Auth.Module
#Module({
imports: [ConfigModule.forRoot(), TypeOrmModule.forFeature([UserEntity])],
providers: [
{
provide: AuthUserRepository,
useFactory: (dataSource: DataSource) => {
return new AuthUserRepository(
dataSource.getMongoRepository(UserEntity),
);
},
inject: [getDataSourceToken()],
},
{
provide: JsonWebToken,
useFactory: () => {
return new JsonWebToken();
},
},
{
provide: AuthServices,
useFactory: (
jwt: JsonWebToken,
authUserRepository: AuthUserRepository,
) => {
return new AuthServices(jwt, authUserRepository);
},
inject: [JsonWebToken, AuthUserRepository],
},
],
exports: [AuthServices],
})
export class AuthModule {}
Error
Use #Inject(AuthService) instead of #Inject('AuthService'). The important distinction is that 'AuthService' is a string token that matches AuthService.name and AuthService is a class reference. Also, make sure the CreateUsersModule has AuthModule in its imports array

How to use factory pattern correctly in nestjs

In Nestjs, I have a Module, which using useFactory to dynamically create class base on configValue
There is no dedicated service in that module, instead, it return a service that depends on config, therefore the DriverService1 and DriverService2 will construct together
export const createFactory = (config: IConfig):Provider<IService> => {
return {
provide: 'FACTORY',
useFactory: (service1: DriverService1, service2: DriverService2): IService => {
if (config.driver == 'DriverService1')
{
return service1;
}
else if (config.driver == 'DriverService2')
{
return service2;
}
throw new Error('not implemented')
},
inject: [ DriverService1, DriverService2 ],
}
};
#Module({})
export class MyModule {
static register(config?: IConfig): DynamicModule {
const factory = createFactory(config)
return {
module: MyModule,
providers: [
{
provide: 'CONFIG',
useValue: config,
},
DriverService1,
DriverService2,
factory
],
exports: [factory],
};
}
}
but im not sure is it a correct way to do that
or i should create a dedicated service in this module , e.g "MyModuleService", and then do the factory pattern inside the service? which the driver will only construct when it use
interface IDriver {
action1():void
action2():void
action3():void
}
class Driver1 implements IDriver{
public action1():void {
console.log("DriverService1 action1")
}
public action2():void {
console.log("DriverService1 action2")
}
public action3():void {
console.log("DriverService1 action3")
}
}
class Driver2 implements IDriver{
public action1():void {
console.log("DriverService2 action1")
}
public action2():void {
console.log("DriverService2 action2")
}
public action3():void {
console.log("DriverService2 action3")
}
}
export const createFactory = (config: IConfig):Provider<MyModuleSerice> => {
return {
provide: 'BROKER_FACTORY',
useFactory: (service:MyModuleSerice): MyModuleSerice => {
if (config.driver == 'Driver1')
{
service.setDriver(new Driver1());
}
else if (config.driver == 'Driver2')
{
service.setDriver(new Driver2());
}
else{
throw new Error('not implemented')
}
return service
},
inject: [ MyModuleSerice ],
}
};
#Module({})
export class MyModule {
static register(config?: IConfig): DynamicModule {
const facotry = createFactory(config)
return {
module: MyModule,
providers: [
{
provide: 'CONFIG',
useValue: config,
},
facotry
],
exports: [facotry],
};
}
}
#Injectable()
class MyModuleSerice {
protected driver:IDriver
constructor() {
}
public setDriver(driver:IDriver) {
this.driver = driver
}
public doSomething():void {
this.driver.action1()
this.driver.action2()
}
public doSomething2():void {
this.driver.action1()
this.driver.action3()
}
}
This is where providers come into play. You can create a custom provider that can handle this logic for you. See below.
https://docs.nestjs.com/fundamentals/custom-providers
Here's the example provided by NestJS that leverages a config file to create an instance of a service.
const configServiceProvider = {
provide: ConfigService,
useClass:
process.env.NODE_ENV === 'development'
? DevelopmentConfigService
: ProductionConfigService,
};
#Module({
providers: [configServiceProvider],
})
export class AppModule {}

How to check for roles in NestJS Guard

I have an external service providing a JWT token. In Nestjs i first have JwtGuard class:
#Injectable()
export class JwtGuard extends AuthGuard('JWT_STRATEGY') {
constructor() {
super();
}
getRequest(context: ExecutionContext) {
console.log('JwtGuard');
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
and then a passport strategy:
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'JWT_STRATEGY') {
constructor(private configService: ConfigService) {
super({
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: configService.get<string>('ADFS_KEYS_URL'),
}),
ignoreExpiration: false,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
audience: configService.get<string>('ADFS_AUDIENCE'),
issuer: configService.get<string>('ADFS_ISSUER'),
algorithms: ['RS256'],
});
}
validate(payload: unknown): unknown {
console.log('jwt strategy');
console.log(payload);
return payload;
}
}
It seems that JwtGuard is running first, then the strategy. But if i want to do additional guards and checks, say for roles. Where does one do that? Do i need another guard that runs after the passport strategy? I have two roles "User" and "Admin".
First of all, define a global guard (called RolesGuard) in the AppModule as following:
providers: [
AppService,
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
{
provide: APP_GUARD,
useClass: RolesGuard,
},
]
Then within RolesGuard we have the following:
export enum FamilyRole {
Admin = 'Admin',
User = 'User',
}
...
export class FamilyRolesGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const requiredRoles = this.reflector.getAllAndOverride<FamilyRole>(
ROLES_KEY,
[context.getHandler(), context.getClass()],
);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
// do the rest and return either true or false
}
}
Then create your own decorator and you can decorate your APIs if you need that API to protect your app based on your guard.
import { SetMetadata } from '#nestjs/common';
export const ROLES_KEY = 'FamilyRoles';
export const FamilyRoles = (...roles: FamilyRole[]) =>
SetMetadata(ROLES_KEY, roles);
Then you can use your decorator in your API like this:
#Post('user')
#FamilyRoles(FamilyRole.Admin)
...
So, in your API, if you won't have FamilyRoles, in the guard you won't have requiredRoles, and the if block will return true.
More info: https://docs.nestjs.com/security/authorization

Unable to implement api key authentication passport NestJS

I'm trying here to implement a token-based authentication using the passport-headerapikey library.
This is what I've tried so far, and for some reason I have a 500 server error popping from somewhere I couldn't find.
This is the structure of my authentication system (I also have a JWT-token based strategy in parallel on my graphQL queries).
app.module
#Module({
imports: [
AuthModule,
],
controllers: [AppController],
providers: [
AppService
],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes('/datasource/:id');
}
}
auth.module
#Module({
imports: [
PassportModule,
],
providers: [
AuthService,
DatasourceTokenStrategy,
],
controllers: [],
exports: [AuthService],
})
export class AuthModule {}
datasourceToken.strategy
#Injectable()
export class DatasourceTokenStrategy extends PassportStrategy(
HeaderAPIKeyStrategy,
'datasourceToken',
) {
constructor(private authService: AuthService) {
super(
{ header: 'datasourceToken', prefix: '' },
true,
(apikey, done, req) => {
const checkKey = authService.validateDatasourceToken(apikey);
if (!checkKey) {
return done(false);
}
return done(true);
},
);
}
}
authMiddleware.strategy
import {
Injectable,
NestMiddleware,
UnauthorizedException,
} from '#nestjs/common';
import * as passport from 'passport';
#Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
passport.authenticate(
'datasourceToken',
{ session: false, failureRedirect: '/api/unauthorized' },
(value) => {
if (value) {
next();
} else {
throw new UnauthorizedException();
}
},
)(req, res, next);
}
}
This is the error thrown when testing the endpoint with Jest:
When running my debug mode, I can see that the datasourceToken strategy is ok (I can retrieve the datasourceToken properly and validate it), but I think the problem is happening after my auth middleware..
Thanks guys for your insights
The function "done()" takes 3 arguments.
done(error, user, info)
You need to pass null as the first argument to let passport know that there was no error while authenticating.
done(null, true)

How to change a Database connection dynamically with Request Scope Providers in Nestjs?

Working on a project with Nestjs 6.x, Mongoose, Mongo, etc...
Regarding to the Back End, in my use case, I must change the connection of one of my databases depending of some conditions/parameters coming from some requests.
Basically, I have this
mongoose.createConnection('mongodb://127.0.0.1/whatever-a', { useNewUrlParser: true })
and I want to change to, for example
mongoose.createConnection('mongodb://127.0.0.1/whatever-b', { useNewUrlParser: true })
Therefore, I have in Nestjs the first provider
export const databaseProviders = [
{
provide: 'DbConnectionToken',
useFactory: async (): Promise<typeof mongoose> =>
await mongoose.createConnection('mongodb://127.0.0.1/whatever', { useNewUrlParser: true })
}
I was researching for a while and I found out that in release Nestjs 6.x there are provider requests allowing me to modify dynamically Per-request the injection of some providers.
Anyway, I don't know how to achieve my change neither if it is going to be working in case I'd achieve that
Can anyone help or guide me?
Many thanks in advance.
You can do the following using Nest's built-in Mongoose package:
/*************************
* mognoose.service.ts
*************************/
import { Inject, Injectable, Scope } from '#nestjs/common';
import { MongooseOptionsFactory, MongooseModuleOptions } from '#nestjs/mongoose';
import { REQUEST } from '#nestjs/core';
import { Request } from '#nestjs/common';
#Injectable({ scope: Scope.REQUEST })
export class MongooseConfigService implements MongooseOptionsFactory {
constructor(
#Inject(REQUEST) private readonly request: Request,) {
}
createMongooseOptions(): MongooseModuleOptions {
return {
uri: request.params.uri, // Change this to whatever you want; you have full access to the request object.
};
}
}
/*************************
* mongoose.module.ts
*************************/
import { Module } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { MongooseConfigService } from 'mognoose.service';
#Module({
imports: [
MongooseModule.forRootAsync({
useClass: MongooseConfigService,
}),
]
})
export class DbModule {}
Then, you can attach whatever you want to the request and change the database per request; hence the use of the Scope.REQUEST. You can read more about Injection Scopes on their docs.
Edit: If you run into issues with PassportJS (or any other package) or the request is empty, it seems to be an error that relates to PassportJS (or the other package) not supporting request scopes; you may read more about the issue on GitHub regarding PassportJS.
I did a simple implementation for nest-mongodb,
The main changes are in mongo-core.module.ts where I store the connections in a map and used them if available instead of creating a new connection every time.
import {
Module,
Inject,
Global,
DynamicModule,
Provider,
OnModuleDestroy,
} from '#nestjs/common';
import { ModuleRef } from '#nestjs/core';
import { MongoClient, MongoClientOptions } from 'mongodb';
import {
DEFAULT_MONGO_CLIENT_OPTIONS,
MONGO_MODULE_OPTIONS,
DEFAULT_MONGO_CONTAINER_NAME,
MONGO_CONTAINER_NAME,
} from './mongo.constants';
import {
MongoModuleAsyncOptions,
MongoOptionsFactory,
MongoModuleOptions,
} from './interfaces';
import { getClientToken, getContainerToken, getDbToken } from './mongo.util';
import * as hash from 'object-hash';
#Global()
#Module({})
export class MongoCoreModule implements OnModuleDestroy {
constructor(
#Inject(MONGO_CONTAINER_NAME) private readonly containerName: string,
private readonly moduleRef: ModuleRef,
) {}
static forRoot(
uri: string,
dbName: string,
clientOptions: MongoClientOptions = DEFAULT_MONGO_CLIENT_OPTIONS,
containerName: string = DEFAULT_MONGO_CONTAINER_NAME,
): DynamicModule {
const containerNameProvider = {
provide: MONGO_CONTAINER_NAME,
useValue: containerName,
};
const connectionContainerProvider = {
provide: getContainerToken(containerName),
useFactory: () => new Map<any, MongoClient>(),
};
const clientProvider = {
provide: getClientToken(containerName),
useFactory: async (connections: Map<any, MongoClient>) => {
const key = hash.sha1({
uri: uri,
clientOptions: clientOptions,
});
if (connections.has(key)) {
return connections.get(key);
}
const client = new MongoClient(uri, clientOptions);
connections.set(key, client);
return await client.connect();
},
inject: [getContainerToken(containerName)],
};
const dbProvider = {
provide: getDbToken(containerName),
useFactory: (client: MongoClient) => client.db(dbName),
inject: [getClientToken(containerName)],
};
return {
module: MongoCoreModule,
providers: [
containerNameProvider,
connectionContainerProvider,
clientProvider,
dbProvider,
],
exports: [clientProvider, dbProvider],
};
}
static forRootAsync(options: MongoModuleAsyncOptions): DynamicModule {
const mongoContainerName =
options.containerName || DEFAULT_MONGO_CONTAINER_NAME;
const containerNameProvider = {
provide: MONGO_CONTAINER_NAME,
useValue: mongoContainerName,
};
const connectionContainerProvider = {
provide: getContainerToken(mongoContainerName),
useFactory: () => new Map<any, MongoClient>(),
};
const clientProvider = {
provide: getClientToken(mongoContainerName),
useFactory: async (
connections: Map<any, MongoClient>,
mongoModuleOptions: MongoModuleOptions,
) => {
const { uri, clientOptions } = mongoModuleOptions;
const key = hash.sha1({
uri: uri,
clientOptions: clientOptions,
});
if (connections.has(key)) {
return connections.get(key);
}
const client = new MongoClient(
uri,
clientOptions || DEFAULT_MONGO_CLIENT_OPTIONS,
);
connections.set(key, client);
return await client.connect();
},
inject: [getContainerToken(mongoContainerName), MONGO_MODULE_OPTIONS],
};
const dbProvider = {
provide: getDbToken(mongoContainerName),
useFactory: (
mongoModuleOptions: MongoModuleOptions,
client: MongoClient,
) => client.db(mongoModuleOptions.dbName),
inject: [MONGO_MODULE_OPTIONS, getClientToken(mongoContainerName)],
};
const asyncProviders = this.createAsyncProviders(options);
return {
module: MongoCoreModule,
imports: options.imports,
providers: [
...asyncProviders,
clientProvider,
dbProvider,
containerNameProvider,
connectionContainerProvider,
],
exports: [clientProvider, dbProvider],
};
}
async onModuleDestroy() {
const clientsMap: Map<any, MongoClient> = this.moduleRef.get<
Map<any, MongoClient>
>(getContainerToken(this.containerName));
if (clientsMap) {
await Promise.all(
[...clientsMap.values()].map(connection => connection.close()),
);
}
}
private static createAsyncProviders(
options: MongoModuleAsyncOptions,
): Provider[] {
if (options.useExisting || options.useFactory) {
return [this.createAsyncOptionsProvider(options)];
} else if (options.useClass) {
return [
this.createAsyncOptionsProvider(options),
{
provide: options.useClass,
useClass: options.useClass,
},
];
} else {
return [];
}
}
private static createAsyncOptionsProvider(
options: MongoModuleAsyncOptions,
): Provider {
if (options.useFactory) {
return {
provide: MONGO_MODULE_OPTIONS,
useFactory: options.useFactory,
inject: options.inject || [],
};
} else if (options.useExisting) {
return {
provide: MONGO_MODULE_OPTIONS,
useFactory: async (optionsFactory: MongoOptionsFactory) =>
await optionsFactory.createMongoOptions(),
inject: [options.useExisting],
};
} else if (options.useClass) {
return {
provide: MONGO_MODULE_OPTIONS,
useFactory: async (optionsFactory: MongoOptionsFactory) =>
await optionsFactory.createMongoOptions(),
inject: [options.useClass],
};
} else {
throw new Error('Invalid MongoModule options');
}
}
}
Check out the full implementation

Resources