Skip to content
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

Closed
wants to merge 2 commits into from

Conversation

XVINILX
Copy link

@XVINILX XVINILX commented Aug 20, 2024

Addition of new decorators, using RestFul Api

  1. api routes and documentation factory;
  2. current user; from RestFul Api;

Comment on lines 16 to 22
HODLER
RECYCLER
WASTE_GENERATOR
ADMIN
}

enum ResidueType {
Copy link
Contributor

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 ?

Comment on lines 1 to 14
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;
},
);
Copy link
Contributor

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-

Comment on lines 1 to 79
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);
}
}
}
Copy link
Contributor

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

Copy link
Contributor

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;

Copy link
Contributor

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'],
      }),
    );

Copy link
Contributor

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.

Copy link
Author

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

Copy link
Author

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

Copy link
Contributor

@yurimutti yurimutti left a 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')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove?

Copy link

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;

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')
Copy link

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.

Comment on lines 33 to 37
options.authRoute &&
options.roles &&
options.roles.length &&
Roles(options.roles) &&
UseGuards(RestAuthorizationGuard),
Copy link

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.

Copy link
Contributor

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

Copy link
Contributor

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' }),
  );
}

Copy link
Author

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)

package.json Show resolved Hide resolved
…st authorization guards, migration of admin type and addition of exception that will throw when user doesn't have permission to acces the route
@yurimutti
Copy link
Contributor

We will moving Auth discuss to foward.

@yurimutti yurimutti closed this Aug 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants