So I have a sample app im building in nest js and I hit an error on npm start
Nest can't resolve dependencies of the ClientsService (?). Please make sure that the argument ClientModel at index [0] is available in the ClientsModule context.
So I have checked it over but cant seem to find why the error is happening
My client.service.ts
import { Injectable } from '#nestjs/common';
import { Model } from 'mongoose';
import { InjectModel } from '#nestjs/mongoose';
import { Client } from 'clients/interfaces/client.interface';
import { CreateClientDTO } from 'clients/dto/create-client.dto';
#Injectable()
export class ClientsService {
constructor(#InjectModel('Client') private readonly clientModel: Model<Client>) { }
// Get all clients
async getClients(): Promise<Client[]> {
const clients = await this.clientModel.find().exec();
return clients
}
//Get single client
async getClient(clientID: Promise<Client>) {
const client = await this.clientModel
.findById(clientID)
.exec();
return client;
}
//Add client
async addClient(createClientDTO: CreateClientDTO): Promise<Client> {
const newClient = await new this.clientModel(createClientDTO);
return newClient.save()
}
}
and my client.module.ts
import { Module } from '#nestjs/common';
import { ClientsService } from './clients.service';
import { ClientsController } from './clients.controller';
import { MongooseModule } from '#nestjs/mongoose';
import { ClientSchema } from 'clients/schemas/clients.schema';
#Module({
imports: [
MongooseModule.forFeature([{name: 'Clients', schema: ClientSchema}])
],
providers: [ClientsService],
controllers: [ClientsController]
})
export class ClientsModule {}
The InjectModel decorator expects to take the schema name of your entity.
So you tell the mongoose in ClientsModule that the schema name is Clients, but in ClientsService you try to inject the model with the schema name Client, which is different from the contract in the module.
MongooseModule.forFeature([{name: 'Clients', schema: ClientSchema}])
constructor(#InjectModel('Client') private readonly clientModel: Model<Client>) { }
Related
I'm trying to implement JWT into my project. I've followed the steps as outline in https://www.npmjs.com/package/#nestjs/jwt#usage
auth.module.ts
import { Module } from '#nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { TypeOrmModule } from '#nestjs/typeorm';
import { JwtModule } from '#nestjs/jwt';
import { AuthRepository } from './auth.repository';
#Module({
imports: [
JwtModule.register({ secret: process.env.JWT_SECRET || 'ABCDE12345' }),
TypeOrmModule.forFeature([AuthRepository]),
],
exports: [TypeOrmModule, AuthService],
providers: [AuthService],
controllers: [AuthController],
})
export class AuthModule {}
auth.service.ts
import { Injectable, NotFoundException, UnauthorizedException } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { AuthEntity } from './auth.entity';
import { LoginDTO } from './dto/login.dto';
import * as bcrypt from 'bcrypt';
import { JwtService } from '#nestjs/jwt';
import crypto from 'crypto';
import { AuthRepository } from './auth.repository';
// export interface FindWhereData {
// readonly email: string;
// readonly password: string;
// }
export interface LoginResponse {
readonly token: string;
readonly refresh_token: string;
}
#Injectable()
export class AuthService {
constructor(
#InjectRepository(AuthRepository)
private authRepository: AuthRepository,
private jwtService: JwtService
) {}
/**
* Login user service
*
* #param doc
*/
public async login(doc: LoginDTO): Promise<LoginResponse> {
// verify user email
const user = await this.authRepository.findOne({ email: doc.email });
if (!user) {
throw new NotFoundException('Could not find user');
}
// verify password
const passwordsMatch = await this.passwordsAreEqual(doc.password, user.password);
if (!passwordsMatch) {
throw new UnauthorizedException('Incorrect login credentials');
}
// generate JWT
const token = await this.jwtService.signAsync({ id: user.id });
// create the refresh token
const refreshToken = crypto.randomBytes(256).toString('hex');
// store the refresh token
return {
token: token,
refresh_token: refreshToken,
};
}
/**
* Generate a hashed password
*
* #param password
*/
public async hashPassword(password: string): Promise<string> {
const salt = await bcrypt.genSalt();
return await bcrypt.hash(password, salt);
}
/**
* Compare password against an encrypted string
*
* #param password
* #param encryptedPassword
*/
public async passwordsAreEqual(password: string, encryptedPassword: string): Promise<boolean> {
return await bcrypt.compare(password, encryptedPassword);
}
/**
* Find a record by column attribute and value
*
* #param queryObject
*
*/
public async findWhere(queryObject): Promise<AuthEntity> {
const authEntity = await this.authRepository.findOne({ where: queryObject });
if (!authEntity) {
return null;
}
return authEntity;
}
public async findOne(id: string): Promise<AuthEntity> {
return this.authRepository.findOne(id);
}
}
auth.repository.ts
import { EntityRepository, Repository } from "typeorm";
import { AuthEntity } from "./auth.entity";
#EntityRepository(AuthEntity)
export class AuthRepository extends Repository<AuthEntity> {}
app.module.ts
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { RouterModule } from 'nest-router';
import { routes } from './routes';
import { ConfigModule } from '#nestjs/config';
import configuration from './config/configuration';
import { TypeOrmModule } from '#nestjs/typeorm';
import { Connection } from 'typeorm';
import { AuthModule } from './auth/auth.module';
#Module({
imports: [
RouterModule.forRoutes(routes),
ConfigModule.forRoot({
load: [configuration],
}),
TypeOrmModule.forRoot({
type: 'postgres',
host: process.env.POSTGRES_HOST || 'localhost',
port: 5432,
username: process.env.POSTGRES_USERNAME || 'postgres',
password: process.env.POSTGRES_PASSWORD || 'password',
database: process.env.POSTGRES_DATABASE || 'service-auth',
autoLoadEntities: true,
synchronize: true,
}),
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
constructor(private readonly connection: Connection) {
console.log('connection status: ' + connection.isConnected);
}
}
auth.service.spec.ts
import { JwtModule, JwtService } from '#nestjs/jwt';
import { Test, TestingModule } from '#nestjs/testing';
import { AuthEntity } from './auth.entity';
import { AuthRepository } from './auth.repository';
import { AuthService } from './auth.service';
describe('AuthService', () => {
let authService: AuthService;
let authRepository: AuthRepository;
const mockAuthRepository = () => ({
login: jest.fn(),
});
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
// JwtModule,
{
provide: getRepositoryToken(AuthRepository),
useFactory: mockAuthRepository,
},
{
provide: JwtService,
useValue: {
signAsync: jest.fn(() => 'token'),
}
}
]
}).compile();
authService = await module.get<AuthService>(AuthService);
authRepository = await module.get<AuthRepository>(AuthRepository);
});
/**
* Test that the service is defined
*/
it('should be defined', () => {
expect(authService).toBeDefined();
});
});
When I run npm run test I get the following error message:
FAIL src/auth/auth.service.spec.ts
● AuthService › should be defined
Nest can't resolve dependencies of the AuthService (AuthRepository, ?). Please make sure that the argument JwtService at index [1] is available in the RootTestModule context.
Potential solutions:
- If JwtService is a provider, is it part of the current RootTestModule?
- If JwtService is exported from a separate #Module, is that module imported within RootTestModule?
#Module({
imports: [ /* the Module containing JwtService */ ]
})
I know the error is probably pretty clear to seasoned Node/Nest developer but I cannot figure out what the RootTestModule is and how to get JwtModule imported.
I believe I have followed the setup correctly but adding this JwtModule to the AuthService is causing the service to be undefined in my unit tests.
Repo
https://github.com/marcuschristiansen/nestjs-auth
You should be adding a custom provider for the JwtService so that you can mock it. A custom provider could look like
{
provide: JwtService,
useValue: {
signAsync: jest.fn(() => 'token'),
}
}
To tell Nest to inject an object that has a signAsync() method that when called returns the string 'token' so that it will always be the same in your tests.
This object goes in the providers array, just like the AuthRepository mock
I am trying to get started with NestJS which uses TypeORM.
I have connected to a database. At least I think I have, because I was getting a lot of errors and after enough tuning of the config, the errors went away and the connection seems to be successful.
So now I want to fetch any bit of data just to get started.
There is a table in the database called RESULT_PAGE, so I just want to fetch any record from that. This is what I have tried:
result-page.entity.ts
import { Entity, PrimaryColumn, Column } from "typeorm";
#Entity()
export class ResultPage {
#PrimaryColumn()
result_page_id: number;
#Column({ length: 1 })
approval: string;
#Column({ length: 1})
manually_uploaded: string;
}
result-page.controller.ts
import { Controller, Get, Request } from '#nestjs/common';
import { ResultPageService } from './result-page.service';
#Controller('result-page')
export class ResultPageController {
constructor(
private resultPageService: ResultPageService
) { }
#Get('get-all')
getProfile() {
return this.resultPageService.findAll();
}
}
result-page.service.ts
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { ResultPage } from './result-page.entity';
#Injectable()
export class ResultPageService {
constructor(
#InjectRepository(ResultPage)
private readonly resultPageRepository: Repository<ResultPage>,
) {}
findAll(): Promise<ResultPage[]> {
return this.resultPageRepository.find();
}
}
If I edit the service to look like this:
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { ResultPage } from './result-page.entity';
#Injectable()
export class ResultPageService {
constructor(
#InjectRepository(ResultPage)
private readonly resultPageRepository: Repository<ResultPage>,
) {}
findAll(): Promise<string> {
return new Promise((resolve, reject) => { resolve('hello world') })
// return this.resultPageRepository.find();
}
}
then I get 'hello world', so it is definitely that the RESULT_PAGE table isn't connected
In the AppModule I am loading the entities like this
const typeOrmModuleOptions: TypeOrmModuleOptions = {
...
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true
}
I am sure that I am making some absolute noob mistake so if someone could help me out here it would be greatly appreciated. I am quite new to databases and api's so any info would help a lot. TIA
SOLVED
Solved by adding a connectString https://github.com/typeorm/typeorm/issues/3484#issuecomment-472315370
A little bit confusing the question. I miss the error and the database config.
result_page vs RESULT_PAGE: tablename
on linux/unix the tablenames are case sensitive, so you should set it in the annotation
#Entity({name: 'RESULT_PAGE'})
Please give some details to find the root cause if it was not that.
Can't figure out what's the problem of my code. (I'm new with nestjs, I'm trying to learn it by passing some apps to it). Console log says:
Nest can't resolve dependencies of the UrlsAfipService (?). Please
make sure that the argument at index [0] is available in the ApiModule
context.
UrlsAfipService
import { Injectable } from '#nestjs/common';
import { AfipUrls } from './urls'
#Injectable()
export class UrlsAfipService {
constructor(
private readonly afipUrls: AfipUrls,
) {}
getWSAA () {
return this.afipUrls.homo().wsaa; // <- change to prod() for production
}
getService (service: string) {
return this.afipUrls.homo().service.replace('{service}', service)
}
}
AfipUrls
export class AfipUrls {
homo() {
return {
wsaa: 'https://url.com',
service: 'https://url.com'
}
}
prod() {
return {
wsaa: 'url.com',
service: 'url.com'
}
}
}
ApiModule
import { Module } from '#nestjs/common';
import { ApiController } from './api.controller';
import { UrlsAfipService } from './urls-afip.service'
import { WsaaService } from './wsaa.service'
import { DescribeService } from './describe.service';
#Module({
controllers: [ApiController],
providers: [UrlsAfipService, WsaaService, DescribeService]
})
export class ApiModule {}
AppModule
import { Module } from '#nestjs/common';
import { ApiModule } from './api/api.module';
import { AppController } from './app.controller';
import { AppService } from './app.service';
#Module({
imports: [ApiModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
You have declared AfipUrls as a dependency for UrlsAfipService but it is not provided in any module.
So you have to add AfipUrls to the providers array of your ApiModule. Then it can be injected.
providers: [UrlsAfipService, WsaaService, DescribeService, AfipUrls]
// ^^^^^^^^
Note though, that encoding environment specific values in your code base might be a code smell. Consider creating a ConfigService that encapsulates environment specific variables that are read from environment variables or .env files using dotenv. See this answer for more information.
The documentation is kinda thin here so I ran into a problem. I try to use Guards to secure Controller or it's Actions, so I gonna ask for the role of authenticated requests (by JWT). In my auth.guard.ts I ask for "request.user" but it's empty, so I can't check the users role. I don't know how to define "request.user". Here is my auth module and it's imports.
auth.controller.ts
import { Controller, Get, UseGuards } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
import { AuthService } from './auth.service';
import { RolesGuard } from './auth.guard';
#Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
#Get('token')
async createToken(): Promise<any> {
return await this.authService.signIn();
}
#Get('data')
#UseGuards(RolesGuard)
findAll() {
return { message: 'authed!' };
}
}
roles.guard.ts
Here user.request is empty, because I never define it. The documentation doesn't show how or where.
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user; // it's undefined
const hasRole = () =>
user.roles.some(role => !!roles.find(item => item === role));
return user && user.roles && hasRole();
}
}
auth.module.ts
import { Module } from '#nestjs/common';
import { AuthService } from './auth.service';
import { HttpStrategy } from './http.strategy';
import { UserModule } from './../user/user.module';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
import { PassportModule } from '#nestjs/passport';
import { JwtModule } from '#nestjs/jwt';
#Module({
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secretOrPrivateKey: 'secretKey',
signOptions: {
expiresIn: 3600,
},
}),
UserModule,
],
providers: [AuthService, HttpStrategy],
controllers: [AuthController],
})
export class AuthModule {}
auth.service.ts
import { Injectable } from '#nestjs/common';
import { UserService } from '../user/user.service';
import { JwtService } from '#nestjs/jwt';
#Injectable()
export class AuthService {
constructor(
private readonly userService: UserService,
private readonly jwtService: JwtService,
) {}
async signIn(): Promise<object> {
// In the real-world app you shouldn't expose this method publicly
// instead, return a token once you verify user credentials
const user: any = { email: 'user#email.com' };
const token: string = this.jwtService.sign(user);
return { token };
}
async validateUser(payload: any): Promise<any> {
// Validate if token passed along with HTTP request
// is associated with any registered account in the database
return await this.userService.findOneByEmail(payload.email);
}
}
jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from './auth.service';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, UnauthorizedException } from '#nestjs/common';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'secretKey',
});
}
async validate(payload: any) {
const user = await this.authService.validateUser(payload);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
Documentation: https://docs.nestjs.com/guards
Thanks for any help.
Additionally to your RolesGuard you need to use an AuthGuard.
Standard
You can use the standard AuthGuard implementation which attaches the user object to the request. It throws a 401 error, when the user is unauthenticated.
#UseGuards(AuthGuard('jwt'))
Extension
If you need to write your own guard because you need different behavior, extend the original AuthGuard and override the methods you need to change (handleRequest in the example):
#Injectable()
export class MyAuthGuard extends AuthGuard('jwt') {
handleRequest(err, user, info: Error) {
// don't throw 401 error when unauthenticated
return user;
}
}
Why do this?
If you look at the source code of the AuthGuard you can see that it attaches the user to the request as a callback to the passport method. If you don't want to use/extend the AuthGuard, you will have to implement/copy the relevant parts.
const user = await passportFn(
type || this.options.defaultStrategy,
options,
// This is the callback passed to passport. handleRequest returns the user.
(err, info, user) => this.handleRequest(err, info, user)
);
// Then the user object is attached to the request
// under the default property 'user' which you can change by configuration.
request[options.property || defaultOptions.property] = user;
You can attach multiple guards together (#UseGuards(AuthGuard('jwt'), RolesGuard)) to pass the context between them. Then you will have access 'req.user' object inside 'RolesGuard'.
After I got the selected answer working (thank you), I found this option as well that you can add to the constructor that essentially does the same thing.
http://www.passportjs.org/docs/authorize/
Association in Verify Callback
One downside to the approach described above is that it requires two
instances of the same strategy and supporting routes.
To avoid this, set the strategy's passReqToCallback option to true.
With this option enabled, req will be passed as the first argument to
the verify callback.
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
constructor(private authService: AuthService) {
super({
passReqToCallback: true
})
}
// rest of the strategy (validate)
}
Does it work if you use req.authInfo?
As long as you don't provide a custom callback to passport.authenticate method, the user data should be attached to the request object like this.
req.authInfo should be the object you returned in your validate method
I'm working on a NativeScript app by using AngularJS2 & TypeScript. I've created one file config.ts for storing my API URL.
code for config.ts
export class Config {
apiUrl = "https://incandescent-fire-8397.firebaseio.com/";
}
Then from an another file called user.service.ts I'm trying to access apiUrl value. But, don't know how to do it. Tried couple of permutation & combination but till now no luck.
Code for user.service.ts
import {Injectable} from "#angular/core";
import {User} from "./user";
import Config = require("../config");
#Injectable()
export class UserService {
config: any;
constructor() {
this.config = new Config();
}
register(user: User) {
alert("API url going to use is : "+this.config.apiUrl);
}
}
Need some guidance.
Regards
------Issue Fixed-------
Updated the user.service.ts file
import {Injectable} from "#angular/core";
import {User} from "./user";
import {ConfigService} from "../config";
#Injectable()
export class UserService {
apiUrl = "https://incandescent-fire-8397.firebaseio.com/";
register(user: User,config:ConfigService) {
//alert("About to register: " + user.email);
alert("API url going to use is : "+config.apiUrl);
}
}
Then, the module(app.componets.ts) from where I'm accessing the register method updated like this -
import {Component} from "#angular/core";
import {User} from "./shared/user/user";
import {UserService} from "./shared/user/user.service";
import {ConfigService} from "./shared/config";
import {HTTP_PROVIDERS} from "#angular/http";
import firebase = require("nativescript-plugin-firebase");
#Component({
selector: "my-app",
providers: [UserService, HTTP_PROVIDERS,ConfigService],
templateUrl: "pages/login/login.html",
styleUrls: ["pages/login/login-common.css", "pages/login/login.css"]
})
export class AppComponent {
user: User;
isLoggingIn = true;
config:any;
constructor(private _userService: UserService, private _configSerice:ConfigService) {
this.user = new User();
this.config = new ConfigService();
}
submit() {
if (this.isLoggingIn) {
this.login();
} else {
this.signUp();
}
}
login() {
// TODO: Define
console.log('Clicked on Login button');
firebase.init(<any>{
persist: true // Allow disk persistence. Default false.
}).then(
function (instance) {
console.log("firebase.init done");
},
function (error) {
console.log("firebase.init error: " + error);
}
);
}
signUp() {
this._userService.register(this.user,this.config);
}
toggleDisplay() {
this.isLoggingIn = !this.isLoggingIn;
}
}
Don't know how much it's a right way to do. But, it's fixed the problem. Will appriciate a lot if someone know any better way to do this.
I thing you're initiating the Config module instead of the Config class inside it.
You can try adding the 'default' keyword to the "export class Config" line, or try initiating the config instance with "new Config.Config()".