diff --git a/docsfera.external.json b/docsfera.external.json index 9af7d01..5823882 100644 --- a/docsfera.external.json +++ b/docsfera.external.json @@ -1752,7 +1752,7 @@ } }, "info": { - "title": "Maestro - feature/customer-links", + "title": "Maestro - feat/generate-token", "description": "This is the Maestro API", "version": "1.0.0", "contact": {} diff --git a/docsfera.json b/docsfera.json index 6cfda58..d7e9b6c 100644 --- a/docsfera.json +++ b/docsfera.json @@ -5022,6 +5022,44 @@ ] } }, + "/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", @@ -5038,7 +5076,7 @@ } }, "info": { - "title": "Maestro - main", + "title": "Maestro - feat/generate-token", "description": "This is the Maestro API", "version": "1.0.0", "contact": {} diff --git a/package-lock.json b/package-lock.json index f1bf0a9..baf956a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,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", @@ -4570,6 +4571,11 @@ } } }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/decimal.js": { "version": "10.3.1", "dev": true, @@ -6225,6 +6231,11 @@ "ms": "^2.0.0" } }, + "node_modules/humanize-ms/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/iconv-lite": { "version": "0.4.24", "license": "MIT", @@ -7449,6 +7460,11 @@ "npm": ">=6" } }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/jwa": { "version": "1.4.1", "license": "MIT", @@ -7585,6 +7601,11 @@ "triple-beam": "^1.3.0" } }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/long": { "version": "4.0.0", "license": "Apache-2.0" @@ -7858,8 +7879,12 @@ "integrity": "sha512-YRIr1exCIfBDLZle8WHOfSo7Xg3M+phcZfq9Fx1L6Abo+atGp7cge5pM7PjyBn4s1oZI/BRD4EMrzQBbPpVb5Q==" }, "node_modules/ms": { - "version": "2.1.2", - "license": "MIT" + "version": "3.0.0-canary.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", + "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==", + "engines": { + "node": ">=12.13" + } }, "node_modules/multer": { "version": "1.4.5-lts.1", @@ -9126,7 +9151,8 @@ }, "node_modules/send/node_modules/ms": { "version": "2.1.3", - "license": "MIT" + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serialize-javascript": { "version": "6.0.1", @@ -13943,6 +13969,13 @@ "version": "4.3.4", "requires": { "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } } }, "decimal.js": { @@ -15083,6 +15116,13 @@ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "requires": { "ms": "^2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } } }, "iconv-lite": { @@ -15908,6 +15948,13 @@ "lodash": "^4.17.21", "ms": "^2.1.1", "semver": "^7.3.8" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } } }, "jwa": { @@ -16010,6 +16057,13 @@ "ms": "^2.1.1", "safe-stable-stringify": "^2.3.1", "triple-beam": "^1.3.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } } }, "long": { @@ -16195,7 +16249,9 @@ "integrity": "sha512-YRIr1exCIfBDLZle8WHOfSo7Xg3M+phcZfq9Fx1L6Abo+atGp7cge5pM7PjyBn4s1oZI/BRD4EMrzQBbPpVb5Q==" }, "ms": { - "version": "2.1.2" + "version": "3.0.0-canary.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", + "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==" }, "multer": { "version": "1.4.5-lts.1", @@ -17054,7 +17110,9 @@ } }, "ms": { - "version": "2.1.3" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, diff --git a/package.json b/package.json index 9df10e5..6240d5a 100644 --- a/package.json +++ b/package.json @@ -53,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", diff --git a/src/authentication/permissions.enum.ts b/src/authentication/permissions.enum.ts index 7266fd1..64e7a26 100644 --- a/src/authentication/permissions.enum.ts +++ b/src/authentication/permissions.enum.ts @@ -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', + }, + }, }, }, diff --git a/src/modules/customers/customers.controller.ts b/src/modules/customers/customers.controller.ts index fa98530..f88a677 100644 --- a/src/modules/customers/customers.controller.ts +++ b/src/modules/customers/customers.controller.ts @@ -7,6 +7,7 @@ import { Inject, Param, Put, + Query, UseFilters, } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; @@ -19,6 +20,8 @@ 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') @@ -53,4 +56,19 @@ export class CustomersController { 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 { + 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); + } } diff --git a/src/modules/customers/customers.service.ts b/src/modules/customers/customers.service.ts index 3b32007..ea9a93b 100644 --- a/src/modules/customers/customers.service.ts +++ b/src/modules/customers/customers.service.ts @@ -4,6 +4,7 @@ import { Injectable, HttpException, HttpStatus, + InternalServerErrorException, } from '@nestjs/common'; import { firstValueFrom, lastValueFrom } from 'rxjs'; @@ -14,7 +15,15 @@ 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; @@ -60,4 +69,55 @@ export class CustomersService implements OnModuleInit { 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; + } } diff --git a/src/utils/getEnv.ts b/src/utils/getEnv.ts new file mode 100644 index 0000000..9ad4d0b --- /dev/null +++ b/src/utils/getEnv.ts @@ -0,0 +1,10 @@ +const availableLocalEnvs: Array = ['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; +}