mirror of
https://github.com/dadosfera/maestro.git
synced 2026-11-12 08:59:09 +00:00
FIX: moved decorators to a separate folder
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
Reference in New Issue
Block a user