feat: database secrets
This commit is contained in:
		@@ -6,7 +6,7 @@ import fs from 'fs/promises';
 | 
			
		||||
import { ComposeFile, createDirectories, decrypt, defaultComposeConfiguration, encrypt, errorHandler, executeDockerCmd, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePublicPort, listSettings, makeLabelForStandaloneDatabase, prisma, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common';
 | 
			
		||||
import { day } from '../../../../lib/dayjs';
 | 
			
		||||
 | 
			
		||||
import { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
 | 
			
		||||
import { DeleteDatabaseSecret, GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSecret, SaveDatabaseSettings, SaveVersion } from '../../../../types';
 | 
			
		||||
import { DeleteDatabase, SaveDatabaseType } from './types';
 | 
			
		||||
 | 
			
		||||
export async function listDatabases(request: FastifyRequest) {
 | 
			
		||||
@@ -220,7 +220,7 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
 | 
			
		||||
 | 
			
		||||
        const database = await prisma.database.findFirst({
 | 
			
		||||
            where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
 | 
			
		||||
            include: { destinationDocker: true, settings: true }
 | 
			
		||||
            include: { destinationDocker: true, settings: true, databaseSecret: true }
 | 
			
		||||
        });
 | 
			
		||||
        const { arch } = await listSettings();
 | 
			
		||||
        if (database.dbUserPassword) database.dbUserPassword = decrypt(database.dbUserPassword);
 | 
			
		||||
@@ -230,7 +230,8 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
 | 
			
		||||
            destinationDockerId,
 | 
			
		||||
            destinationDocker,
 | 
			
		||||
            publicPort,
 | 
			
		||||
            settings: { isPublic }
 | 
			
		||||
            settings: { isPublic },
 | 
			
		||||
            databaseSecret
 | 
			
		||||
        } = database;
 | 
			
		||||
        const { privatePort, command, environmentVariables, image, volume, ulimits } =
 | 
			
		||||
            generateDatabaseConfiguration(database, arch);
 | 
			
		||||
@@ -240,7 +241,11 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
 | 
			
		||||
        const labels = await makeLabelForStandaloneDatabase({ id, image, volume });
 | 
			
		||||
 | 
			
		||||
        const { workdir } = await createDirectories({ repository: type, buildId: id });
 | 
			
		||||
 | 
			
		||||
        if (databaseSecret.length > 0) {
 | 
			
		||||
            databaseSecret.forEach((secret) => {
 | 
			
		||||
                environmentVariables[secret.name] = decrypt(secret.value);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        const composeFile: ComposeFile = {
 | 
			
		||||
            version: '3.8',
 | 
			
		||||
            services: {
 | 
			
		||||
@@ -262,25 +267,16 @@ export async function startDatabase(request: FastifyRequest<OnlyId>) {
 | 
			
		||||
            },
 | 
			
		||||
            volumes: {
 | 
			
		||||
                [volumeName]: {
 | 
			
		||||
                    external: true
 | 
			
		||||
                    name: volumeName,
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const composeFileDestination = `${workdir}/docker-compose.yaml`;
 | 
			
		||||
        await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
 | 
			
		||||
        try {
 | 
			
		||||
            await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker volume create ${volumeName}` })
 | 
			
		||||
        } catch (error) { }
 | 
			
		||||
        try {
 | 
			
		||||
            await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up -d` })
 | 
			
		||||
            if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
 | 
			
		||||
            return {};
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            throw {
 | 
			
		||||
                error
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        await executeDockerCmd({ dockerId: destinationDocker.id, command: `docker compose -f ${composeFileDestination} up -d` })
 | 
			
		||||
        if (isPublic) await startTraefikTCPProxy(destinationDocker, id, publicPort, privatePort);
 | 
			
		||||
        return {};
 | 
			
		||||
 | 
			
		||||
    } catch ({ status, message }) {
 | 
			
		||||
        return errorHandler({ status, message })
 | 
			
		||||
    }
 | 
			
		||||
@@ -462,4 +458,69 @@ export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseS
 | 
			
		||||
    } catch ({ status, message }) {
 | 
			
		||||
        return errorHandler({ status, message })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
export async function getDatabaseSecrets(request: FastifyRequest<OnlyId>) {
 | 
			
		||||
    try {
 | 
			
		||||
        const { id } = request.params
 | 
			
		||||
        let secrets = await prisma.databaseSecret.findMany({
 | 
			
		||||
            where: { databaseId: id },
 | 
			
		||||
            orderBy: { createdAt: 'desc' }
 | 
			
		||||
        });
 | 
			
		||||
        secrets = secrets.map((secret) => {
 | 
			
		||||
            secret.value = decrypt(secret.value);
 | 
			
		||||
            return secret;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            secrets
 | 
			
		||||
        }
 | 
			
		||||
    } catch ({ status, message }) {
 | 
			
		||||
        return errorHandler({ status, message })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function saveDatabaseSecret(request: FastifyRequest<SaveDatabaseSecret>, reply: FastifyReply) {
 | 
			
		||||
    try {
 | 
			
		||||
        const { id } = request.params
 | 
			
		||||
        let { name, value, isNew } = request.body
 | 
			
		||||
 | 
			
		||||
        if (isNew) {
 | 
			
		||||
            const found = await prisma.databaseSecret.findFirst({ where: { name, databaseId: id } });
 | 
			
		||||
            if (found) {
 | 
			
		||||
                throw `Secret ${name} already exists.`
 | 
			
		||||
            } else {
 | 
			
		||||
                value = encrypt(value.trim());
 | 
			
		||||
                await prisma.databaseSecret.create({
 | 
			
		||||
                    data: { name, value, database: { connect: { id } } }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            value = encrypt(value.trim());
 | 
			
		||||
            const found = await prisma.databaseSecret.findFirst({ where: { databaseId: id, name } });
 | 
			
		||||
 | 
			
		||||
            if (found) {
 | 
			
		||||
                await prisma.databaseSecret.updateMany({
 | 
			
		||||
                    where: { databaseId: id, name },
 | 
			
		||||
                    data: { value }
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                await prisma.databaseSecret.create({
 | 
			
		||||
                    data: { name, value, database: { connect: { id } } }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return reply.code(201).send()
 | 
			
		||||
    } catch ({ status, message }) {
 | 
			
		||||
        return errorHandler({ status, message })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
export async function deleteDatabaseSecret(request: FastifyRequest<DeleteDatabaseSecret>) {
 | 
			
		||||
    try {
 | 
			
		||||
        const { id } = request.params
 | 
			
		||||
        const { name } = request.body
 | 
			
		||||
        await prisma.databaseSecret.deleteMany({ where: { databaseId: id, name } });
 | 
			
		||||
        return {}
 | 
			
		||||
    } catch ({ status, message }) {
 | 
			
		||||
        return errorHandler({ status, message })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import { FastifyPluginAsync } from 'fastify';
 | 
			
		||||
import { deleteDatabase, getDatabase, getDatabaseLogs, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
 | 
			
		||||
import { deleteDatabase, deleteDatabaseSecret, getDatabase, getDatabaseLogs, getDatabaseSecrets, getDatabaseStatus, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSecret, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
 | 
			
		||||
 | 
			
		||||
import type { DeleteDatabase, GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
 | 
			
		||||
import type { DeleteDatabase, DeleteDatabaseSecret, GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSecret, SaveDatabaseSettings, SaveVersion } from '../../../../types';
 | 
			
		||||
import type { SaveDatabaseType } from './types';
 | 
			
		||||
 | 
			
		||||
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
 | 
			
		||||
@@ -19,6 +19,10 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
 | 
			
		||||
 | 
			
		||||
    fastify.post<SaveDatabaseSettings>('/:id/settings', async (request) => await saveDatabaseSettings(request));
 | 
			
		||||
 | 
			
		||||
    fastify.get<OnlyId>('/:id/secrets', async (request) => await getDatabaseSecrets(request));
 | 
			
		||||
    fastify.post<SaveDatabaseSecret>('/:id/secrets', async (request, reply) => await saveDatabaseSecret(request, reply));
 | 
			
		||||
    fastify.delete<DeleteDatabaseSecret>('/:id/secrets', async (request) => await deleteDatabaseSecret(request));
 | 
			
		||||
 | 
			
		||||
    fastify.get('/:id/configuration/type', async (request) => await getDatabaseTypes(request));
 | 
			
		||||
    fastify.post<SaveDatabaseType>('/:id/configuration/type', async (request, reply) => await saveDatabaseType(request, reply));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user