How to read user from request in a Mongoose midddleware hook or a service in NestJS - express

Assume a user is logged in and there is a Post document, I want to save the user who created and updated the post.
export interface Post extends Document {
readonly title: string;
readonly content: string;
readonly createdAt?: Date;
readonly updatedAt?: Date;
readonly createdBy?: User;
readonly updatedBy?: User;
}
export const PostSchema = new Schema({
title: SchemaTypes.String,
content: SchemaTypes.String,
createdAt: { type: SchemaTypes.Date, required: false },
updatedAt: { type: SchemaTypes.Date, required: false },
createdBy: { type: SchemaTypes.ObjectId, ref: 'User', required: false },
updatedBy: { type: SchemaTypes.ObjectId, ref: 'User', required: false },
});
But I have no idea how to read the user from the request in a service component or Mongoose document schema.

It really depends on how you authenticate your users, but you should be able to intercept whichever access token, cookies (...) that the frontend uses to authentifies itself.
You can intercept it in the controller and get the corresponding user (or userId) which you can then pass to your service.
In case you're using nestjs passport, your example could look like this:
import { Injectable, Request, Body } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
import { Controller, Bind, Request, Post, UseGuards } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
import { JwtAuthGuard } from './auth/jwt-auth.guard';
#Controller()
export class PostController {
#UseGuards(JwtAuthGuard)
#Put('post')
addPost(#Request() request, #Body() body) {
const user = request.user._id;
// We only need the id to add it to mongo but you could also pass the whole user
return this.createPost(body, user._id)
}
}
#Injectable()
class PostService
//...
async createPost(body, userId) {
this.postModel.create({
...body,
createdBy: userId
})
}
//...
If you want more information about how to use nestjs passport and implement auth guards, see here nestjs docs

Related

NestJS - No metadata for "EmployeeRepository" was found with authentication

I try to do some authentication for my nestjs app but I got stuck with this error and I don't know where to look at
My app.module.ts
#Module({
imports: [
AgenciesModule,
ActionsModule,
AuthModule,
TypeOrmModule.forRoot({
type: 'mysql',
host: '************',
username: '*********',
password: '*********',
database: '*********',
synchronize: true,
autoLoadEntities: true,
}),
EmployeesModule,
],
})
export class AppModule {}
My auth.service.ts
import { EmployeeRepository } from 'src/employees/entities/employee.repository';
#Injectable()
export class AuthService {
constructor(
#InjectRepository(EmployeeRepository)
private employeeRepository: EmployeeRepository,
) {}
async validateUser(email: string, password: string): Promise<any> {
const user = await this.employeeRepository.findOne({
where: { email, password },
});
// this work with postman if I put false data
//const user = {
// email: "email",
// password: "password",
//}
if (user && user.email === email && user.password === password) {
const { password, ...result } = user;
return result;
}
return null;
}
}
My auth.controller.ts
#Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
#Post('login')
async login(#Body() body) {
return this.authService.login(body.email, body.password);
}
}
My auth.module.ts
imports: [TypeOrmModule.forFeature([EmployeeRepository])],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
And my employee.repository.ts
import { EntityRepository, Repository } from 'typeorm';
import { Employee } from './employee.entity';
#EntityRepository(Employee)
export class EmployeeRepository extends Repository<Employee> {}
I didn't put the different import for each file but I can provide them if needed
I checked all the file name and path from the differents import and they are all correct and I also updated my packages just in case.
These posts dosen't help :
NestJS - No metadata for "<Entity>" was found
No metadata for "User" was found using TypeOrm
Try this in auth.module.ts
imports: [TypeOrmModule.forFeature([EmployeeRepository])]
Change like this
imports: [TypeOrmModule.forFeature([Employee])]

Get user data in RoleGuard with passport JWT authentication

I've managed to get JWT authentication in my nestJS application.
Now I want to implement the role guard and have therefore to check the role of the authenticated user.
Therefore, I thought of requesting the respective user role from the database. But this call is async and this is not doable within the guard.
My question is:
How can I get the user role information within the Guard?
I could put the information in the JWT token, but this seems not right to me, or am I wrong?
Here, Implementing Passport JWT you can put your findUser in the validate function that is async. And then create a decorator to return the user Auth JWT in decorator in NESTJS
So you need to do some things like this
//jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { jwtConstants } from './constants';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload: any) {
// Your JWT payload
// Insert here the findOne from you're BDD
return { userId: payload.sub, username: payload.username };
}
}
And then
//user.decorator.ts
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const User = createParamDecorator((data: any, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
});
And in your controller juste use
//user.controller.ts
import { User } from './user.decorator';
#Get()
async getUser(#User() user) {
//console.log(user);
}

nestjsx/crud + TypeORM: patch and post results in empty request

I'm trying to get into nestjs by creating a simple REST Api with TypeORM and the crud library. So far I have created a working role based authentication, but I'm running into a strange problem. I used the crud library to create a simple controller for the User entity. The GET-requests are working without any problems. But I can't POST to create a new user, neither can I use PATCH to update one. I think it might just be a very stupid mistake by me, but as I did not write much code, I can't find any differences to the examples in the doc.
When I try to patch a property, it just responds me with the original user object, no changes made (It's like I send an empty request).
When I try to post a new user, the response is the following error message:
{
"statusCode": 400,
"error": "Bad Request",
"message": "Empty data. Nothing to save."
}
It might have something to do with validation..
This is my user controller:
import { Controller, UseGuards } from '#nestjs/common';
import { UserService } from './user.service';
import { User } from './user.entity';
import { AuthGuard } from '#nestjs/passport';
import { ApiTags, ApiSecurity } from '#nestjs/swagger';
import { RolesGuard } from 'src/auth/role.guard';
import { Roles } from './roles.decorator';
import { Crud, CrudController } from '#nestjsx/crud';
#UseGuards(AuthGuard('jwt'), RolesGuard)
#Crud({
model: {
type: User
},
routes: {
exclude: ['createManyBase', 'replaceOneBase'],
},
//validation: false,
})
#Roles('admin')
#ApiSecurity('bearer')
#ApiTags('user')
#Controller('user')
export class UserController implements CrudController<User> {
constructor(public service: UserService) {}
}
This is my user service:
import { Injectable, Body, NotFoundException } from '#nestjs/common';
import { CreateUserDTO } from './dto/create-user.dto';
import { User } from './user.entity';
import { GetUsersFilterDto } from './dto/get-users-filter.dto';
import { InjectRepository } from '#nestjs/typeorm';
import { UserRepository } from './user.repository';
import { Role } from './role.entity';
import { TypeOrmCrudService } from '#nestjsx/crud-typeorm';
#Injectable()
export class UserService extends TypeOrmCrudService<User> {
constructor(
#InjectRepository(User) user,
private userRepository: UserRepository
) {
super(user);
}
async getUserByName(username: string): Promise<User>{
const found = await this.userRepository.findOne({
where: {
username: username,
},
relations: ["roles"]
});
if (!found){
throw new NotFoundException('User "${username}" not found!');
}
return found;
}
async getUserById(id: number): Promise<User>{
const found = await this.userRepository.findOne(id, {relations: ["roles"] });
if (!found){
throw new NotFoundException('User with "${id}" not found');
}
return found;
}
async matchRoles(roles: string[], userroles: Role[]){
let match = false;
console.log(userroles)
userroles.forEach(r => {
if (roles.indexOf('r.name')){
match = true;
}
})
return match;
}
}
This is the entity:
import { Entity, Column, PrimaryGeneratedColumn, ManyToMany, JoinTable, BeforeInsert, Unique } from 'typeorm';
import { Role } from './role.entity';
import * as bcrypt from 'bcryptjs';
import { Exclude } from 'class-transformer';
import { ApiProperty } from '#nestjs/swagger';
#Entity('auth_user')
#Unique(['username'])
export class User {
#PrimaryGeneratedColumn()
id: number;
#ApiProperty()
#Column({ length: 30 })
username: string;
#ApiProperty()
#Column()
firstName: string;
#ApiProperty()
#Column()
lastName: string;
#ApiProperty()
#Column()
email: string;
#BeforeInsert()
async hashPassword() {
this.password = await bcrypt.hash(this.password, 10);
}
#ApiProperty()
#Column()//({select: false})
#Exclude()
password: string;
#ApiProperty()
#Column({ default: true })
isActive: boolean;
#ManyToMany(
type => Role,
role => role.users,
{ cascade: true },
)
#JoinTable()
roles?: Role[];
}
Any hints are appreciated
As it turned out, it was the validation. Crud already has validation activated and I had this in the main.ts:
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true}));
So it was validated twice, what somehow led to an empty body in the request. I removed this and now I'm able to post/patch/put.

NestJS passport authentication returns 401 when using email for authentication

I have a problem that seems to be not that uncommon, but the solutions that I found did not work in my project.
What I want to do is a simple authentication using passport as this tutorial suggests: https://docs.nestjs.com/techniques/authentication
I followed this tutorial all along and at first it worked. Later I decided to use the users E-Mail and password as authentication instead of a username. So I changed my variable names and parameters in the authentication process to email and that was the point where everything broke apart. Am I missing something here?
auth.module.ts
import {Module} from '#nestjs/common';
import {UsersModule} from "../users/users.module";
import {AuthService} from "./services/auth.service";
import {PassportModule} from "#nestjs/passport";
import {LocalStrategy} from "./strategies/local.strategy";
import {AuthController} from "./controllers/auth.controller";
import {JwtModule} from "#nestjs/jwt";
import {jwtConstants} from "./constants";
import {JwtStrategy} from "./strategies/jwt.strategy";
import {EncryptionModule} from "../encryption/encryption.module";
#Module({
imports: [
UsersModule,
EncryptionModule,
PassportModule.register({defaultStrategy: 'jwt'}),
JwtModule.register({
secret: jwtConstants.secret,
signOptions: {
expiresIn: '30s'
}
})
],
providers: [
AuthService,
LocalStrategy,
JwtStrategy
],
controllers: [
AuthController
]
})
export class AuthModule {
}
controllers/auth.controller.ts
import {Controller, Get, Post, Request, UseGuards} from '#nestjs/common';
import {AuthService} from "../services/auth.service";
import {JwtAuthGuard} from "../guards/jwt-auth.guard";
import {LocalAuthGuard} from "../guards/local-auth.guard";
#Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {
}
#UseGuards(LocalAuthGuard)
#Post('login')
login(#Request() req) {
return this.authService.login(req.user);
}
#UseGuards(JwtAuthGuard)
#Get('profile')
getProfile(#Request() req) {
return req.user;
}
}
services/auth.service.ts
import {Injectable} from '#nestjs/common';
import {UsersService} from "../../users/services/users.service";
import {User} from "../../users/interfaces/user.interface";
import {JwtService} from "#nestjs/jwt";
import {JwtPayloadDto} from "../models/jwt-payload.dto";
import {EncryptionService} from "../../encryption/services/encryption.service";
#Injectable()
export class AuthService {
constructor(private usersService: UsersService,
private jwtService: JwtService,
private encryptionService: EncryptionService) {
}
async validateUser(email: string, pass: string): Promise<User | undefined> {
/**
* The findOne-method sends a database query
* to my mongodb via mongoose.
* I don't think it's necessary to post the UserService here, is it?
*/
const user: User = await this.usersService.findOne(email);
return this.encryptionService.compare(pass, user.password).then((result) => {
if (result) {
return user;
}
return undefined;
});
}
async login(user: User) {
const payload: JwtPayloadDto = {
email: user.email,
sub: user.id
}
return {
accessToken: this.jwtService.sign(payload)
};
}
}
strategies/local.strategy.ts
import {Injectable, UnauthorizedException} from "#nestjs/common";
import {PassportStrategy} from "#nestjs/passport";
import {Strategy} from "passport-local";
import {AuthService} from "../services/auth.service";
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super();
}
async validate(email: string, password: string): Promise<any> {
const user = await this.authService.validateUser(email, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
guards/local-auth.guard.ts
import {Injectable} from "#nestjs/common";
import {AuthGuard} from "#nestjs/passport";
#Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
}
According to this question I found out that the validate-methods signature has to have the same parameter names as the request payloads keys.
For debugging purposes I have put a console.log()-call on the first line of my validate-method in the strategies/local.strategy.ts but it seems as it does not get called at all.
Thanks for any answer in advance.
Have a good one!
for me, when create LocalStrategy, I passed {usernameField: 'email'} to ParentClass.
If you want to check user authenticate with custom column like 'email', try pass it.
my user.entity.ts:
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column({ unique: true })
email: string;
#Column()
name: string;
}
my local.strategy.ts:
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ usernameField: 'email' });
}
async validate(email: string, password: string): Promise<User> {
console.log(email, password); // it works
}
}
Well, I solved it myself. 5 hours of debugging wasted!
Turned out that somehow my Postman did not send the Content-Type header with the request. Restarting Postman fixed it.

Autoinjected API

i am developing an application using Angular2 (actually, ionic framework). Application itself contains a login form that should work via API.
Structure:
form.ts
Contains logic related to login form
import {Component, Injectable} from '#angular/core';
import { Http } from '#angular/http';
import {User} from '../../../base/user';
import {API} from '../../../base/API';
#Component({
templateUrl: 'build/pages/account/loginForm/form.html',
selector: 'login-form',
providers: [Http, API]
})
export class AccountForm {
constructor() {
...
}
submitLogin($event) {
var username = this.username.value;
var password = this.password.value;
this.user = new User(username, password);
this.user.authenticate()
}
}
user.ts
Class that contains some user information (username, password hash)
import { Inject } from '#angular/core';
import { Hash } from "./hash";
import { API } from "./API";
export class User {
public username: string;
public password: string;
public pwdhash: string;
constructor(
username: string,
password: string,
private api: API ///// problem is here
) {
let hash = new Hash();
this.username = username;
this.password = password;
this.pwdhash = hash.hash(username, password);
}
authenticate() {
var promise = this.api.loginUser({username: this.username, pwdhash: this.pwdhash});
console.log(promise);
}
}
API.ts
Class with API request to the backed
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Injectable, Inject } from '#angular/core';
import { Hash } from './hash';
#Injectable()
export class API {
private http: Http;
constructor(#Inject(Http) http: Http) {
}
loginUser (user) {
return this.http.post(url, body, options);
}
}
What i want to do is use API class in a User.authenticate() call, but i don't want to pass it as a parameter to the User.constructor(), as this is a bad style.
Another option is to create a User.API member in a constructor, which looks ugly too.
Looks like i need just a function that makes API requests. However, i am not sure this is a good idea for Object-Oriented Programming.
Is there any way to inject API into User class without creating a new API object of this class?
Thanks