Compare commits

...
Author SHA1 Message Date
Gabriel Rosa b280871bce CI: fix syntax 2024-01-04 14:44:20 -03:00
Gabriel Rosa fd73663940 CI: fix syntax 2024-01-04 14:37:32 -03:00
Gabriel Rosa e420bf6547 CI: choose wether to deploy to dockerhub 2024-01-04 10:25:51 -03:00
Gabriel Rosa cc4cc99ccc FEAT: new refreshToken response
- temporarily disable mixpanel tracking
2023-12-09 11:58:03 -03:00
Gabriel Rosa fd0d68b149 FIX: removed state 2023-12-07 19:39:09 -03:00
Gabriel Rosa 98759b1cf5 FIX: logging oauth error 2023-12-07 17:27:30 -03:00
Gabriel Rosa 695fac97e9 FIX: get frontend url 2023-12-07 11:01:38 -03:00
Gabriel Rosa 86b076246e FEAT: magalu id login 2023-12-06 20:36:14 -03:00
Rafael Santana faa98cae8b UPDATE: triple equals 2023-12-01 13:20:40 -03:00
Rafael Santana 313ecc81ec UPDATE: missing semicolon 2023-12-01 13:20:04 -03:00
Rafael Santana 820d71d16d UPDATE: Adding the cloud_environment to point to the correct nimbus in maestro 2023-12-01 13:13:26 -03:00
Rafael Santana 734978b76f UPDATE: Increasing log level 2023-11-29 17:47:08 -03:00
16 changed files with 2990 additions and 1609 deletions
+15 -9
View File
@@ -13,6 +13,11 @@ on:
- stg
- stg2
- prd
push_to_dockerhub:
description: "Push image to Dockerhub?"
required: true
type: boolean
default: false
jobs:
extract_environment:
@@ -39,11 +44,11 @@ jobs:
new_release_version: ${{ (steps.semantic.outputs.new_release_published == 'true' && steps.semantic.outputs.new_release_version) || (github.event_name == 'workflow_dispatch' && '0.0.0') }}
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- if: github.event_name != 'workflow_dispatch'
name: Semantic Release
uses: cycjimmy/semantic-release-action@v3
uses: cycjimmy/semantic-release-action@v4
id: semantic
with:
extra_plugins: |
@@ -76,7 +81,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Update Pip
run: |
@@ -95,14 +100,14 @@ jobs:
python3 -m pip install awsebcli --upgrade
- name: Configure AWS Region
uses: aws-actions/configure-aws-credentials@v1-node16
uses: aws-actions/configure-aws-credentials@v4
id: aws
with:
aws-region: us-east-1
- name: Login to AWS ECR
id: login_ecr
uses: aws-actions/amazon-ecr-login@v1
uses: aws-actions/amazon-ecr-login@v2
- name: Build, Tag, and Push Image to AWS ECR
env:
@@ -114,20 +119,21 @@ jobs:
docker-compose -f build.docker-compose.yml push
- name: Login to Docker Hub
if: ${{inputs.push_to_dockerhub}}
uses: docker/login-action@v2
with:
username: dadosfera
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Build, Tag, and Push Image to Dockerhub
if: ${{inputs.push_to_dockerhub}}
env:
ENV: ${{ needs.extract_environment.outputs.environment }}
IMAGE_TAG: ${{ needs.semantic_release.outputs.new_release_version }}
ACCOUNT_ID: ${{ steps.aws.outputs.aws-account-id }}
run: |
docker-compose -f build.docker-compose.dockerhub.yml build
docker-compose -f build.docker-compose.dockerhub.yml push
docker-compose -f build.docker-compose.dockerhub.yml build
docker-compose -f build.docker-compose.dockerhub.yml push
- name: Create ZIP file to Deploy AWS Beanstalk
env:
+2 -1
View File
@@ -12,6 +12,7 @@
"start:debug"
],
"runtimeExecutable": "npm",
"runtimeVersion": "18.10.0",
"skipFiles": [
"<node_internals>/**"
],
@@ -19,4 +20,4 @@
"console": "integratedTerminal"
}
]
}
}
+75 -3
View File
@@ -542,6 +542,67 @@
]
}
},
"/auth/oauth/magalu-id": {
"get": {
"operationId": "AuthController_magaluIdOauth",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "boolean"
}
}
}
}
},
"tags": [
"Auth"
]
}
},
"/auth/oauth/magalu-id/callback": {
"get": {
"operationId": "AuthController_magaluIdOauthCallback",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"Auth"
]
}
},
"/connections": {
"post": {
"operationId": "ConnectionController_createConnection",
@@ -4949,7 +5010,14 @@
"parameters": [],
"responses": {
"201": {
"description": ""
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"security": [
@@ -5076,7 +5144,7 @@
}
},
"info": {
"title": "Maestro - feat/generate-token",
"title": "Maestro - ci/dockerhub",
"description": "This is the Maestro API",
"version": "1.0.0",
"contact": {}
@@ -5300,11 +5368,15 @@
},
"accessToken": {
"type": "string"
},
"refreshToken": {
"type": "string"
}
},
"required": [
"permissions",
"accessToken"
"accessToken",
"refreshToken"
]
},
"CreateConnectionDto": {
+1
View File
@@ -4,6 +4,7 @@ declare global {
NODE_ENV: 'test';
ENV: 'local' | 'stg' | 'prd' | 'test';
LOCAL_ENV: 'stg' | 'prd';
CLOUD_ENVIRONMENT: 'aws' | 'gcp' | 'mgc';
DUC_URL: string;
INFACTORY_URL: string;
+2707 -1557
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -29,7 +29,7 @@
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.112.0",
"@dadosfera/dadosfera-logs": "^1.0.0-beta.4",
"@dadosfera/protospack-v2": "3.31.0",
"@dadosfera/protospack-v2": "3.32.0-beta.2",
"@grpc/grpc-js": "^1.6.7",
"@grpc/proto-loader": "^0.6.13",
"@nestjs/cli": "^9.4.2",
@@ -43,7 +43,7 @@
"@nestjs/schematics": "^9.1.0",
"@nestjs/swagger": "^6.3.0",
"@nestjs/testing": "^9.4.0",
"axios": "^0.27.2",
"axios": "^1.6.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cron-parser": "^4.4.0",
@@ -80,8 +80,8 @@
"@types/passport-google-oauth20": "^2.0.11",
"@types/passport-oauth2": "^1.4.11",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0",
"@typescript-eslint/eslint-plugin": "^5.35.0",
"@typescript-eslint/parser": "^5.35.0",
"eslint": "^8.18.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0",
+5 -1
View File
@@ -79,9 +79,13 @@ export class AuthenticationGuard
request,
mustBeAuthenticated: boolean,
): RequestUser | false {
const accessToken = request.get('Authorization');
const accessToken: string = request.get('Authorization');
let accessTokenPayload: RequestUser;
this.logger.info(`mustBeAuthenticated: ${mustBeAuthenticated}`);
this.logger.info(
`accessToken (first 5 chars): ${accessToken?.slice(0, 5)}`,
);
// If the user isn't authenticated, an error will occurr anywhere here.
// Fancy error avoidance isn't performed by purpose, such as avoiding to access null values.
try {
+92 -28
View File
@@ -52,6 +52,7 @@ import jwt from 'jsonwebtoken';
import { LanguageEnum } from 'src/utils/languages.enum';
import { Language } from 'src/decorators/language.decorator';
import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator';
import { getFrontendUrl } from 'src/utils/getFrontendBaseUrl';
@ApiTags('Auth')
@ApiHeaders([{ name: 'dadosfera-lang', enum: LanguageEnum, required: false }])
@@ -59,7 +60,7 @@ import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator';
@Controller('auth')
export class AuthController {
logger: DadosferaLogger;
redirectUrl: string;
frontendRedirectUrl: string;
constructor(
@Inject(DadosferaLogger)
@@ -67,17 +68,7 @@ export class AuthController {
private authClient: AuthClientService,
) {
this.logger = dadosferaLogger.logger;
switch (process.env.ENV) {
case 'stg':
this.redirectUrl = `https://app.${process.env.ENV}.dadosfera.ai/auth/login`;
break;
case 'prd':
this.redirectUrl = `https://app.dadosfera.ai/auth/login`;
break;
default:
this.redirectUrl = `http://localhost:4200/auth/login`;
}
this.frontendRedirectUrl = getFrontendUrl('/auth/login').href;
}
@Post('sign-in')
@@ -105,7 +96,7 @@ export class AuthController {
customer_name,
language,
});
this.logger.info(`metadata: ${metadata}`);
return this.authClient.refreshAccessToken({ refreshToken }, metadata);
}
@@ -269,7 +260,13 @@ export class AuthController {
@UseGuards(AuthGuard('google-login'))
@Redirect()
async googleOauthCallback(@Req() req) {
const { url, email, token, language = 'pt-br' } = await this.callback(req);
const {
url,
email,
token,
refreshToken,
language = 'pt-br',
} = await this.callback(req);
if (url.searchParams.get('error')) {
this.logger.error('/oauth/google - ERROR');
return { url: url.href };
@@ -279,6 +276,7 @@ export class AuthController {
.oauthSignIn({
username: email,
token,
refreshToken,
})
.then(({ session }) => {
this.logger.info('/oauth/google - SUCESS');
@@ -308,18 +306,79 @@ export class AuthController {
return { url: url.href };
}
async callback(req: Request) {
@ApiInternalOnlyEndpoint()
@Get('oauth/magalu-id')
@UseGuards(AuthGuard('magalu-id-login'))
magaluIdOauth() {
this.logger.info('/oauth/magalu-id');
return true;
}
@ApiInternalOnlyEndpoint()
@Get('oauth/magalu-id/callback')
@UseGuards(AuthGuard('magalu-id-login'))
@Redirect()
async magaluIdOauthCallback(@Req() req) {
const {
url,
email,
token,
refreshToken,
language = 'pt-br',
} = await this.callback(req, 'magalu-id');
if (url.searchParams.get('error')) {
this.logger.error('/oauth/magalu-id/callback - ERROR');
this.logger.error(url.searchParams.get('error'));
return { url: url.href };
}
await this.authClient
.oauthSignIn({
username: email,
token,
refreshToken,
})
.then(({ session }) => {
this.logger.info('/oauth/magalu-id/callback - SUCESS');
url.searchParams.set('session', session);
})
.catch((err) => {
this.logger.error('/oauth/magalu-id/callback - LOGIN ERROR');
let error = OauthErrors.INVALID_CREDENTIALS[language].error;
let error_description =
OauthErrors.INVALID_CREDENTIALS[language].error_description;
switch (err.details) {
case ErrorCodes.USER.NOT_FOUND:
error = OauthErrors.USER_NOT_FOUND[language].error;
error_description =
OauthErrors.USER_NOT_FOUND[language].error_description(email);
break;
case ErrorCodes.AUTH.UNAUTHORIZED:
error = OauthErrors.INVALID_SESSION[language].error;
error_description =
OauthErrors.INVALID_SESSION[language].error_description;
break;
}
url.searchParams.set('error', error);
url.searchParams.set('error_description', error_description);
return null;
});
return { url: url.href };
}
async callback(req: Request, oauth_type?: 'google' | 'magalu-id') {
const { error, state } = req.query;
const { authInfo } = req;
const url = new URL(this.redirectUrl);
let email, token, error_title, error_description;
let language: 'pt-br' | 'en-us' = 'pt-br';
const stateObject = jwt.verify(
state as string,
process.env.JWT_PRIVATE_KEY,
);
if (typeof stateObject != 'string') language = stateObject.language;
const url = new URL(this.frontendRedirectUrl);
let email, token, refreshToken, error_title, error_description;
const language: 'pt-br' | 'en-us' = 'pt-br';
// let stateObject;
// try {
// stateObject = jwt.verify(state as string, process.env.JWT_PRIVATE_KEY);
// } catch (error) {
// console.log('error', error);
// }
// if (typeof stateObject != 'string') language = stateObject.language;
if (error || !authInfo) {
this.logger.error(error);
@@ -330,11 +389,16 @@ export class AuthController {
OauthErrors.INVALID_CREDENTIALS[language].error_description;
if (error) error_description += ` - [${error}]`;
} else {
const { accessToken } = authInfo as any;
const { _json: userInfo } = req.user as any;
const { accessToken, refreshToken: rt } = authInfo as any;
const { _json: userInfo = {} } = req.user as any;
email = userInfo.email;
token = accessToken;
refreshToken = rt;
if (oauth_type === 'magalu-id') {
const jwtDecoded = jwt.decode(token, { json: true });
email = jwtDecoded.email;
}
}
if (error_title) {
@@ -342,6 +406,6 @@ export class AuthController {
url.searchParams.set('error_description', error_description);
}
return { token, email, url, language };
return { token, refreshToken, email, url, language };
}
}
+2
View File
@@ -8,6 +8,7 @@ import { AuthClientService } from './auth.service';
import { DucClient } from '../duc/client.config';
import { GoogleLoginStrategy } from './passport-strategies/google-strategy';
import { getOauthSecrets } from 'src/utils/OauthSecrets';
import { MagaluIdStrategy } from './passport-strategies/magalu-id-strategy';
const client = new DucClient();
@Module({
@@ -17,6 +18,7 @@ const client = new DucClient();
AuthClientService,
DadosferaLogger,
GoogleLoginStrategy,
MagaluIdStrategy,
{ provide: 'OAUTH_SECRETS', useValue: getOauthSecrets() },
],
exports: [AuthClientService],
+3 -3
View File
@@ -17,6 +17,7 @@ import {
AuthResetPasswordRequest,
AuthVerifyResetPasswordCodeRequest,
AuthConfirmResetPasswordRequest,
AuthOauthSignInRequest,
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
import { DucClient } from '../duc/client.config';
import { Metadata } from '@grpc/grpc-js';
@@ -162,8 +163,7 @@ export class AuthClientService implements OnModuleInit {
return lastValueFrom(this.authService.AuthGetSession({ session }));
}
async oauthSignIn(data: { username: string; token: string }) {
const { username, token } = data;
return lastValueFrom(this.authService.AuthOauthSignIn({ token, username }));
async oauthSignIn(data: AuthOauthSignInRequest) {
return lastValueFrom(this.authService.AuthOauthSignIn(data));
}
}
+2
View File
@@ -117,4 +117,6 @@ export class AuthRefreshAccessTokenRes {
permissions: string[];
@ApiProperty()
accessToken: string;
@ApiProperty()
refreshToken: string;
}
@@ -0,0 +1,47 @@
import {
Strategy as Oauth2Strategy,
StrategyOptions,
VerifyCallback,
VerifyFunction,
} from 'passport-oauth2';
import { PassportStrategy } from '@nestjs/passport';
import { Inject, Injectable } from '@nestjs/common';
import { OauthSecrets } from 'src/utils/OauthSecrets';
import { Request } from 'express';
import jwt from 'jsonwebtoken';
@Injectable()
export class MagaluIdStrategy extends PassportStrategy(
Oauth2Strategy,
'magalu-id-login',
) {
redirect_uri: string;
constructor() {
const options: StrategyOptions = {
clientID: process.env.MAGALU_ID_CLIENT_ID,
clientSecret: process.env.MAGALU_ID_CLIENT_SECRET,
callbackURL: process.env.MAGALU_ID_CALLBACK_URL,
scope: ['openid'],
authorizationURL: 'https://id.magalu.com/login',
tokenURL: 'https://id.magalu.com/oauth/token',
};
const verify: VerifyFunction = (
accessToken: string,
refreshToken: string,
profile: any,
verified: VerifyCallback,
) => {
return verified(null, profile, { accessToken, refreshToken });
};
super(options, verify);
}
// authenticate(req: Request, options: Record<string, any>) {
// const language =
// req.headers['dadosfera-lang'] || req.query.language || 'pt-br';
// // options.state = jwt.sign({ language }, process.env.JWT_PRIVATE_KEY);
// super.authenticate(req, options);
// console.log('authenticate', JSON.stringify(options));
// }
}
+7 -1
View File
@@ -49,10 +49,16 @@ class CatalogService implements OnModuleInit {
_getNimbusUrl(body) {
const customer = body.info.customer.toLowerCase();
if (process.env.ENV === 'prd') {
const cloud_environment = process.env.CLOUD_ENVIRONMENT || 'aws';
if (process.env.ENV === 'prd' && cloud_environment === 'aws') {
return `https://nimbus-${customer}.dadosfera.ai`;
}
if (process.env.ENV === 'prd' && cloud_environment === 'mgc') {
return `https://nimbus-${customer}.dadosfera.com`;
}
return `https://nimbus-${customer}.${process.env.ENV.replace(
'local',
'stg',
@@ -15,6 +15,8 @@ export class MixpanelController {
@Post(':id')
async trackEvent(@Param('id') id, @Body() body, @User() user: RequestUser) {
delete body.info;
//TODO: remove this when mixpanel track is working
return true;
const mixpanel = init(this.mixpanelToken);
const separator = user.username.includes('-') ? '-' : '.';
+6 -2
View File
@@ -152,7 +152,9 @@ export class UsersController {
});
this.logger.info('createUser', { user });
this.userService.setLanguage(language);
this.logger.info(`user: ${user}`);
this.logger.info(`body: ${body}`);
this.logger.info(`metadata: ${metadata}`);
return await this.userService.createUser(body, metadata);
}
@@ -171,7 +173,9 @@ export class UsersController {
access_token: user.access_token,
language,
});
this.logger.info(`user: ${user}`);
this.logger.info(`body: ${body}`);
this.logger.info(`metadata: ${metadata}`);
return await this.userService.batchCreateUser(body, metadata);
}
+20
View File
@@ -0,0 +1,20 @@
import getEnv from './getEnv';
export function getFrontendUrl(path?: string): URL {
const frontendUrl = new URL('https://app.dadosfera.ai');
if (path) frontendUrl.pathname = path;
const env = getEnv();
if (process.env.ENV === 'local') {
frontendUrl.protocol = 'http';
frontendUrl.host = 'localhost:4200';
return frontendUrl;
}
if (process.env.CLOUD_ENVIRONMENT === 'mgc')
frontendUrl.host = 'app.dadosfera.com';
if (env !== 'prd')
frontendUrl.host = frontendUrl.host.replace('app.', `app.${env}.`);
return frontendUrl;
}