From 575bcf244a465f72b07190b86aef236371460c89 Mon Sep 17 00:00:00 2001 From: Jay McDoniel <jmcdo29@gmail.com> Date: Wed, 7 Feb 2024 13:18:55 -0800 Subject: [PATCH 1/2] feat: allow for and guard guards to be ran sequentially --- .vscode/extensions.json | 3 +-- packages/or-guard/README.md | 11 +++++++---- packages/or-guard/src/lib/and.guard.ts | 17 +++++++++-------- packages/or-guard/test/app.controller.ts | 16 +++++++++++++++- packages/or-guard/test/app.module.ts | 4 ++++ packages/or-guard/test/or.guard.spec.ts | 22 ++++++++++++++++++++-- packages/or-guard/test/read-user.guard.ts | 12 ++++++++++++ packages/or-guard/test/set-user.guard.ts | 12 ++++++++++++ 8 files changed, 80 insertions(+), 17 deletions(-) create mode 100644 packages/or-guard/test/read-user.guard.ts create mode 100644 packages/or-guard/test/set-user.guard.ts diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 6a302fe..462e29b 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,7 +2,6 @@ "recommendations": [ "nrwl.angular-console", "esbenp.prettier-vscode", - "dbaeumer.vscode-eslint", - "firsttris.vscode-jest-runner" + "dbaeumer.vscode-eslint" ] } diff --git a/packages/or-guard/README.md b/packages/or-guard/README.md index f4376c9..60b5139 100644 --- a/packages/or-guard/README.md +++ b/packages/or-guard/README.md @@ -90,17 +90,20 @@ And this library will set up the handling of the logic for under the hood. ```ts -AndGuard(guards: Array<Type<CanActivate> | InjectionToken>, orGuardOptions?: OrGuardOptions): CanActivate +AndGuard(guards: Array<Type<CanActivate> | InjectionToken>, andGuardOptions?: AndGuardOptions): CanActivate ``` - `guards`: an array of guards or injection tokens for the `AndGuard` to resolve and test -- `orGuardOptions`: an optional object with properties to modify how the - `OrGuard` functions +- `andGuardOptions`: an optional object with properties to modify how the + `AndGuard` functions ```ts -interface OrGuardOptions { +interface AndGuardOptions { + // immediately stop all other guards and throw an error throwOnFirstError?: boolean; + // run the guards in order they are declared in the array rather than in parallel + sequential?: boolean; } ``` diff --git a/packages/or-guard/src/lib/and.guard.ts b/packages/or-guard/src/lib/and.guard.ts index 23aa477..c1cc699 100644 --- a/packages/or-guard/src/lib/and.guard.ts +++ b/packages/or-guard/src/lib/and.guard.ts @@ -15,27 +15,28 @@ import { OperatorFunction, throwError, } from 'rxjs'; -import { catchError, last, mergeMap, every } from 'rxjs/operators'; +import { catchError, last, mergeMap, every, concatMap } from 'rxjs/operators'; -interface OrGuardOptions { +interface AndGuardOptions { throwOnFirstError?: boolean; + sequential?: boolean; } export function AndGuard( guards: Array<Type<CanActivate> | InjectionToken>, - orGuardOptions?: OrGuardOptions + orGuardOptions?: AndGuardOptions ) { class AndMixinGuard implements CanActivate { private guards: CanActivate[] = []; constructor(@Inject(ModuleRef) private readonly modRef: ModuleRef) {} canActivate(context: ExecutionContext): Observable<boolean> { this.guards = guards.map((guard) => this.modRef.get(guard)); - const canActivateReturns: Array<Observable<boolean>> = this.guards.map( - (guard) => this.deferGuard(guard, context) - ); + const canActivateReturns: Array<() => Observable<boolean>> = + this.guards.map((guard) => () => this.deferGuard(guard, context)); + const mapOperator = orGuardOptions?.sequential ? concatMap : mergeMap; return from(canActivateReturns).pipe( - mergeMap((obs) => { - return obs.pipe(this.handleError()); + mapOperator((obs) => { + return obs().pipe(this.handleError()); }), every((val) => val === true), last() diff --git a/packages/or-guard/test/app.controller.ts b/packages/or-guard/test/app.controller.ts index 0acfe5e..87fe4dd 100644 --- a/packages/or-guard/test/app.controller.ts +++ b/packages/or-guard/test/app.controller.ts @@ -1,8 +1,10 @@ import { Controller, Get, UseGuards } from '@nestjs/common'; -import { OrGuard } from '../src'; +import { AndGuard, OrGuard } from '../src'; import { ObsGuard } from './obs.guard'; import { PromGuard } from './prom.guard'; +import { ReadUserGuard } from './read-user.guard'; +import { SetUserGuard } from './set-user.guard'; import { SyncGuard } from './sync.guard'; import { ThrowGuard } from './throw.guard'; @@ -32,4 +34,16 @@ export class AppController { getLogicalAnd() { return this.message; } + + @UseGuards(AndGuard([SetUserGuard, ReadUserGuard])) + @Get('set-user-fail') + getSetUserFail() { + return this.message; + } + + @UseGuards(AndGuard([SetUserGuard, ReadUserGuard], { sequential: true })) + @Get('set-user-pass') + getSetUserPass() { + return this.message; + } } diff --git a/packages/or-guard/test/app.module.ts b/packages/or-guard/test/app.module.ts index 861ee34..2a91b5d 100644 --- a/packages/or-guard/test/app.module.ts +++ b/packages/or-guard/test/app.module.ts @@ -6,6 +6,8 @@ import { ObsGuard } from './obs.guard'; import { PromGuard } from './prom.guard'; import { SyncGuard } from './sync.guard'; import { ThrowGuard } from './throw.guard'; +import { SetUserGuard } from './set-user.guard'; +import { ReadUserGuard } from './read-user.guard'; @Module({ controllers: [AppController], @@ -14,6 +16,8 @@ import { ThrowGuard } from './throw.guard'; SyncGuard, PromGuard, ThrowGuard, + SetUserGuard, + ReadUserGuard, { provide: 'SyncAndProm', useClass: AndGuard([SyncGuard, PromGuard]), diff --git a/packages/or-guard/test/or.guard.spec.ts b/packages/or-guard/test/or.guard.spec.ts index 69ee16a..50a0666 100644 --- a/packages/or-guard/test/or.guard.spec.ts +++ b/packages/or-guard/test/or.guard.spec.ts @@ -8,7 +8,7 @@ import { ObsGuard } from './obs.guard'; import { PromGuard } from './prom.guard'; import { SyncGuard } from './sync.guard'; -describe('Or Guard Integration Test', () => { +describe('OrGuard and AndGuard Integration Test', () => { let moduleConfig: TestingModuleBuilder; beforeEach(() => { @@ -55,7 +55,7 @@ describe('Or Guard Integration Test', () => { await app.close(); }); /** - * OrGuard([SyncGuard, PromGuard, ObsGuard]) + * OrGuard([AndGuard([SyncGuard, PromGuard]), ObsGuard]) * * | Sync | Prom | Obs | Final | * | - | - | - | - | @@ -185,4 +185,22 @@ describe('Or Guard Integration Test', () => { }); } ); + + describe('AndGuard with options', () => { + let app: INestApplication; + beforeAll(async () => { + const testMod = await moduleConfig.compile(); + app = testMod.createNestApplication(); + await app.init(); + }); + afterAll(async () => { + await app.close(); + }); + it('should throw an error if not ran sequentially', async () => { + return supertest(app.getHttpServer()).get('/set-user-fail').expect(403); + }); + it('should not throw an error if ran sequentially', async () => { + return supertest(app.getHttpServer()).get('/set-user-pass').expect(200); + }); + }); }); diff --git a/packages/or-guard/test/read-user.guard.ts b/packages/or-guard/test/read-user.guard.ts new file mode 100644 index 0000000..5a13ddf --- /dev/null +++ b/packages/or-guard/test/read-user.guard.ts @@ -0,0 +1,12 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { Observable } from 'rxjs'; + +@Injectable() +export class ReadUserGuard implements CanActivate { + canActivate( + context: ExecutionContext + ): boolean | Promise<boolean> | Observable<boolean> { + const req = context.switchToHttp().getRequest(); + return !!req.user; + } +} diff --git a/packages/or-guard/test/set-user.guard.ts b/packages/or-guard/test/set-user.guard.ts new file mode 100644 index 0000000..c9b7425 --- /dev/null +++ b/packages/or-guard/test/set-user.guard.ts @@ -0,0 +1,12 @@ +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; +import { setTimeout } from 'timers/promises'; + +@Injectable() +export class SetUserGuard implements CanActivate { + async canActivate(context: ExecutionContext): Promise<boolean> { + await setTimeout(500); + const req = context.switchToHttp().getRequest(); + req.user = 'set'; + return true; + } +} From 3a767b5ce9d22194b3de3d745f56ef7426153173 Mon Sep 17 00:00:00 2001 From: Jay McDoniel <jmcdo29@gmail.com> Date: Wed, 7 Feb 2024 13:19:49 -0800 Subject: [PATCH 2/2] chore: changeset for @nest-lab/or-guard@2.4.0 --- .changeset/late-nails-brush.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/late-nails-brush.md diff --git a/.changeset/late-nails-brush.md b/.changeset/late-nails-brush.md new file mode 100644 index 0000000..664d4d8 --- /dev/null +++ b/.changeset/late-nails-brush.md @@ -0,0 +1,5 @@ +--- +'@nest-lab/or-guard': minor +--- + +Allow for AndGuard guards to be ran in sequential order