Compare commits

...
19 Commits
Author SHA1 Message Date
Gabriel Amorim 0970c748e0 Merge pull request #216 from dadosfera/feat/generate-token
Feat: generate token
2023-05-17 10:03:00 -03:00
Gabriel Amorim aabd741cad FEAT: new Customer Get Token Route 2023-05-16 09:05:05 -03:00
Gabriel Amorim 950c6414b6 FIX: update to new nestjs version 2023-05-12 08:28:33 -03:00
Gabriel Amorim d1149a4d52 Merge pull request #215 from dadosfera/alpha
FIX: permissions.enum.ts
2023-05-08 16:02:33 -03:00
Gabriel Amorim 4c6f4bfd84 FIX: renamed reference to intelligence permission 2023-05-08 15:56:52 -03:00
Gabriel Amorim 9caf3dc67f FIX: renamed reference to intelligence permission 2023-05-08 15:48:46 -03:00
Beatriz Antunes 635c4da48c Update permissions.enum.ts 2023-05-08 15:44:05 -03:00
Gabriel Amorim 44bb205483 Merge pull request #214 from dadosfera/fix/get-links-permission
FIX: remove permission check to get customer links
2023-05-05 13:52:38 -03:00
Gabriel Amorim edb4622684 FIX: remove permission to get customer links 2023-05-05 12:13:55 -03:00
Victor Radael abb46d78da Merge pull request #212 from dadosfera/feature/customer-links
SOLENG-1481 - Customer links endpoints
2023-04-17 17:01:41 -03:00
Victor Radael 238f56cb76 Merge branch 'SOLENG-1736' into feature/customer-links 2023-04-14 14:44:57 -03:00
Victor Radael e721d6f5f5 Merge branch 'main' into feature/customer-links 2023-04-14 13:30:57 -03:00
Victor Radael 44df13e820 UPDATE: Att protospack-v2 version 2023-04-14 13:13:47 -03:00
Victor Radael 1fd0f79dd5 Att hubspot connection 2023-04-13 15:41:44 -03:00
Victor Radael 1b92371737 Att hubspot connection 2023-04-13 14:55:33 -03:00
Colombo bf5b3484a1 FEAT: add controller method interfaces 2023-04-11 20:39:47 -03:00
Colombo eb1e248d4b FEAT: add customer (links) endpoint 2023-04-11 17:04:40 -03:00
Colombo 0827414051 FEAT: add new dto property 2023-04-10 19:36:36 -03:00
Colombo 081986607b CHORE: change protospack package to beta 2023-04-06 17:07:23 -03:00
19 changed files with 2176 additions and 2213 deletions
+1 -1
View File
@@ -1 +1 @@
18.3.0
18.10.0
+3 -3
View File
@@ -1,5 +1,5 @@
# install all dependencies
FROM node:18.3.0-alpine3.15 as packages
FROM node:18.10-alpine as packages
WORKDIR /app
COPY package.json package-lock.json ./
RUN apk add --no-cache aws-cli \
@@ -7,12 +7,12 @@ RUN apk add --no-cache aws-cli \
&& npm install
# unit test specific build
FROM node:18.3.0-alpine3.15 as test
FROM node:18.10-alpine as test
WORKDIR /app
COPY --from=packages /app/node_modules ./node_modules
COPY . ./
FROM node:18.3.0-alpine3.15
FROM node:18.10-alpine
WORKDIR /app
COPY . /app/
COPY --from=packages /app/node_modules ./node_modules
+1 -1
View File
@@ -12,7 +12,7 @@ These instructions will allow you to get a working copy of the project on your l
### 📋 Requirements
- [NodeJS v18.3.0 LTS / NPM v8.11](https://nodejs.org/pt-br/download/) (you can opt to use [NVM](https://github.com/nvm-sh/nvm) to easily manage node versions)
- [NodeJS v18.10.0 LTS / NPM v8.11](https://nodejs.org/pt-br/download/) (you can opt to use [NVM](https://github.com/nvm-sh/nvm) to easily manage node versions)
- Request access to AWS Console dev account for **all services** (avoid gradually asking for each needed service. it will slow down your development cycle)
- Create your Access Key on the "Security credentials" menu
- Set the Access Key on your local development machine
+9 -2
View File
@@ -1752,7 +1752,7 @@
}
},
"info": {
"title": "Maestro - feat/trigger-catalog-task",
"title": "Maestro - feat/generate-token",
"description": "This is the Maestro API",
"version": "1.0.0",
"contact": {}
@@ -1812,6 +1812,12 @@
},
"scheduleLimit": {
"type": "string"
},
"links": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
@@ -1819,7 +1825,8 @@
"id",
"name",
"tier",
"scheduleLimit"
"scheduleLimit",
"links"
]
},
"AuthUser": {
+138 -2
View File
@@ -4959,6 +4959,107 @@
]
}
},
"/customers/{id}/links": {
"get": {
"operationId": "CustomersController_getCustomerLinks",
"parameters": [],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CustomerLinksResponse"
}
}
}
}
},
"tags": [
"Customers"
],
"security": [
{
"access-token": []
}
]
},
"put": {
"operationId": "CustomersController_setCustomerLinks",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CustomerDto"
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"Customers"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
}
},
"/customers/token": {
"get": {
"operationId": "CustomersController_getCustomerToken",
"parameters": [
{
"name": "exp",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
}
},
"tags": [
"Customers"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
}
},
"/health": {
"get": {
"operationId": "HealthController_check",
@@ -4975,7 +5076,7 @@
}
},
"info": {
"title": "Maestro - feat/trigger-catalog-task",
"title": "Maestro - feat/generate-token",
"description": "This is the Maestro API",
"version": "1.0.0",
"contact": {}
@@ -5030,6 +5131,12 @@
},
"scheduleLimit": {
"type": "string"
},
"links": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
@@ -5037,7 +5144,8 @@
"id",
"name",
"tier",
"scheduleLimit"
"scheduleLimit",
"links"
]
},
"AuthUser": {
@@ -7702,6 +7810,34 @@
"content",
"tags"
]
},
"CustomerLinksResponse": {
"type": "object",
"properties": {
"links": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"links"
]
},
"CustomerDto": {
"type": "object",
"properties": {
"links": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"links"
]
}
}
}
+1740 -2179
View File
File diff suppressed because it is too large Load Diff
+15 -15
View File
@@ -6,7 +6,7 @@
"private": true,
"license": "UNLICENSED",
"engines": {
"node": "18.3.0"
"node": "18.10.0"
},
"scripts": {
"preinstall": "aws codeartifact login --tool npm --namespace @dadosfera --repository dadosfera-npm --domain dadosfera --domain-owner 611330257153 --region us-east-1",
@@ -29,21 +29,23 @@
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.112.0",
"@dadosfera/dadosfera-logs": "^1.0.0-beta.4",
"@dadosfera/protospack-v2": "3.30.0",
"@dadosfera/protospack-v2": "3.31.0",
"@grpc/grpc-js": "^1.6.7",
"@grpc/proto-loader": "^0.6.13",
"@nestjs/common": "^8.4.7",
"@nestjs/config": "^1.2.1",
"@nestjs/core": "^8.4.7",
"@nestjs/mapped-types": "*",
"@nestjs/microservices": "^8.4.7",
"@nestjs/passport": "^8.2.2",
"@nestjs/platform-express": "^8.4.7",
"@nestjs/schedule": "^1.1.0",
"@nestjs/swagger": "^5.2.1",
"@nestjs/cli": "^9.4.2",
"@nestjs/common": "^9.4.0",
"@nestjs/config": "^2.3.1",
"@nestjs/core": "^9.4.0",
"@nestjs/mapped-types": "^1.2.2",
"@nestjs/microservices": "^9.4.0",
"@nestjs/passport": "^9.0.3",
"@nestjs/platform-express": "^9.4.0",
"@nestjs/schematics": "^9.1.0",
"@nestjs/swagger": "^6.3.0",
"@nestjs/testing": "^9.4.0",
"axios": "^0.27.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"class-validator": "^0.14.0",
"cron-parser": "^4.4.0",
"dotenv": "^14.3.2",
"elastic-apm-node": "^3.36.0",
@@ -51,6 +53,7 @@
"jsonwebtoken": "^9.0.0",
"jwk-to-pem": "^2.0.5",
"mixpanel": "^0.17.0",
"ms": "^3.0.0-canary.1",
"passport": "^0.6.0",
"passport-facebook": "^3.0.0",
"passport-forcedotcom": "^0.2.0",
@@ -67,9 +70,6 @@
"multer": "1.4.5-lts.1"
},
"devDependencies": {
"@nestjs/cli": "^8.2.8",
"@nestjs/schematics": "^8.0.11",
"@nestjs/testing": "^8.4.7",
"@types/express": "^4.17.13",
"@types/jest": "27.0.2",
"@types/jsonwebtoken": "^8.5.8",
+2
View File
@@ -25,6 +25,7 @@ import { ConfigModule } from '@nestjs/config';
import { PipelinesV2Module } from './modules/pipelinesV2/pipelines.module';
import { ProductboardModule } from './modules/productboard/productboard.module';
import { MixpanelModule } from './modules/mixpanel/mixpanel.module';
import { CustomersModule } from './modules/customers/customers.module';
@Module({
controllers: [],
@@ -56,6 +57,7 @@ import { MixpanelModule } from './modules/mixpanel/mixpanel.module';
OauthModule,
ProductboardModule,
MixpanelModule,
CustomersModule,
//Always leave HealthModule last, so it is on the bottom of swagger
HealthModule,
],
@@ -44,7 +44,7 @@ class NoClassAuthController {
@Get('has-all-permissions')
@RequireAllPermissions(
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
PERMISSIONS_GROUPS.ANALYZE.permissions.METABASE,
PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE,
)
async hasAllPermissions(@Body() body) {
return { body };
@@ -53,7 +53,7 @@ class NoClassAuthController {
@Get('has-some-permission')
@RequireSomePermission(
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
PERMISSIONS_GROUPS.ANALYZE.permissions.METABASE,
PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE,
)
async hasSomePermission(@Body() body) {
return { body };
@@ -62,7 +62,7 @@ class NoClassAuthController {
@Controller('class-auth-condition')
@AuthenticateCondition((req) => req.get('x-on-class') === 'ok')
@RequireSomePermission(PERMISSIONS_GROUPS.ANALYZE.permissions.METABASE)
@RequireSomePermission(PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE)
class ClassAuthConditionController {
@Get('body')
async getBody(@Body() body) {
@@ -444,13 +444,13 @@ describe('authentication.guard', () => {
NoClassAuthTest(tokenZ, ['zendesk']);
ClassAuthConditionTest(tokenZ, ['zendesk']);
const tokenM = CreateToken([PERMISSIONS_GROUPS.ANALYZE.permissions.METABASE]);
const tokenM = CreateToken([PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE]);
NoClassAuthTest(tokenM, ['metabase']);
ClassAuthConditionTest(tokenM, ['metabase']);
const tokenZM = CreateToken([
PERMISSIONS_GROUPS.ZENDESK.permissions.OPEN,
PERMISSIONS_GROUPS.ANALYZE.permissions.METABASE,
PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE,
]);
NoClassAuthTest(tokenZM, ['zendesk', 'metabase']);
ClassAuthConditionTest(tokenZM, ['zendesk', 'metabase']);
+22 -2
View File
@@ -55,6 +55,16 @@ export const PERMISSIONS_GROUPS = {
'es-es': '',
},
},
GENERATE_TOKEN: {
seqid: 46,
claim: 'customer:generate-token',
usage: PermissionUsages.PUBLIC,
name: {
'pt-br': 'Gerar Token de Acesso',
'en-us': 'Generate Acess Token',
'es-es': 'Gerar Token de Acceso',
},
},
},
},
@@ -449,7 +459,7 @@ export const PERMISSIONS_GROUPS = {
},
},
ANALYZE: {
DATAVIZ: {
title: {
'pt-br': 'Analisar | Visualização',
'en-us': 'Analyze | Visualization',
@@ -466,6 +476,15 @@ export const PERMISSIONS_GROUPS = {
'es-es': 'Acceso Metabase',
},
},
},
},
INTELLIGENCE: {
title: {
'pt-br': 'Analisar | Inteligência',
'en-us': 'Analyze | Intelligence',
'es-es': 'Analizar | Inteligencia',
},
permissions: {
INTELLIGENCE: {
seqid: 31,
claim: 'intelligence:open',
@@ -587,7 +606,8 @@ export const DADOSFERA_MODULES: Array<DadosferaModule> = [
name: 'Intelligence Module',
description: 'Orchest Module',
key: 'intelligence',
permissionSeqId: PERMISSIONS_GROUPS.ANALYZE.permissions.INTELLIGENCE.seqid,
permissionSeqId:
PERMISSIONS_GROUPS.INTELLIGENCE.permissions.INTELLIGENCE.seqid,
},
{
name: 'Proccessing Module',
+1 -1
View File
@@ -46,7 +46,7 @@ describe('user.decorator', () => {
const fakeUserPayload = {
user_id: 'd50d33c7-6c2b-463c-861f-e21667e7c125',
username: 'super.admin',
permissions: [PERMISSIONS_GROUPS.ANALYZE.permissions.METABASE].map(
permissions: [PERMISSIONS_GROUPS.DATAVIZ.permissions.METABASE].map(
({ seqid }) => seqid,
),
customer_id: '9d18e8ae-24b9-41a3-9e8f-a25ce57555b11',
+3
View File
@@ -1,3 +1,4 @@
import { Link } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities';
import {
AuthSignInRequest,
AuthSignInResponse,
@@ -74,6 +75,8 @@ export class AuthCustomer {
tier: string;
@ApiProperty()
scheduleLimit: string;
@ApiProperty()
links: Link[];
}
export class AuthSignInReq implements AuthSignInRequest {
@@ -0,0 +1,74 @@
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { IdResponse } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
import {
Body,
Controller,
Get,
Inject,
Param,
Put,
Query,
UseFilters,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { PERMISSIONS_GROUPS } from 'src/authentication/permissions.enum';
import {
Authenticated,
RequireAllPermissions,
} from 'src/decorators/authentication.decorator';
import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator';
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
import { CustomersService } from './customers.service';
import { CustomerDto, CustomerLinksResponse } from './dtos/customers';
import { RequestUser, User } from 'src/decorators/user.decorator';
import type { StringValue } from 'ms';
@ApiInternalOnlyController()
@ApiTags('Customers')
@Controller('customers')
@Authenticated()
@UseFilters(GrpcToHttpExceptionFilter)
export class CustomersController {
logger: DadosferaLogger;
constructor(
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
private customersService: CustomersService,
) {
this.logger = dadosferaLogger.logger;
}
@Get(':id/links')
async getCustomerLinks(@Param('id') id): Promise<CustomerLinksResponse> {
this.logger.info('getCustomerLinks', { id });
const links = await this.customersService.getLinks(id);
return { links };
}
@Put(':id/links')
@RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN)
setCustomerLinks(
@Body() body: CustomerDto,
@Param('id') id,
): Promise<IdResponse> {
const { links } = body;
this.logger.info('setCustomerLinks', { id, links });
return this.customersService.setLinks(id, links);
}
@Get('token')
@RequireAllPermissions(PERMISSIONS_GROUPS.AUTH.permissions.GENERATE_TOKEN)
async getCustomerToken(
@Query('exp') exp: StringValue,
@User() user: RequestUser,
): Promise<string> {
this.logger.info('getCustomerToken', { exp, user });
const data = {
customerId: user.customer_id,
userId: user.user_id,
customerName: user.customer_name,
};
return this.customersService.generateToken(exp, data);
}
}
+16
View File
@@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { ClientsModule } from '@nestjs/microservices';
import { DucClient } from '../duc/client.config';
import { CustomersController } from './customers.controller';
import { CustomersService } from './customers.service';
const client = new DucClient();
@Module({
imports: [ClientsModule.register([client.providerOptions])],
controllers: [CustomersController],
providers: [CustomersService, DadosferaLogger],
exports: [CustomersService],
})
export class CustomersModule {}
+123
View File
@@ -0,0 +1,123 @@
import {
OnModuleInit,
Inject,
Injectable,
HttpException,
HttpStatus,
InternalServerErrorException,
} from '@nestjs/common';
import { firstValueFrom, lastValueFrom } from 'rxjs';
import { Link } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities';
import { DucClient } from '../duc/client.config';
import { ClientGrpc } from '@nestjs/microservices';
import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc';
import { CustomerUpdateRequest } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
import { CustomersProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service';
import ErrorCodes from 'src/utils/errorCodes';
import jwt from 'jsonwebtoken';
import {
GetSecretValueCommand,
SecretsManagerClient,
} from '@aws-sdk/client-secrets-manager';
import getEnv from 'src/utils/getEnv';
import { logger } from 'elastic-apm-node';
// This function will accept any string, which may result in a bug.
@Injectable()
export class CustomersService implements OnModuleInit {
private customerService: CustomersProtoService;
constructor(
@Inject(DucClient.name) private readonly grpcClient: ClientGrpc,
) {}
onModuleInit() {
this.customerService = this.grpcClient.getService<CustomersProtoService>(
ProtoServices.CustomersProtoService,
);
}
async getLinks(customerId: string) {
try {
const result = await lastValueFrom(
this.customerService.CustomerFindOneById({ id: customerId }),
);
return result.customer?.links || [];
} catch (err) {
if (err.details === ErrorCodes.CUSTOMER.NOT_FOUND)
throw new HttpException(err.details, HttpStatus.NOT_FOUND);
throw err;
}
}
async setLinks(customerId: string, links: Link[]) {
if (!customerId || !links) {
throw new HttpException(null, HttpStatus.BAD_REQUEST);
}
try {
return await firstValueFrom(
this.customerService.CustomerUpdate({
id: customerId,
links,
} as CustomerUpdateRequest),
);
} catch (err) {
if (err.details === ErrorCodes.CUSTOMER.NOT_FOUND)
throw new HttpException(err.details, HttpStatus.NOT_FOUND);
else throw err;
}
}
async generateToken(
expiresIn = '30m',
data: { customerId: string; userId: string; customerName: string },
) {
const { customerId, userId: generatedById, customerName } = data;
const secretsManagerClient = new SecretsManagerClient({});
const getSecretComand = new GetSecretValueCommand({
SecretId: `${getEnv()}/${customerName}/jwt-signing-key`,
});
let private_key_pem;
await secretsManagerClient
.send(getSecretComand)
.then((res) => {
const secret = JSON.parse(res.SecretString);
private_key_pem = secret.private_key_pem;
})
.catch((err) => {
logger.error(err.stack);
throw new InternalServerErrorException(
'Error finding keys for customer',
);
});
let jwt_token;
try {
jwt_token = jwt.sign(
{
customerId,
generatedById,
},
private_key_pem,
{
algorithm: 'RS256',
expiresIn,
issuer: 'maestro',
},
);
} catch (err) {
logger.error(err.stack);
throw new InternalServerErrorException('Error generating token');
}
// const verify = jwt.verify(jwt_token, public_key_pem, {
// algorithms: ['RS256'],
// issuer: 'maestro',
// });
// const decoded = jwt.decode(jwt_token, { complete: true });
return jwt_token;
}
}
+12
View File
@@ -0,0 +1,12 @@
import { Link } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/entities';
import { ApiProperty } from '@nestjs/swagger';
export class CustomerDto {
@ApiProperty()
links: Link[];
}
export class CustomerLinksResponse {
@ApiProperty()
links: Link[];
}
-2
View File
@@ -5,7 +5,6 @@ import {
Inject,
Injectable,
} from '@nestjs/common';
import { Timeout } from '@nestjs/schedule';
import CronParser, { CronExpression } from 'cron-parser';
import { ClientGrpc } from '@nestjs/microservices';
import DadosferaLogger from '@dadosfera/dadosfera-logs/dist';
@@ -222,7 +221,6 @@ export class InputsService {
return lastValueFrom(this.inputWriteService.InputRemove(idRequest));
}
@Timeout(60000 * 10) // Timeout set for 10 minutes
async testConnection(data) {
try {
const testConnectionInputResponse =
+1
View File
@@ -39,6 +39,7 @@ export const CUSTOMER = {
INVALID_ID: 'CUSTOMER.INVALID_ID',
NOT_FOUND: 'CUSTOMER.NOT_FOUND',
ALREADY_EXISTS: 'CUSTOMER.ALREADY_EXISTS',
BAD_REQUEST: 'CUSTOMER.BAD_REQUEST',
};
const TERMS_OF_USE = {
UP_TO_DATE: 'TERMS_OF_USE.UP_TO_DATE',
+10
View File
@@ -0,0 +1,10 @@
const availableLocalEnvs: Array<typeof process.env.ENV> = ['stg', 'test'];
export default function getEnv(): typeof process.env.ENV {
if (process.env.ENV === 'local') {
if (availableLocalEnvs.includes(process.env.LOCAL_ENV))
return process.env.LOCAL_ENV;
return 'stg';
}
return process.env.ENV;
}