Refactor client config (#529)

* Refactor client config

* Fix server tests

* Fix lint
This commit is contained in:
Charles Bochet
2023-07-07 11:10:42 -07:00
committed by GitHub
parent 11d18cc269
commit 26b033abc9
38 changed files with 386 additions and 180 deletions

View File

@ -1,5 +1,4 @@
DEBUG_MODE=false
AUTH_GOOGLE_ENABLED=false
ACCESS_TOKEN_SECRET=secret_jwt
ACCESS_TOKEN_EXPIRES_IN=5m
LOGIN_TOKEN_SECRET=secret_login_token

View File

@ -1,13 +1,21 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AnalyticsResolver } from './analytics.resolver';
import { AnalyticsService } from './analytics.service';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
describe('AnalyticsResolver', () => {
let resolver: AnalyticsResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AnalyticsResolver, AnalyticsService],
providers: [
AnalyticsResolver,
AnalyticsService,
{
provide: EnvironmentService,
useValue: {},
},
],
}).compile();
resolver = module.get<AnalyticsResolver>(AnalyticsResolver);

View File

@ -1,12 +1,19 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AnalyticsService } from './analytics.service';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
describe('AnalyticsService', () => {
let service: AnalyticsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AnalyticsService],
providers: [
AnalyticsService,
{
provide: EnvironmentService,
useValue: {},
},
],
}).compile();
service = module.get<AnalyticsService>(AnalyticsService);

View File

@ -3,12 +3,13 @@ import { User, Workspace } from '@prisma/client';
import axios, { AxiosInstance } from 'axios';
import { CreateAnalyticsInput } from './dto/create-analytics.input';
import { anonymize } from 'src/utils/anonymize';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
@Injectable()
export class AnalyticsService {
private readonly httpService: AxiosInstance;
constructor() {
constructor(private readonly environmentService: EnvironmentService) {
this.httpService = axios.create({
baseURL: 'https://t.twenty.com/api/v1/s2s',
});
@ -19,15 +20,26 @@ export class AnalyticsService {
user: User | undefined,
workspace: Workspace | undefined,
) {
if (process.env.IS_TELEMETRY_ENABLED === 'false') {
return;
if (!this.environmentService.isTelemetryEnabled()) {
return { success: true };
}
const anonymizationEnabled =
this.environmentService.isTelemetryAnonymizationEnabled();
const data = {
type: createEventInput.type,
data: {
userUUID: user ? anonymize(user.id) : undefined,
workspaceUUID: workspace ? anonymize(workspace.id) : undefined,
userUUID: user
? anonymizationEnabled
? anonymize(user.id)
: user.id
: undefined,
workspaceUUID: workspace
? anonymizationEnabled
? anonymize(workspace.id)
: workspace.id
: undefined,
workspaceDomain: workspace ? workspace.domainName : undefined,
...createEventInput.data,
},

View File

@ -1,5 +1,5 @@
import { Args, Mutation, Resolver, Query } from '@nestjs/graphql';
import { AuthTokens, ClientConfig } from './dto/token.entity';
import { AuthTokens } from './dto/token.entity';
import { TokenService } from './services/token.service';
import { RefreshTokenInput } from './dto/refresh-token.input';
import { BadRequestException } from '@nestjs/common';
@ -61,17 +61,4 @@ export class AuthResolver {
return { tokens: tokens };
}
@Query(() => ClientConfig)
async clientConfig(): Promise<ClientConfig> {
const displayGoogleLogin = process.env.AUTH_GOOGLE_CLIENT_ID !== undefined;
const prefillLoginWithSeed = process.env.NODE_ENV === 'development';
const clientConfig: ClientConfig = {
display_google_login: displayGoogleLogin,
prefill_login_with_seed: prefillLoginWithSeed,
};
return Promise.resolve(clientConfig);
}
}

View File

@ -23,12 +23,3 @@ export class AuthTokens {
@Field(() => AuthTokenPair)
tokens: AuthTokenPair;
}
@ObjectType()
export class ClientConfig {
@Field(() => Boolean)
display_google_login: boolean;
@Field(() => Boolean)
prefill_login_with_seed: boolean;
}

View File

@ -7,7 +7,7 @@ import { GoogleStrategy } from '../strategies/google.auth.strategy';
export class GoogleProviderEnabledGuard implements CanActivate {
constructor(private readonly environmentService: EnvironmentService) {}
canActivate(): boolean | Promise<boolean> | Observable<boolean> {
if (!this.environmentService.getAuthGoogleEnabled()) {
if (!this.environmentService.isAuthGoogleEnabled()) {
throw new NotFoundException('Google auth is not enabled');
}

View File

@ -0,0 +1,37 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
class AuthProviders {
@Field(() => Boolean)
google: boolean;
@Field(() => Boolean)
magicLink: boolean;
@Field(() => Boolean)
password: boolean;
}
@ObjectType()
class Telemetry {
@Field(() => Boolean)
enabled: boolean;
@Field(() => Boolean)
anonymizationEnabled: boolean;
}
@ObjectType()
export class ClientConfig {
@Field(() => AuthProviders, { nullable: false })
authProviders: AuthProviders;
@Field(() => Telemetry, { nullable: false })
telemetry: Telemetry;
@Field(() => Boolean)
demoMode: boolean;
@Field(() => Boolean)
debugMode: boolean;
}

View File

@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { ClientConfigResolver } from './client-config.resolver';
@Module({
providers: [ClientConfigResolver],
})
export class ClientConfigModule {}

View File

@ -0,0 +1,25 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ClientConfigResolver } from './client-config.resolver';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
describe('ClientConfigResolver', () => {
let resolver: ClientConfigResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ClientConfigResolver,
{
provide: EnvironmentService,
useValue: {},
},
],
}).compile();
resolver = module.get<ClientConfigResolver>(ClientConfigResolver);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@ -0,0 +1,28 @@
import { Resolver, Query } from '@nestjs/graphql';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
import { ClientConfig } from './client-config.entity';
@Resolver()
export class ClientConfigResolver {
constructor(private environmentService: EnvironmentService) {}
@Query(() => ClientConfig)
async clientConfig(): Promise<ClientConfig> {
const clientConfig: ClientConfig = {
authProviders: {
google: this.environmentService.isAuthGoogleEnabled() ?? false,
magicLink: false,
password: true,
},
telemetry: {
enabled: this.environmentService.isTelemetryEnabled() ?? false,
anonymizationEnabled:
this.environmentService.isTelemetryAnonymizationEnabled() ?? false,
},
demoMode: this.environmentService.isDemoMode() ?? false,
debugMode: this.environmentService.isDebugMode() ?? false,
};
return Promise.resolve(clientConfig);
}
}

View File

@ -8,6 +8,7 @@ import { AuthModule } from './auth/auth.module';
import { WorkspaceModule } from './workspace/workspace.module';
import { AnalyticsModule } from './analytics/analytics.module';
import { FileModule } from './file/file.module';
import { ClientConfigModule } from './client-config/client-config.module';
@Module({
imports: [
@ -20,6 +21,7 @@ import { FileModule } from './file/file.module';
WorkspaceModule,
AnalyticsModule,
FileModule,
ClientConfigModule,
],
exports: [
AuthModule,

View File

@ -19,7 +19,7 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
private readonly logger = new Logger(PrismaService.name);
constructor(private readonly environmentService: EnvironmentService) {
const debugMode = environmentService.getDebugMode();
const debugMode = environmentService.isDebugMode();
super({
errorFormat: 'minimal',
log: debugMode

View File

@ -8,8 +8,22 @@ import { StorageType } from './interfaces/storage.interface';
export class EnvironmentService {
constructor(private configService: ConfigService) {}
getDebugMode(): boolean | undefined {
return this.configService.get<boolean>('DEBUG_MODE')!;
isDebugMode(): boolean {
return this.configService.get<boolean>('DEBUG_MODE') ?? false;
}
isDemoMode(): boolean {
return this.configService.get<boolean>('DEMO_MODE') ?? false;
}
isTelemetryEnabled(): boolean {
return this.configService.get<boolean>('TELEMETRY_ENABLED') ?? true;
}
isTelemetryAnonymizationEnabled(): boolean | undefined {
return (
this.configService.get<boolean>('TELEMETRY_ANONYMIZATION_ENABLED') ?? true
);
}
getPGDatabaseUrl(): string {
@ -44,7 +58,7 @@ export class EnvironmentService {
return this.configService.get<string>('FRONT_AUTH_CALLBACK_URL')!;
}
getAuthGoogleEnabled(): boolean | undefined {
isAuthGoogleEnabled(): boolean | undefined {
return this.configService.get<boolean>('AUTH_GOOGLE_ENABLED');
}

View File

@ -16,12 +16,27 @@ import { IsAWSRegion } from './decorators/is-aws-region.decorator';
import { CastToBoolean } from './decorators/cast-to-boolean.decorator';
export class EnvironmentVariables {
// Stage
// Misc
@CastToBoolean()
@IsOptional()
@IsBoolean()
DEBUG_MODE?: boolean;
@CastToBoolean()
@IsOptional()
@IsBoolean()
DEMO_MODE?: boolean;
@CastToBoolean()
@IsOptional()
@IsBoolean()
TELEMETRY_ENABLED?: boolean;
@CastToBoolean()
@IsOptional()
@IsBoolean()
TELEMETRY_ANONYMIZATION_ENABLED?: boolean;
// Database
@IsUrl({ protocols: ['postgres'], require_tld: false })
PG_DATABASE_URL: string;

View File

@ -1,4 +1,4 @@
import { Global, Module } from '@nestjs/common';
import { Module } from '@nestjs/common';
import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
import { EnvironmentModule } from './environment/environment.module';
import { EnvironmentService } from './environment/environment.service';

View File

@ -1,9 +1,6 @@
import crypto from 'crypto';
export function anonymize(input) {
if (process.env.IS_TELEMETRY_ANONYMIZATION_ENABLED === 'false') {
return input;
}
export function anonymize(input: string) {
// md5 shorter than sha-256 and collisions are not a security risk in this use-case
return crypto.createHash('md5').update(input).digest('hex');
}