I'm in the middle of developing a Graphql API with Nest JS (I just upgraded it to 5.0.0), in which I need to allow only certain users (by roles) to access sub-parts of queries that are public.
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) // if no roles specified
return true;
console.log(req); // Displays Query() parent result instead of HTTP Request Object
return false;
}
}
Sub-parts of queries are handled by #ResolveProperty() decorated functions.
Following the documentation I started using Guards, but I just found out that when using guards, in GraphQL resolvers context, the context.switchToHttp().getRequest() is only returning the result of the parent Query()...
I don't really know if this is an expected behavior, and if it is, if there's a way to access full express Request object from a GraphQL Guard ?
Related
I have the need to dynamically create mongo databases when a new user is registered on a SaaS platform.
How do I configure NestJS to run the scripts needed, to create the database, as well as how to dynamically connect with database created for each user?
And are there any better approaches to my usecase?
New User => Create New Database for them, and store details in the main SaaS database.
When user login, fetch DB details from main database, and create a connection to it, to read their data.
I don't see the need to create a new db for each user. Why not craeting a users collection and than for each user create a new object. Mongo object can store quite a lot of information.
I'm not a pro in nestjs, but I don't think that there is a way to dynamically connect more db's. When you start your app a mongoDB instance is initiated, don't forget that all classes in nestjs are singletones.
So, I was able to make it thus.
I created a database service class, in it, I used the MongooseModuleOptions to create a new connection, and injecting the Request Scope to it, I extracted the database name from the request and dynamically pass it to the uri in the createMongooseOptions method and that dynamically creates a connection.
import { Inject } from '#nestjs/common';
import { MongooseOptionsFactory, MongooseModuleOptions } from '#nestjs/mongoose';
import { REQUEST } from '#nestjs/core';
import { Request } from 'express';
export class DatabaseService implements MongooseOptionsFactory{
constructor(
#Inject(REQUEST) private readonly request: Request) {
}
createMongooseOptions(): MongooseModuleOptions {
const urii = process.env.CLUSTER+'/'+this.request.body.dbName+"?retryWrites=true&w=majority"
return {
uri: urii
};
}
}
Then inside the users module, I insert data into a collection specified inside the schema, and that automatically uses the connection to create a database, collection and entry, if it exists, it uses the database and collection.
import { Inject, Injectable } from '#nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { User, UserDocument } from './schemas/user.schema';
import { InjectConnection, InjectModel } from '#nestjs/mongoose';
import { Model } from 'mongoose';
#Injectable()
export class UserService {
constructor(#InjectModel(User.name) private userModel:Model<UserDocument>){}
async create(createUserDto: CreateUserDto) {
const createdUser = new this.userModel(createUserDto)
return createdUser.save()
}
}
That worked perfectly for me.
Thank you all 🙏🙏🙏
In my angular application I have interceptor class like that:
import { Injectable, Inject, Optional, PLATFORM_ID } from '#angular/core';
import {
HttpInterceptor,
HttpHandler,
HttpRequest,
} from '#angular/common/http';
import { REQUEST } from '#nguniversal/express-engine/tokens';
import { isPlatformServer } from '#angular/common';
#Injectable()
export class UniversalInterceptor implements HttpInterceptor {
constructor(
#Inject(PLATFORM_ID) private platformId,
#Optional() #Inject(REQUEST) private request
) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
if (isPlatformServer(this.platformId)) {
req = req.clone({ headers: req.headers.set('Cookie', this.request.headers.cookie) });
}
return next.handle(req);
}
}
I am using Angular Universal server side rendering, so I take token from server and manually set in every API call which will be called by server. Everything works fine, but is it safe? I set token cookie manually inside every API request's header, maybe that's somehow risky?
I mayb be wrong, but I think it only matters if you send cookies to an API that you don't own, in which case you might be sending sensitive information to a 3rd party.
If you don't own the API, you could try parsing the cookies string (this.request.headers.cookie) and only pass the ones that the API need
I am building an application using Angular 7, I have handled the API calls, the JWT Token authentication system using C#, and also updating the LocalStorage() when necessary, when the user logs in and logs out, and all these are working perfectly.
My problem is I want it to run a login check as a middleware within the application rather than on the lifecycle method - ng.onInit(). How do I go about this?
Is there a way to execute lifecycle events as an entry component or service. That is, before any component loads it is able to check if the user is logged in or not and redirect via Router to a desired page.
Guard is based on the routes... so I think you should prefer a module/service solution.
import { APP_INITIALIZER } from '#angular/core';
then add it as a provider like this :
export function initApp(initService: YourInitService) {
return () => {
initService.Init();
}
}
{ provide: APP_INITIALIZER,useFactory: initApp, deps: [YourInitService], multi: true }
Routing Decisions Based on Token Expiration
If you’re using JSON Web Tokens (JWT) to secure your Angular app (and I recommend that you do), one way to make a decision about whether or not a route should be accessed is to check the token’s expiration time. It’s likely that you’re using the JWT to let your users access protected resources on your backend. If this is the case, the token won’t be useful if it is expired, so this is a good indication that the user should be considered “not authenticated”.
Create a method in your authentication service which checks whether or not the user is authenticated. Again, for the purposes of stateless authentication with JWT, that is simply a matter of whether the token is expired. The JwtHelperService class from angular2-jwt can be used for this.
// src/app/auth/auth.service.ts
import { Injectable } from '#angular/core';
import { JwtHelperService } from '#auth0/angular-jwt';
#Injectable()
export class AuthService {
constructor(public jwtHelper: JwtHelperService) {}
// ...
public isAuthenticated(): boolean {
const token = localStorage.getItem('token');
// Check whether the token is expired and return
// true or false
return !this.jwtHelper.isTokenExpired(token);
}
}
Note: This example assumes that you are storing the user’s JWT in local storage.
Create a new service which implements the route guard. You can call it whatever you like, but something like auth-guard.service is generally sufficient.
// src/app/auth/auth-guard.service.ts
import { Injectable } from '#angular/core';
import { Router, CanActivate } from '#angular/router';
import { AuthService } from './auth.service';
#Injectable()
export class AuthGuardService implements CanActivate {
constructor(public auth: AuthService, public router: Router) {}
canActivate(): boolean {
if (!this.auth.isAuthenticated()) {
this.router.navigate(['login']);
return false;
}
return true;
}
}
The service injects AuthService and Router and has a single method called canActivate. This method is necessary to properly implement the CanActivate interface.
The canActivate method returns a boolean indicating whether or not navigation to a route should be allowed. If the user isn’t authenticated, they are re-routed to some other place, in this case a route called /login.
Now the guard can be applied to any routes you wish to protect.
// src/app/app.routes.ts
import { Routes, CanActivate } from '#angular/router';
import { ProfileComponent } from './profile/profile.component';
import {
AuthGuardService as AuthGuard
} from './auth/auth-guard.service';
export const ROUTES: Routes = [
{ path: '', component: HomeComponent },
{
path: 'profile',
component: ProfileComponent,
canActivate: [AuthGuard]
},
{ path: '**', redirectTo: '' }
];
The /profile route has an extra config value now: canActivate. The AuthGuard that was created above is passed to an array for canActivate which means it will be run any time someone tries to access the /profile route. If the user is authenticated, they get to the route. If not, they are redirected to the /login route.
Note: The canActivate guard still allows the component for a given route to be activated (but not navigated to). If we wanted to prevent activation altogether, we could use the canLoad guard.
more info here
You should check for Guard in angular, especially canActivate Guard: https://angular.io/guide/router
A guard is created like this:
#Injectable({
providedIn: 'root'
})
export class MyGuard implements CanLoad {
constructor() {}
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean> |
Promise<boolean> | boolean {
const x = true;
if (x) {
return true; // It allows access to the route;
} else {
// redirect where you want;
return false; // it doesnt allow to access to the route
}
}
}
Then in your routing Module:
{
path: "yourRoute",
canActivate: [MyGuard],
component: YourComponent
}
For authentication, you have a good library that uses guard here:
https://www.npmjs.com/package/ngx-auth
You should implement an authGuardService or something like that to use as middleware for your routing (using the canActivate section)
See: https://angular.io/api/router/CanActivate
This prevents routes from being loaded if the canActivate fails the condition (which is preferred when using a login system etc instead of checking in lifecycle hooks).
I've got a #BeforeInsert() listener in my Post entity. The listener is supposed to create a unique slug for the slug column before insertion.
For example:
export class Post extends BaseEntity {
#PrimaryGeneratedColumn()
id: number
#Column()
title: string
#Column()
slug: string
#BeforeInsert()
private updateSlug() {
this.slug = slugify(this.title)
// I will need to access the database to check whether the same slug has existsed already or not.
// How do I access the database in this listener method?
}
}
Since the slug column is supposed to be unique, I will have to check in the database to know whether the same slug already exists. If the slug already exists, I will then need to append a number behind the slug, like hello-word-1.
However, to do so, I will need to access the Post repository of the entity in the Post entity class before I can access the database. But I don't see how I can inject the repository into my Post entity class to access the database for this purpose.
How should I approach this problem?
As far as I know it's not possible to use dependency injection in typeorm entities, since they are not instantiated by nest. You can however use an EntitySubscriber instead which can inject dependencies. See the solution from this Github issue:
import { Injectable } from '#nestjs/common';
import { InjectConnection, InjectRepository } from '#nestjs/typeorm';
import { Connection, EntitySubscriberInterface, InsertEvent, Repository } from 'typeorm';
import { Post } from '../models';
#Injectable()
export class PostSubscriber implements EntitySubscriberInterface {
constructor(
#InjectConnection() readonly connection: Connection,
// Inject your repository here
#InjectRepository(Photo) private readonly postRepository: Repository<Post>,
) {
connection.subscribers.push(this);
}
listenTo() {
return Post;
}
beforeInsert(event: InsertEvent<Post>) {
// called before insert
};
}
I have a typescript class representing a model and I would like instances to communicate with an API via angular's Http service.
But the constructor of the model needs arguments when creating instances. For example something super simple:
class SomeModel{
constructor(public id:number, public name:string, ){
}
I would like to inject the Http service so it is available to my instances, but it seems like the canonical way to do this commandeers the constructor with:
constructor(http:Http)
I've been digging through the Injector docs, but it's a little sparse and I haven't found anything that works. Is there a way to get a reference to a service like Http from the DI system without using the constructor pattern?
I managed to solve the same problem using angular 4. First you create new injector that uses component injector. It knows about your SomeModel class and passes modelParams as instance of SomeModelParameters class. Then you use this newly created injector to create class instance.
#Injectable()
class SomeModel {
constructor(http: Http, someModelParamters: SomeModelParameters) { }
}
export class MyComponent {
constructor(injector: Injector) {
const modelParams = new SomeModelParameters();
const injectorWithModelParams = ReflectiveInjector.resolveAndCreate(
[
SomeModel,
{ provide: SomeModelParameters, useValue: modelParams }
],
injector);
this.someModel = injectorWithModelParams.resolveAndInstantiate([SomeModel]);
}
}
update
HTTP_PROVIDERS is long gone.
HttpClientModule is the current replacement.
original
If you inject a class that has constructor parameters the #Injectable annotation needs to be added.
#Injectable()
class SomeModel{
// constructor(public id:number, public name:string, ){
// }
constructor(http:Http) {}
}
For this to work HTTP_PROVIDERS needs to be added to bootstrap(AppComponent, [HTTP_PROVIDERS]);
See also Angular2 beta - bootstrapping HTTP_PROVIDERS - "Unexpected Token <"
If you need to pass other arguments from your component, youcoud pass them using a function instead.
Another way is to create the instance manually and request Http from the injector.
export class MyComponent {
constructor(injector: Injector) {
this.someModel = new SomeModel(Injector.resolveAndCreate(Http), 1, 2);
}
}