Add endpoint to create postgres credentials (#5767)

First step for creating credentials for database proxy.

In next PRs:
- When calling endpoint, create database Postgres on proxy server 
- Setup user on database using postgresCredentials
- Build remote server on DB to access workspace data
This commit is contained in:
Thomas Trompette
2024-06-07 14:45:24 +02:00
committed by GitHub
parent e478c68734
commit 1208fed7b3
9 changed files with 264 additions and 0 deletions

View File

@ -0,0 +1,20 @@
import { ObjectType, Field } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@ObjectType('PostgresCredentials')
export class PostgresCredentialsDTO {
@IDField(() => UUIDScalarType)
id: string;
@Field()
user: string;
@Field()
password: string;
@Field()
workspaceId: string;
}

View File

@ -0,0 +1,40 @@
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
ManyToOne,
Relation,
} from 'typeorm';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
@Entity({ name: 'postgresCredentials', schema: 'core' })
export class PostgresCredentials {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ nullable: false })
user: string;
@Column({ nullable: false })
passwordHash: string;
@CreateDateColumn({ type: 'timestamptz' })
createdAt: Date;
@UpdateDateColumn({ type: 'timestamptz' })
updatedAt: Date;
@Column({ nullable: true, type: 'timestamptz' })
deletedAt: Date;
@ManyToOne(() => Workspace, (workspace) => workspace.allPostgresCredentials, {
onDelete: 'CASCADE',
})
workspace: Relation<Workspace>;
@Column({ nullable: false, type: 'uuid' })
workspaceId: string;
}

View File

@ -0,0 +1,20 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PostgresCredentials } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.entity';
import { PostgresCredentialsResolver } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.resolver';
import { PostgresCredentialsService } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.service';
import { EnvironmentModule } from 'src/engine/integrations/environment/environment.module';
@Module({
imports: [
TypeOrmModule.forFeature([PostgresCredentials], 'core'),
EnvironmentModule,
],
providers: [
PostgresCredentialsResolver,
PostgresCredentialsService,
PostgresCredentials,
],
})
export class PostgresCredentialsModule {}

View File

@ -0,0 +1,35 @@
import { UseGuards } from '@nestjs/common';
import { Resolver, Mutation, Query } from '@nestjs/graphql';
import { PostgresCredentialsDTO } from 'src/engine/core-modules/postgres-credentials/dtos/postgres-credentials.dto';
import { PostgresCredentialsService } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
@Resolver(() => PostgresCredentialsDTO)
export class PostgresCredentialsResolver {
constructor(
private readonly postgresCredentialsService: PostgresCredentialsService,
) {}
@UseGuards(JwtAuthGuard)
@Mutation(() => PostgresCredentialsDTO)
async enablePostgresProxy(@AuthWorkspace() { id: workspaceId }: Workspace) {
return this.postgresCredentialsService.enablePostgresProxy(workspaceId);
}
@UseGuards(JwtAuthGuard)
@Mutation(() => PostgresCredentialsDTO)
async disablePostgresProxy(@AuthWorkspace() { id: workspaceId }: Workspace) {
return this.postgresCredentialsService.disablePostgresProxy(workspaceId);
}
@UseGuards(JwtAuthGuard)
@Query(() => PostgresCredentialsDTO, { nullable: true })
async getPostgresCredentials(
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
return this.postgresCredentialsService.getPostgresCredentials(workspaceId);
}
}

View File

@ -0,0 +1,117 @@
import { InjectRepository } from '@nestjs/typeorm';
import { BadRequestException } from '@nestjs/common';
import { randomBytes } from 'crypto';
import { Repository } from 'typeorm';
import {
decryptText,
encryptText,
} from 'src/engine/core-modules/auth/auth.util';
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
import { PostgresCredentials } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.entity';
import { NotFoundError } from 'src/engine/utils/graphql-errors.util';
import { PostgresCredentialsDTO } from 'src/engine/core-modules/postgres-credentials/dtos/postgres-credentials.dto';
export class PostgresCredentialsService {
constructor(
@InjectRepository(PostgresCredentials, 'core')
private readonly postgresCredentialsRepository: Repository<PostgresCredentials>,
private readonly environmentService: EnvironmentService,
) {}
async enablePostgresProxy(
workspaceId: string,
): Promise<PostgresCredentialsDTO> {
const user = `user_${randomBytes(4).toString('hex')}`;
const password = randomBytes(16).toString('hex');
const key = this.environmentService.get('LOGIN_TOKEN_SECRET');
const passwordHash = encryptText(password, key);
const existingCredentials =
await this.postgresCredentialsRepository.findOne({
where: {
workspaceId,
},
});
if (existingCredentials) {
throw new BadRequestException(
'Postgres credentials already exist for this workspace',
);
}
const postgresCredentials = await this.postgresCredentialsRepository.create(
{
user,
passwordHash,
workspaceId,
},
);
await this.postgresCredentialsRepository.save(postgresCredentials);
return {
id: postgresCredentials.id,
user,
password,
workspaceId,
};
}
async disablePostgresProxy(
workspaceId: string,
): Promise<PostgresCredentialsDTO> {
const postgresCredentials =
await this.postgresCredentialsRepository.findOne({
where: {
workspaceId,
},
});
if (!postgresCredentials?.id) {
throw new NotFoundError(
'No valid Postgres credentials not found for this workspace',
);
}
await this.postgresCredentialsRepository.delete({
id: postgresCredentials.id,
});
const key = this.environmentService.get('LOGIN_TOKEN_SECRET');
return {
id: postgresCredentials.id,
user: postgresCredentials.user,
password: decryptText(postgresCredentials.passwordHash, key),
workspaceId: postgresCredentials.workspaceId,
};
}
async getPostgresCredentials(
workspaceId: string,
): Promise<PostgresCredentialsDTO | null> {
const postgresCredentials =
await this.postgresCredentialsRepository.findOne({
where: {
workspaceId,
},
});
if (!postgresCredentials) {
return null;
}
const key = this.environmentService.get('LOGIN_TOKEN_SECRET');
return {
id: postgresCredentials.id,
user: postgresCredentials.user,
password: decryptText(postgresCredentials.passwordHash, key),
workspaceId: postgresCredentials.workspaceId,
};
}
}