NestJS handle config service dynamically - axios

The following code:
import { HttpModuleOptions, HttpModuleOptionsFactory, Inject, Injectable } from '#nestjs/common';
import { ConfigType } from '#nestjs/config';
import { DotEnv } from '../../../common/enums/dotenv.enum';
import { DotEnvError } from '../../../common/errors/dot-env.error';
import zendeskConfig from '../../../config/zendesk.config';
#Injectable()
export class ZendeskHttpConfigService implements HttpModuleOptionsFactory {
constructor(#Inject(zendeskConfig.KEY) private zendesk: ConfigType<typeof zendeskConfig>) {}
createHttpOptions(): HttpModuleOptions {
if (!this.zendesk.url || !this.zendesk.username || !this.zendesk.password)
throw new DotEnvError(DotEnv.ZENDESK_URL, DotEnv.ZENDESK_USERNAME, DotEnv.ZENDESK_PASSWORD);
return {
baseURL: this.zendesk.url,
auth: {
username: this.zendesk.username,
password: this.zendesk.password
}
};
}
}
I would like to handle this.zendesk.url dynamically.
In my controller I call an endpoint with a query param.
For example if the query param is "a" I would like to set this.zendesk.urlA and
if "b" I would like to set this.zendesk.urlB
Does NestJs allow that?

Related

Nest JS user authentication issue with parameter name

I am just learning nestjs for about a day and I came across this strange bug, probably has something to do with me not understanding what Im doing and rushing the project so please bear with me. My main issue is that while using JWT authentication, JSON coming from body is "username" and I can't change it. I want to log in using {"email":"test#gmail.com", "password": "password123"}, but instead it only accepts {"username":"test#gmail.com", "password": "password123"}. The word "username" is not defined or mentioned anywhere in my codebase
users.controller.ts
import { Controller, Get, Post, Body, Param, UseGuards } from '#nestjs/common';
import { UsersService} from './users.service';
import { CreateUserDto} from './dto/create-user.dto';
import { AuthGuard} from '#nestjs/passport';
#Controller('/users')
export class UsersController {
// constructor(private readonly usersService: UsersService) {}
constructor(private readonly userService: UsersService) {}
#UseGuards(AuthGuard('jwt'))
#Get('username')
getUserByEmail(#Param() param) {
return this.userService.getUserByEmail(param.email);
}
#Post('register')
registerUser(#Body() createUserDto: CreateUserDto) {
return this.userService.registerUser(createUserDto);
}
}
users.service.ts
import { Injectable, BadRequestException } from '#nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { Model } from 'mongoose';
import { InjectModel } from '#nestjs/mongoose';
import { HashService } from './hash.service';
import { User, UserDocument} from '../schemas/user.schema'
#Injectable()
export class UsersService {
constructor(#InjectModel(User.name) private userModel: Model < UserDocument > , private hashService: HashService) {}
async getUserByEmail(email: string) {
return this.userModel.findOne({
email
})
.exec();
}
async registerUser(createUserDto: CreateUserDto) {
// validate DTO
const createUser = new this.userModel(createUserDto);
// check if user exists
const user = await this.getUserByEmail(createUser.email);
if (user) {
throw new BadRequestException();
}
// Hash Password
createUser.password = await this.hashService.hashPassword(createUser.password);
return createUser.save();
}
}
auth.controller.ts
import { AuthService} from './auth.service';
import { Controller, Request, UseGuards, Post} from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
#UseGuards(AuthGuard('local'))
#Post(`/login`)
async login(#Request() req) {
console.log(req.user, "here")
return this.authService.login(req.user);
}
}
Here is the source code https://github.com/networkdavit/pillicam_test
Any help or suggestion is highly appreciated!
I tried changing all the parameter names, user schemas, adding a DTO, I googled how to add a custom parameter name or override it, tried to find if "default username param" actually exists. Nothing has worked for me so far
It's in there username in your code. https://github.com/networkdavit/pillicam_test/blob/main/src/users/entities/user.entity.ts#:~:text=class%20User%20%7B-,username%3A%20string%3B,-password%3A%20string
You can change it.
Or you can refer to this article for JWT implementation in nest.js
Just in case anyone ever gets this problem, I found a solution.
All I had to do was to add this to my local.strategy.ts file in constructor
super({
usernameField: 'email',
passwordField: 'password'
});
The default expects a username and password, so have to modify it manually

Trouble with module import [ NEST JS ]

I'm trying to use a jwt.strategy.ts to protect my endpoints with jwt verification. Everything was going ok until I decided to import a custom JWTService inside this jwt.strategy which contains its own JWTModule but Nest doesn't seems to recognize it. I can use the JWTService in other services but it doesn't work inside the strategy. What should I do ? What am I doing wrong ?
The NEST Message:
[Nest] 53 - 09/22/2022, 6:32:25 PM ERROR [ExceptionHandler] Nest can't resolve dependencies of the JwtStrategy (ConfigService, ?). Please make sure that the argument Object at index [1] is available in the AuthModule context.
Potential solutions:
- If Object is a provider, is it part of the current AuthModule?
- If Object is exported from a separate #Module, is that module imported within AuthModule?
#Module({
imports: [ /* the Module containing Object */ ]
})
Error: Nest can't resolve dependencies of the JwtStrategy (ConfigService, ?). Please make sure that the argument Object at index [1] is available in the AuthModule context.
Potential solutions:
- If Object is a provider, is it part of the current AuthModule?
- If Object is exported from a separate #Module, is that module imported within AuthModule?
#Module({
imports: [ /* the Module containing Object */ ]
})
The JWTModule:
import { Module } from '#nestjs/common';
import { JwtModule } from '#nestjs/jwt';
import { jwtOptions } from './jwt.config';
import { JWTService } from './jwt.service';
#Module({
imports: [JwtModule.registerAsync(jwtOptions)],
providers: [JWTService],
exports: [JWTService],
})
export class JWTModule {}
The JWTService:
import { Request } from 'express';
import { DecodeOptions } from 'jsonwebtoken';
import { Injectable, UnprocessableEntityException } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { JwtService, JwtSignOptions, JwtVerifyOptions } from '#nestjs/jwt';
import { CookieHttpConfig } from '../auth';
#Injectable()
export class JWTService {
constructor(
private readonly jwtService: JwtService,
private readonly configService: ConfigService,
) {}
sign(payload: string | object | Buffer, options?: JwtSignOptions) {
return this.jwtService.sign(payload, options);
}
async signAsync(payload: string | object | Buffer, options?: JwtSignOptions) {
return this.jwtService.signAsync(payload, options);
}
verify(token: string, options?: JwtVerifyOptions) {
return this.jwtService.verify(token, options);
}
async verifyAsync(token: string, options?: JwtVerifyOptions) {
return this.jwtService.verifyAsync(token, options);
}
decode(token: string, options?: DecodeOptions) {
return this.jwtService.decode(token, options);
}
async getToken(tokenPayload: any): Promise<string> {
try {
const token: string = await this.jwtService.signAsync(tokenPayload);
return `Bearer ${token}`;
} catch (error) {
throw new UnprocessableEntityException(error.message);
}
}
async refreshToken(
cookieName: string,
request: Request,
payload: any,
): Promise<void> {
const token: string = await this.getToken(payload);
request.res.cookie(cookieName, token, CookieHttpConfig.Options());
}
}
jwt.strategy:
import { JWTService } from '#app/common';
import { Injectable } from '#nestjs/common';
import { ConfigService } from '#nestjs/config';
import { PassportStrategy } from '#nestjs/passport';
import { Request } from 'express';
import { ExtractJwt, Strategy } from 'passport-jwt';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly configService: ConfigService,
private readonly jwtService: JWTService,
) {
super({
jwtFromRequest: ExtractJwt.fromExtractors([
(request: Request) => {
try {
const token = request.signedCookies['Authorization'].split(' ')[1];
return token;
} catch (error) {
return null;
}
},
]),
ignoreExpiration: false,
secretOrKey: configService.get('AUTH_JWT_SECRET'),
});
}
async validate(request: Request, payload: any): Promise<any> {
const tokenPayload = {
email: payload.email,
id: payload.id,
};
await this.jwtService.refreshToken('Authorization', request, tokenPayload);
return tokenPayload;
}
}
Extra information about my project:
I divided the project into a monorepo, so I imported the JWTModule inside the AuthModule but it still doesn't work. The jwt.strategy.ts and the JWTModule is inside a shared lib created at the same level as the apps folders containing the microservices.
The AuthModule:
import { Module } from '#nestjs/common';
import { PassportModule } from '#nestjs/passport';
import { UsersModule } from '../users/users.module';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import {
AuthLibModule,
JWTModule,
JwtStrategy,
LocalStrategy,
} from '#app/common';
import { Web3Module } from '../web3/web3.module';
import { VonageModule } from '#app/common';
#Module({
imports: [
UsersModule,
PassportModule,
JWTModule,
Web3Module,
VonageModule,
AuthLibModule,
],
controllers: [AuthController],
providers: [AuthService, LocalStrategy, JwtStrategy],
})
export class AuthModule {}

How to save api response in MongoDB in NestJs

I am using NestJs as a backend service where I am hitting some third party API and want to save response in MongoDB. I am unable to get how can I save data in MongoDB as I have DTO class for the data I want to save.
Below is my code:
app.module.ts
import { Module } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
#Module({
imports: [UserModule,
MongooseModule.forRoot('mongodb://localhost/status')],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
user.module.ts
import { HttpModule } from '#nestjs/axios';
import { Module } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { ScheduleModule } from '#nestjs/schedule';
import { StatusSchema } from './schemas/status.schema';
import { UserController } from './user.controller';
import { UserService } from './user.service';
#Module({
imports:[HttpModule,
ScheduleModule.forRoot(),
MongooseModule.forFeature([{ name: 'Status', schema: StatusSchema }])],
controllers: [UserController],
providers: [UserService]
})
export class UserModule {}
status.schema.ts
import { Prop, Schema, SchemaFactory } from "#nestjs/mongoose";
import { Document } from "mongoose";
export type StatusDocument = Status & Document;
#Schema()
export class Status{
#Prop()
total:String;
}
export const StatusSchema = SchemaFactory.createForClass(Status);
status.dto.ts
export class StatusDto{
total:string;
}
user.service.ts
#Injectable()
export class UserService {
constructor(private httpService:HttpService,
private schedulerRegistry:SchedulerRegistry,
#InjectModel('Status') private readonly statusModel:Model<Status>){}
private readonly logger = new Logger(UserService.name);
async dynamicJob(){
this.logger.log("in main function");
const dat = await this.nameFun();
this.logger.warn(dat);
//Here I want to save the dat inside MongoDB
}
nameFun = async () =>{
const url = 'https://reqres.in/api/unknown';
const result = await axios.get(url);
this.logger.log("In nameFun " + result.data.total);
return result.data.total;
}
}
How can I add data inside MongoDB at specified place in above function?
Here's a working example with json placeholder data that I can test with since I don't know what your response looks like. I'm just passing in the text from the title field of the response into the total field of your Status schema.
Your 2 functions of UserService would look like this.
async dynamicJob() : Promise<Status> {
this.logger.log('in main function');
const dat = await this.nameFun();
this.logger.warn(dat);
const dto = { total: dat }; // creating dto in the form of your schema
this.logger.log(dto);
return await this.statusModel.create(dto); // saves and returns saved object
}
nameFun = async () => {
const url = 'https://jsonplaceholder.typicode.com/todos/2';
const result = await axios.get(url);
// you'll need to change this back to correct parsing of your actual response
this.logger.log('In nameFun ' + result.data.title);
return result.data.title;
};
Then the corresponding function in your UserController would look something like this, which whatever endpoint you want to use. Here I'm just using from-api.
#Get('from-api')
async getFromApi() : Promise<Status> {
return this.userService.dynamicJob();
}
Get request from this endpoint
http://localhost:3000/user/from-api/
returns the newly created document in Mongo
{
"total": "quis ut nam facilis et officia qui",
"_id": "622a1a6e990efa55c984dc4b",
"__v": 0
}

Type 'Task' does not satisfy the constraint 'Document'

I'm creating API using Nestjs and Mongodb. tasks.service.ts, tried to create a getAll endpoint and got typescript error:
Type 'Task' does not satisfy the constraint 'Document'. Type 'Task' is missing the following properties from type 'Document': increment, model, $isDeleted, remove, and 51 more.
tasks.service.ts
import { Injectable, HttpStatus } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { Model } from 'mongoose';
import { Task } from './dto/task.inferface';
#Injectable()
export class TasksService {
private readonly Tasks: Task[] = [];
constructor(#InjectModel('Task') private readonly TaskModel: Model<Task>) {}
async getAll(): Promise<Task> {
const tasks = await this.TaskModel.find().exec();
return tasks;
}
}
Extend the document class from the interface.
import { Document } from 'mongoose';
export interface Task extends Document {
//Task info ...
}
Task shouldn't be a DTO, it should be entity like:
tasks.entity.ts
import { Schema, Prop, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
#Schema()
export class Task extends Document {
#Prop()
name: string;
}
export const TaskSchema = SchemaFactory.createForClass(Task);
You also need to register that model in your module:
#Module({
imports: [
MongooseModule.forFeature([
{
name: Task.name,
schema: TaskSchema,
},
]),
],
})
So your code will be something like
import { Injectable, HttpStatus } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { Model } from 'mongoose';
import { Task } from './entities/task.entity';
#Injectable()
export class TasksService {
constructor(#InjectModel(Task.name) private readonly TaskModel: Model<Task>) {}
async getAll(): Promise<Task> {
const tasks = await this.TaskModel.find().exec();
return tasks;
}
}

API calls from one angular service is not being intercepted by jwt -interceptor - while all others are ok

In my angular-ionic app I have several services calling REST apis in the backend.
All API calls are intercepted by jwt-interceptor and bearer token is being added except 1 .
The funny thing is that one method (getVehicles) is begin intercepted correctly while the concerned method (createVehicle) is not being intercepted. Any ideas?
Pls find code for my service
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable } from 'rxjs';
import { VehicleModel } from '../models/vehicle.model';
import { environment } from 'src/environments/environment';
import { Criteria } from 'src/app/common/model/criteria.model';
#Injectable({
providedIn: 'root'
})
export class VehicleService {
constructor(private httpClient: HttpClient) { }
criteria: Criteria;
vehicle: VehicleModel;
baseUrl = environment.API_URL + '/api/vehicles';
vehicleRegistrationNumber: string;
// List of vehicles
public getVehicles(criteria: Criteria): Observable<VehicleModel[]> {
return this.httpClient.post<VehicleModel[]>(this.baseUrl + '/search', criteria);
}
// Create a new item
public createVehicle(vehicle: VehicleModel): Observable<VehicleModel> {
return this.httpClient.post<VehicleModel>(this.baseUrl + '/createVehicle', vehicle);
}
// Edit vehicle details
public updateVehicle(vehicle: VehicleModel): Observable<any> {
return this.httpClient.put<VehicleModel>(
this.baseUrl + '/updateVehicle', vehicle);
}
}
and code for jwt-inteceptor:
import { Injectable } from '#angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '#angular/common/http';
import { Observable } from 'rxjs';
import { AuthenticationService } from '#/_shared/_services';
#Injectable({ providedIn: 'root' })
export class JwtInterceptor implements HttpInterceptor {
constructor(private authenticationService: AuthenticationService) { }
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add authorization header with jwt token if available
const currentUser = this.authenticationService.currentUserValue;
if (currentUser && currentUser.token) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.token}`
}
});
}
return next.handle(request);
}
}