In my middleware, Im decoding the request token and extract msisdn from it to pass to the controllers as custom decorator.But i got this error
Property 'decorate' does not exist on type
'Request'.
auth.midleware.ts
export class AuthMiddleware implements NestMiddleware {
constructor(
private readonly commonHelper: Common,
) {}
async use(req: Request, res: Response, next: NextFunction) {
const token = req?.headers?.authorization;
try {
const response = await this.commonHelper.verifyToken({
token,
});
if (response.statusCode === HttpStatus.OK) {
req.decorate = { // Property 'decorate' does not exist on type 'Request<ParamsDictionary>'.
msisdn: response?.data?.msisdn,
};
next();
} else {
}
} catch (error) {
}
}
}
Related
I have this basic CRUD methods in Nestjs.
The issue I am facing is that when I am applying the getCurrentUserId() method on top on all methods it works fine but when I am applying in bottom it doesnt work and gives error.
Is there anything wrong with middleware ?
user.controller.ts
#Controller('users')
#Serialize(UserDto)
export class UsersController {
constructor(private usersService: UsersService) {}
#Post('/signup')
create(#Body() createUserDto: CreateUserDto): Promise<User> {
return this.usersService.create(createUserDto);
}
#Get('/#:userName')
async getUserByUsername(#Param('userName') userName: string) {
const user = await this.usersService.findByName(userName);
console.log(userName);
if (!user) {
throw new NotFoundException('User Not Found');
}
return user;
}
//! Testing for current user
#Get('/current')
#UseGuards(JwtAuthGuard)
async getCurrentUserId(#CurrentUser() id: string) {
console.log('running endpoint');
return id;
}
}
current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const CurrentUser = createParamDecorator(
(data : unknown , context : ExecutionContext) => {
const req = context.switchToHttp().getRequest();
console.log("I am running")
return req.id;
}
)
current-user.middleware.ts
#Injectable()
export class CurrentUserMiddleware implements NestMiddleware {
constructor(private usersService: UsersService) {}
async use(req: RequestId, res: Response, next: NextFunction) {
const token = req.headers['authorization'];
console.log(token);
if (!token) {
throw new UnauthorizedException('Unauthorized');
}
try {
const { userId } =
await this.usersService.getUserByToken(token);
req.id = userId;
console.log(req.id)
next();
} catch {
throw new UnauthorizedException();
}
}
}
And I have added the middleware to user.module.ts like this
export class UsersModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(CurrentUserMiddleware).forRoutes(
'users/current'
);
}
}
The route is matching on #Get('/#:userName') before it makes it to #Get('/current') so its executing the code inside of your getUserByUsername method instead.
Just move getCurrentUserId to the top and you should be fine.
Routes are evaluated in the order they are defined and the first matching one is used to handle the request. In general you should always put the most specific routes (the ones without route params) at the top of your controller to avoid this problem.
I am trying to modify an NestJS incoming request and append some data either to header or Body. I was able to replace all the body data with my data but i would like to append and not remove the incoming body data.
Here is the code i have
export class MyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const token = request.headers['authorization'];
if (token) {
const decoded = jwt_decode(token);
request.body['userId'] = decoded['id'];
}
return next.handle();
}
}
Thanks in advance
I have added two examples as after running testing for the interceptor, it passed without any issue. Of course, my example will be very different to your set up, however, hopefully it'll give you enough insight:
The test file:
test('should not mutate entire request body object', () => {
const dto = {
username: 'testuser',
email: 'test#domain.com',
};
const headers = {
authorization: 'Bearer sdkfjdsakfjdkjfdal',
};
return request(app.getHttpServer())
.post('/')
.send(dto)
.set(headers)
.expect(({ body }) => {
expect(body.userId).toBeDefined();
delete body.userId;
expect(body).toStrictEqual(dto);
});
});
I understand your problem as attempting to obtain information about the authenticated user, and return it/use it later on? However, your current implementation seems to completely override the request.body instead of append your property to the original object.
Interceptor:
#Injectable()
export class HttpRequestBodyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable {
const request = context.switchToHttp().getRequest();
const token = request.headers['authorization'];
if (token) {
// decode token
request.body['userId'] = 'user_123456789';
}
return next.handle();
}
}
Controller:
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Post()
#UseInterceptors(HttpRequestBodyInterceptor)
getHello(#Req() req): string {
return req.body;
}
}
This returns the correct response and the test will pass. However, you may find a more robust solution would be:
#Injectable()
export class HttpRequestBodyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable {
const request = context.switchToHttp().getRequest();
const token = request.headers['authorization'];
if (token) {
// decode token
request.userId = 'user_123456789';
}
return next.handle();
}
}
And then access this in your controller by:
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Post()
#UseInterceptors(HttpRequestBodyInterceptor)
getHello(#Req() req) {
return {
userId: req.userId,
...req.body,
};
}
}
Finally, if your only need for an interceptor is to obtain that userId property, you may find that https://docs.nestjs.com/security/authentication#jwt-functionality is useful.
#Injectable()
export class JwtInterceptor implements NestInterceptor {
constructor(private readonly jwtService: JwtService, private readonly
userService: UserService) { }
async intercept(context: ExecutionContext, next: CallHandler):
Promise<Observable<any>> {
var request: WsArgumentsHost = context.switchToWs();
var { handshake: { headers: { authorization } } } =
request.getClient();
try {
var jwt = authorization.split(" ")[1];
var { phone } = await this.jwtService.verify(jwt, jwtConstraints)
var user: User = await this.userService.findUserByPhoneNumber(phone);
request.getData()["user"]=user;
return next.handle().pipe(map((data) => { return { ...data, 'user': "david" }; }));
i hope this will help someone in future while working with socket.i wanted the user object in the body after they pass authentication .the above trick worked out for me
I am attempting an OOP approach to my express rest API with Typescript and having issues composing classes.
The idea is to have a AuthController class that calls a private AuthService class to interact with the database. However, when I attempt to call the AuthService class with the AuthController class, it always returns undefined.
App class which initializes my express app and registers my controllers:
class App {
private app;
private controllers: Controller[];
constructor(controllers: Controller[]) {
this.app = express();
this.controllers = controllers;
this.config();
this.initializeRoutes();
this.initializeErrorHandler();
}
config() {
console.log("running config");
this.app.use(cors());
this.app.use(express.json());
this.app.use(helmet());
}
public listen() {
this.app.listen(3000);
}
initializeRoutes() {
this.controllers.forEach((controller: any) => {
this.app.use("/", controller.router);
});
}
initializeErrorHandler() {
this.app.use(errorMiddleware);
}
}
(async () => {
try {
await connection(); //creates my database connection
} catch (error) {
console.log("Error while connecting to the database", error);
return error;
}
const app = new App([new AuthController()]);
app.listen();
})();
here is my AuthController being initialized in my call to const app = new App([new AuthController()]);
export default class AuthController implements Controller {
public path = "/api/auth";
public router = Router();
private authService: AuthService = new AuthService();
constructor() {
this.initializeRoutes();
}
public initializeRoutes() {
//login route
this.router.post(this.path.concat("/login"), this.login);
this.router.post(
this.path.concat("/register"),
validationMiddleware(CreateUserDto),
this.register
);
this.router.post(this.path.concat("/resetpassword"), this.resetPassword);
this.router.post(this.path.concat("/newpassword"), this.newPassword);
}
public async register(req: Request, res: Response, next: NextFunction) {
const userData: CreateUserDto = req.body;
try {
let {
tokens: { xAuthToken, xRefreshToken },
user,
} = await this.authService.register(userData);
res.setHeader("x-auth-token", xAuthToken);
res.setHeader("x-refresh-token", xRefreshToken);
res.json(user);
} catch (e) {
next(e);
}
}
}
and finally AuthService class
export class AuthService {
private userRepo: Repository<User> = getRepository(User);
constructor() {}
public async register(userData: CreateUserDto) {
let foundUser = await this.userRepo.findOne({
where: { email: userData.email.toLowerCase() },
});
console.log(foundUser);
if (foundUser) {
throw new EmailInUseException();
} else {
console.log(foundUser);
const user = new User();
user.email = userData.email.toLowerCase();
user.password = userData.password;
await this.userRepo.save(user);
const tokens = this.createTokens(user);
return {
tokens,
user,
};
}
}
}
Anytime I call the AuthService from the AuthController, I receive an 'error cannot read property 'authService' of undefined'.
I have tried changing the code to initialize the AuthService directly const app = new App([new AuthController(new AuthService()]); but this doesn't fix the issue.
Any help is appreciated!
Found the issue. The 'this' context was being changed. I switched my 'register' method to an arrow function which fixed the problem
original:
class AuthController{
public async register(req: Request, res: Response, next: NextFunction) {
const userData: CreateUserDto = req.body;
try {
let {
tokens: { xAuthToken, xRefreshToken },
user,
} = await this.authService.register(userData);
res.setHeader("x-auth-token", xAuthToken);
res.setHeader("x-refresh-token", xRefreshToken);
res.json(user);
} catch (e) {
next(e);
}
}
}
new:
class AuthController{
public register = async (req: Request, res: Response, next: NextFunction) => {
const userData: CreateUserDto = req.body;
try {
let {
tokens: { xAuthToken, xRefreshToken },
user,
} = await this.authService.register(userData);
res.setHeader("x-auth-token", xAuthToken);
res.setHeader("x-refresh-token", xRefreshToken);
res.json(user);
} catch (e) {
next(e);
}
};
}
In my Angular application, there is a global error handler and a service responsible for making http calls, having return type as Observable<any>. Some services have handled the errors explicitly and some not.
For those, which has not been catched, the global error (having class 'CustomErrorHandler') runs fine. For the service calls, which has been handled and catched gracefully, the global error doesn't seem to fire.
My question: Is there a way to execute the global error handling irrespective of whether the http service calls has been handled or not?
custom-error-handler.service.ts
#Injectable()
export class CustomErrorHandler implements ErrorHandler {
constructor(private injector: Injector) { }
handleError(error) {
error = error || new Error('There was an unexpected error');
const loggingService: LoggerService = this.injector.get(LoggerService);
const location = this.injector.get(LocationStrategy);
const message = error.message ? error.message : error.toString();
const url = location instanceof PathLocationStrategy
? location.path() : '';
// get the stack trace, lets grab the last 10 stacks only
StackTrace.fromError(error).then(stackframes => {
const stackString = stackframes
.splice(0, 20)
.map((sf) => {
return sf.toString();
}).join('\n');
// log with logging service
loggingService.error({ message, url, stack: stackString });
});
throw error;
}
}
auth.service.ts
#Injectable()
export class UserAuthService {
login(emailAddress: string, password: string): Observable<boolean> {
if (this.isAuthenticated()) {
return Observable.of(true);
}
return Observable.create(observer => {
this.http.get('http://someurl/login')
.map(res => res.json())
.subscribe(
data => {
observer.next(this.isAuthorized);
observer.complete();
}, err => {
observer.error(err);
}
);
});
}
}
my-component.ts
import { UserAuthService } from '../services/auth.service';
#Component({
selector: 'my-component',
templateUrl: './my-component.html',
styleUrls: ['./my-component.scss']
})
export class MyComponent {
constructor(private authService: UserAuthService) {}
handleLogin() {
this.authService.login(formValues.emailAddress, formValues.password)
.subscribe(res => {
console.log('Logged In');
}, err => {
console.log('Logging Failed');
// Global Error DOESN'T fire here as the Error has been handled
});
}
handleLoginPart2() {
this.authService.login(formValues.emailAddress, formValues.password)
.subscribe(res => {
console.log('Logged In');
}); // Global Error does fire here as the Error has NOT been handled
}
}
I was able to resolve the issue myself, by creating a HttpClient which inherits from Http.
By doing this I am able to handle the error gracefully.
http-client.service.ts
import { ConnectionBackend, Http, RequestOptions, RequestOptionsArgs, Response } from '#angular/http';
#Injectable()
export class HttpClient extends Http {
http;
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {
super(backend, defaultOptions);
}
get(url, options?: RequestOptionsArgs): Observable<Response> {
return super.get(url, options)
.catch(this.handleError);
}
private handleError(errorRes: Response | any) {
return Observable.throw(retError);
}
}
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from './auth.service';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { JwtPayload } from './model/jwt-payload.model';
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'secretKey',
});
}
async validate(payload: JwtPayload) {
const user = await this.authService.validateUser(payload);
if (!user) {
throw new UnauthorizedException();
}
return true;
}
}
Token is extracted from the request by PassportStrategy. I don't know how to catch the error when the token expires or gets invalid. My purpose is if there is an error because the token expired, I need to refresh the token. Otherwise do something else.
Refresh token implementation could be handled in canActivate method in custom auth guard.
If the access token is expired, the refresh token will be used to obtain a new access token. In that process, refresh token is updated too.
If both tokens aren't valid, cookies will be cleared.
#Injectable()
export class CustomAuthGuard extends AuthGuard('jwt') {
private logger = new Logger(CustomAuthGuard.name);
constructor(
private readonly authService: AuthService,
private readonly userService: UserService,
) {
super();
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
try {
const accessToken = ExtractJwt.fromExtractors([cookieExtractor])(request);
if (!accessToken)
throw new UnauthorizedException('Access token is not set');
const isValidAccessToken = this.authService.validateToken(accessToken);
if (isValidAccessToken) return this.activate(context);
const refreshToken = request.cookies[REFRESH_TOKEN_COOKIE_NAME];
if (!refreshToken)
throw new UnauthorizedException('Refresh token is not set');
const isValidRefreshToken = this.authService.validateToken(refreshToken);
if (!isValidRefreshToken)
throw new UnauthorizedException('Refresh token is not valid');
const user = await this.userService.getByRefreshToken(refreshToken);
const {
accessToken: newAccessToken,
refreshToken: newRefreshToken,
} = this.authService.createTokens(user.id);
await this.userService.updateRefreshToken(user.id, newRefreshToken);
request.cookies[ACCESS_TOKEN_COOKIE_NAME] = newAccessToken;
request.cookies[REFRESH_TOKEN_COOKIE_NAME] = newRefreshToken;
response.cookie(ACCESS_TOKEN_COOKIE_NAME, newAccessToken, COOKIE_OPTIONS);
response.cookie(
REFRESH_TOKEN_COOKIE_NAME,
newRefreshToken,
COOKIE_OPTIONS,
);
return this.activate(context);
} catch (err) {
this.logger.error(err.message);
response.clearCookie(ACCESS_TOKEN_COOKIE_NAME, COOKIE_OPTIONS);
response.clearCookie(REFRESH_TOKEN_COOKIE_NAME, COOKIE_OPTIONS);
return false;
}
}
async activate(context: ExecutionContext): Promise<boolean> {
return super.canActivate(context) as Promise<boolean>;
}
handleRequest(err, user) {
if (err || !user) {
throw new UnauthorizedException();
}
return user;
}
}
Attaching user to the request is done in validate method in JwtStrategy class, it will be called if the access token is valid
#Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
readonly configService: ConfigService,
private readonly userService: UserService,
) {
super({
jwtFromRequest: cookieExtractor,
ignoreExpiration: false,
secretOrKey: configService.get('jwt.secret'),
});
}
async validate({ id }): Promise<User> {
const user = await this.userService.get(id);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
Example for custom cookie extractor
export const cookieExtractor = (request: Request): string | null => {
let token = null;
if (request && request.signedCookies) {
token = request.signedCookies[ACCESS_TOKEN_COOKIE_NAME];
}
return token;
};
Instead of using the built-in AuthGuard you can create your own one and overwrite the request handler:
#Injectable()
export class MyAuthGuard extends AuthGuard('jwt') {
handleRequest(err, user, info: Error) {
if (info instanceof TokenExpiredError) {
// do stuff when token is expired
console.log('token expired');
}
return user;
}
}
Depending on what you want to do, you can also overwrite the canActivate method where you have access to the request object. Have a look at the AuthGuard sourcecode.