-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: addition of apiDecoratorFactory to manage the creation of docum… #15
Conversation
…entation and guards for restful api
HODLER | ||
RECYCLER | ||
WASTE_GENERATOR | ||
ADMIN | ||
} | ||
|
||
enum ResidueType { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will need run a migration here ?
src/auth/rest-current-user.ts
Outdated
import { createParamDecorator, ExecutionContext } from '@nestjs/common'; | ||
|
||
export interface AuthUser { | ||
sub: string; | ||
permissions?: string[]; | ||
} | ||
|
||
export const RestCurrentUser = createParamDecorator( | ||
(data: unknown, context: ExecutionContext): AuthUser => { | ||
const request = context.switchToHttp().getRequest(); | ||
|
||
return request.user; | ||
}, | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very useful this decorator! Maybe we can rename to only current-user.ts and remove prefix rest-
import { | ||
CanActivate, | ||
ExecutionContext, | ||
Injectable, | ||
UnauthorizedException, | ||
} from '@nestjs/common'; | ||
import { ConfigService } from '@nestjs/config'; | ||
import { Reflector } from '@nestjs/core'; | ||
import { Request, Response } from 'express'; // Use Request from 'express' | ||
import { expressjwt } from 'express-jwt'; | ||
import { expressJwtSecret, GetVerificationKey } from 'jwks-rsa'; | ||
import { PERMISSION_SCOPES, Role, ROLES_KEY } from 'src/util/constants'; | ||
import { promisify } from 'util'; | ||
|
||
interface AuthenticatedRequest extends Request { | ||
auth?: { | ||
permissions?: string[]; | ||
[key: string]: any; | ||
}; | ||
} | ||
|
||
@Injectable() | ||
export class RestAuthorizationGuard implements CanActivate { | ||
private AUTH0_AUDIENCE: string; | ||
private AUTH0_DOMAIN: string; | ||
|
||
constructor( | ||
private configService: ConfigService, | ||
private reflector: Reflector, | ||
) { | ||
this.AUTH0_AUDIENCE = this.configService.get('AUTH0_AUDIENCE'); | ||
this.AUTH0_DOMAIN = this.configService.get('AUTH0_DOMAIN'); | ||
} | ||
|
||
async canActivate(context: ExecutionContext): Promise<boolean> { | ||
const req = context.switchToHttp().getRequest<AuthenticatedRequest>(); | ||
const res = context.switchToHttp().getResponse<Response>(); | ||
|
||
const checkJWT = promisify( | ||
expressjwt({ | ||
secret: expressJwtSecret({ | ||
cache: true, | ||
rateLimit: true, | ||
jwksRequestsPerMinute: 5, | ||
jwksUri: `${this.AUTH0_DOMAIN}.well-known/jwks.json`, | ||
}) as GetVerificationKey, | ||
audience: this.AUTH0_AUDIENCE, | ||
issuer: this.AUTH0_DOMAIN, | ||
algorithms: ['RS256'], | ||
}), | ||
); | ||
|
||
try { | ||
await checkJWT(req, res); | ||
|
||
const requiredRoles = this.reflector.getAllAndOverride<Role[]>( | ||
ROLES_KEY, | ||
[context.getHandler(), context.getClass()], | ||
); | ||
|
||
const scopeRules = req?.auth?.permissions as string[]; | ||
if (requiredRoles) { | ||
if (!scopeRules?.length) return false; | ||
|
||
const [requiredRole] = requiredRoles; | ||
|
||
const hasAccess = scopeRules.every((scopeType) => | ||
PERMISSION_SCOPES[requiredRole].includes(scopeType), | ||
); | ||
|
||
return hasAccess; | ||
} | ||
|
||
return true; | ||
} catch (err) { | ||
throw new UnauthorizedException(err); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! could you move to outside a graqhql folder ? and remove rest- prefix
For now please follow this estructure:
├── shared/
│ ├── decorators/
│ │ ├── current-user.decorator.ts
│ │ └── roles.decorator.ts
│ └── guards/
│ ├── auth.guard.ts
│ ├── roles.guard.ts
│ └── authorization.guard.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could type this?
[key: string]: any;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interessant avoid use AS on typescript code:
const SECRET_OPTIONS: GetVerificationKey = {
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `${this.AUTH0_DOMAIN}.well-known/jwks.json`,
}
const checkJWT = promisify(
expressjwt({
secret: expressJwtSecret(SECRET_OPTIONS),
audience: this.AUTH0_AUDIENCE,
issuer: this.AUTH0_DOMAIN,
algorithms: ['RS256'],
}),
);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const scopeRules = req?.auth?.permissions as string[];
We need type auth.permessions correctly and avoid create a fragile type using as to override.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, i will look how to do this properly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
auth0/node-jwks-rsa#412
update of jwks-rsa and express-jwt still doesn't worked the expressJwtSecret function;
As they recomendate: auth0/express-jwt#288, i will keep the "as GetVerificationKey"
Also, removed the [key: string]: any; it wasn't necessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Create shared folder and remove rest prefix
- Remove code from graphql folder
@@ -19,7 +19,7 @@ import { | |||
import { FormsService } from './forms.service'; | |||
|
|||
@ApiTags('forms') | |||
@ApiBearerAuth('access-token') | |||
// @ApiBearerAuth('access-token') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This decorator is now being applied individually to each route, using the custom decorator created by Vinicius. When authRoute is true, it applies @ApiBearerAuth('access-token') to the route in question. In this case, to maintain the same logic as before, I believe authRoute should remain true for all the form endpoints.
|
||
const scopeRules = req?.auth?.permissions as string[]; | ||
if (requiredRoles) { | ||
if (!scopeRules?.length) return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should it throw an exception?
@@ -19,7 +19,7 @@ import { | |||
import { FormsService } from './forms.service'; | |||
|
|||
@ApiTags('forms') | |||
@ApiBearerAuth('access-token') | |||
// @ApiBearerAuth('access-token') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This decorator is now being applied individually to each route, using the custom decorator created by Vinicius. When authRoute is true, it applies @ApiBearerAuth('access-token') to the route in question. In this case, to maintain the same logic as before, I believe authRoute should remain true for all the form endpoints.
options.authRoute && | ||
options.roles && | ||
options.roles.length && | ||
Roles(options.roles) && | ||
UseGuards(RestAuthorizationGuard), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this code, it’s confusing when you’re adding a new condition (AND) versus when you’re using the Nullish Coalescing Operator. It would be clearer with some parentheses:
(options.authRoute && options.roles && options.roles.length) &&
(Roles(options.roles) && UseGuards(RestAuthorizationGuard))
I’m not sure if this is the logic you intended to implement, but I think this part could be made clearer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could be more clear here.
Please, consider using compositon here:
https://docs.nestjs.com/custom-decorators#decorator-composition
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Decorator composition[#](https://docs.nestjs.com/custom-decorators#decorator-composition)
Nest provides a helper method to compose multiple decorators. For example, suppose you want to combine all decorators related to authentication into a single decorator. This could be done with the following construction:
auth.decorator.tsJS
import { applyDecorators } from '@nestjs/common';
export function Auth(...roles: Role[]) {
return applyDecorators(
SetMetadata('roles', roles),
UseGuards(AuthGuard, RolesGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({ description: 'Unauthorized' }),
);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed the conditions to apply UseGuards and Roles Decorator;
using the return applyDecorators(...decorators);
at the end of ApiAuthOperation to keep conditionally addition of other decorators: (Get, Put, Delete)
…st authorization guards, migration of admin type and addition of exception that will throw when user doesn't have permission to acces the route
We will moving Auth discuss to foward. |
Addition of new decorators, using RestFul Api