# Maestro Maestro is the Dadosfera's gateway, it's responsible for the communication between the frontend application and Dadosfera's microservices. ## 🚀 Starting These instructions will allow you to get a working copy of the project on your local machine for development and testing purposes. ### 📋 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) - 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 - Request access to the dev, stg and prd VPNs ### 🔧 Installation - Clone the repository - SSH ``` git clone git@github.com:dadosfera/maestro.git ``` or - HTTPS ``` git clone https://github.com/dadosfera/maestro.git ``` - Select the correct node version (optional, only if using [NVM](https://github.com/nvm-sh/nvm)): ```sh nvm use ``` - Install the project dependencies: ```sh npm i ``` - Setup the following enviroment variables: ``` ENV= DUC_URL= INFACTORY_URL= OTFACTORY_URL= PIFACTORY_URL= SM_OAUTH_PATH= ``` - Start the server: ```sh # dev mode npm run start:dev # or in debug mode npm run start:debug ``` The service should start successfully. ## Authentication decorators Maestro have utilities to ease the user authentication on every controller and route. The following decorators are available: ### `@Authenticated` If the user must be authenticated to make request, we can use the `@Authenticated` decorator in the controller or route, as needed. ```ts import { Authenticated } from '../../authentication/authentication.decorator'; @Controller('foo') @Authenticated() class FooController { /* ... */ @Get('bar') async getBar() { this.logger.info('user is authenticated!'); return { authenticated: true }; } } ``` ```ts import { Authenticated } from '../../authentication/authentication.decorator'; @Controller('foo') class FooController { /* ... */ @Get('bar') @Authenticated() async getBar() { this.logger.info('user is authenticated!'); return { authenticated: true }; } @Post('bar') async postBar() { this.logger.info('user is NOT authenticated!'); return { authenticated: false }; } } ``` ### `@RequireAllPermissions` This decorator requires that **all permissions** listed are granted to the requesting user. ```ts import { RequireAllPermissions } from '../../authentication/authentication.decorator'; import { Permissions } from '../../authentication/permissions.enum'; @Controller('foo') class FooController { /* ... */ @Get('bar') @RequireAllPermissions(Permissions.BAR.MANAGE, Permissions.BAR.CREATE) async getBar() { /* ... */ } } ``` ### `@RequireSomePermission` In the following case, the user is required to have **at least one** listed permission. ```ts import { RequireSomePermission } from '../../authentication/authentication.decorator'; import { Permissions } from '../../authentication/permissions.enum'; @Controller('foo') @RequireSomePermission(Permissions.FOO.MANAGE, Permissions.FOO.CREATE) class FooController { /* ... */ @Get('bar') async getBar() { /* ... */ } } ``` ### `@AuthenticateCondition` If a more complicated authentication check needs to be done, we can use the `@AuthenticateCondition` decorator to define it. The custom function must return `true` to authenticate the request. In the following example: - all routes on the `FooController` controller can only be requested from localhost - `POST /foo/bar` can only be requested from localhost **and** by users from customer id `111...eef` ```ts import { AuthenticateCondition } from '../../authentication/authentication.decorator'; @Controller('foo') // allow requests only from localhost @AuthenticateCondition((request: Request) => request.ip === '::ffff:127.0.0.1') class FooController { /* ... */ @Get('bar') async getBar() { /* ... */ } @Post('bar') // allow requests only from a specific customer @AuthenticateCondition((request: Request, user: RequestUser) => user.customer_id === '1113e943-2187-4fdd-9c2c-54338fedaeef') async postBar() { /* ... */ } } ``` ### Note on authentication decorators - The old authentication method placed the user data in the `request.body.info` field, this imposes certain issues regarding the request body because this data should be from the frontend without any modification by Maestro. Now this usage is ⚠️ **DEPRECATED** ⚠️. We are working to migrate to the `@User` parameter decorator. The old method is working while the migration is in progress. - Authentication decorators can be used together and all of them **must** pass to the request be authenticated, but in the general case you don't need to (*and wouldn't like to...*) use all of them together, as you can code all of the authentication logic in the `@AuthenticateCondition` decorator. ```ts import { RequireAllPermissions, RequireSomePermission, AuthenticateCondition, } from '../../authentication/authentication.decorator'; import { Permissions } from '../../authentication/permissions.enum'; import { Waa, Baz } from './authenticationFunctions' @Controller('foo') @RequireAllPermissions(Permissions.FOO.USE, Permissions.FOO.REQUEST) @RequireSomePermission(Permissions.FOO.MANAGE, Permissions.FOO.ADMIN) @AuthenticateCondition(Waa) @AuthenticateCondition(Baz) class FooController { /* ... */ @Get('bar') async getBar() { /* ... */ } @Post('bar') @RequireAllPermissions(Permissions.BAR.MANAGE, Permissions.BAR.USE, Permissions.BAR.REQUEST) async postBar() { /* ... */ } } ``` - If `@RequireAllPermissions` and `@RequireSomePermission` are used with **only a single permission**, they present the **exactly same behavior**. ```ts // same behavior @RequireAllPermissions(Permissions.BAR.MANAGE) @RequireSomePermission(Permissions.BAR.MANAGE) ``` ## `@User` parameter decorator The requesting user data can be obtained using the @User parameter decorator, like in the following snippet: ```ts import { User, RequestUser } from '../../authentication/user.decorator'; @Controller('foo') class FooController { /* ... */ @Get('bar') async getBar(@User() user: RequestUser) { this.logger.info(user); return { user }; } } ``` If the user is required to be logged in, set `required` to `true`, as you would want in the `change-password` operation: ```ts import { User, RequestUser } from '../../authentication/user.decorator'; @Controller('user') class UserController { /* ... */ @Post('change-password') @HttpCode(HttpStatus.OK) async changePassword( @User({ required: true }) user: RequestUser, @Body() body: AuthChangePasswordRequest, ) { const { oldPassword, newPassword } = body; return this.authClient.changePassword({ userId: user.user_id, oldPassword, newPassword, }); } } ``` ## 📦 Development ### ⌨️ Coding Style By default, we use [ESLint](https://eslint.org/) + [Prettier](https://prettier.io/) with default settings. **We recommend using Visual Studio Code and installing the recommended extensions to ease the development process.** ### Commits pattern Our workflow pipeline follows the conventional commits specs ([cheat sheets](https://cheatography.com/albelop/cheat-sheets/conventional-commits/)) to release versions accordingly. Format: `[optional scope]: ` Example: `FIX: ensure Range headers adhere more closely to RFC 2616` ### Branching naming convention - **Feature**: Any code changes for a new module or use case should be done on a feature branch. This branch is created based on the `main` branch. When all changes are done, a Pull Request/Merge Request is needed to put all of these changes back to the `main` branch. Examples: `feature/integrate-swagger`, `feature/JIRA-1234`, `feature/JIRA-1234_support-dark-theme`. **It is recommended to use all lower caps letters and hyphen (-) to separate words unless it is a specific item name or ID. Underscore (_) could be used to separate the ID and description.** - **Bug Fix**: If the code changes made from the feature branch were rejected after a release, sprint or demo, any necessary fixes after that should be done on the bugfix branch. Examples: `bugfix/more-gray-shades`, `bugfix/JIRA-1444_gray-on-blur-fix`. - **Hot Fix**: If there is a need to fix a blocker, do a temporary patch, apply a critical framework or configuration change that should be handled immediately, it should be created as a Hotfix. Examples: `hotfix/disable-endpoint-zero-day-exploit`, `hotfix/increase-scaling-threshold`. - **Experimental**: A branch for playing around. Any new feature or idea that is not part of a release or a sprint. Example: `experimental/dark-theme-support`. ### Making a Pull Request 1. Commit your changes 2. Open the Pull Request on GitHub 3. Send Pull Request link in microsfera Google Chat Group for review and possible approval ## 🛠️ Built with Some technologies used in this project: - [NestJS](https://docs.nestjs.com) - Framework for building efficient and scalable NodeJS server-side applications ## ⚙️ Back-end Architecture The architecture can be found at [this link](https://sites.google.com/dadosfera.ai/wikidoproduto/time/back-end).