Let say I have a file named api-key.strategy.ts
import { PassportStrategy } from '#nestjs/passport';
import { Injectable } from '#nestjs/common';
import { HeaderAPIKeyStrategy } from 'passport-headerapikey';
import { ConfigService } from '#nestjs/config';
#Injectable()
export class ApiKeyStrategy extends PassportStrategy(HeaderAPIKeyStrategy) {
constructor(private readonly configService: ConfigService) {
super(
{
header: 'x-api-key',
prefix: ''
},
true,
(apiKey: string, done: any) => {
if (apiKey === 'MY_API_KEY') return done(true);
else return done(false);
}
);
}
}
Based on above codes, how to create a Jest unit test?
Thanks
Related
I want to add authentication to my graphql app in nestjs. the token I receive is from auth0.
Im fairly new to nestjs I have looked at its documentation. this is the best I could comeup with and it still isnt working
this is my code where Im trying to add auth
app.module
import { Module } from '#nestjs/common';
import { ConfigModule } from '#nestjs/config';
import { MongooseModule } from '#nestjs/mongoose';
import { databaseConfig, applicationConfig } from '../../config/';
import MongoServiceClass from '../../services/Mongo';
import { ConfigModule as AppConfigModule } from '../config/config.module';
import { GraphQLModule } from '#nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '#nestjs/apollo';
import { LoggingPlugin } from 'src/plugins/LoggerCustom';
import { UserModule } from './user/user.module';
import { QuotaModule } from './quota/quota.module';
import { QuotaManagementModule } from './quota-management/quota-management.module';
import { BobModule } from './bob/bob.module';
import { SlModule } from './sl/sl.module';
import { SlQModule } from './sl-q/sl-q.module';
import { AuthModule } from '../auth/auth.module';
const NODE_ENV = process.env.NODE_ENV;
#Module({
imports: [
LoggingPlugin,
ConfigModule.forRoot({
load: [applicationConfig, databaseConfig],
envFilePath: `.env.${NODE_ENV}`,
isGlobal: true,
}),
MongooseModule.forRootAsync({
useClass: MongoServiceClass,
}),
AuthModule,
GraphQLModule.forRoot<ApolloDriverConfig>({
autoSchemaFile: 'schema.gql',
driver: ApolloDriver,
path: '/graphql',
playground: true,
debug: true,
}),
BobModule,
QuotaModule,
QuotaManagementModule,
AppConfigModule,
SlQModule,
SlModule,
UserModule,
],
})
export class AppModule {}
auth.module
import { Global, Module } from '#nestjs/common';
import { UserModule } from '../app/user/user.module';
import { AuthService } from './auth.service';
import { AuthResolver } from './auth.resolver';
import { GqlAuth0Guard } from './gql.guard';
import { GqlAuth0JwtStrategy } from './gql-auth0-jwt.strategy';
import { PassportModule } from '#nestjs/passport';
#Global()
#Module({
imports: [UserModule, PassportModule.register({ defaultStrategy: 'jwt' })],
providers: [AuthService, AuthResolver, GqlAuth0JwtStrategy, GqlAuth0Guard],
})
export class AuthModule {}
gql-auth0-jwt.strategy
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { PassportStrategy } from '#nestjs/passport';
import { Strategy as BaseStrategy, ExtractJwt, VerifiedCallback } from 'passport-jwt';
import { passportJwtSecret } from 'jwks-rsa';
#Injectable()
export class GqlAuth0JwtStrategy extends PassportStrategy(BaseStrategy) {
domain = 'https://dev-ab454a.us.auth0.com/';
constructor() {
super({
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `https://dev-ab454a.us.auth0.com/.well-known/jwks.json`,
}),
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
audience: 'https://hyyype-app.herokuapp.com/',
issuer: 'https://dev-ab454a.us.auth0.com/',
});
}
validate(payload: any, done: VerifiedCallback) {
console.log('Validate JWT-STRATEGY');
if (!payload) {
done(new UnauthorizedException(), false);
}
return done(null, payload);
}
}
gql.guard
import { ExecutionContext, Injectable } from '#nestjs/common';
import { GqlExecutionContext } from '#nestjs/graphql';
import { AuthGuard } from '#nestjs/passport';
#Injectable()
export class GqlAuth0Guard extends AuthGuard('jwt') {
getRequest(context: ExecutionContext) {
console.log('GQL-AUTH0-GUARD');
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
auth.resolver
import { UnauthorizedException, UseGuards } from '#nestjs/common';
import { Query, Resolver } from '#nestjs/graphql';
import { CurrentUser } from './auth.decorator';
import { GqlAuth0Guard } from './gql.guard';
#Resolver()
export class AuthResolver {
#Query(() => String)
#UseGuards(GqlAuth0Guard)
getAuthenticatedUser(#CurrentUser() user: any) {
if (!user) {
throw new UnauthorizedException('Get Auth User');
}
return user;
}
}
this is my response after i send a request I have checked the token is valid
GQL-AUTH0-GUARD
error: 586ms - Unauthorized
I have the following error in nest. I've read the doc but I still don't understand what's going on. This is the full error: "[Nest] 9420 - 21/02/2022, 12:39:24 p. m. ERROR [ExceptionHandler] Nest can't resolve dependencies of the Season3Service (?). Please make sure that
the argument gModel at index [0] is available in the AppModule context". Here is my code:
app.controller.ts:
import { Controller, Get } from '#nestjs/common';
import { ClientRequest } from 'http';
import { AppService } from './app.service';
#Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
#Get()
getHello(): string {
return 'Sarani Mukoe! En el Arco de la Villa del Herrero';
}
}
app.module.ts:
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { Season3Controller } from './season3/season3.controller';
import { Season3Service } from './season3/season3.service';
import { Season3Module } from './season3/season3.module';
import { MongooseModule } from '#nestjs/mongoose';
#Module({
imports: [Season3Module, MongooseModule.forRoot('mongodb://127.0.0.1:27017/DemonSlayer')],
controllers: [AppController, Season3Controller],
providers: [AppService, Season3Service],
})
export class AppModule {}
app.service:
import { Injectable } from '#nestjs/common';
#Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
Then, I have the season3 module folder:
season3.controller.ts:
import { Controller, Get, Post, Put, Delete, Body, Param } from '#nestjs/common';
import { CreateSeasonDto } from './dto/create-season.dto';
import { Season } from './interfaces/Season';
import { Season3Service } from "./season3.service";
#Controller('season3')
export class Season3Controller {
constructor(private season3: Season3Service) {}
#Get()
getSeasons(): Promise<Season[]> {
return this.season3.getSeasons();
}
#Get(':p')
getSeason(#Param('p') id: string): Promise<Season>{
console.log(id);
return this.season3.getSeason(id);
}
#Post()
createSeason(#Body() season: CreateSeasonDto): string {
console.log(
`Título: ${season.titulo}. Cuerpo: ${season.cuerpo}. Realizado: ${season.realizado}`
);
return 'Insertando.... Hinokami Kagura';
}
#Put(':p1')
updateSeason(#Body() cuerpo: CreateSeasonDto, #Param('p1') id): string {
console.log(cuerpo);
console.log(id);
return `Actualizando.... Velocidad Extrema`;
}
#Delete(':id')
deleteSeason(#Param('id') id): string {
console.log(id);
return `Eliminando(${id}) .... Colmillos Afilados`;
}
}
season3.module.ts:
import { Module } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { Season3Controller } from './season3.controller';
import { Season3Service } from './season3.service';
import { SeasonSchema} from "./schemas/season.schema";
#Module({
imports: [MongooseModule.forFeature([
{name:'g', schema:SeasonSchema}
])],
controllers: [Season3Controller],
providers: [Season3Service],
})
export class Season3Module {}
season3.service.ts:
import { Model } from "mongoose";
import { Injectable } from '#nestjs/common';
import { InjectModel } from '#nestjs/mongoose';
import { Season } from "./interfaces/Season";
#Injectable()
export class Season3Service {
constructor(#InjectModel('g') private seasonModel: Model<Season>) {}
async getSeasons() {
return await this.seasonModel.find();
}
async getSeason(id: string) {
return await this.seasonModel.findById(id);
}
}
It looks like Season3Service is not exported so you won't be able to use it in other modules.
in season3.module.ts add export
import { Module } from '#nestjs/common';
import { MongooseModule } from '#nestjs/mongoose';
import { Season3Controller } from './season3.controller';
import { Season3Service } from './season3.service';
import { SeasonSchema} from "./schemas/season.schema";
#Module({
imports: [MongooseModule.forFeature([
{name:'g', schema:SeasonSchema}
])],
controllers: [Season3Controller],
providers: [Season3Service],
exports: [Season3Service], // <----- make Season3Service public
})
export class Season3Module {}
doc: https://docs.nestjs.com/modules#shared-modules
After this, any other module that imports Season3Module will be able to inject (user) Season3Service
in app.module.ts
import { Module } from '#nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { Season3Controller } from './season3/season3.controller';
import { Season3Module } from './season3/season3.module';
import { MongooseModule } from '#nestjs/mongoose';
#Module({
imports: [Season3Module, MongooseModule.forRoot('mongodb://127.0.0.1:27017/DemonSlayer')],
controllers: [AppController, Season3Controller],
providers: [AppService], // <---- remove Season3Service from providers
})
export class AppModule {}
Now any providers/controllers in AppModule will have access to Season3Service
import {
Component,
ViewChild
} from '#angular/core';
import {
Platform,
NavController
} from 'ionic-angular';
import {
StatusBar
} from '#ionic-native/status-bar';
import {
SplashScreen
} from '#ionic-native/splash-screen';
import {
Storage
} from '#ionic/Storage';
import {
HomePage
} from '../pages/home/home';
import {
LoginPage
} from '../pages/login/login';
import {
RegisterPage
} from '../pages/register/register';
#Component({
templateUrl: 'app.html'
}) export class MyApp {
_platform: Platform;
public get platform(): Platform {
return this._platform;
}
public set platform(value: Platform) {
this._platform = value;
}
#ViewChild('content') nav: NavController;
rootPage: any;
initialization: any;
initializationApp: any;
Platform: any;
statusBar: any;
splashScreen: any;
Storage: any;
constructor(public platform1: Platform, public StatusBar: StatusBar, public SplashScreen: SplashScreen, private storage: Storage) {
this.initializationApp();
}
initializeApp() {
this.platform.ready().then(() => {
this.statusBar.styleDefault();
this.splashScreen.hide();
});
this.storage.get('session_storage').then((res) => {
if (res == null) {
this.rootPage = LoginPage;
} else {
this.rootPage = HomePage;
}
});
}
}
In your constructor method, you are calling this.initializationApp();, but the method is actually called initializeApp(). Change the constructor to call this.initializeApp(); and you should be good to go.
I have been going round in circles trying to unit test a Service (AuthService) that depends upon AngularFireAuth.
I am trying to find a way to mock, or highjack the Observable AngularFireAuth.authState instead of the Service actually talking to Firebase.
Here is my test spec:
import { inject, TestBed } from '#angular/core/testing';
import { AngularFireModule } from 'angularfire2';
import { AngularFireAuth, AngularFireAuthModule } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
import 'rxjs/add/observable/of';
// import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Rx';
import { AuthService } from './auth.service';
import { environment } from '../../environments/environment';
const authState: firebase.User = null;
const mockAngularFireAuth: any = { authState: Observable.of(authState) };
describe('AuthService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [AngularFireModule.initializeApp(environment.firebaseAppConfig)],
providers: [
{ provider: AngularFireAuth, useValue: mockAngularFireAuth },
AuthService
]
});
});
it('should be defined', inject([ AuthService ], (service: AuthService) => {
expect(service).toBeDefined();
}));
it('.authState should be null', inject([ AuthService ], (service: AuthService) => {
expect(service.authState).toBe(null);
}));
});
And here is my (simplified) Service:
import { Injectable } from '#angular/core';
import { AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
import { Observable } from 'rxjs/Rx';
#Injectable()
export class AuthService {
private authState: firebase.User;
constructor(private afAuth: AngularFireAuth) { this.init(); }
private init(): void {
this.afAuth.authState.subscribe((authState) => {
if (authState === null) {
this.afAuth.auth.signInAnonymously()
.then((authState) => {
this.authState = authState;
})
.catch((error) => {
throw new Error(error.message);
});
} else {
this.authState = authState;
}
}, (error) => {
throw new Error(error.message);
});
}
public get currentUser(): firebase.User {
return this.authState ? this.authState : undefined;
}
public get currentUserObservable(): Observable<firebase.User> {
return this.afAuth.authState;
}
public get currentUid(): string {
return this.authState ? this.authState.uid : undefined;
}
public get isAnonymous(): boolean {
return this.authState ? this.authState.isAnonymous : false;
}
public get isAuthenticated(): boolean {
return !!this.authState;
}
public logout(): void {
this.afAuth.auth.signOut();
}
}
I get the error Property 'authState' is private and only accessible within class 'AuthService'.
Of course it is, but I don't want to actually access it — I want to mock or highjack it so I can control it's value from within my test spec. I believe I am way off-course with my code here.
Please note I am using version ^4 of AngularFire2 and there were breaking changes introduced; documented here: https://github.com/angular/angularfire2/blob/master/docs/version-4-upgrade.md
Encapsulated members can be reflected.
The hard way:
expect(Reflect.get(service, 'authState')).toBe(null);
The easy way:
expect(service['authState']).toBe(null);
expect((service as any).authState).toBe(null);
Using Angular 4, Ngrx Store & AngularFire2
I am having real problems understanding how I can restrict a list of items from Firebase based on the currently logged in user account id.
I am using ngrx as well including ngrx effects.
The steps I need to follow are:
• Get Current Users UID – Auth Object
• Get User Object based on the UID from step above
• Get Company List based on the User Account ID Above
My problem is that because I am calling firebase as an Observable the call to company list is being made before I complete the first two steps.
The code is as per below, if someone can assist that would be appreciated:
The problem is in the getEntityList Method on the generic firebase service - I have marked where the problem is
Company Component
import { Component, OnInit, ChangeDetectionStrategy } from '#angular/core';
import { Observable } from 'rxjs/Rx';
import { Store } from '#ngrx/store';
import { AppState } from './../../../core/models/index';
import { CompanyModel } from './../../../core/models/index';
import { getCompanies} from './../../../core/store/actions/company.actions';
#Component({
selector: 'mj-company',
templateUrl: './company.component.html',
styleUrls: ['./company.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CompanyComponent implements OnInit {
entityList$: Observable<CompanyModel[]>;
constructor(private store: Store<AppState>) {
this.entityList$ = this.store.select(state => state.companies);
}
ngOnInit() { this.store.dispatch(getCompanies()); }
}
Company Actions
import { CompanyModel } from './../../models';
import { Action } from '#ngrx/store';
export const ActionTypes = {
GET_COMPANIES: 'GET_COMPANIES',
GET_COMPANIES_SUCCESS: 'GET_COMPANIES_SUCCESS',
GET_COMPANIES_ERROR: 'GET_COMPANIES_ERROR'
};
export function getCompanies() {
return {
type: ActionTypes.GET_COMPANIES,
entityRef: 'companys'
}
}
}
Company Reducer
import { ActionReducer, Action } from '#ngrx/store';
import { ActionTypes } from '../actions/company.actions';
import { CompanyModel } from '../../models';
export function companyReducer(state = [<CompanyModel>{}], action: Action) {
switch (action.type) {
case ActionTypes.GET_COMPANIES:
return action.payload;
case ActionTypes.GET_COMPANIES_SUCCESS:
return action.payload;
case ActionTypes.GET_COMPANIES_ERROR:
return action.payload;
default:
return state;
}
};
Company Effect
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs/Rx';
import { ActionTypes } from '../actions/company.actions';
import { Actions, Effect } from '#ngrx/effects';
import { FirebaseDataService } from './../../services/firebase-data.service';
#Injectable()
export class CompanyEffects {
constructor(
private actions$: Actions,
private firebaseDataService: FirebaseDataService
) { }
// tslint:disable-next-line:member-ordering
#Effect() getCompanies$ = this.actions$
.ofType(ActionTypes.GET_COMPANIES)
.switchMap(action =>
this.firebaseDataService.getEntityList(action.entityRef)
.map(companies => ({ type: ActionTypes.GET_COMPANIES_SUCCESS, payload: companies }))
.catch(() => Observable.of({ type: ActionTypes.GET_COMPANIES_ERROR })));
Firebase Generic Data Service
import { Injectable } from '#angular/core';
import { AngularFireDatabase, FirebaseListObservable, FirebaseObjectObservable } from 'angularfire2/database';
import { Observable } from 'rxjs/Rx';
import { AuthService } from './auth.service';
import { FirebaseUtilityService } from './../../core/services/firebase-utility.service';
import { UserModel, CompanyModel } from './../models/index';
#Injectable()
export class FirebaseDataService {
$key: string;
loginId: string;
currentUser: any;
constructor(private db: AngularFireDatabase,
private authService: AuthService,
private firebaseService: FirebaseUtilityService) { }
// Return an observable list with optional query
getEntityList(firebaseRef: string, query = {}): FirebaseListObservable<any[]> {
this.loginId = this.authService.currentUserId;
// I get this instantly which is good
console.log('logId: ', this.loginId);
this.currentUser = this.db.object('users/' + this.loginId);
console.log('accountId: ', this.currentUser.accountId);
// This is where the problem is because at this stage the subscription above is not complete so accountId is undefined.
return this.db.list(firebaseRef, {
query: {
orderByChild: 'accountId',
equalTo: this.currentUser.accountId
}
});
// return this.db.list(firebaseRef, query);
}
// Return a single observable item
getEntity(firebaseRef: string, key: string): FirebaseObjectObservable<any> {
const itemPath = `${firebaseRef}/${key}`;
return this.db.object(itemPath)
}
// Default error handling for all actions
private handleError(error) {
console.log(error)
}
}
Problem solved - using switchMap
return this.currentUser.switchMap(user => {
return this.db.list(firebaseRef, {
query: {
orderByChild: 'accountId',
equalTo: user.accountId
}
});
})