Secure connexion between TinyBird and webhookResponseGraph (#7913)

TLDR:
Secure connexion between tinybird and twenty using jwt when accessing
datasource from tinybird.

Solves:
https://github.com/twentyhq/private-issues/issues/73


In order to test:

1. Set ANALYTICS_ENABLED to true
2. Set TINYBIRD_JWT_TOKEN to the ADMIN token from the workspace
twenty_analytics_playground
3. Set TINYBIRD_JWT_TOKEN to the datasource or your admin token from the
workspace twenty_analytics_playground
4. Create a Webhook in twenty and set wich events it needs to track
5. Run twenty-worker in order to make the webhooks work.
6. Do your tasks in order to populate the data
7. Enter to settings> webhook>your webhook and the statistics section
should be displayed.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Ana Sofia Marin Alexandre
2024-10-21 12:42:52 -03:00
committed by GitHub
parent edf4ae084b
commit 373926b895
19 changed files with 178 additions and 46 deletions

View File

@ -1,6 +1,8 @@
import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module';
import { AnalyticsResolver } from './analytics.resolver';
import { AnalyticsService } from './analytics.service';
@ -9,6 +11,7 @@ const TINYBIRD_BASE_URL = 'https://api.eu-central-1.aws.tinybird.co/v0';
@Module({
providers: [AnalyticsResolver, AnalyticsService],
imports: [
JwtModule,
HttpModule.register({
baseURL: TINYBIRD_BASE_URL,
}),

View File

@ -1,7 +1,4 @@
import { Test, TestingModule } from '@nestjs/testing';
import { HttpService } from '@nestjs/axios';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { AnalyticsResolver } from './analytics.resolver';
import { AnalyticsService } from './analytics.service';
@ -13,13 +10,8 @@ describe('AnalyticsResolver', () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AnalyticsResolver,
AnalyticsService,
{
provide: EnvironmentService,
useValue: {},
},
{
provide: HttpService,
provide: AnalyticsService,
useValue: {},
},
],

View File

@ -1,6 +1,5 @@
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
@ -13,10 +12,7 @@ import { CreateAnalyticsInput } from './dtos/create-analytics.input';
@Resolver(() => Analytics)
export class AnalyticsResolver {
constructor(
private readonly analyticsService: AnalyticsService,
private readonly environmentService: EnvironmentService,
) {}
constructor(private readonly analyticsService: AnalyticsService) {}
@Mutation(() => Analytics)
track(

View File

@ -1,7 +1,8 @@
import { Test, TestingModule } from '@nestjs/testing';
import { HttpService } from '@nestjs/axios';
import { Test, TestingModule } from '@nestjs/testing';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
import { AnalyticsService } from './analytics.service';
@ -16,6 +17,10 @@ describe('AnalyticsService', () => {
provide: EnvironmentService,
useValue: {},
},
{
provide: JwtWrapperService,
useValue: {},
},
{
provide: HttpService,
useValue: {},

View File

@ -4,6 +4,7 @@ import { Injectable, Logger } from '@nestjs/common';
import { AxiosRequestConfig } from 'axios';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
type CreateEventInput = {
action: string;
@ -16,6 +17,7 @@ export class AnalyticsService {
private readonly defaultDatasource = 'event';
constructor(
private readonly jwtWrapperService: JwtWrapperService,
private readonly environmentService: EnvironmentService,
private readonly httpService: HttpService,
) {}
@ -58,7 +60,7 @@ export class AnalyticsService {
const config: AxiosRequestConfig = {
headers: {
Authorization:
'Bearer ' + this.environmentService.get('TINYBIRD_TOKEN'),
'Bearer ' + this.environmentService.get('TINYBIRD_INGEST_TOKEN'),
},
};
@ -86,4 +88,25 @@ export class AnalyticsService {
return { success: true };
}
async generateWorkspaceJwt(workspaceId: string | undefined) {
const pipeId = 't_b49e0fe60f9e438eae81cb31c5260df2'; // refactor this pass as params
//perhaps a constant of name:pipeId??? better typing in this func^
const payload = {
name: 'my_demo_jwt',
workspace_id: this.environmentService.get('TINYBIRD_WORKSPACE_UUID'),
scopes: [
{
type: 'PIPES:READ',
resource: pipeId,
fixed_params: { workspaceId: workspaceId },
},
],
};
return this.jwtWrapperService.sign(payload, {
secret: this.environmentService.get('TINYBIRD_GENERATE_JWT_TOKEN'),
expiresIn: '7d',
});
}
}

View File

@ -95,7 +95,15 @@ export class EnvironmentVariables {
@IsString()
@ValidateIf((env) => env.ANALYTICS_ENABLED)
TINYBIRD_TOKEN: string;
TINYBIRD_INGEST_TOKEN: string;
@IsString()
@ValidateIf((env) => env.ANALYTICS_ENABLED)
TINYBIRD_WORKSPACE_UUID: string;
@IsString()
@ValidateIf((env) => env.ANALYTICS_ENABLED)
TINYBIRD_GENERATE_JWT_TOKEN: string;
@CastToPositiveNumber()
@IsNumber()

View File

@ -7,6 +7,7 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { AnalyticsModule } from 'src/engine/core-modules/analytics/analytics.module';
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
import { FileModule } from 'src/engine/core-modules/file/file.module';
import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
@ -37,6 +38,7 @@ import { UserService } from './services/user.service';
OnboardingModule,
TypeOrmModule.forFeature([KeyValuePair], 'core'),
UserVarsModule,
AnalyticsModule,
],
exports: [UserService],
providers: [UserService, UserResolver, TypeORMService],

View File

@ -19,6 +19,7 @@ import { Repository } from 'typeorm';
import { SupportDriver } from 'src/engine/core-modules/environment/interfaces/support.interface';
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
import { AnalyticsService } from 'src/engine/core-modules/analytics/analytics.service';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
import { FileService } from 'src/engine/core-modules/file/services/file.service';
@ -55,6 +56,7 @@ export class UserResolver {
private readonly onboardingService: OnboardingService,
private readonly userVarService: UserVarsService,
private readonly fileService: FileService,
private readonly analyticsService: AnalyticsService,
) {}
@Query(() => User)
@ -154,6 +156,15 @@ export class UserResolver {
return getHMACKey(parent.email, key);
}
@ResolveField(() => String, {
nullable: true,
})
async analyticsTinybirdJwt(
@AuthWorkspace() workspace: Workspace | undefined,
): Promise<string> {
return await this.analyticsService.generateWorkspaceJwt(workspace?.id);
}
@Mutation(() => String)
async uploadProfilePicture(
@AuthUser() { id }: User,