I'm doing a server-side application with NestJS and TypeScript in combination with the implementation of Passport JWT.
A little bit of context first:
My JwtStrategy (no issues here):
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private userService: UserService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'hi',
});
}
async validate(payload: IJwtClaims): Promise<UserEntity> {
const { sub: id } = payload;
// Find the user's database record by its "id" and return it.
const user = await this.userService.findById(id);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
According to the documentation about the validate()
method:
Passport will build a user object based on the return value of our validate() method, and attach it as a property on the Request object.
Thanks to this behavior, I can access the user
object in my handler like this:
@Get('hi')
example(@Req() request: Request) {
const userId = (request.user as UserEntity).id;
}
Did you notice that I have used a Type Assertion (tells the compiler to consider the user object as UserEntity) ? Without it, I won't have auto-completion about my entity's properties.
As a quick solution, I have created a class that extends the Request
interface and include my own property of type UserEntity
.
import { Request } from 'express';
import { UserEntity } from 'entities/user.entity';
export class WithUserEntityRequestDto extends Request {
user: UserEntity;
}
Now, my handler will be:
@Get('hi')
example(@Req() request: WithUserEntityRequestDto) {
const userId = request.user.id; // Nicer
}
The real issue now:
I have (and will have more) a handler that will receive a payload, let's call it for this example PasswordResetRequestDto
.
export class PasswordResetRequestDto {
currentPassword: string;
newPassword: string;
}
The handler will be:
@Get('password-reset')
resetPassword(@Body() request: PasswordResetRequestDto) {
}
Now, I don't have access to the user's object. I would like to access it to know who is the user that is making this request.
What I have tried:
Use TypeScript Generics and add a new property to my previous WithUserEntityRequestDto
class like this:
export class WithUserEntityRequestDto<T> extends Request {
user: UserEntity;
newProp: T;
}
And the handler will be:
@Get('password-reset')
resetPassword(@Req() request: WithUserEntityRequestDto<PasswordResetRequestDto>) {
}
But now the PasswordResetRequestDto
will be under newProp
, making it not a scalable solution. Any type that I pass as the generic will be under newProp
. Also, I cannot extends T
because a class cannot extends two classes. I don't see myself doing classes like this all the time.
What I expect to accomplish:
Pass a type to my WithUserEntityRequestDto
class to include the passed type properties and also the user object by default. A way that I can do for example:
request: WithUserEntityRequestDto<AwesomeRequestDto>
request: WithUserEntityRequestDto<BankRequestDto>
And the value will be something like:
{
user: UserEntity, // As default, always present
// all the properties of the passed type (T),
// all the properties of the Request interface
}
My goal is to find an easy and scalable way to extends the Request
interface and include any type/class on it, while having the user object (UserEntity
) always present.
Thanks for the time and any help/advice/approach will be appreciated.