Error on ordering of methods in controller Nestjs - javascript

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.

Related

Nest.js cannot pass parameter to controller

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) {
}
}
}

NestJS Interceptor - Append data to incoming request Header or Body

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

Typescript ExpressJS - injecting services into REST API Controllers

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);
}
};
}

How to refresh token in Nestjs

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.

Error when using resolve with http requests in Angular 4

I am trying to use resolve for prefetching a http request in my Angular 4 project, but its throwing a ERROR Error: Uncaught (in promise): Response with status whenever there is an error response from Node server. The code works fine when there is a success http code like 200. Here's the resolve code I am working on:
#Injectable()
export class LoginResolverService implements Resolve<Object> {
constructor(private http: Http) {
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Object> | Promise<Object> | Object {
const headers = new Headers({'Content-Type': 'application/json'});
return this.http.get('http://localhost:3000/login_check', {headers: headers})
.map((response: Response) => {
return response.json();
}).catch((error: Response) => Observable.throw(error.json()));
}
}
Component file code:
loginState: Observable<Object>;
constructor(private route: ActivatedRoute, private router: Router) { }
ngOnInit() {
this.route.data.subscribe((data: Data) => {
this.loginState = data['isLoggedin'];
this.loginState.subscribe((response: any) => {
console.log(response);
}, (error: any) => {
console.log(error)
})
});
}
Routes related code:
{path: 'notifications', loadChildren: './notifications/notifications.module#NotificationsModule', resolve: {isLoggedin: LoginResolverService}}
Please help me solve this issue, as I couldn't find any proper information about using resolve with http
In my resolver service, I call a separate data service to actually get the data:
resolve(route: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<IProduct> {
let id = route.paramMap.get('id');
if (isNaN(+id)) {
console.log(`Product id was not a number: ${id}`);
this.router.navigate(['/products']);
return Observable.of(null);
}
return this.productService.getProduct(+id)
.map(product => {
if (product) {
return product;
}
console.log(`Product was not found: ${id}`);
this.router.navigate(['/products']);
return null;
})
.catch(error => {
console.log(`Retrieval error: ${error}`);
this.router.navigate(['/products']);
return Observable.of(null);
});
}
Then in the component:
ngOnInit(): void {
this.product = this.route.snapshot.data['product'];
}
That's it!
Or if you expect that the resolved data property will change without changing the route (not including user edits to that data), then you can use:
ngOnInit(): void {
this.route.data.subscribe(data => {
this.product = data['product'];
});
}
The router itself calls the resolver and handles the subscription. So you don't have to.

Categories

Resources