Home > Software design >  Inject Service to Guard - Nestjs
Inject Service to Guard - Nestjs

Time:11-15

I have the following global guard:

authorization.guard.ts

import { ExecutionContext, Injectable } from "@nestjs/common"
import { Reflector } from "@nestjs/core"
import { AuthGuard } from "@nestjs/passport"

@Injectable()
export class AuthorizationGuard extends AuthGuard(["azure-ad"]) {
  public constructor(private readonly reflector: Reflector) {
    super()
  }

  async canActivate(context: ExecutionContext) {
    const isPublic = this.reflector.get<boolean>(
      "isPublic",
      context.getHandler(),
    )

    if (isPublic) {
      return true
    }

    const req = context.switchToHttp().getRequest()
    if(req.headers.isbypass){
      //help needed
    }


    const result = (await super.canActivate(context)) as boolean
    await super.logIn(req)
    return result
  }
}

and the following auth module and strategy:

import { Module } from "@nestjs/common";
import { PassportModule } from "@nestjs/passport";
import { UsersModule } from "modules/users/users.module";
import { AzureADStrategy } from "./azureAD.strategy";
import { SessionSerializer } from "./session.serializer";

@Module({
    imports: [PassportModule, UsersModule],
    providers: [AzureADStrategy, SessionSerializer],
})
export class AuthModule {}
import {
    BearerStrategy,
    IBearerStrategyOption,
    ITokenPayload,
    VerifyCallback,
} from "passport-azure-ad";
import {
    Inject,
    Injectable,
    OnModuleInit,
    UnauthorizedException,
} from "@nestjs/common";
import passport = require("passport");
import { UsersService } from "modules/users/users.service";
import env from "../../config";

const tenantId = env.TENANT_ID;
const clientID = env.CLIENT_ID || "";

const azureCredentials: IBearerStrategyOption = {
//
};

@Injectable()
export class AzureADStrategy extends BearerStrategy implements OnModuleInit {
    onModuleInit() {
        passport.use("azure-ad", this);
    }

    constructor(
        @Inject(UsersService) private usersService: UsersService
    ) {
        super(
            azureCredentials,
            async (token: ITokenPayload, done: VerifyCallback) => {
                if (Date.now() / 1000 > token.exp) {
                    return done(new UnauthorizedException("access token is expired"));
                }

                const tokenUsername = token?.preferred_username?.slice(0, 9);
                const tokenAppId = !tokenUsername && token?.azp;

                if (!tokenUsername && !tokenAppId) {
                    return done(new UnauthorizedException("Missing User"));
                }

                let user;

                if (tokenUsername) {
                        try {
                            user = await this.usersService.getUser(
                                tokenUsername
                            );

                            if (!user) {
                                return done(
                                    new UnauthorizedException("User is not a test user")
                                );
                            }
                        } catch (err) {
                            return done(err);
                        }

                    user.tz = tokenUsername;
                } else {
                    user.appId = tokenAppId;
                }

                return done(null, user, token);
            }
        );
    }
}

The guard is defined globally using:

  const reflector = app.get(Reflector);
  app.useGlobalGuards(
        new AuthorizationGuard(reflector),
    );

And the auth module is imported in app.module.ts:

@Module({
  imports: [
    AuthModule,
    ...
]

Now, for the question.

I would like to have a way to "backdoor" the global authorization by checking if req.headers.isbypass exists in the request's headers, and if it does use userService in authorizationGuard, so i can inject the user from the DB to req.user myself and continue the request. How do I achieve that?

CodePudding user response:

I would change the app.useGlobalGuards() to be a global guard provider, adding

{
  provider: APP_GUARD,
  useClass: AuthorizationGuard,
}

Into the providers of your AppModule so that Nest handles all of the DI for you. From there, it's just adding the UsersService to the constructor like you already have for the Reflector

@Injectable()
export class AuthorizationGuard extends AuthGuard(["azure-ad"]) {
  public constructor(
    private readonly reflector: Reflector,
    private readonly usersService: UsersServce
  ) {
    super()
  }

  async canActivate(context: ExecutionContext) {
    const isPublic = this.reflector.get<boolean>(
      "isPublic",
      context.getHandler(),
    )

    if (isPublic) {
      return true
    }

    const req = context.switchToHttp().getRequest()
    if(req.headers.isbypass){
      //help needed
    }


    const result = (await super.canActivate(context)) as boolean
    await super.logIn(req)
    return result
  }
}
  • Related