NestJS Permissions Guard - the most efficient way - javascript

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> {
// ...
}

Related

Firebase Realtime database count active users by status value

I have a service that detects the presence when the user is online, away and offline in firebase.
presence.service.ts
import { Injectable } from "#angular/core";
import { AngularFireAuth } from "#angular/fire/auth";
import { AngularFireDatabase } from "#angular/fire/database";
import * as firebase from "firebase/app";
import { tap, map, switchMap, first } from "rxjs/operators";
import { of } from "rxjs";
import { SuperUserService } from "./../services/superuser.service";
#Injectable({
providedIn: "root",
})
export class PresenceService {
constructor(
private afAuth: AngularFireAuth,
private db: AngularFireDatabase,
private superuser: SuperUserService
) {
console.log("Verificação de status em execução");
this.setName();
this.updateOnUser().subscribe();
this.updateOnDisconnect().subscribe();
this.updateOnAway();
}
getPresence(uid: string) {
return this.db.object(`status/${uid}`).valueChanges();
}
getUser() {
return this.afAuth.authState.pipe(first()).toPromise();
}
async setPresence(status: string) {
const user = await this.getUser();
if (user) {
return this.db.object(`status/${user.uid}`).update({
status,
timestamp: this.timestamp,
});
}
}
async setName() {
const user = await this.getUser();
if (user) {
return this.db.object(`status/${user.uid}`).update({
nome: this.superuser.user.displayName,
});
}
}
get timestamp() {
return firebase.database.ServerValue.TIMESTAMP;
}
updateOnUser() {
const connection = this.db
.object(".info/connected")
.valueChanges()
.pipe(map((connected) => (connected ? "online" : "offline")));
return this.afAuth.authState.pipe(
switchMap((user) => (user ? connection : of("offline"))),
tap((status) => this.setPresence(status))
);
}
updateOnDisconnect() {
return this.afAuth.authState.pipe(
tap((user) => {
if (user) {
return this.db
.object(`status/${user.uid}`)
.query.ref.onDisconnect()
.update({
status: "offline",
timestamp: this.timestamp,
});
}
})
);
}
async signOut() {
await this.setPresence("offline");
await this.afAuth.signOut();
}
updateOnAway() {
document.onvisibilitychange = (e) => {
if (document.visibilityState === "hidden") {
this.setPresence("away");
} else {
this.setPresence("online");
}
};
}
}
Firebase path
With this in mind I am wanting to implement a way to bring up how many users are active (online and away) Ex: 15 active users
I tried this, but it only brings me if exist or not
ref.child("status").orderByChild("status").equalTo("online")
.once("value",snapshot => {
if (snapshot.exists()){
// if exist
}
});
If you want to show the number of online users with your current data structure, you can keep the same query as you have but then use snapshot.numChildren() to determine the number of online users it returned.
If you want to show counts of both online and offline users with your current data structure, you can either:
Use a second query for the offline users, and use the same approach as above to get the count.
Use a single read without a condition on the status field, and then count the individual nodes in your application code with:
ref.child("status")
.once("value",snapshot => {
let onlineCount=0, offlineCount=0;
snapshot.forEach((user) => {
const status = user.child("status").val();
if (status === "online) onlineCount++
else if (status === "offline) offlineCount++
else console.error(`Unexpected status '${status}' for user ${user.key}`);
})
console.log(`Online: ${onlineCount}, Offline: ${offlineCount}`);
});
If you're reading all these user profiles to simply show two counters in your app, you're wasting quite a bit of bandwidth (and thus money) for both yourself and your users. Consider storing the actual counts that you want to display in the database itself, and updating them as each user goes online/offline in something like Cloud Functions.

Nest.js use custom value in the request object

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.

TypeScript - Rest API - Manage router / controller / Class

I'm trying to transpose an initial Node project to a TypeScript one using Express and CoreModel.
In my initial project, the architecture is as the following exemple:
to handle users
accountRouter <- accountController <- User (Class) <- CoreModel (parent Class of user)
User (Class): contain specific method according User need
CoreModel (parent Class): contain global method as CRUD
I have tried to keep this way of doing on TypeScript, with few modifications
accountRouter
import { Router } from 'express';
import { accountController } from '../controllers/accountController'
const router = Router()
const controller = new accountController()
router
.route('/account')
.get(controller.showUsers)
export default router
accountController
import { Request, Response } from 'express'
import { User } from '../models/User'
import * as bcrypt from 'bcrypt'
import { UserInformations, UserInformationsEdit } from '../models/interface'
export class accountController {
private user: User = new User();
public async showUsers(req: Request, res: Response): Promise<Response> {
try {
console.log('this', this);
const result = await this.user.findAll()
return res.json(result)
} catch (error) {
throw new Error('error: ' + error.message)
}
}
}
User
import { CoreModel } from './CoreModel'
export class User extends CoreModel {};
CoreModel
import db from "../config/db"
export abstract class CoreModel {
name: any;
public async findAll(): Promise<any[]> {
try {
const result = await db.query(
`SELECT * FROM "${this.name.toLowerCase()}"`
);
return result.rows;
} catch (error) {
throw new Error('error: ' + error.message)
}
}
}
This way I'm able to transpile my code, but when I try to reach http://localhost:1234/account
I got
Cannot read property 'user' of undefined
According to me, since I'm getting an instance of accountController in the router, this should be accountController, isn't it ?
On a broader level,, I'm pretty sure that the way of I'm building my API in TS is not that good, I have tried to find example on internet to reproduce concept, but I didn't find some cases according my need.
I will be glad to take from you some input to get over it
Paul
The problem is the following line:
router
.route('/account')
.get(controller.showUsers)
You pass controller.showUsers to the get-handler. By doing this, the this context will no longer refer to the controller. Fix it by using:
router
.route('/account')
.get(controller.showUsers.bind(controller))
As a side note: you may want to consider the NestJS framework to build a robust API that is based on Typescript.

FeathersJS Twitch OAuth 401 Unauthorized

I'm new to FeathersJS. I tried to set up OAuth login with Twitch. I created a twitch oauth application and did the same as here with github login. I want to save the user in my MongoDB database, but I'm getting redirected to http://localhost:3030/#error=401%20Unauthorized after logging in on twitch. I've generated a fresh application with the feathers cli. What am I doing wrong?
config/default.json
...
"oauth": {
"redirect": "/",
"twitch": {
"key": ""*****************",",
"secret": ""***************************************"",
"scope": ["user:read:email"]
},
"github": {
"key": "*****************",
"secret": "***************************************"
}
}...
This is because the built-in strategy is attempting to fetch the user profile, yet it is not including your Client ID in the headers (see Twitch API - Getting Started.
To solve this you need to create your own strategy that implements getProfile. I used the Facebook demo from the feathersjs cookbook as a reference which can be found here.
Here is my implementation:
./strategies/TwitchStrategy.ts
import { Params } from '#feathersjs/feathers'
import { AuthenticationRequest } from '#feathersjs/authentication'
import { OAuthStrategy, OAuthProfile } from '#feathersjs/authentication-oauth'
import axios from 'axios'
import { Application } from '../declarations'
export class TwitchStrategy extends OAuthStrategy {
// we need a reference to the app instance
app: Application = {} as Application
// when the strategy is initialized this method is called with an app instance
setApplication(appInstance: Application): void {
this.app = appInstance
}
// this method is used to get the user profile after they authorize with the provider
async getProfile(authResult: AuthenticationRequest, _params: Params) {
const accessToken = authResult.access_token
const { data } = await axios.get('https://api.twitch.tv/helix/users', {
headers: {
Authorization: `Bearer ${accessToken}`, //our users access token to look them up
'Client-ID': this.app.get('authentication').oauth.twitch.key //we need to send the Client-ID
},
params: {
fields: 'id,name,email'
}
})
console.log(data)
return data
}
async getEntityData(profile: OAuthProfile, existing: any, params: Params) {
// `profile` is the data returned by getProfile
const baseData = await super.getEntityData(profile, existing, params)
return {
...baseData,
email: profile.email
}
}
}
./authentication.ts
import { ServiceAddons } from '#feathersjs/feathers'
import { AuthenticationService, JWTStrategy } from '#feathersjs/authentication'
import { LocalStrategy } from '#feathersjs/authentication-local'
// import our strategy
import { TwitchStrategy } from './strategies/TwitchStrategy'
import { expressOauth } from '#feathersjs/authentication-oauth'
import { Application } from './declarations'
declare module './declarations' {
interface ServiceTypes {
authentication: AuthenticationService & ServiceAddons<any>
}
}
export default function (app: Application): void {
const authentication = new AuthenticationService(app)
authentication.register('jwt', new JWTStrategy())
// register our custom strategy
authentication.register('twitch', new TwitchStrategy())
authentication.register('local', new LocalStrategy())
app.use('/authentication', authentication)
app.configure(expressOauth())
}

Nest js POST Request Not Recognizing DTO method

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.

Categories

Resources