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 (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.gitor
-
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:debugThe 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.
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
FooControllercontroller can only be requested from localhost POST /foo/barcan only be requested from localhost and by users from customer id111...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.infofield, 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@Userparameter 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
@AuthenticateConditiondecorator.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
@RequireAllPermissionsand@RequireSomePermissionare 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
mainbranch. When all changes are done, a Pull Request/Merge Request is needed to put all of these changes back to themainbranch. 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
- Commit your changes
- Open the Pull Request on GitHub
- 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.