Nestjs-Query Keycloak-connect - nestjs

Nest-Keycloak-connect is protecting all DTOs, Public() Decorator not working when querying a housing. Getting always Unauthorized when i want to query public items.
import { ID, ObjectType } from "#nestjs/graphql";
import { FilterableField, Relation } from "#ptc-org/nestjs-query-graphql";
import { Public, Unprotected } from "nest-keycloak-connect";
import { PropertyDTO } from "../property/property.dto";
#Public()
#ObjectType("Housing")
#Relation("property",() => PropertyDTO, {disableRemove: true})
export class HousingDTO {
#FilterableField(() => ID)
id !: number
}
Housing Module
import { Module } from "#nestjs/common";
import { NestjsQueryGraphQLModule } from "#ptc-org/nestjs-query-graphql";
import { NestjsQueryTypeOrmModule } from "#ptc-org/nestjs-query-typeorm";
import { HousingDTO } from "./housing.dto";
import { HousingEntity } from "./housing.entity";
import { HousingInputDTO } from "./housing.input.dto";
#Module({
imports: [
NestjsQueryGraphQLModule.forFeature({
imports: [NestjsQueryTypeOrmModule.forFeature([HousingEntity])],
resolvers: [{
DTOClass: HousingDTO,
EntityClass: HousingEntity,
CreateDTOClass: HousingInputDTO,
}]
})
],
})
export class HousingModule {}
Keycloak configured and working when logged in. But i need also to query the housing when not logged in.
#Module({
imports: [
....
HousingModule,
....
KeycloakConnectModule.register({
authServerUrl: 'https://auth.xxx.com/auth/',
realm: 'xxx',
clientId: 'xxx',
secret: process.env.KEYCLOAK_SECRET,
policyEnforcement: PolicyEnforcementMode.PERMISSIVE, // optional
tokenValidation: TokenValidation.ONLINE, // optional
logLevels: ['warn', 'error'],
}),
],
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
{
provide: APP_GUARD,
useClass: ResourceGuard,
},
{
provide: APP_GUARD,
useClass: RoleGuard,
}
],
})
export class AppModule {}

It won't work, because you must use the Public decorator on the endpoints.
Simply put the decorator on one of your controller endpoints to achieve the unauthenticated access.
Example for that:
import { Resource, Roles, Scopes, Public, RoleMatchingMode } from 'nest-keycloak-connect';
import { Controller, Get, Delete, Put, Post, Param } from '#nestjs/common';
import { Product } from './product';
import { ProductService } from './product.service';
#Controller()
#Resource(Product.name)
export class ProductController {
constructor(private service: ProductService) {}
/**
* if you use decorator here, the endpoint will
* be accessible without authentication
**/
#Get()
#Public() // <-- Used here
async findAll() {
return await this.service.findAll();
}
}
You can read more here.

Related

[NestJS]Custom Parameter in Class Constructor

I am trying to pass custom arguments into a class that injects another service(ApiCallService).
export class FPCT {
constructor(
payload: {
isLive: boolean;
exchangeReference?: string | number;
},
private apiCallService: ApiCallService,
) {}
}
ApiCallService is already added to the module provider but the service keeps returning undefined when I try to access it.
I have tried to add the FPCT class as a provider but nest complains about it not being able to define the first parameter(the payload object)
The first parameter(the payload object) is going to be passed from a controller
You could write it like this:
#Module({
providers: [
FPCT,
ApiCallService,
{
provide: 'payload',
useValue: {
isLive: true,
exchangeReference: '1234',
},
},
],
})
export class MyModule {}
// ...
#Injectable()
export class FPCT {
constructor(
#Inject('payload')
payload: {
isLive: boolean;
exchangeReference?: string | number;
},
private apiCallService: ApiCallService,
) {}
}
You could use ModuleRef#get, or app.get(ApiCallService).
It all depends on what you're trying to do, which wasn't clear.

Nestjs Authentication Project: "statusCode": 401, "message": "Unauthorized"

I am learning NestJS. I tried to build a basic authentication project using NestJS and passport-local. But I got the error of "statusCode": 401,"message": "Unauthorized".
Here is the app module file:
import { Module } from '#nestjs/common';
import { UsersModule } from './users/users.module';
import { AppController } from './app.controller';
import { AuthModule } from './auth/auth.module';
import { UserService } from './users/users.services';
#Module({
imports: [UsersModule, AuthModule],
controllers: [AppController],
providers: [UserService]
})
export class AppModule { }
Here is the app controller file:
import { Controller, Get, Post, Request, UseGuards } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Controller("app")
export class AppController {
constructor() { }
#Post()
#UseGuards(AuthGuard("local"))
async login(#Request() req) {
// console.log(req.user);-----undefined
return "This is private msg";
}
}
Now here is the user data file:
export class User {
name: string;
password: string;
age: number;
}
Now here is the user service file:
import { Injectable } from "#nestjs/common";
import { User } from "./users.data";
#Injectable()
export class UserService {
public users: User[] = [
{
"name": "user1",
"password": "admin",
"age": 10
},
{
"name": "user2",
"password": "admin",
"age": 102
},
{
"name": "user3",
"password": "admin",
"age": 103
},
{
"name": "user4",
"password": "admin",
"age": 104
},
];
async getUserByName(userName: string): Promise<User | undefined> {
return this.users.find(user => user.name === userName);
}
}
Here is the user module file:
import { Module } from '#nestjs/common';
import { UserService } from './users.services';
#Module({
imports: [],
controllers: [],
providers: [UserService],
exports: [UserService]
})
export class UsersModule { }
here is the Auth passport local file:
import { Injectable, Request, UnauthorizedException } from "#nestjs/common";
import { PassportStrategy } from "#nestjs/passport";
import { Strategy } from "passport-local";
import { UserService } from '../users/users.services';
import { User } from '../users/users.data';
#Injectable()
export class PassportlocalStragey extends PassportStrategy(Strategy) {
constructor(private userService: UserService) {
super();
}
async validate(username: string, password: string): Promise<any> {
console.log('Here I go');
const user = await this.userService.getUserByName(username);
console.log('Got user', user);
if (user == undefined) throw new UnauthorizedException();
if (user && user.password === password) {
return user;
} else {
console.log('Password mismatch');
throw new UnauthorizedException();
}
}
}
Here is Auth module file:
import { Module } from '#nestjs/common';
import { PassportlocalStragey } from './passportlocal.auth';
import { UsersModule } from '../users/users.module';
import { PassportModule } from '#nestjs/passport';
import { UserService } from 'src/users/users.services';
#Module({
imports: [UsersModule, PassportModule],
controllers: [],
providers: [PassportlocalStragey],
exports: []
})
export class AuthModule { }
Here is the output of the error:
{
"statusCode": 401,
"message": "Unauthorized"
}
Where is the problem? How can I solve this? Thank You.
In the PassportlocalStragey the validation method should be
async validate(username: string, password: string)
not validateUser.
EDIT:
I have reproduced your code locally as it was bugging me. It works for me, here are my classes that might be slightly different from yours:
AuthModule
#Module({
imports: [PassportModule],
providers: [UserService, PassportLocalStrategy],
exports: [UserService],
})
export class AuthModule {}
PassportLocalStrategy
#Injectable()
export class PassportLocalStrategy extends PassportStrategy(Strategy) {
constructor(private userService: UserService) {
super();
}
async validate(username: string, password: string): Promise<any> {
console.log('Here I go');
const user = await this.userService.getUserByName(username);
console.log('Got user', user);
if (user == undefined) throw new UnauthorizedException();
if (user && user.password === password) {
return user;
} else {
console.log('Password mismatch');
throw new UnauthorizedException();
}
}
}
#Injectable()
export class LocalGuard extends AuthGuard('local') {}
AppController
#Controller('api')
export class AppController {
constructor() {}
#Post('login')
#UseGuards(AuthGuard('local'))
// #UseGuards(LocalGuard)
login(#Req() req): any {
return req.user;
}
}
I did add LocalGuard class to the PassportLocalStrategy file. The guard on the route works for both #UseGuards(AuthGuard('local')) or #UseGuards(LocalGuard).
AppModule
#Module({
imports: [AuthModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Passport expects you to send username and password as properties of req.body. You are sending name and password. If you want to use name instead of username you need to pass usernameField: 'name' to the super() call in your PassportlocalStragey #constructor

NestJs Subsciber + TypeOrm

2
I have a subscriber for NestJS to listen to any create, update or delete events (TypeORM). When one of these events is fired, I'd like to use an injected service in order to create a new revision entry.
However, it seems I cannot get the dependency . I getting error Nest can't resolve dependencies of the JobInterviewerSubscriber (?). Please make sure that the argument Connection at index [0] is available in the JobPostingModule context
Keys Files:
Jobs.entity.ts
JobPostingModule.ts
Jobs.entity.ts
import { BaseEntity, PrimaryGeneratedColumn, Column, Entity, CreateDateColumn, UpdateDateColumn, OneToMany, ManyToOne, ManyToMany, JoinTable, EventSubscriber, EntitySubscriberInterface, InsertEvent, Repository, UpdateEvent, Connection } from 'typeorm';
import { Panelist } from './panelist.entity';
import { JobStatus, JobLocation, JobType, JobPriority } from '../job-posting/enum/job.enum';
import { Agency } from './agency.entity';
import { Candidates } from '../entities/candidates.entity';
import { JobInterviewers } from './job-interviewers.entity';
import { User } from '../entities/user.entity';
import { Injectable } from '#nestjs/common';
import { InjectConnection } from '#nestjs/typeorm/dist/common/typeorm.decorators';
#Entity()
export class Jobs extends BaseEntity{
#PrimaryGeneratedColumn()
id: number;
#Column({type: 'varchar', length: 100})
title: string;
#Column({type: 'enum', enum: JobPriority})
priority: JobPriority;
#Column({type: 'int2', default: 0})
roundCount: number;
#OneToMany(type => JobInterviewers, jobInterviewers => jobInterviewers.jobs, {eager:true})
jobInterviewers: JobInterviewers[];
#ManyToMany(type => Agency, agency => agency.jobs)
#JoinTable({name: 'job_agency_table'})
agencies: Agency[];
#UpdateDateColumn()
updatedAt: Date;
}
#EventSubscriber()
#Injectable()
export class JobInterviewerSubscriber
implements EntitySubscriberInterface<JobInterviewers> {
constructor(
#InjectConnection() readonly connection: Connection,
) {
connection.subscribers.push(this);
}
listenTo() {
return JobInterviewers;
}
async afterInsert(event: InsertEvent<JobInterviewers>) {
const jobsRepo: Repository<Jobs> = event.connection.manager.getRepository<Jobs>('Jobs');
const jobInterviewersRepo: Repository<JobInterviewers> = event.connection.manager.getRepository<JobInterviewers>('JobInterviewers');
jobInterviewersRepo
.count({
where: { jobs: { id: event.entity.jobs.id } },
})
.then((count: number) => {
jobsRepo.update({ id: event.entity.jobs.id }, { roundCount: count });
});
}
}
JobPostingModule.ts
import { Module } from '#nestjs/common';
import { JobPostingController } from './job-posting.controller';
import { JobPostingService } from './job-posting.service';
import { AuthModule } from '../auth/auth.module';
import { RecruitmentService } from '../recruitment/recruitment.service';
import { JobInterviewerSubscriber } from 'src/entities/job-posting.entity';
#Module({
imports: [
AuthModule
],
controllers: [JobPostingController],
providers: [JobPostingService, RecruitmentService,JobInterviewerSubscriber]
})
export class JobPostingModule {}
Hmm, there any many different possibilities:
is the connection loaded globally?
is the connection properly configured at all? Does it give any errors of connection?
I see that your module does not import TypeOrmModule.forFeature([Jobs]). If your TypeORM configuration doesn't load entities automatically, you should load them as imports manually

NestJS - How to call service method from mongoose schema inside custom validation

I have two modules-
courses
-courses.module.ts
-courses.services.ts
-courses.schema.ts
-courses.controller.ts
groups
-groups.module.ts
-groups.services.ts
-groups.schema.ts
-groups.controller.ts
Content of courses.module.ts is-
import { Module } from '#nestjs/common';
import { CoursesService } from './courses.service';
import { CoursesController } from './courses.controller';
import { MongooseModule, getModelToken } from '#nestjs/mongoose';
import { CourseSchema } from './schemas/course.schema';
import { GroupsService } from 'src/groups/groups.service';
import { GroupsModule } from 'src/groups/groups.module';
#Module({
imports: [
MongooseModule.forFeature([{
name: 'Course', schema: CourseSchema }]),
GroupsModule,
],
providers: [
CoursesService,
],
controllers: [CoursesController],
exports: [CoursesService],
})
export class CoursesModule {}
Content of groups.module.ts is-
import { Module } from '#nestjs/common';
import { GroupsService } from './groups.service';
import { GroupsController } from './groups.controller';
import { MongooseModule } from '#nestjs/mongoose';
import { GroupSchema } from './schemas/group.schema';
#Module({
imports: [
MongooseModule.forFeature([{ name: 'Group', schema: GroupSchema }]),
],
providers: [GroupsService],
controllers: [GroupsController],
exports:[
GroupsService,
]
})
export class GroupsModule {}
I want to call methods of GroupsService inside courses.schema.ts like this-
import * as mongoose from 'mongoose';
import { GroupsService } from 'src/groups/groups.service';
import { GroupsModule } from 'src/groups/groups.module';
import { GroupSchema } from 'src/groups/schemas/group.schema';
export const CourseSchema = new mongoose.Schema({
groupId: {
type: Number,
isAsync: true,
validate(value) {
if(GroupsService.getByCode()==='xyz'){
return false;
}
}
},
})
How can I achieve this? I have gone through the documentation of NestJS but didn't find any appropriate method to inject service directly inside schema and access the service methods during custom validation.
In your main.ts exports the app reference.
export let app;
async function bootstrap() {
app = await NestFactory.create(AppModule);
}
bootstrap();
Then in your schema file you may do something like this
import { app } from 'path/to/main.ts';
const service = app.get(GroupsService);
Here you are using the service container to get a reference from the GroupsService.

How to implement role redirection in angular 6

I have created a frontend using Angular 6 and I want implement role based authorisation how can i achieve this am using angular 6 node.js, passport ,mongoose its a mean stack application
Here is my auth.guard.ts
import { Injectable } from '#angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '#angular/router';
import { Observable } from 'rxjs';
import { UserService } from '../shared/user.service';
import { Router } from '#angular/router';
#Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private userService:UserService,private router: Router) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
if(!this.userService.isloggedIn()){
this.router.navigateByUrl('/user');
this.userService.deleteToken();
return false;
}
return true;
}
}
Here is my user.model.ts
export class User {
fullname:string;
email:string;
university:string;
College:string;
Department:string;
password:string;
admintype:string;
}
Here is my routes.ts
import { Routes } from '#angular/router';
import { UserComponent } from './user/user.component';
import { SignUpComponent } from './user/sign-up/sign-up.component';
import { SignInComponent } from './user/sign-in/sign-in.component';
import { AuthGuard } from './auth/auth.guard';
import { AdminComponent } from './user/admin/admin.component';
import { Role } from './shared/role';
export const appRoutes: Routes = [
{
path: 'user',component:SignInComponent
},
{
path:'signup', component:SignUpComponent, canActivate:[AuthGuard]
},
{
path:'admin', component:AdminComponent,
canActivate:[AuthGuard]
},
{
path: '',component:SignInComponent
},
{
path:'',redirectTo:'/user', pathMatch:'full'
}
];
Here is my Mongoose model
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const userSchema = new mongoose.Schema({
fullname:{
type:String,
required:'Full name cant be empty',
min:6,
max:255
},
email:{
type:String,
required:'Email cant be Empty',
max:255,
unique:true
},
University:{
type:String,
default:"Sokoine University of Agriculture"
},
College:{
type:String,
required:'College cant be Empty'
},
Department:{
type:String,
required:'department cant be empty'
},
password:{
type:String,
required:'pasword cant be empty',
max :1024,
minlength: [6,'password must be atlest 6 character long']
},
admintype:{
type:String,
enum :['HOD','CICT','Sports','SUASAB','Admin']
},
date:{
type:Date,
default:Date.now
},
saltSecret:String
});
//custom validation
userSchema.path('email').validate((val) => {
emailRegex = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return emailRegex.test(val);
},'Invalid E-mail. ');
//events
userSchema.pre('save', function(next) {
bcrypt.genSalt(10, (err, salt)=> {
bcrypt.hash(this.password, salt, (err, hash) => {
this.password = hash;
this.saltSecret = salt;
next();
});
});
});
//methods
userSchema.methods.verifyPassword = function(password){
return bcrypt.compareSync(password, this.password);
};
userSchema.methods.generateJwt = function() {
return jwt.sign({ _id:this._id},
process.env.JWT_SECRET,
{expiresIn:process.env.JWT_EXP});
}
module.exports = mongoose.model('User',userSchema)
Here is my signin.component.ts
```import { Component, OnInit } from '#angular/core';
import { NgForm } from '#angular/forms';
import { UserService } from 'src/app/shared/user.service';
import { Router } from '#angular/router';
#Component({
selector: 'app-sign-in',
templateUrl: './sign-in.component.html',
styleUrls: ['./sign-in.component.css']
})
export class SignInComponent implements OnInit {
constructor( private userService:UserService, private router:Router) { }
model = {
email:'',
password:''
};
emailRegex = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
serverErrorMessages : string;
ngOnInit() {
if(this.userService.isloggedIn())
{
this.router.navigateByUrl('/admin');
}
}
onSubmit(form :NgForm)
{
this.userService.login(form.value).subscribe(
res =>{
this.userService.setToken(res['token']);
this.router.navigateByUrl('/admin');
},
err =>{
this.serverErrorMessages = err.message;
});
}
}```
Here is my role.ts
```export enum Role {
Admin = 'Admin',
Cict = 'admincict',
Sport = 'adminsport',
SuaHub = 'adminsuahub'
}```
Have tried several tutrials but non helps me at all
See this example in stackblitz
In your app-routing.module.ts
const routes: Routes = [
{
path: "admin",
component: AdminOnlyComponent,
canActivate: [RoleGuardService],
data: { roles: ['admin']}
},
...
}
In your RoleGuardService
import { Injectable } from '#angular/core';
import { UserRolesService} from './user-roles.service';
import { Router, ActivatedRouteSnapshot } from '#angular/router';
#Injectable({
providedIn: 'root'
})
export class RoleGuardService {
constructor(private getUserRoles: UserRolesService) { }
canActivate(route: ActivatedRouteSnapshot): boolean {
return route.data.roles.some( ai => this.getUserRoles.getRoles().includes(ai) );
}
}
In UserRolesService
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class UserRolesService {
userRoles: string[] = [];
constructor() { }
setRoles(Roles: string[]){
this.userRoles = Roles.slice(0);
}
getRoles(){
return this.userRoles;
}
}
Set roles when user logged in to the system or get those roles from your localstorage....

Resources