I'm trying to get the nest validator working as in the example in the 'pipes' document (https://docs.nestjs.com/pipes) section "Object schema validation" . I'm trying the example using Joi which works except the passing of the schema from the controller to the validate service.
import * as Joi from 'joi';
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException
} from '#nestjs/common';
#Injectable()
export class JoiValidationPipe implements PipeTransform {
constructor(private readonly schema) {}
transform(value: any, metadata: ArgumentMetadata) {
const { error } = Joi.validate(value, this.schema);
if (error) {
throw new BadRequestException('Validation failed');
}
return value;
}
}
The compiler complains:
Nest can't resolve dependencies of the JoiValidationPipe (?). Please
make sure that the argument at index [0] is available in the current
context.
In the controller
#Post()
#UsePipes(new JoiValidationPipe(createCatSchema))
async create(#Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
there the compiler complains one argument given zero expected.
It looks like a declaration issue but I don't really know. Why doesn't this work and how should I pass the schema to the service?
As you stated yourself JoiValidationPipe must not be declared as a provider in any module.
I can only reproduce the error with this code (not passing a schema):
#UsePipes(JoiValidationPipe)
async create(#Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
Make sure you do not have this anywhere in your code.
This works for me:
#UsePipes(new JoiValidationPipe(Joi.object().keys({ username: Joi.string().min(3) })))
async create(#Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
Related
TL;DR
I am trying to run mongoose query in my validator
Hello, I am trying to make a custom decorator which throws an error if a value for that field already exists. I am trying to use the mongoose model inside the class that validates the route. Unlike in resolver/controller, #InjectModel() does not work in validator class. My validator is like this
import { getModelToken, InjectModel } from "#nestjs/mongoose";
import {
ValidationArguments,
ValidatorConstraint,
ValidatorConstraintInterface,
} from "class-validator";
import { Model } from "mongoose";
import { User } from "../schema/user.schema";
#ValidatorConstraint({ name: "IsUniqueUser", async: true })
export class UniqueValidator implements ValidatorConstraintInterface {
constructor(
#InjectModel(User.name)
private readonly userModel: Model<User>,
) {}
async validate(value: any, args: ValidationArguments) {
const filter = {};
console.log(this.userModel);
console.log(getModelToken(User.name));
filter[args.property] = value;
const count = await this.userModel.count(filter);
return !count;
}
defaultMessage(args: ValidationArguments) {
return "$(value) is already taken";
}
}
and my DTO that uses the above decorator is
#InputType({})
export class UserCreateDTO {
#IsString()
name: string;
#IsUniqueUser({
message: "Phone number is already taken",
})
#Field(() => String)
phone: string;
}
The console says
cannot read value count of undefined implying that userModel is undefined.
InShort
I want to run the query in my validator. How can I do so?
According to this issue (you can't inject a dependency)
You should to add in your main.ts
import { useContainer } from 'class-validator';
useContainer(app.select(AppModule), {fallbackOnErrors: true});
Then you need to add your UniqueValidator to your module like an #Injectable() class
so
...
providers: [UniqueValidator],
...
Then, in your DTO you can add:
#Validate(UniqueValidator, ['email'], {
message: 'emailAlreadyExists',
})
I am trying to set up an integration test which will grab some data from a backend API service using ngrx/data entities.
I have this StackBlitz set up: https://stackblitz.com/edit/ngrxdata-testing-not-working-pxfmkb?file=src/main.ts
It should run tests on startup - there are no expectations in my test cases, however I am looking in the console logs and expecting it to show the log in ClientDataService (/src/app/data/client/client-data.service.ts), that is:
console.log('never goes here :(');
In the integration test (data.integration.spec.ts) I am configuring the module, defining the Client entity type and including the AppDataServiceModule which in turn does this:
import { NgModule } from '#angular/core';
import { ClientDataService } from './client/client-data.service';
import { EntityDataService, EntityDefinitionService } from '#ngrx/data';
#NgModule({
providers: [
ClientDataService,
],
})
export class AppDataServiceModule {
constructor(
entityDataService: EntityDataService,
clientDataService: ClientDataService
) {
entityDataService.registerService('Client', clientDataService);
}
}
As you can see I am registering the data service as suggested by the ngrx docs, here
I feel like I am pretty close but just need a nudge in the right direction to get it working!
A custom DataService has to extend the DefaultDataService. Should look something like this:
export class ClientDataService extends DefaultDataService<Client> {
constructor(
http: HttpClient, httpUrlGenerator: HttpUrlGenerator
) {
super('Client', http, httpUrlGenerator);
}
public getAll(): Observable<any> {
// get Data here
}
}
The BackendService has to return the Observable:
public getClients(): Observable<Array<Client>> {
// will be mocked
return of([
{
id: '1: Will not return as it will be mocked'
},
{
id: '2: Will not return as it will be mocked'
}
])
}
There are two more things which look suspicious to me:
There is no subscription in the code, so I assume your Observable is cold.
The clientResolve.resolve({}, {}) call expects an ActivatedRouteSnapshot as first parameter. I'm not so familiar with the Resolve interface but maybe thats an issue too.
I can't understand why the next issue happens. Code with the same structure works fine in any other object-oriented languages like C# or Java.
I am writing Node.Js app using Typescript.
So I have a class ServiceLocator with two static variables:
//ServiceLocator.ts
export class ServiceLocator {
public static dataRepository: IDataRepository = new DataRepository();
public static authService: IAuthService = new AuthService();
}
Class from the second one variable using the first one static variable. This how it looks:
//AuthService.ts
const dataRepository: IDataRepository = ServiceLocator.dataRepository;
export class AuthService implements IAuthService {
...
}
But once I am trying to get authService link like this:
//AuthController.ts
const authService: IAuthService = ServiceLocator.authService;
export class AuthController {
public async signIn(request: Request, response: Response) {
..
}
..
}
I got an error:
TypeError: Cannot read property 'dataRepository' of undefined
What am I do wrong?
You have a circular reference here. In order to construct the AuthService module you need a fully constructed ServiceLocator class. However, JavaScript needs a fully constructed AuthService module in order to construct the ServiceLocator class. Switching the order of instantiation would simply result in an error from ServiceModule <cint> (to coin a Java-ism) along the lines of "Uncaught TypeError: undefined is not a constructor".
The solution is to simply make the dependency lazy:
//AuthService.ts
const dataRepositoryReference: IDataRepository = import('./ServiceLocator')
.then(({ ServiceLocator }) => ServiceLocator.dataRepository);
export class AuthService implements IAuthService {
public async findUserByUsername(username: UserName) {
const dataRepository = await dataRepositoryReference;
// ... snip ...
}
}
I have a typescript interface cRequest that's being passed as a type in a class method.
This interface also extends express Request type. What's the correct way to mock this in jest or typemoq?
import { Request } from 'express';
import { User } from '.';
export interface cRequest extends Request {
context: {
ID?: string;
user?: string;
nonUser?: string;
};
user?: User;
}
export default cRequest;
This is being used in a class method as follows
import { Response } from 'express';
public getData = async (req: cRequest, res: Response) => {}
And if i try to test it as follows it fails
const expRespMock: TypeMoq.IMock<Response> = TypeMoq.Mock.ofType<Response>();
const cReqMock: TypeMoq.IMock<cRequest> = TypeMoq.Mock.ofType<cRequest>();
await classObj.getData(cReqMock, expRespMock);
with the following message
Argument of type 'IMock<cRequest>' is not assignable to parameter of type 'cRequest'.
Property 'context' is missing in type 'IMock<cRequest>'.
What's the correct way to inject this interface mock into the method in tests?
I was able to overcome this problem as follows
const cRequestStub= <cRequest> {};
And also it's now possible to selectively inject params as i need without any errors
const cRequestStub= <cRequest> {user: undefined}; or
const cRequestStub= <cRequest> {context: {ID: '', user: '', nonUser: ''}};
await classObj.getData(cRequestStub, expRespMock);
Why to invent the wheel?
Jest has an interface that "decorates" given interface with the additional methods.
For example:
interface IRequest {
req: string;
}
requestMock: jest.Mocked<IRequest>;
requestMock will have the IRequest interface + the mocks interface.
The simplest way to learn about new jest types is to check the source-code of the #types/jest lib, for example I've checked that is the return type of jest.spyOn and from that I've learned about jest.Mocked<>.
I'm using NestJS as the framework for a client API. Within the framework we are using a pretty standard Passport/JWT auth infrastructure that is working fine. Our AuthGuard is firing when the bearer token is found and, in secure API endpoints, I can inject the HTTP context via '#Res() request' and get access to the 'request.user' property which contains the payload of my Jwt token.
On top of this we are attempting to implement a 'RolesGuard' in a very similar fashion to the sample code provided in the documentation and some of the sample projects on GitHub (none of which actually use this guard but they include it as a sample guard).
Our issue is that our AuthGuard fires and validates the Jwt token and THEN our RolesGuard fires but the request object it is passed does not have the user meta-data attached to the request.
The key code in our RolesGuard is:
const request = context.switchToHttp().getRequest();
const user = request.user;
if (!user) {
return false;
}
In the above snipped the user is always false. Has anyone written a role/permission based guard in Nest that successfully gets access to the scope of the current user? All the code is firing and everything appears registered correctly.
-Kevin
Ultimately this appears to be an ordering issue with the guards and it doesn't look like it can be easily resolved (without the framework allowing some control over the ordering).
My hope was to register the RolesGuard globally but that causes it to be registered first and fire first.
#UseGuards(AuthGuard('jwt'), RolesGuard)
#Roles('admin')
If I register it at the endpoint level and put it after the AuthGuard then it fires second and I get the user context I am expecting within the guard itself. It isn't perfect but it works.
-Kevin
register RoleGuard at the endpoint level and put it after the AuthGuard then it fires second and I get the user context I am expecting within the guard itself.
don't register RoleGuard at module causes it'll be registered first and fire first.
*.module.ts
imports: [],
providers: [{provide: APP_GUARD, useClass: RolesGuard} ,], // remove guard
controllers: [],
exports: [],
Make your RolesGuard extend AuthGuard('StrategyName') and then call super.canActivate for example:
#Injectable()
export class RolesGuard extends AuthGuard('jwt') {
async canActivate(context: ExecutionContext): Promise<boolean> {
// call AuthGuard in order to ensure user is injected in request
const baseGuardResult = await super.canActivate(context);
if(!baseGuardResult){
// unsuccessful authentication return false
return false;
}
// successfull authentication, user is injected
const {user} = context.switchToHttp().getRequest();
}
}
In other words you have to Authenticate first then Authorize
If anyone else stumbles across this question: putting multiple guards into one #UseGuards decorator works, but if you want to keep them separated (say, if you use a custom decorator), you can give the 2nd guard access to req.user by placing it before the #UseGuards call that puts the user on the request object, as in this example:
#RestrictTo(UserAuthorities.admin)
#UseGuards(JwtAuthGuard)
#Get("/your-route")
It seems that this is a consequence of how decorators work in TypeScript.
You can also use multiple roles for role-based Authentication.
In UserResolver
import { Args, Mutation, Query, Resolver } from '#nestjs/graphql';
import { UseGuards } from '#nestjs/common';
import { RolesGuard } from 'src/guards/auth.guard';
#UseGuards(new RolesGuard(['admin']))
#Resolver()
export class UserResolver { ... }
In RolesGuard
import { ExecutionContext, Injectable, UnauthorizedException } from '#nestjs/common';
import { ExecutionContextHost } from '#nestjs/core/helpers/execution-context-host';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class RolesGuard extends AuthGuard('jwt') {
constructor(private roles: string[] | null) {
super();
}
canActivate(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
const { req } = ctx.getContext();
return super.canActivate(new ExecutionContextHost([req]));
}
handleRequest(err: any, user: any, info: string) {
if (!this.roles) {
return user || null;
}
if (!user) {
throw new UnauthorizedException('Not Valid User.');
}
const role = user.role;
const doesRoleMatch = this.roles.some(r => r === role);
if (!doesRoleMatch) {
throw new UnauthorizedException('Not Valid User.');
}
return user;
}
}