2022-06-06 17:35:26 -03:00
2022-03-24 12:08:08 -03:00
2022-02-10 14:32:42 -03:00
2024-05-02 12:11:35 -03:00
2022-08-17 18:12:32 -03:00
2023-05-12 08:28:33 -03:00
2022-02-04 10:03:55 -03:00
2022-02-04 10:03:55 -03:00
2022-04-12 17:59:41 -03:00
2022-11-01 15:33:14 -03:00
2023-05-12 08:28:33 -03:00
2023-02-15 13:13:43 -03:00
2023-06-11 22:43:43 -03:00
2023-05-12 08:28:33 -03:00
2022-11-10 09:46:22 -03:00
2022-06-27 19:22:36 -03:00

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.10.0 LTS / NPM v8.11 (you can opt to use 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):

    nvm use
    
  • Install the project dependencies:

    npm i
    
  • Setup the following enviroment variables:

    ENV=
    DUC_URL=
    INFACTORY_URL=
    OTFACTORY_URL=
    PIFACTORY_URL=
    SM_OAUTH_PATH=
    
  • Start the server:

    # dev mode
    npm run start:dev
    
    # or in debug mode
    npm run start:debug
    

    The service should start successfully.

📄 Documentation

NestJs makes it easy to document each route using decorators on all requests and responses properties. It automatically generates a swagger for given information and provides a route to access it http://localhost:3333/api. For more info check the official documentation.

- Multiple documentations

We are currently generating 2 different documentations: Internal and External.

All routes that have the decorator @ApiInternalOnly() will not be visible on the External API swagger.

  • When you start the application with npm run start it will serve and generate the swagger JSON of the External API
  • When you start the application with npm run start:internal it will serve and generate the swagger JSON of the Internal API
  • When you run npm run docs it will generate the swagger JSON of both Internal and External API, and save them to docsfera.json and docsfera.external.json respectively;

It is important to run npm run docs before every deploy so we can always have the most updated docs published.

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.

import { Authenticated } from '../../authentication/authentication.decorator';

@Controller('foo')
@Authenticated()
class FooController {
  /* ... */

  @Get('bar')
  async getBar() {
    this.logger.info('user is authenticated!');

    return { authenticated: true };
  }
}
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.

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.

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
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.

    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.

    // 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:

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:

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 + Prettier 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) to release versions accordingly.

Format: <type>[optional scope]: <description>

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 - Framework for building efficient and scalable NodeJS server-side applications

⚙️ Back-end Architecture

The architecture can be found at this link.

S
Description
No description provided
Readme
7.1 MiB
Languages
TypeScript 98.9%
HTML 0.7%
Dockerfile 0.3%