I create a guard that are like these
import { Injectable, CanActivate, ExecutionContext } from '#nestjs/common';
import { AuthService } from './auth.service';
#Injectable()
export class CompanyGuard implements CanActivate {
constructor(private readonly authService: AuthService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = request.headers['token'];
if (token) {
const company = await this.authService.validateToken(token);
return company ? true : false;
} else {
return false;
}
}
}
I want to keep the value of company on the request object, so I can use it later on my controller. How to do that in Nest.js? I tried to look it on the docs, but I'm confused. Should I use the session or there are more simple way to do that?
You can just add the company to a custom field on the request. request.company = company and then in the controller you can use #Req() to get the request object and .company to get the company value. Or you could create a custom decorator to get the company for you.
Related
i try passa values to child component using subject service, I am based on Documentatio, but somethink is wrong and when a try print data dont show nothing.
i have the navComponent which request api base on select input. When changing select update request and send new data to child page.
Service:
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs';
#Injectable()
export class SearchService {
// Observable string sources
private data = new Subject<any>();
// Observable string streams
value$ = this.data.asObservable();
// Service message commands
setValeu(value: any) {
this.data.next(value);
}
}
NavComponent:
async consultaIndicadores() {
try {
this.loading = true
let con = await this.rq.postRequest(... request ...).toPromise()
this.loading = false
if (con.status == 200) {
this.dados = con.data;
this.search.setValeu(this.dados)
}
}
} catch (error) {
this.loading = false
console.log(error)
}
}
childComponent
constructor(private trans: SearchService) {
this.subscription = trans.value$.subscribe(val => {
console.log(val);
})
}
in each component i seted provider, pass service to constructor etc... I dont know what its wrong. please help me
Currently, I'm working on NestJS API. I'd like to prepare Permissions Guard and I have a problem with this. Users can have only one role, one role can have a lot of permissions. Permissions for roles are set on the Admin panel, so role permissions can be often changed. I cannot understand how can I deal with permissions in PermissionGuard. I know that I can check the current state of them in the database, but I think it's not the best way to do that because the database will be queried too often.
What should I do? Any idea?
Works nice. It's a JwtAuthGuard improvement and checking one permission.
import { CanActivate, ExecutionContext, Type, mixin } from '#nestjs/common';
import { EPermission } from '../path-with-your-enum-values';
import { JWTRequestPayload } from '../request-payload-type';
import { JwtAuthGuard } from './jwt-auth.guard';
export const PermissionGuard = (permission: EPermission): Type<CanActivate> => {
class PermissionGuardMixin extends JwtAuthGuard {
async canActivate(context: ExecutionContext) {
await super.canActivate(context);
const request = context.switchToHttp().getRequest<JWTRequestPayload>();
const user = request.user;
if (!user || !user.permissions) {
return false;
}
return user.permissions.includes(permission);
}
}
return mixin(PermissionGuardMixin);
};
And with controller:
#Post(':taskId/moderate')
#UseGuards(PermissionGuard(EPermission.MODERATE))
public async moderate(#Param('taskId') taskId: string): Promise<any> {
// ...
}
So I have an Angular 8 application that uses cookies to handle authentication (Laravel server using Laravel Sanctum). Because I am using cookies and I dont store any user data/tokens in localStorage, I have to fetch the current user from my server every time my app initializes using a stored cookie.
auth.service.ts
export class AuthService {
public readonly INITIAL_PATH = '/app/dashboard';
public readonly LOGIN_PATH = '/login';
private loggedUserSubject: Subject<User>;
public loggedUser: Observable<User>;
constructor(
private apiService: ApiService,
private http: HttpClient,
private router: Router,
private csrfService: CsrfService,
) {
this.loggedUserSubject = new Subject;
this.loggedUser = this.loggedUserSubject.asObservable();
this.getCurrentUser().subscribe(); // Initial call to server to get current user
}
...
getCurrentUser(): Observable<User> {
return this.http.get<User>(`${config.authUrl}/user`)
.pipe(
tap(user => {
this.loggedUserSubject.next(user);
}),
share()
);
}
isLoggedIn(): Observable<boolean> {
return this.loggedUser.pipe(
tap(user => {
console.log('logged user: ', user);
}),
map(user => !!user),
catchError(() => of(false)),
)
}
...
}
auth.guard.ts
export class AuthGuard implements CanActivate {
constructor(
private _router: Router,
private _authService: AuthService
) { }
canActivate(): Observable<boolean> {
return this._authService.isLoggedIn().pipe(
tap(isLoggedIn => {
if (isLoggedIn) {
this._router.navigate(['/app/dashboard']);
}
}),
map(isLoggedIn => !isLoggedIn)
);
}
}
Whenever my AuthGuard is hit, it has to call AuthService.isLoggedIn() to return a Observable<boolean> of whether or not a User is in memory. The problem is, I am returning a Subject which may or may not have already emitted a value because I have to make an asynchronous request to my server to get the current user initially. I cant use a BehaviourSubject because canActivate returns the first emitted value... which I wont know until the server responds.
How do I set this up to make AuthGuard.canActivate() wait for my service to provide the authenticated User?
I figured it out. First I changed the loggedUserSubject from a Subject to a BehaviorSubject with an initial value of undefined (I dont know if that made any difference TBH). I then had to add a filter operator the isLoggedIn() Observable to return only the first defined value (User if logged in, null if not). Otherwise its returning the first emitted value, which was undefined before.
auth.service.ts
isLoggedIn(): Observable<boolean> {
return this.auth.getCurrentUser().pipe(
filter(user => user !== undefined),
map(user => {
return !!user;
}),
);
}
auth.guard.ts
canActivate(): Observable<boolean> {
return this._authService.isLoggedIn().pipe(
first(),
tap(isLoggedIn => {
if (isLoggedIn) {
this._router.navigate(['/app/dashboard']);
}
}),
map(isLoggedIn => !isLoggedIn)
);
}
I'm having some trouble hitting a POST endpoint that triggers a typeorm repository.save() method to my postgres DB.
Here's my DTO object:
import { ApiProperty } from '#nestjs/swagger/';
import { IsString, IsUUID} from 'class-validator';
import { Client } from '../../../models';
import { User } from '../../../user.decorator';
export class ClientDTO implements Readonly<ClientDTO> {
#ApiProperty({ required: true })
#IsUUID()
id: string;
#ApiProperty({ required: true })
#IsString()
name: string;
public static from(dto: Partial<ClientDTO>) {
const cl = new ClientDTO();
cl.id = dto.id;
cl.name = dto.name;
return cl;
}
public static fromEntity(entity: Client) {
return this.from({
id: entity.id,
name: entity.name,
});
}
public toEntity = (user: User | null) => {
const cl = new Client();
cl.id = this.id;
cl.name = this.name;
cl.createDateTime = new Date();
cl.createdBy = user ? user.id : null;
cl.lastChangedBy = user ? user.id : null;
return cl;
}
}
My controller at POST - /client:
import {
Body,
Controller,
Get, Post
} from '#nestjs/common';
import { ClientDTO } from './dto/client.dto';
import { ClientService } from './client.service';
import { User } from 'src/user.decorator';
#Controller('client')
export class ClientController {
constructor(
private clientService: ClientService
) { }
#Get()
public async getAllClients(): Promise<ClientDTO[]> {
return this.clientService.getAllClients();
}
#Post()
public async createClient(#User() user: User, #Body() dto: ClientDTO): Promise<ClientDTO> {
return this.clientService.createClient(dto, user);
}
}
And my service:
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { Client } from '../../models';
import { ClientDTO } from './dto/client.dto';
import { User } from '../../user.decorator';
#Injectable()
export class ClientService {
constructor(
#InjectRepository(Client) private readonly clientRepository: Repository<Client>
) {}
public async getAllClients(): Promise<ClientDTO[]> {
return await this.clientRepository.find()
.then(clients => clients.map(e => ClientDTO.fromEntity(e)));
}
public async createClient(dto: ClientDTO, user: User): Promise<ClientDTO> {
return this.clientRepository.save(dto.toEntity(user))
.then(e => ClientDTO.fromEntity(e));
}
}
I get a 500 internal server error with log message stating that my ClientDTO.toEntity is not a function.
TypeError: dto.toEntity is not a function
at ClientService.createClient (C:\...\nest-backend\dist\features\client\client.service.js:29:47)
at ClientController.createClient (C:\...\nest-backend\dist\features\client\client.controller.js:27:35)
at C:\...\nest-backend\node_modules\#nestjs\core\router\router-execution-context.js:37:29
at process._tickCallback (internal/process/next_tick.js:68:7)
I'm confused because this only happens via http request. I have a script that seed my dev database after I launch it fresh in a docker container called seed.ts:
import * as _ from 'lodash';
import { Client } from '../models';
import { ClientDTO } from '../features/client/dto/client.dto';
import { ClientService } from '../features/client/client.service';
import { configService } from '../config/config.service';
import { createConnection, ConnectionOptions } from 'typeorm';
import { User } from '../user.decorator';
async function run() {
const seedUser: User = { id: 'seed-user' };
const seedId = Date.now()
.toString()
.split('')
.reverse()
.reduce((s, it, x) => (x > 3 ? s : (s += it)), '');
const opt = {
...configService.getTypeOrmConfig(),
debug: true
};
const connection = await createConnection(opt as ConnectionOptions);
const clientService = new ClientService(connection.getRepository(Client));
const work = _.range(1, 10).map(n => ClientDTO.from({
name: `seed${seedId}-${n}`,
}))
######################## my service calls ClientDTO.toEntity() without issue ###########################
.map(dto => clientService.createClient(dto, seedUser)
.then(r => (console.log('done ->', r.name), r)))
return await Promise.all(work);
}
run()
.then(_ => console.log('...wait for script to exit'))
.catch(error => console.error('seed error', error));
It makes me think I am missing something simple/obvious.
Thanks!
Looks like you are using ValidationPipe. The solution is mentioned here
https://github.com/nestjs/nest/issues/552
when setting your validation pipe you need to tell it to transform for example
app.useGlobalPipes(new ValidationPipe({
transform: true
}));
The fact that the dto is declared like this dto: ClientDTO in the controller is not enough to create instances of the class. This is just an indication for you and the other developers on the project, to prevent misuses of the dto object in the rest of the application.
In order to have instances of classes, and use the methods from the class, you have to explicitly set a mapping like this:
#Post()
public async createClient(#User() user: User, #Body() dto: ClientDTO): Promise<ClientDTO> {
const client = ClientDTO.from(dto);
return this.clientService.createClient(client, user);
}
Assuming ClientDTO.from is the right function to use for the data contained in dto. If not, adapt it, create a new one, or add a constructor.
Your dto was not a class-based object when coming in through your api call-- it's just a generic object. Therefore it can't have methods and so your toEntity method won't work. The error message you get is a red herring that doesn't tell you the true cause of the failure.
You can fix this by creating a new object based on your class and then calling a method on the new object to copy the properties in from your plain object dto, or by using the class-transformer library, or by whatever you want that achieves the same result.
I have several methods to access my server. All of those methods don't return promises or observables, I'm passing callbacks in.
Now I need to write a guard, that ensures, that user data has been loaded before the user can use several routes. The canActivate method returns a promise, but there is code to navigate away within that promise and that does not work.
Has anybody an idea how to fix this. Do I have to rewrite my api methods to return promises or obervables? Or is there a way to make a redirect from with a promise, that is returned my canActivate. Would it be better, to move the code to fetch the user data (if a user is logged in) into a resolve object?
import {Injectable} from '#angular/core';
import {CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot} from '#angular/router';
import {GlobalStateService} from './globalState.service';
import {AuthTokenService} from './authToken.service';
import {UserApi} from '../api/user.api';
#Injectable()
export class AfterLogInGuardService implements CanActivate {
constructor(
private globalState: GlobalStateService,
private authToken: AuthTokenService,
private router: Router,
private userApi: UserApi
) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean|Promise<boolean> {
// The tho user is not logged in, redirect her always to the login page, if
// she calls routes with this guard.
if (!this.authToken.has()) {
this.router.navigateByUrl('/logIn');
return false;
}
const thisGuard = this;
// If the user has a token, we have to ensure, that we have the user data
// loaded from the server.
return this.afterUserDataHasBeenLoaded(function () {
// Redirect the user to the "Please enter your personal data" page, if
// she has not entered them yet.
if (!thisGuard.globalState.personalDataStored && route.url.toString() !== 'signUp,personalData') {
//
// This here doesn't work!
//
thisGuard.router.navigateByUrl('/signUp/personalData');
return false;
}
return true;
});
};
private afterUserDataHasBeenLoaded(callback: () => boolean) {
const thisGuard = this;
return new Promise<boolean>(function(resolve, reject) {
if (thisGuard.globalState.userDataLoaded)
return callback();
const innerCallback = function () {
thisGuard.globalState.userDataLoaded = true;
resolve(callback());
};
thisGuard.userApi.getUserData(innerCallback);
});
}
}
Following Implementation can be Useful. Promise is defined in auth.service
auth.guard
import { Injectable } from '#angular/core';
import { Router, CanActivate } from '#angular/router';
import { AuthService} from './services/auth.service';
#Injectable()
export class AuthGuard implements CanActivate {
status:boolean=false;
constructor(private authService:AuthService, private router:Router) { }
canActivate() {
var self=this;
this.authService.isAuth()
.then((res)=>{if(res==true)return true;else self.router.navigate(['/login']);});
}
}
auth.service
isAuth(): Promise<Status> {
const url = "/account/check";
return this.http.get(url)
.toPromise()
.then(response=>return response.json().status as Status)
.catch(this.handleError);
}
Server Response
{_body: "{"status":false}", status: 200, ok: true, statusText: "OK",
headers: Headers…}