Compare commits

...
23 Commits
Author SHA1 Message Date
Marcos Rodrigues Silva e7f410831f Merge pull request #395 from dadosfera/beta
Beta
2025-12-01 17:47:24 -03:00
Marcos Rodrigues Silva cd2c53b5c5 Merge pull request #398 from dadosfera/feat/user-properties
FIX: user response
2025-12-01 17:36:56 -03:00
Marcos Rodrigues adf1b3b97e FIX: user response 2025-12-01 17:30:37 -03:00
Marcos Rodrigues Silva d84b5e184b Merge pull request #397 from dadosfera/feat/user-properties
FEAT: include more user info in /me
2025-12-01 12:36:16 -03:00
Marcos Rodrigues 6d608a0457 FEAT: include more user info in /me 2025-12-01 12:26:33 -03:00
Marcos Rodrigues Silva b8be2c7803 Merge pull request #396 from dadosfera/fix/api-key-catalog-roles
FIX: api key catalog roles
2025-12-01 09:34:21 -03:00
Marcos Rodrigues 24fce721e3 FIX: api key catalog roles 2025-12-01 09:32:26 -03:00
Marcos Rodrigues Silva 23a9a27db1 Merge pull request #394 from dadosfera/fix/private-front-cors
FIX: 404 in  get public key
2025-11-28 16:44:54 -03:00
Marcos Rodrigues 0f5ed50af9 FIX: 404 in get public key 2025-11-28 16:37:38 -03:00
Marcos Rodrigues Silva 5f8f6a64ab Merge pull request #393 from dadosfera/fix/private-front-cors
FIX: include private frontend url in cors origins list
2025-11-28 10:53:45 -03:00
Marcos Rodrigues 8800ac2736 FIX: include private frontend url in cors origins list 2025-11-28 10:50:31 -03:00
Marcos Rodrigues Silva bdb82c2ce4 Merge pull request #392 from dadosfera/fix/authenticated-track-events
Fix/authenticated track events
2025-11-27 09:45:46 -03:00
Marcos Rodrigues 6d9ecc3568 FIX: mixpanel events 2025-11-27 09:43:43 -03:00
Marcos Rodrigues Silva 7764447adc Merge pull request #391 from dadosfera/fix/private-ingress-annotations
FIX: ci to oracle github worker
2025-11-25 11:37:29 -03:00
Marcos Rodrigues 576fdecf89 FIX: ci to oracle github worker 2025-11-25 11:27:04 -03:00
Marcos Rodrigues Silva 3de1e90fa8 Merge pull request #390 from dadosfera/fix/private-ingress-annotations
Fix/private ingress annotations
2025-11-25 11:12:58 -03:00
Marcos Rodrigues 5e90950660 FIX: restore whitelist annotation 2025-11-25 10:59:06 -03:00
Marcos Rodrigues 3ed26e648f FIX: ci 2025-11-25 10:48:06 -03:00
Marcos Rodrigues Silva fff3523152 Merge pull request #389 from dadosfera/fix/private-ingress-annotations
FIX: private ingress annotations
2025-11-25 10:38:52 -03:00
Marcos Rodrigues 19e4daeea4 FIX: private ingress annotations 2025-11-25 10:35:08 -03:00
Marcos Rodrigues Silva 8624d3f016 Merge pull request #388 from dadosfera/fix/reset-theme
Fix/reset theme
2025-11-21 11:01:48 -03:00
Marcos Rodrigues Silva dc1d1400d8 Merge pull request #387 from dadosfera/feat/lineage
FIX: change wget to curl
2025-11-13 18:19:20 -03:00
Marcos Rodrigues Silva 06ba759ea3 Merge pull request #386 from dadosfera/feat/lineage
Feat/lineage
2025-11-13 17:58:33 -03:00
10 changed files with 139 additions and 56 deletions
+2 -2
View File
@@ -56,9 +56,9 @@ jobs:
- name: Install Helmfile - name: Install Helmfile
run: | run: |
wget https://github.com/helmfile/helmfile/releases/download/v0.148.0/helmfile_0.148.0_linux_amd64.tar.gz curl -fsSLO https://github.com/helmfile/helmfile/releases/download/v0.148.0/helmfile_0.148.0_linux_amd64.tar.gz
tar -xzf helmfile_0.148.0_linux_amd64.tar.gz tar -xzf helmfile_0.148.0_linux_amd64.tar.gz
mv helmfile /usr/local/bin/ sudo mv helmfile /usr/local/bin/
helmfile --version helmfile --version
- name: Install Helm Diff Plugin - name: Install Helm Diff Plugin
@@ -4,9 +4,18 @@ metadata:
annotations: annotations:
nginx.ingress.kubernetes.io/whitelist-source-range: "69.49.241.121/32" # hostgator ip nginx.ingress.kubernetes.io/whitelist-source-range: "69.49.241.121/32" # hostgator ip
nginx.ingress.kubernetes.io/proxy-body-size: "0" nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
nginx.ingress.kubernetes.io/server-snippet: | nginx.ingress.kubernetes.io/server-snippet: |
underscores_in_headers on; underscores_in_headers on;
ignore_invalid_headers on; ignore_invalid_headers on;
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "8"
nginx.ingress.kubernetes.io/proxy-busy-buffers-size: "64k"
{{- if .Values.maestro.restricted_ip}}
nginx.ingress.kubernetes.io/whitelist-source-range: {{ .Values.maestro.restricted_ip }}
{{- end }}
generation: 1 generation: 1
labels: labels:
+20
View File
@@ -0,0 +1,20 @@
import jwt, { JwtPayload } from 'jsonwebtoken';
export function extractUserFrom(aRawJwt: string) {
const decodedToken = jwt.decode(aRawJwt, {
complete: true,
});
const payload = decodedToken.payload as JwtPayload;
return {
user_id: payload.user_id,
username: payload.username,
permissions: payload.permissions,
customer_id: payload.customer_id,
customer_name: payload.customer_name,
customer_tier: payload.customer_tier,
customer_modules: payload.customer_modules,
access_token: aRawJwt,
}
}
+1
View File
@@ -26,6 +26,7 @@ async function bootstrap() {
corsOrigins.push( corsOrigins.push(
'https://app.stg.dadosfera.ai', 'https://app.stg.dadosfera.ai',
'https://app.dadosfera.ai', 'https://app.dadosfera.ai',
'https://private-frontend.stg.dadosfera.ai',
'https://unimed.dadosfera.ai', 'https://unimed.dadosfera.ai',
'https://boston-scientific.dadosfera.ai', 'https://boston-scientific.dadosfera.ai',
'https://plataforma.dadosfera.ai' 'https://plataforma.dadosfera.ai'
+6 -2
View File
@@ -1,4 +1,4 @@
import { Controller, Post, Body, Put, Get} from '@nestjs/common'; import { Controller, Body, Put, Get, NotFoundException} from '@nestjs/common';
import { AssignService } from './assign.service'; import { AssignService } from './assign.service';
import { CreateAssignDto } from './dto/create-assign.dto'; import { CreateAssignDto } from './dto/create-assign.dto';
import { Authenticated, RequireModule, RequireSomePermission } from 'src/decorators/authentication.decorator'; import { Authenticated, RequireModule, RequireSomePermission } from 'src/decorators/authentication.decorator';
@@ -28,6 +28,10 @@ export class AssignController {
@RequireModule(DADOSFERA_MODULES_KEYS.EMBED_ASSIGNED) @RequireModule(DADOSFERA_MODULES_KEYS.EMBED_ASSIGNED)
async get(@User() user: RequestUser) { async get(@User() user: RequestUser) {
const metadata = PackTheMetadata(user); const metadata = PackTheMetadata(user);
return await this.assignService.get(metadata); try {
return await this.assignService.get(metadata);
} catch (error) {
throw new NotFoundException(error.message)
}
} }
} }
+4 -34
View File
@@ -54,7 +54,6 @@ import jwt, { JwtPayload } from 'jsonwebtoken';
import { LanguageEnum } from 'src/utils/languages.enum'; import { LanguageEnum } from 'src/utils/languages.enum';
import { Language } from 'src/decorators/language.decorator'; import { Language } from 'src/decorators/language.decorator';
import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator'; import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator';
import { Cookie } from 'express-session';
type CookiesValues = { type CookiesValues = {
accessToken?: string; accessToken?: string;
@@ -498,29 +497,10 @@ export class AuthController {
const userId = req.cookies['ddf-user-id']; const userId = req.cookies['ddf-user-id'];
this.logger.info('Has cookie: ' + Boolean(accessToken)) this.logger.info('Has cookie: ' + Boolean(accessToken))
let payload: any;
let userInfo: any = {};
try { try {
// Decodifica e valida o JWT de acesso // Decodifica e valida o JWT de acesso
const decoded: any = accessToken && jwt.decode(accessToken, { complete: true }); const userDto = await this.authClient.extractUserFrom(accessToken);
if (!decoded) throw new Error('Invalid token') return res.status(200).json(userDto);
const { kid } = decoded.header;
// Busca a chave pública
const { keys } = await this.authClient.getPublicKeys();
const pemValue = keys.find((k) => k.kid === kid)?.pem;
if (!pemValue) throw new Error('Public key not found');
jwt.verify(accessToken, pemValue);
payload = decoded.payload;
userInfo = {
id: payload.user_id,
name: payload.username,
customer: {
id: payload.customer_id,
name: payload.customer_name,
tier: payload.customer_tier,
}
};
return res.status(200).json(userInfo);
} catch (err) { } catch (err) {
this.logger.error(err.message); this.logger.error(err.message);
const refreshToken = req.cookies['ddf-refresh-auth']; const refreshToken = req.cookies['ddf-refresh-auth'];
@@ -545,18 +525,8 @@ export class AuthController {
userId userId
}); });
// Decodifica novo token // Decodifica novo token
const decoded: any = jwt.decode(data.accessToken, { complete: true }); const userDto = await this.authClient.extractUserFrom(accessToken);
const payload = decoded.payload; return res.status(200).json(userDto);
userInfo = {
id: payload.user_id,
name: payload.username,
customer: {
id: payload.customer_id,
name: payload.customer_name,
tier: payload.customer_tier,
}
};
return res.status(200).json(userInfo);
} catch (refreshErr) { } catch (refreshErr) {
this.logger.error(refreshErr) this.logger.error(refreshErr)
return res.status(401).json({ error: 'Not authenticated' }); return res.status(401).json({ error: 'Not authenticated' });
+55 -8
View File
@@ -4,7 +4,7 @@ import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
import { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc'; import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc';
import { AuthProtoService as AuthServiceInterface, IdentityProviderProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service'; import { AuthProtoService as AuthServiceInterface, IdentityProviderProtoService, UsersProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service';
import { import {
AuthSnowflakeSignInRequest, AuthSnowflakeSignInRequest,
AuthSignInRequest, AuthSignInRequest,
@@ -21,18 +21,18 @@ import {
} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages'; } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/messages';
import { DucClient } from '../duc/client.config'; import { DucClient } from '../duc/client.config';
import { Metadata } from '@grpc/grpc-js'; import { Metadata } from '@grpc/grpc-js';
import { BulkEditResponse } from './dtos/login'; import { BulkEditResponse, UserDTO } from './dtos/login';
import jwt from 'jsonwebtoken';
import { PackTheMetadata } from 'src/utils/PackTheMetadata';
@Injectable() @Injectable()
export class AuthClientService implements OnModuleInit { export class AuthClientService implements OnModuleInit {
logger: DadosferaLogger; logger: DadosferaLogger;
private authService: AuthServiceInterface; private authService: AuthServiceInterface;
private identityProviderService: IdentityProviderProtoService; private userService: UsersProtoService;
constructor( constructor(
@Inject(DadosferaLogger) @Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger, dadosferaLogger: DadosferaLogger,
@@ -46,8 +46,8 @@ export class AuthClientService implements OnModuleInit {
ProtoServices.AuthProtoService, ProtoServices.AuthProtoService,
); );
this.identityProviderService = this.grpcClient.getService<IdentityProviderProtoService>( this.userService = this.grpcClient.getService<UsersProtoService>(
ProtoServices.IdentityProviderProtoService, ProtoServices.UsersProtoService,
); );
} }
@@ -283,4 +283,51 @@ export class AuthClientService implements OnModuleInit {
throw error; throw error;
} }
} }
private async getUser(id: string, metadata: Metadata) {
this.logger.info('getUser');
return await lastValueFrom(
this.userService.UserFindOneById({ id }, metadata),
);
}
public async extractUserFrom(token: string) {
const decoded: any = token && jwt.decode(token, { complete: true });
if (!decoded) throw new Error('Invalid token');
const { kid } = decoded.header;
// Busca a chave pública
const { keys } = await this.getPublicKeys();
const pemValue = keys.find((k) => k.kid === kid)?.pem;
if (!pemValue)
throw new Error('Public key not found');
jwt.verify(token, pemValue);
const payload = decoded.payload;
const metadata = PackTheMetadata({
customer_id: payload.customer_id
})
const {
user
} = await this.getUser(
payload.user_id,
metadata
);
const userDto: UserDTO = {
id: user.id,
name: user.username,
jobTitle: user?.jobTitle || null,
department: user?.department || null,
hierarchy: user?.hierarchy || null,
customer: {
id: payload.customer_id,
name: payload.customer_name,
tier: payload.customer_tier,
}
};
return userDto;
}
} }
+13
View File
@@ -140,3 +140,16 @@ export interface BulkEditResponse {
successfulUsers: string[]; successfulUsers: string[];
failedUsers: string[]; failedUsers: string[];
} }
export type UserDTO = {
id: string,
name: string,
jobTitle?: string,
department?: string,
hierarchy?: string,
customer: {
id: string,
name: string,
tier: string,
}
}
+4 -2
View File
@@ -197,9 +197,11 @@ class CatalogService implements OnModuleInit {
async getUserRolesIds(userId: string) { async getUserRolesIds(userId: string) {
const result = await this.userService.findOneById(userId).catch(() => null); const result = await this.userService.findOneById(userId).catch(() => null);
const roles_ids = result.user.roles.map((role) => role.id); if (result) {
return result.user.roles.map((role) => role.id);
}
return roles_ids; return [];
} }
async searchDataAssets( async searchDataAssets(
+25 -8
View File
@@ -1,29 +1,46 @@
import { Body, Controller, Inject, Param, Post, Req } from '@nestjs/common'; import { Body, Controller, Inject, Param, Post, Req } from '@nestjs/common';
import { init } from 'mixpanel';
import { Authenticated } from 'src/decorators/authentication.decorator';
import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator'; import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator';
import { RequestUser, User } from 'src/decorators/user.decorator'; import { RequestUser } from 'src/decorators/user.decorator';
import { MixpanelService } from './mixpanel.service'; import { MixpanelService } from './mixpanel.service';
import { extractUserFrom } from 'src/authentication/extract-user';
import DadosferaLogger from '@dadosfera/dadosfera-logs';
@ApiInternalOnlyController() @ApiInternalOnlyController()
@Controller('trackEvent') @Controller('trackEvent')
export class MixpanelController { export class MixpanelController {
logger: DadosferaLogger;
constructor( constructor(
private mixpanelService: MixpanelService @Inject(DadosferaLogger)
) {} dadosferaLogger: DadosferaLogger,
private mixpanelService: MixpanelService,
) {
this.logger = dadosferaLogger.logger;
}
@Post(':id') @Post(':id')
async trackEvent( async trackEvent(
@Param('id') id, @Param('id') id,
@Body() body, @Body() body,
@User() user: RequestUser,
@Req() request @Req() request
) { ) {
this.logger.info(`POST Track Event: ${id}`)
delete body.info; delete body.info;
const anonymousUser = {
username: "anonymous",
customer_name: "anonymous"
} as RequestUser
const hasToken = request.headers['authorization'];
const user = hasToken ? extractUserFrom(hasToken) : anonymousUser;
this.logger.info(`Has user: ${typeof hasToken == "string"}`)
await this.mixpanelService.track(id, user, request, body) await this.mixpanelService.track(id, user, request, body)
this.logger.info(`Event successful`)
return { id, body, user: user.username }; return { id, body, user: user.username };
} }
} }