-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
With the new`AndGuard`, devs can now set up cases where they want to use an `OrGuard` to handle an `(A && B) || C` kind of condition, or any other complex logical approach they'd like. Devs are also now able to correctly pass injection tokens to the `AndGuard` and `OrGuard` and use custom providers for their guards that are registered as providers
- Loading branch information
Showing
12 changed files
with
2,141 additions
and
1,940 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
--- | ||
'@nest-lab/or-guard': minor | ||
--- | ||
|
||
Add a new AndGuard to handle complex logical cases | ||
|
||
With the new `AndGuard` it is now possible to create logical cases like | ||
`(A && B) || C` using the `OrGuard` and a composite guard approach. Check out | ||
the docs for more info |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
{ | ||
"singleQuote": true | ||
} | ||
"printWidth": 80, | ||
"singleQuote": true, | ||
"proseWrap": "always" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
# or-guard | ||
# @nest-lab/or-guard | ||
|
||
This library contains a single guard that allows for checking multiple guards and if **any one of them passes** the entire request will be considered authenticated. | ||
This library contains a two guards that allows for checking multiple guards and | ||
creating complex logical statements based on the results of those guards for if | ||
the request should be completed or not. | ||
|
||
## Installation | ||
|
||
|
@@ -12,39 +14,104 @@ yarn add @nest-lab/or-guard | |
pnpm i @nest-lab/or-guard | ||
``` | ||
|
||
## Usage | ||
## OrGuard | ||
|
||
To use the `OrGuard`, there are a couple of things that need to happen, due to how the guard resolves the guards it's going to be using. | ||
To use the `OrGuard`, there are a couple of things that need to happen, due to | ||
how the guard resolves the guards it's going to be using. | ||
|
||
First, make sure to add all the guards the `OrGuard` will be using to the current module's `providers` array. Enhancer in Nest are just specialized providers after all. This will allow the `OrGuard` to use a `ModuleRef` to get these guards. | ||
First, make sure to add all the guards the `OrGuard` will be using to the | ||
current module's `providers` array. Enhancer in Nest are just specialized | ||
providers after all. This will allow the `OrGuard` to use a `ModuleRef` to get | ||
these guards. The guards can either be registered directly as providers, or set | ||
up as custom providers and you may use an injection token reference. Make sure, | ||
that if you use a custom provider, the _instance_ of the guard is what is tied | ||
to the token, not the reference to the class. | ||
|
||
Second, make sure **none** of these guards are `REQUEST` or `TRANSIENT` scoped, as this **will** make the `OrGuard` throw an error. | ||
Second, make sure **none** of these guards are `REQUEST` or `TRANSIENT` scoped, | ||
as this **will** make the `OrGuard` throw an error. | ||
|
||
Third, make use of it! The `OrGuard` takes in an array of guard to use for the first parameter, and an optional second parameter for options as described below. | ||
Third, make use of it! The `OrGuard` takes in an array of guard to use for the | ||
first parameter, and an optional second parameter for options as described | ||
below. | ||
|
||
> **important**: for Nest v7, use `@nest-lab/[email protected]`, for Nest v8, please use v2 | ||
> **important**: for Nest v7, use `@nest-lab/[email protected]`, for Nest v8, | ||
> please use v2 | ||
```ts | ||
OrGuard(guards: CanActivate[], orGuardOptions?: OrGuardOptions): CanActivate | ||
OrGuard(guards: Array<Type<CanActivate> | InjectionToken>, orGuardOptions?: OrGuardOptions): CanActivate | ||
``` | ||
|
||
- `guards`: an array of guards for the `OrGuard` to resolve and test | ||
- `orGuardOptions`: an optional object with properties to modify how the `OrGuard` functions | ||
- `guards`: an array of guards or injection tokens for the `OrGuard` to resolve | ||
and test | ||
- `orGuardOptions`: an optional object with properties to modify how the | ||
`OrGuard` functions | ||
|
||
```ts | ||
interface OrGuardOptions { | ||
throwOnFirstError?: boolean; | ||
} | ||
``` | ||
|
||
- `throwOnFirstError`: a boolean to tell the `OrGuard` whether to throw if an error is encountered or if the error should be considered a `return false`. The default value is `false`. If this is set to `true`, the **first** error encountered will lead to the same error being thrown. | ||
- `throwOnFirstError`: a boolean to tell the `OrGuard` whether to throw if an | ||
error is encountered or if the error should be considered a `return false`. | ||
The default value is `false`. If this is set to `true`, the **first** error | ||
encountered will lead to the same error being thrown. | ||
|
||
> **Note**: guards are ran in a non-deterministic order. All guard returns are | ||
> transformed into Observables and ran concurrently to ensure the fastest | ||
> response time possible. | ||
## AndGuard | ||
|
||
Just like the `OrGuard`, you can create a logic grouping of situations that | ||
should pass. This is Nest's default when there are multiple guards passed to the | ||
`@UseGuards()` decorator; however, there are situations where it would be useful | ||
to use an `AndGuard` inside of an `OrGuard` to be able to create logic like | ||
`(A && B) || C`. With using an `AndGuard` inside of an `OrGuard`, you'll most | ||
likely want to create a dedicated [custom provider][customprov] for the guard | ||
like so: | ||
|
||
```typescript | ||
{ | ||
provide: AndGuardToken, | ||
useClass: AndGuard([GuardA, GuardB]) | ||
} | ||
``` | ||
|
||
With this added to the module's providers where you plan to use the related | ||
`OrGuard` you can use the following in a controller or resolve: | ||
|
||
```typescript | ||
@UseGuards(OrGuard([AndGuardToken, GuardC])) | ||
``` | ||
|
||
And this library will set up the handling of the logic for | ||
`(GuardA && GuardB) || GuardC` without having to worry about the complexities | ||
under the hood. | ||
|
||
```ts | ||
AndGuard(guards: Array<Type<CanActivate> | InjectionToken>, orGuardOptions?: OrGuardOptions): 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 | ||
|
||
> **Note**: guards are ran in a non-deterministic order. All guard returns are transformed into Observables and ran concurrently to ensure the fastest response time possible. | ||
```ts | ||
interface OrGuardOptions { | ||
throwOnFirstError?: boolean; | ||
} | ||
``` | ||
|
||
## Local Development | ||
|
||
Feel free to pull down the repository and work locally. If any changes are made, please make sure tests are added to ensure the functionality works and nothing is broken. | ||
Feel free to pull down the repository and work locally. If any changes are made, | ||
please make sure tests are added to ensure the functionality works and nothing | ||
is broken. | ||
|
||
### Running unit tests | ||
|
||
Run `nx test or-guard` to execute the unit tests via [Jest](https://jestjs.io). | ||
|
||
[customprov]: https://docs.nestjs.com/fundamentals/custom-providers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './lib/and.guard'; | ||
export * from './lib/or.guard'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { | ||
CanActivate, | ||
ExecutionContext, | ||
Inject, | ||
InjectionToken, | ||
mixin, | ||
Type, | ||
} from '@nestjs/common'; | ||
import { ModuleRef } from '@nestjs/core'; | ||
import { | ||
defer, | ||
from, | ||
Observable, | ||
of, | ||
OperatorFunction, | ||
throwError, | ||
} from 'rxjs'; | ||
import { catchError, last, mergeMap, every } from 'rxjs/operators'; | ||
|
||
interface OrGuardOptions { | ||
throwOnFirstError?: boolean; | ||
} | ||
|
||
export function AndGuard( | ||
guards: Array<Type<CanActivate> | InjectionToken>, | ||
orGuardOptions?: OrGuardOptions | ||
) { | ||
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) | ||
); | ||
return from(canActivateReturns).pipe( | ||
mergeMap((obs) => { | ||
return obs.pipe(this.handleError()); | ||
}), | ||
every((val) => val === true), | ||
last() | ||
); | ||
} | ||
|
||
private deferGuard( | ||
guard: CanActivate, | ||
context: ExecutionContext | ||
): Observable<boolean> { | ||
return defer(() => { | ||
const guardVal = guard.canActivate(context); | ||
if (this.guardIsPromise(guardVal)) { | ||
return from(guardVal); | ||
} | ||
if (this.guardIsObservable(guardVal)) { | ||
return guardVal; | ||
} | ||
return of(guardVal); | ||
}); | ||
} | ||
|
||
private handleError(): OperatorFunction<boolean, boolean> { | ||
return catchError((err) => { | ||
if (orGuardOptions?.throwOnFirstError) { | ||
return throwError(() => err); | ||
} | ||
return of(false); | ||
}); | ||
} | ||
|
||
private guardIsPromise( | ||
guard: boolean | Promise<boolean> | Observable<boolean> | ||
): guard is Promise<boolean> { | ||
return !!(guard as Promise<boolean>).then; | ||
} | ||
|
||
private guardIsObservable( | ||
guard: boolean | Observable<boolean> | ||
): guard is Observable<boolean> { | ||
return !!(guard as Observable<boolean>).pipe; | ||
} | ||
} | ||
|
||
const Guard = mixin(AndMixinGuard); | ||
return Guard as Type<CanActivate>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.