I have a users REST API built with Nestjs, it connects to a mongo atlas db and fetchs the users correctly, now i want to add a ldap verification, right now i have a docker container running ldap. I've followed the documentation for using passport and
paspport strategies but i haven't figured out how to implement a ldap strategy yet. How to implement the ldap strategy using guards and routing from a users module?
I have an auth folder like this
//auth.module.ts
import { Module } from '#nestjs/common';
import { PassportModule } from '#nestjs/passport';
import { UsersModule } from 'src/users/users.module';
import { AuthService } from './auth.service';
import { LdapStrategy } from './ldap.strategy';
#Module({
providers: [AuthService, LdapStrategy],
imports: [UsersModule, PassportModule],
})
export class AuthModule {}
//ldap.strategy.ts
import * as Strategy from 'passport-ldapauth';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { AuthService } from './auth.service';
const OPTS = {
server: {
url: 'ldap://localhost:389',
bindDN: 'cn=admin,dc=gamestack,dc=unal,dc=edu,dc=co',
bindCredentials: 'admin',
searchBase: 'ou=sa',
searchFilter: '(uid={{username}})',
},
};
#Injectable()
export class LdapStrategy extends PassportStrategy(Strategy, 'ldapauth') {
constructor(private authService: AuthService) {
super(OPTS);
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
ldap-auth.guard.ts
import { Injectable } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class LdapAuthGuard extends AuthGuard('ldapauth') {}
Related
I'm following the official NestJS documentation. Currently, I'm trying to implement the authentication step with Passport strategy. I did every step, as the documentation says, but I got stuck where I need to generate the JWT with the jwtService.sign() method.
The error, that I'm getting is:
ERROR [ExceptionsHandler] secretOrPrivateKey must have a value`.
Here are the code snippets:
AuthModule:
#Module({
imports: [
UserModule,
PassportModule,
User,
TypeOrmModule.forFeature([User]),
JwtModule.register({
secret: 'somerandomsecret',
signOptions: { expiresIn: '60s' }
})
],
providers: [AuthService, LocalStrategy, UserService],
exports: [AuthService]
})
export class AuthModule {}
AuthService
#Injectable()
export class AuthService {
constructor(
private userService: UserService,
private jwtService: JwtService
){}
async validateUser(email: string, pass: string): Promise<any> {
const user = await this.userService.findByEmail(email);
const isMatch = await comparePasswords(pass, user.password);
if( user && isMatch) {
const { password, ...result } = user;
return result;
}
return null;
}
async signIn(user: any) {
const payload = { username: user.email, sub: user.id };
return this.jwtService.sign(payload)
}
}
And inside the User controller, I'm calling the method signIn from the AuthService.
UserController
import {
Body,
Controller,
Post,
HttpException,
HttpStatus,
Request,
UseGuards,
Bind,
} from "#nestjs/common";
import { UserService } from "./user.service";
import { SignUpDataValidation } from "./user.validation";
import { hashPassword } from "../../utils/hash-password";
import { AuthGuard } from '#nestjs/passport';
import { AuthService } from '../auth/auth.servers';
import { LocalAuthGuard } from '../auth/local-auth.guard';
#Controller("user")
export class UserController {
constructor(
private userService: UserService,
private authService: AuthService
) {}
#UseGuards(LocalAuthGuard)
#Post("/signin")
#Bind(Request())
async signIn(req) {
return this.authService.signIn(req.user)
}
}
UserModule
import { Module } from "#nestjs/common";
import { UserController } from "./user.controller";
import { UserService } from "./user.service";
import { TypeOrmModule } from "#nestjs/typeorm";
import { User } from "src/modules/user/user.entity";
import { AuthService } from '../auth/auth.servers';
import { JwtService } from '#nestjs/jwt';
#Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService, AuthService, JwtService],
})
export class UserModule {}
LocalStrategy
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.servers';
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({usernameField: 'email'});
}
async validate(email: string, password: string): Promise<any> {
const user = await this.authService.validateUser(email,
password);
if(!user) {
throw new UnauthorizedException()
}
return user;
}
}
As you can see, I'm also using Guards, but I'm not going to send you that code to avoid confusion.
So, can anybody tell me, why I'm getting this ERROR? Am I missing something?
Here is the link to the particular NestJS documentation page with a feature that I'm trying to implement: https://docs.nestjs.com/security/authentication
I created a custom passport for authentication, as described here: https://docs.nestjs.com/security/authentication.
My problem is a different behavior between importing the passport I created from a local folder versus installing it from a package. When I install it from a package, I get 500 error when providing wrong credentials (works fine with valid credentials), while getting 401 error when using it locally.
This is how I use it locally and it works:
import { Controller, Get, UseGuards } from '#nestjs/common';
import { AppService } from './app.service';
import { AuthGuard } from '#nestjs/passport';
import { ApiKeyAuthGuard } from './auth/guards/api-key-auth.guard';
#Controller()
#UseGuards(AuthGuard('api-key'))
export class AppController {
constructor(
private readonly appService: AppService,
) {}
#Get()
getHello(){}
}
But when I import it from an installed package and I provide wrong credentials, I'm getting 500 error:
import { ApiKeyAuthGuard } from 'shared-auth-package';
api-key.strategy.js:
import { PassportStrategy } from '#nestjs/passport';
import {
fromAuthHeaderAsApiKey,
Strategy,
} from '../passports/passport-api-key/strategy';
import { InjectRepository } from '#nestjs/typeorm';
import { UnauthorizedException } from '#nestjs/common';
import { Repository } from 'typeorm';
import { TokenEntity } from '../../lib/entity/token.entity';
import { UserEntity } from '../../lib/entity/user.entity';
export class ApiKeyStrategy extends PassportStrategy(Strategy, 'api-key') {
constructor(
#InjectRepository(TokenEntity, process.env.mysql_connection_name)
private tokenRepository: Repository<TokenEntity>,
) {
super({
tokenFunc: fromAuthHeaderAsApiKey(),
passReqToCallback: false,
});
}
async validate(token: string): Promise<UserEntity> {
let user: UserEntity;
const tokenEntity = await this.tokenRepository
.createQueryBuilder('t')
.innerJoinAndSelect('t.user', 'u')
.where('t.token = :token', { token })
.getOne();
if (tokenEntity && tokenEntity.user_id && tokenEntity.validateToken()) {
user = tokenEntity.user;
}
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
return user;
}
}
api-key-auth.guard.ts:
import { AuthGuard } from '#nestjs/passport';
export class ApiKeyAuthGuard extends AuthGuard('api-key') {}
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.
Everyone,
I'm trying to setup my first NestJS application. It is backed by Serverless on AWS.
I created a simple Controller that has a Service as a dependency. When I hit the endpoint with my HTTP Client, the object that should contain the Service instance is undefined. I'm not able to make it work. Could you help?
handler.ts
import { Context, Handler } from 'aws-lambda';
import { NestFactory } from '#nestjs/core';
import { AppModule } from './src/module';
import { Server } from 'http';
import { ExpressAdapter } from '#nestjs/platform-express';
import * as serverless from 'aws-serverless-express';
import * as express from 'express';
import {DB} from './src/libs/db';
let cachedServer: Server;
function bootstrapServer(): Promise<Server> {
const expressApp = express();
const adapter = new ExpressAdapter(expressApp);
return NestFactory.create(AppModule, adapter)
.then(app => app.enableCors())
.then(app => app.init())
.then(() => DB.connect())
.then(() => serverless.createServer(expressApp));
}
export const handle: Handler = (event: any, context: Context) => {
if (!cachedServer) {
bootstrapServer().then(server => {
cachedServer = server;
return serverless.proxy(server, event, context);
});
} else {
return serverless.proxy(cachedServer, event, context);
}
};
module.ts
import { Module } from '#nestjs/common';
import { EventController } from './event.controller';
import { EventService } from './event.service';
#Module({
controllers: [EventController],
providers: [EventService],
})
export class AppModule {}
event.service.ts
import { Injectable } from '#nestjs/common';
interface Event{}
#Injectable()
export class EventService {
create(event: Event) {
return {
id: Date.now()
}
}
}
event.controller.ts
import { Controller, Post, Body } from '#nestjs/common';
import { EventService } from './event.service';
interface Event { }
#Controller('event')
export class EventController {
constructor(private readonly eventService: EventService) { }
#Post()
async create(#Body() req)
{
this.eventService.create(req);
}
}
So this.eventService is always undefined. What is wrong with this implementation?
Maybe you are missing a line from tsconfig add this below:
"emitDecoratorMetadata": true
Credits to the God of Nestjs Mr. Kamil's reply:
https://stackoverflow.com/a/50121886/6301493
I'm trying to create a generic HttpService for my Angular 5 project.
The error I am getting:
Uncaught Error: Can't resolve all parameters for HttpService: ([object Object], [object Object], [object Object], ?)
app/util/constants.ts
import { Injectable } from "#angular/core";
#Injectable()
export class SERVER {
readonly ROOT:string = 'http://localhost:8888/';
readonly API_PATH:string = 'api/v1/';
}
#Injectable()
export class API_URLS {
readonly USER:string = 'user/';
}
app/serializers/serializer.ts
import { Resource } from 'app/models/resource';
export interface Serializer {
fromJson(json: any): Resource;
toJson(resource: Resource): any;
}
app/models/resource.ts
export class Resource {
id: number;
}
app/models/user.model.ts
import { Resource } from 'app/models/resource';
export class User extends Resource{
id: number;
name: string;
email: string;
gender: string;
address: string;
landmark: string;
client_id: number;
dealer_id: number;
phone_number: string;
alternate_phone_number: string;
status: string;
is_active: boolean;
date_created: string;
date_updated: string;
}
app/serializers/user.serializer.ts
import { User } from 'app/models/user.model';
import { Serializer } from 'app/serializers/serializer';
export class UserSerializer implements Serializer {
fromJson(json: any): User {
const user = new User();
user.id = json.id;
user.client_id = json.client_id;
user.dealer_id = json.dealer_id;
user.status = json.status;
user.is_active = json.is_active;
user.date_created = json.date_created;
user.date_updated = json.date_updated;
return user;
}
toJson(user: User): any {
return {
id: user.id,
client_id: user.client_id,
dealer_id: user.dealer_id,
status: user.status,
is_active: user.is_active,
date_created: user.date_created,
date_updated: user.date_updated,
};
}
}
app/services/user.service.ts
import { Injectable } from '#angular/core';
import { SERVER } from 'app/util/constants';
import { API_URLS } from 'app/util/constants';
import { User } from 'app/models/user.model';
import { HttpClient } from '#angular/common/http';
import { HttpService } from 'app/services/http.service';
import { UserSerializer } from 'app/serializers/user.serializer';
#Injectable()
export class UserService extends HttpService<User> {
constructor(
server: SERVER,
API_URLS: API_URLS,
httpClient: HttpClient
) {
super(
server,
httpClient,
API_URLS.USER,
new UserSerializer());
}
}
app/app.module.ts
import { Observable } from 'rxjs/Rx';
import { NgModule } from '#angular/core';
import { AppComponent } from './app.component';
import { CommonModule } from '#angular/common';
import { RouterModule } from '#angular/router';
import { AppRoutingModule } from './app-routing.module';
import { HttpClientModule } from '#angular/common/http';
import { BrowserModule } from '#angular/platform-browser';
/************** Services *****************/
import { UserService } from './services/user.service';
/************** Components *****************/
import { UserComponent } from './components/user/user.component';
/************** Constant *****************/
import { SERVER } from './util/constants';
import { API_URLS } from './util/constants';
import { OPERATION_TYPES } from './util/constants';
import { REQUEST_METHODS } from './util/constants';
import { Resource } from './models/resource';
#NgModule({
declarations: [
AppComponent,
UserComponent,
],
imports: [
CommonModule,
BrowserModule,
AppRoutingModule,
HttpClientModule
],
providers: [
SERVER,
API_URLS,
OPERATION_TYPES,
REQUEST_METHODS,
Resource,
UserService,
],
bootstrap: [AppComponent]
})
export class AppModule { }
Edit: Added missing code
app/services/http.service.ts
import { Observable } from 'rxjs/Rx';
import { Resource } from 'app/models/resource';
import { Serializer } from 'app/serializers/serializer';
import { SERVER } from 'app/util/constants';
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class HttpService<T extends Resource> {
constructor(
private SERVER: SERVER,
private httpClient: HttpClient,
private API_URL: string,
private serializer: Serializer) { }
/*
* Get single record by id
*/
read(id: number): Observable<T> {
return this.httpClient.get<T>(`${this.SERVER.ROOT}/${this.SERVER.API_PATH}/${this.API_URL}/${id}`).map(data => this.serializer.fromJson(data) as T);
}
/*
* Get all records by page
*/
readRecords(): Observable<T[]>{
return this.httpClient.get<T[]>(`${this.SERVER.ROOT}/${this.SERVER.API_PATH}/${this.API_URL}`).map((data: any) => this.convertData(data.items) as T[]);
}
/*
* Create new record
*/
create(record: T): Observable<T>{
return this.httpClient.post<T>(`${this.SERVER.ROOT}/${this.SERVER.API_PATH}/${this.API_URL}`, record).map(data => this.serializer.fromJson(data) as T);
}
/*
* Update record
*/
update(record: T): Observable<T>{
return this.httpClient.put<T>(`${this.SERVER.ROOT}/${this.SERVER.API_PATH}/${this.API_URL}/${record.id}`, record).map(data => this.serializer.fromJson(data) as T);
}
/*
* Remove record
*/
remove(id: number) {
return this.httpClient.delete(`${this.SERVER.ROOT}/${this.SERVER.API_PATH}/${this.API_URL}/${id}`);
}
private convertData(data: any): T[] {
return data.map(item => this.serializer.fromJson(item));
}
}
What am I missing?
I solved this by making the Serializer abstract class and making serializers injectable.
import { Injectable } from '#angular/core';
import { Resource } from 'app/models/resource';
#Injectable()
export abstract class Serializer {
abstract fromJson(json: any): Resource;
abstract toJson(resource: Resource): any;
}
And
import { Injectable } from '#angular/core';
import { User } from 'app/models/user.model';
import { Serializer } from 'app/serializers/serializer';
#Injectable()
export class UserSerializer implements Serializer {
fromJson(json: any): User {
const user = new User();
user.id = json.id;
user.client_id = json.client_id;
user.dealer_id = json.dealer_id;
user.status = json.status;
user.is_active = json.is_active;
user.date_created = json.date_created;
user.date_updated = json.date_updated;
return user;
}
toJson(user: User): any {
return {
id: user.id,
client_id: user.client_id,
dealer_id: user.dealer_id,
status: user.status,
is_active: user.is_active,
date_created: user.date_created,
date_updated: user.date_updated,
};
}
}