簡體   English   中英

如何使用 Nestjs 測試使用通行證策略實現的 Auth 0

[英]How to test Auth 0 implemented with passport strategy with Nestjs

我一直在研究一項功能,其目標是允許用戶通過 Auth0 登錄。 我正在使用諸如passport-auth0包之類的passport-auth0來實現它。 我能夠開始工作。 但是,我無法對其進行測試。 我想知道如何測試auth/loginauth/callback控制器方法。

此外,我想了解如何模擬@UseGuards(AuthGuard('auth0'))和中間件,因為我已經使用過它們。

我嘗試過的不同方法出現以下錯誤

[Nest] 23402   - 03/30/2020, 5:45:37 PM   [ExceptionHandler] Cannot set property 'authParams' of undefined
**TypeError: Cannot set property 'authParams' of undefined**
    at Auth0Strategy.Strategy.authenticate (/Users/directory/node_modules/passport-auth0/lib/index.js:82:28)
    at attempt (/Users/directory/node_modules/passport/lib/middleware/authenticate.js:366:16)
    at authenticate (/Users/directory/node_modules/passport/lib/middleware/authenticate.js:367:7)
    at /Users/directory/node_modules/@nestjs/passport/dist/auth.guard.js:84:3
    at new Promise (<anonymous>)
    at /Users/directory/node_modules/@nestjs/passport/dist/auth.guard.js:76:83
    at MixinAuthGuard.<anonymous> (/Users/directory/node_modules/@nestjs/passport/dist/auth.guard.js:48:36)
    at Generator.next (<anonymous>)
    at /Users/directory/node_modules/@nestjs/passport/dist/auth.guard.js:20:71
    at new Promise (<anonymous>)
✨  Done in 38.11s.
// Auth.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { Auth0Strategy } from './auth.strategy';
import { AuthMiddleware } from './ middlewares/auth.middleware'

@Module({
  controllers: [AuthController],
  providers: [
    AuthService,
    Auth0Strategy
  ],
  exports: [AuthService],
})
export class AuthModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(AuthMiddleware)
      .forRoutes('auth/callback');
  }
}
// auth.strategy.ts
import { Injectable, Query } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-auth0';
import { AuthService, Provider } from './auth.service';
import { config } from 'dotenv';

config();
@Injectable()
export class Auth0Strategy extends PassportStrategy(Strategy, 'auth0') {
  constructor(
    private readonly authService:AuthService
  )
  {
    super(
      {
        domain: process.env.AUTH0_DOMAIN,
        clientID: process.env.AUTH0_CLIENT_ID,
        clientSecret: process.env.AUTH0_CLIENT_SECRET,
        callbackURL: process.env.AUTH0_CALLBACK_URL,
        redirectUri: process.env.AUTH0_CALLBACK_URL,
        audience: process.env.AUTH0_AUDIENCE,
        responseType: 'code',
        scope: 'openid profile email',
      },
    )
  }

  async validate(request: any, accessToken: string, refreshToken: string, profile, done: Function): Promise<any> {
    try {
      const jwt: string = await this.authService.validateOAuthLogin(profile, Provider.Auth0);
      const user =
      {
        jwt
      }
      return done(null, user);
    }
    catch (err) {
      return done(err, false);
    }
  }
}

//auth.service.ts

import { Injectable, InternalServerErrorException, HttpException } from '@nestjs/common';
import { sign } from 'jsonwebtoken';

export enum Provider {
  Auth0 = 'auth0'
}

@Injectable()
export class AuthService {
  private readonly JWT_SECRET_KEY = process.env.JWT_SECRET_KEY
  async validateOAuthLogin(profile: object, provider: Provider): Promise<string> {
    try {
      const isProfileExist = Object.entries(profile).length;
      if (isProfileExist === 0) {
        throw new HttpException('User profile is empty please login again', 400);
      }
      const payload = {
        profile,
        provider
      }
      const jwt: string = sign(payload, this.JWT_SECRET_KEY, { expiresIn: '1h' });
      return jwt;
    }
    catch (err) {
      throw new InternalServerErrorException('validateOAuthLogin', err.message);
    }
  }
}
import { Controller, Get, UseGuards, Res, Req } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Controller('auth')
export class AuthController {

  @Get('login')
  @UseGuards(AuthGuard('auth0'))
  auth0Login() {}

  @Get('callback')
  @UseGuards(AuthGuard('auth0'))
  auth0LoginCallback(@Req() req, @Res() res) {
    const jwt: string = req.user.jwt;
    if (jwt) {
      res.redirect(`${process.env.AUTH0_SUCCESS_REDIRECTION_URL}/${jwt}`);
    } else {
      res.redirect(process.env.AUTH0_FAILURE_REDIRECTION_URL);
    }
  }
}
// auth.controller.spec.ts


import { Test, TestingModule } from '@nestjs/testing';
import { Auth0Strategy } from '../auth.strategy';
import { AuthModule } from '../auth.module';
import { auth0SuccessRequest, fekeToken } from './auth.mock';

describe('AuthService', () => {
  let strategy: Auth0Strategy;
  const { request, profile, accessToken, refreshToken  } = auth0SuccessRequest;
  let done: Function = jest.fn();

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [AuthModule],
      providers: [Auth0Strategy]
    }).compile();
    strategy = module.get<Auth0Strategy>(Auth0Strategy);
  });

  afterAll(async () => {
    jest.clearAllMocks();
  });

  it('should validate Auth 0 data', async () => {
    await strategy.validate(request, accessToken, refreshToken, profile, done)
    expect(done).toBeCalledTimes(1);
  });
  it('should not proceed without a profile function', async () => {
    const failuredAuth = await strategy.validate(request, accessToken, refreshToken, {}, done);
    expect(failuredAuth).toBeFalsy();
    expect(failuredAuth).toBeUndefined();
  });
});

// custom-guard.ts
import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class CustomGuard extends AuthGuard('auth0') {
  canActivate(context: ExecutionContext) {
    return super.canActivate(context);
  }

  handleRequest(err, user, info) {
    if (err || !user) {
      throw err || new UnauthorizedException();
    }
    return user;
  }
}

使用您的 CustomGuard 並對其進行測試

@Controller('auth')
export class AuthController {
  @UseGuards(CustomGuard) // use you guard
  auth0LoginCallback(@Req() req, @Res() res) {

  }
}

希望它會幫助你,它是https://docs.nestjs.com/guards文檔

你是對的@harut Barseghyan。 使用您與我共享的自定義防護,我可以在需要時在測試環境中activate或覆蓋防護。

// auth.controller.ts

import { Controller, Get, UseGuards, Res, Req } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthCustomGuard } from '../auth/guards/auth.guard';

@Controller('auth')
export class AuthController {

  @Get('login')
  // @UseGuards(AuthGuard('auth0')).    // I no longer use this guard
  @UseGuards(AuthCustomGuard).          // I instead use Auth0 custom guards
  auth0Login() {}

  @Get('callback')
  // @UseGuards(AuthGuard('auth0'))
  @UseGuards(AuthCustomGuard)
  auth0LoginCallback(@Req() req, @Res() res) {
    const jwt: string = req.user.jwt;
    if (jwt) {
      res.redirect(`${process.env.AUTH0_SUCCESS_REDIRECTION_URL}/${jwt}`);
    } else {
      res.redirect(process.env.AUTH0_FAILURE_REDIRECTION_URL);
    }
  }
}

由於我要授權Auth0路由,我仍然需要Auth 0守衛。 @harut Barseghyan 共享的代碼片段。

// auth.guard.ts


import { ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class AuthCustomGuard extends AuthGuard('auth0') {
  canActivate(context: ExecutionContext) {
    return super.canActivate(context);
  }
}

事實上我有一個自定義的守衛,我有能力在測試環境中激活它,反之亦然

// auth.controller.spec.ts


import { Test, TestingModule } from '@nestjs/testing';
import { AuthController } from '../auth.controller';
import { INestApplication } from '@nestjs/common';
import { AuthModule } from '../auth.module';
import * as request from 'supertest';
import { AuthCustomGuard } from '../guards/auth.guard';

describe('Auth Controller', () => {
  let app: INestApplication;


  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      imports: [AuthModule],
      controllers: [AuthController],
    })
      .overrideGuard(AuthCustomGuard)         // can you see that I override it
      .useValue({ canActivate: () => true })  // true helped me to skip
      .compile();

    app = module.createNestApplication();
    app.init()
  });

  it('should be visit auth login route', async () => {
    return request(app.getHttpServer())
      .get('/auth/login')
      .expect(302)
  });

  it('should not redirect user to login page if auth 0 throws an error such as an invalid error request', async () => {
    return request(app.getHttpServer())
      .get('/auth/callback?error=invalid_request')
      .expect(302)
  });

  afterAll(async () => {
    jest.clearAllMocks();
    await app.close();
  });
});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM