I'm using NestJS 10. I would like to create a guard that protects an endpoint, not only if the user is logged in, but also if they have confirmed their email. I'm using NestJS/passport, and create JWT tokens like so
async getTokens(userId: string, username: string) {
const [accessToken, refreshToken] = await Promise.all([
this.jwtService.signAsync(
{
sub: userId,
username,
},
{
secret: this.configService.get<string>('JWT_ACCESS_SECRET'),
expiresIn: ACCESS_TOKEN_DURATION,
},
),
this.jwtService.signAsync(
{
sub: userId,
username,
},
{
secret: this.configService.get<string>('JWT_REFRESH_SECRET'),
expiresIn: REFRESH_TOKEN_DURATION,
},
),
]);
return {
accessToken,
refreshToken,
};
}
I have this AccessTokenStrategy configured in my auth module ...
type JwtPayload = {
sub: string;
username: string;
};
@Injectable()
export class AccessTokenStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor() {
console.log("access token strategy ...");
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_ACCESS_SECRET,
});
}
validate(payload: JwtPayload) {
return payload;
}
}
But I'm not sure how to guard my email confirm guard. My user entity has a boolean "isEmailConfirmed" attribute but I'm not quite sure how to add/extract that from the payload if I am to build such a guard.
You didn't provide your database engine, so I'm going to assume it is Prisma, although it isn't that important here.
You could create a guard as follows:
import { Injectable, type CanActivate, type ExecutionContext } from '@nestjs/common';
import { DbUserService } from '@/modules/database/user.service';
interface GuardRequest {
readonly userId: string;
}
@Injectable()
export class IsEmailConfirmedGuard implements CanActivate {
constructor(private readonly dbUserService: DbUserService) {}
public async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest<GuardRequest>();
const user = request.userId;
const isEmailConfirmed = await this.dbUserService.isEmailConfirmed(
userId,
);
return isEmailConfirmed;
}
}
Of course isEmailConfirmed
function should query your database for the isEmailConfirmed
attribute value of the user in accordance to the userId
, let's say.
Then you apply the guard to your endpoint controller:
import { Body, Controller, HttpCode, HttpStatus, Param, Patch, UseGuards } from '@nestjs/common';
import { IsEmailConfirmedGuard } from '@/guards/is-email-confirmed.guard';
import Routes from './example.routes';
@Controller(Routes.EXAMPLE_CONTROLLER)
export class ExampleController {
@UseGuards(IsEmailConfirmedGuard)
@Patch(Routes.EXAMPLE)
@HttpCode(HttpStatus.OK)
public async example(
): Promise<void> {
return;
}
}