FIX: moved decorators to a separate folder

This commit is contained in:
Gabriel Rosa
2023-02-10 14:23:27 -03:00
parent 9fcbeda470
commit fbc0d2eede
25 changed files with 89 additions and 88 deletions
@@ -1,64 +0,0 @@
import { Request } from 'express';
import { applyDecorators, CustomDecorator } from '@nestjs/common';
import { PermissionsObjectPermission } from './permissions.enum';
import { RequestUser } from './user.decorator';
import { ApiSecurity } from '@nestjs/swagger';
export const AUTH_FUNCTION_KEY = '__AUTH_FUNCTION__';
export type AuthenticationFunction = (req: Request, user: any) => boolean;
// implementation copied from SetMetadata, but tweaked to get existing values
// of the metadataKey and accumulate it with the new metadataValue
function SetMultipleMetadata(metadataKey, metadataValue): CustomDecorator {
const decoratorFactory: CustomDecorator = (target, key?, descriptor?) => {
// .start: tweak
// descriptor?.value = function decorator; target = class decorator
const accumulatedVal =
Reflect.getMetadata(metadataKey, descriptor?.value ?? target) ?? [];
accumulatedVal.push(metadataValue);
// .end: tweak
if (descriptor) {
Reflect.defineMetadata(metadataKey, accumulatedVal, descriptor.value);
return descriptor;
}
Reflect.defineMetadata(metadataKey, accumulatedVal, target);
return target;
};
decoratorFactory.KEY = metadataKey;
return decoratorFactory;
}
function createAuthenticatedDecorator(metadataValue: AuthenticationFunction) {
return applyDecorators(
ApiSecurity('access-token'),
SetMultipleMetadata(AUTH_FUNCTION_KEY, metadataValue),
);
}
export function RequireAllPermissions(
...permissions: PermissionsObjectPermission[]
) {
return createAuthenticatedDecorator((req, user: RequestUser) =>
permissions.every(({ seqid }) => user.permissions.includes(seqid)),
);
}
export function RequireSomePermission(
...permissions: PermissionsObjectPermission[]
) {
return createAuthenticatedDecorator((req, user: RequestUser) =>
permissions.some(({ seqid }) => user.permissions.includes(seqid)),
);
}
export function AuthenticateCondition(func: AuthenticationFunction) {
return createAuthenticatedDecorator(func);
}
export function Authenticated() {
return createAuthenticatedDecorator(() => true);
}
@@ -11,7 +11,7 @@ import {
Authenticated,
RequireAllPermissions,
RequireSomePermission,
} from './authentication.decorator';
} from '../decorators/authentication.decorator';
import { AuthenticationGuard } from './authentication.guard';
import { PERMISSIONS_GROUPS } from './permissions.enum';
import { AuthClientService } from '../modules/auth/auth.service';
+2 -2
View File
@@ -13,8 +13,8 @@ import { AuthClientService } from '../modules/auth/auth.service';
import {
AuthenticationFunction,
AUTH_FUNCTION_KEY,
} from './authentication.decorator';
import { RequestUser } from './user.decorator';
} from '../decorators/authentication.decorator';
import { RequestUser } from '../decorators/user.decorator';
import ErrorBuilder from '../utils/ErrorBuilder';
import ErrorCodes from '../utils/errorCodes';
-182
View File
@@ -1,182 +0,0 @@
import request from 'supertest';
import { Test } from '@nestjs/testing';
import { HttpStatus, INestApplication } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import jwt from 'jsonwebtoken';
import { Controller, Get } from '@nestjs/common';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { AuthenticationGuard } from './authentication.guard';
import { AuthClientService } from '../modules/auth/auth.service';
import ErrorCodes from '../utils/errorCodes';
import { User } from './user.decorator';
import { PERMISSIONS_GROUPS } from './permissions.enum';
const logger = {
info: (...args) => args,
error: (...args) => args,
};
@Controller('user')
class UserController {
@Get()
public getUser(@User() user) {
return { user };
}
@Get('options')
public getUserWithOptions(@User({}) user) {
return { user };
}
@Get('not-required')
public getUserNotRequired(@User({ required: false }) user) {
return { user };
}
@Get('required')
public getUserRequired(@User({ required: true }) user) {
return { user };
}
}
describe('user.decorator', () => {
let app: INestApplication;
const jwt_secret = {
kid: 'token-testing-shared-key',
pem: '$tr0ng-SH4Red-secr3t!!!~gl0ba1~]',
};
const fakeUserPayload = {
user_id: 'd50d33c7-6c2b-463c-861f-e21667e7c125',
username: 'super.admin',
permissions: [PERMISSIONS_GROUPS.ANALYZE.permissions.METABASE].map(
({ seqid }) => seqid,
),
customer_id: '9d18e8ae-24b9-41a3-9e8f-a25ce57555b11',
customer_name: 'dadosfera',
customer_tier: 'BASIC',
access_token: '',
};
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
{
provide: AuthClientService,
useValue: {
getPublicKeys: async () => ({
keys: [jwt_secret],
}),
},
},
{
provide: DadosferaLogger,
useValue: { logger },
},
{
provide: APP_GUARD,
useClass: AuthenticationGuard,
},
],
controllers: [UserController],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
function AssertNoAuth(res: request.Response) {
expect(res.statusCode).toBe(HttpStatus.OK);
expect(res.body).toStrictEqual({});
}
function AssertWithAuth(res: request.Response) {
expect(res.statusCode).toBe(HttpStatus.OK);
expect(res.body).toStrictEqual({ user: fakeUserPayload });
}
function AssertRequiredNoAuth(res: request.Response) {
expect(res.statusCode).toBe(HttpStatus.UNAUTHORIZED);
expect(res.body).toStrictEqual({
code: ErrorCodes.AUTH.UNAUTHORIZED,
error: 'Não autenticado',
message: 'É necessário estar logado para realizar essa operação',
statusCode: HttpStatus.UNAUTHORIZED,
});
}
function UserTest(accessToken: string) {
describe(`with${accessToken ? '' : 'out'} token`, () => {
it('should GET /user', async () => {
const res = await request(app.getHttpServer())
.get('/user')
.set({ Authorization: accessToken });
if (accessToken) {
AssertWithAuth(res);
} else {
AssertNoAuth(res);
}
});
it('should GET /user/options', async () => {
const res = await request(app.getHttpServer())
.get('/user/options')
.set({ Authorization: accessToken });
if (accessToken) {
AssertWithAuth(res);
} else {
AssertNoAuth(res);
}
});
it('should GET /user/not-required', async () => {
const res = await request(app.getHttpServer())
.get('/user/not-required')
.set({ Authorization: accessToken });
if (accessToken) {
AssertWithAuth(res);
} else {
AssertNoAuth(res);
}
});
it('should GET /user/required if logged', async () => {
const res = await request(app.getHttpServer())
.get('/user/required')
.set({ Authorization: accessToken });
if (accessToken) {
AssertWithAuth(res);
} else {
AssertRequiredNoAuth(res);
}
});
});
}
function CreateToken(overrides?) {
const tokenPayload = {
...fakeUserPayload,
token_use: 'access',
...overrides?.user,
};
return jwt.sign(tokenPayload, jwt_secret.pem, {
keyid: jwt_secret.kid,
...overrides?.jwt,
});
}
UserTest(null);
const token = CreateToken();
fakeUserPayload.access_token = token;
UserTest(token);
});
-24
View File
@@ -1,24 +0,0 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import ErrorBuilder from '../utils/ErrorBuilder';
import ErrorCodes from '../utils/errorCodes';
export interface RequestUser {
user_id: string;
username: string;
permissions: number[];
customer_id: string;
customer_name: string;
customer_tier: string;
access_token: string;
}
export const User: (options?: { required?: boolean }) => ParameterDecorator =
createParamDecorator((options: any, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
if (!request.user && options?.required) {
throw new ErrorBuilder(ErrorCodes.AUTH.UNAUTHORIZED);
}
return request.user;
});