"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SecretsProvidersConnectionsService = void 0;
const api_types_1 = require("@n8n/api-types");
const backend_common_1 = require("@n8n/backend-common");
const db_1 = require("@n8n/db");
const di_1 = require("@n8n/di");
const typeorm_1 = require("@n8n/typeorm");
const n8n_core_1 = require("n8n-core");
const n8n_workflow_1 = require("n8n-workflow");
const provider_registry_service_1 = require("./provider-registry.service");
const credential_dependency_service_1 = require("../../credentials/credential-dependency.service");
const bad_request_error_1 = require("../../errors/response-errors/bad-request.error");
const not_found_error_1 = require("../../errors/response-errors/not-found.error");
const event_service_1 = require("../../events/event.service");
const external_secrets_manager_ee_1 = require("../../modules/external-secrets.ee/external-secrets-manager.ee");
const redaction_service_ee_1 = require("../../modules/external-secrets.ee/redaction.service.ee");
let SecretsProvidersConnectionsService = class SecretsProvidersConnectionsService {
    constructor(logger, repository, projectAccessRepository, credentialDependencyService, providerRegistry, cipher, externalSecretsManager, redactionService, eventService) {
        this.logger = logger;
        this.repository = repository;
        this.projectAccessRepository = projectAccessRepository;
        this.credentialDependencyService = credentialDependencyService;
        this.providerRegistry = providerRegistry;
        this.cipher = cipher;
        this.externalSecretsManager = externalSecretsManager;
        this.redactionService = redactionService;
        this.eventService = eventService;
        this.logger = this.logger.scoped('external-secrets');
    }
    async createConnection(proposedConnection, userId, projectRole, userRole) {
        const existing = await this.repository.findOne({
            where: { providerKey: proposedConnection.providerKey },
        });
        if (existing) {
            throw new bad_request_error_1.BadRequestError(`Connection with key "${proposedConnection.providerKey}" already exists`);
        }
        const encryptedSettings = this.encryptConnectionSettings(proposedConnection.settings);
        const connection = this.repository.create({
            ...proposedConnection,
            encryptedSettings,
            isEnabled: true,
        });
        const savedConnection = await this.repository.save(connection);
        if (proposedConnection.projectIds.length > 0) {
            const entries = proposedConnection.projectIds.map((projectId) => this.projectAccessRepository.create({
                secretsProviderConnectionId: savedConnection.id,
                projectId,
                role: projectRole,
            }));
            await this.projectAccessRepository.save(entries);
        }
        const result = (await this.repository.findOne({
            where: { providerKey: proposedConnection.providerKey },
        }));
        await this.externalSecretsManager.syncProviderConnection(proposedConnection.providerKey);
        this.eventService.emit('external-secrets-connection-created', {
            userId,
            userRole,
            providerKey: result.providerKey,
            vaultType: result.type,
            ...this.extractProjectInfo(result),
        });
        return result;
    }
    async updateProjectConnection(providerKey, updates, userId, userRole) {
        const connection = await this.findConnectionOrFail(providerKey);
        this.applyConnectionUpdates(connection, updates);
        await this.repository.save(connection);
        return await this.syncAndEmitUpdate(providerKey, userId, userRole);
    }
    async updateGlobalConnection(providerKey, updates, userId, userRole) {
        const connection = await this.findConnectionOrFail(providerKey);
        this.applyConnectionUpdates(connection, updates);
        await this.repository.save(connection);
        if (updates.projectIds !== undefined) {
            const existing = await this.projectAccessRepository.findByConnectionId(connection.id);
            const existingProjectIds = new Set(existing.map((e) => e.projectId));
            const desiredProjectIds = new Set(updates.projectIds);
            const projectIdsToRemove = existing
                .filter((e) => !desiredProjectIds.has(e.projectId))
                .map((e) => e.projectId);
            const entriesToAdd = updates.projectIds
                .filter((id) => !existingProjectIds.has(id))
                .map((projectId) => ({
                projectId,
                role: 'secretsProviderConnection:user',
            }));
            await this.projectAccessRepository.updateProjectAccess(connection.id, projectIdsToRemove, entriesToAdd);
        }
        return await this.syncAndEmitUpdate(providerKey, userId, userRole);
    }
    applyConnectionUpdates(connection, updates) {
        if (updates.type !== undefined) {
            connection.type = updates.type;
            if (!updates.settings) {
                throw new bad_request_error_1.BadRequestError('When changing the connection type, new settings must be provided as well');
            }
        }
        if (updates.settings !== undefined) {
            const savedSettings = this.decryptConnectionSettings(connection.encryptedSettings);
            const unredactedSettings = this.redactionService.unredact(updates.settings, savedSettings);
            connection.encryptedSettings = this.encryptConnectionSettings(unredactedSettings);
        }
        if (updates.isEnabled !== undefined) {
            connection.isEnabled = updates.isEnabled;
        }
    }
    async syncAndEmitUpdate(providerKey, userId, userRole) {
        await this.externalSecretsManager.syncProviderConnection(providerKey);
        const result = (await this.repository.findOne({
            where: { providerKey },
        }));
        this.eventService.emit('external-secrets-connection-updated', {
            userId,
            userRole,
            providerKey: result.providerKey,
            vaultType: result.type,
            ...this.extractProjectInfo(result),
        });
        return result;
    }
    async deleteConnection(providerKey, userId, userRole) {
        const connection = await this.findConnectionOrFail(providerKey);
        const projectInfo = this.extractProjectInfo(connection);
        const dependencyId = connection.id.toString();
        await this.repository.manager.transaction(async (entityManager) => {
            await this.projectAccessRepository.deleteByConnectionId(connection.id, entityManager);
            await this.credentialDependencyService.deleteDependencyById({
                dependencyType: credential_dependency_service_1.EXTERNAL_SECRET_PROVIDER_DEPENDENCY_TYPE,
                dependencyId,
                entityManager,
            });
            await entityManager.delete(this.repository.target, { id: connection.id });
        });
        await this.externalSecretsManager.syncProviderConnection(providerKey);
        this.eventService.emit('external-secrets-connection-deleted', {
            userId,
            userRole,
            providerKey: connection.providerKey,
            vaultType: connection.type,
            ...projectInfo,
        });
        return connection;
    }
    async findConnectionOrFail(providerKey) {
        const connection = await this.repository.findOne({ where: { providerKey } });
        if (!connection) {
            throw new not_found_error_1.NotFoundError(`Connection with key "${providerKey}" not found`);
        }
        return connection;
    }
    async getConnection(providerKey) {
        const connection = await this.repository.findOne({ where: { providerKey } });
        if (!connection) {
            throw new not_found_error_1.NotFoundError(`Connection with key "${providerKey}" not found`);
        }
        return connection;
    }
    async listConnections() {
        return await this.repository.findAll();
    }
    async getGlobalCompletions() {
        const connectedProviderKeys = this.providerRegistry.getConnectedNames();
        return await this.repository.findEnabledGlobalConnections({
            providerKeys: connectedProviderKeys,
        });
    }
    async getProjectCompletions(projectId) {
        const connectedProviderKeys = this.providerRegistry.getConnectedNames();
        return await this.repository.findEnabledByProjectId(projectId, {
            providerKeys: connectedProviderKeys,
        });
    }
    async listConnectionsForProject(projectId) {
        return await this.repository.findAllAccessibleByProjectWithProjectAccess(projectId);
    }
    toSecretCompletionsResponse(connections) {
        return Object.fromEntries(connections.map((connection) => [
            connection.providerKey,
            this.externalSecretsManager.getSecretNames(connection.providerKey),
        ]));
    }
    toPublicConnectionListItem(connection) {
        const secretNames = this.externalSecretsManager.getSecretNames(connection.providerKey);
        const connectionInstance = this.externalSecretsManager.getProvider(connection.providerKey);
        return {
            id: String(connection.id),
            name: connection.providerKey,
            type: connection.type,
            isEnabled: connection.isEnabled,
            secretsCount: secretNames.length,
            state: connectionInstance?.state ?? 'initializing',
            projects: connection.projectAccess.map((access) => ({
                id: access.project.id,
                name: access.project.name,
                role: access.role,
            })),
            createdAt: connection.createdAt.toISOString(),
            updatedAt: connection.updatedAt.toISOString(),
        };
    }
    toPublicConnection(connection) {
        const decryptedSettings = this.decryptConnectionSettings(connection.encryptedSettings);
        const properties = this.externalSecretsManager.getProviderProperties(connection.type);
        const redactedSettings = this.redactionService.redact(decryptedSettings, properties);
        const secretNames = this.externalSecretsManager.getSecretNames(connection.providerKey);
        const connectionInstance = this.externalSecretsManager.getProvider(connection.providerKey);
        return {
            id: String(connection.id),
            name: connection.providerKey,
            type: connection.type,
            isEnabled: connection.isEnabled,
            secretsCount: secretNames.length,
            state: connectionInstance?.state ?? 'initializing',
            secrets: secretNames.map((name) => ({ name })),
            projects: connection.projectAccess.map((access) => ({
                id: access.project.id,
                name: access.project.name,
                role: access.role,
            })),
            settings: redactedSettings,
            createdAt: connection.createdAt.toISOString(),
            updatedAt: connection.updatedAt.toISOString(),
        };
    }
    async testConnection(providerKey, userId, userRole) {
        const connection = await this.getConnection(providerKey);
        const decryptedSettings = this.decryptConnectionSettings(connection.encryptedSettings);
        const result = await this.externalSecretsManager.testProviderSettings(connection.type, decryptedSettings);
        const response = api_types_1.testSecretProviderConnectionResponseSchema.parse(result);
        this.eventService.emit('external-secrets-connection-tested', {
            userId,
            userRole,
            providerKey: connection.providerKey,
            vaultType: connection.type,
            ...this.extractProjectInfo(connection),
            isValid: response.success,
            ...(response.error && { errorMessage: response.error }),
        });
        return response;
    }
    async reloadConnectionSecrets(providerKey, userId, userRole) {
        try {
            const connection = await this.getConnection(providerKey);
            await this.externalSecretsManager.updateProvider(providerKey);
            this.eventService.emit('external-secrets-connection-reloaded', {
                userId,
                userRole,
                providerKey: connection.providerKey,
                vaultType: connection.type,
                ...this.extractProjectInfo(connection),
            });
            return api_types_1.reloadSecretProviderConnectionResponseSchema.parse({ success: true });
        }
        catch (error) {
            if (error instanceof not_found_error_1.NotFoundError) {
                throw error;
            }
            this.logger.warn(`Failed to reload provider ${providerKey}`, { providerKey });
            return api_types_1.reloadSecretProviderConnectionResponseSchema.parse({ success: false });
        }
    }
    extractProjectInfo(connection) {
        return {
            projects: connection.projectAccess.map((access) => ({
                id: access.project.id,
                name: access.project.name,
            })),
        };
    }
    async cleanupConnectionsForProjectDeletion(projectId) {
        const accessEntries = await this.projectAccessRepository.findByProjectId(projectId);
        const providerKeysToSync = new Set();
        const ownerConnectionIds = new Set();
        const nonOwnerConnectionIds = new Set();
        for (const access of accessEntries) {
            providerKeysToSync.add(access.secretsProviderConnection.providerKey);
            if (access.role === 'secretsProviderConnection:owner') {
                ownerConnectionIds.add(access.secretsProviderConnectionId);
            }
            else {
                nonOwnerConnectionIds.add(access.secretsProviderConnectionId);
            }
        }
        await this.repository.manager.transaction(async (entityManager) => {
            if (ownerConnectionIds.size > 0) {
                await this.credentialDependencyService.deleteDependenciesByIds({
                    dependencyType: credential_dependency_service_1.EXTERNAL_SECRET_PROVIDER_DEPENDENCY_TYPE,
                    dependencyIds: [...ownerConnectionIds].map((id) => id.toString()),
                    entityManager,
                });
                await entityManager.delete(this.repository.target, { id: (0, typeorm_1.In)([...ownerConnectionIds]) });
            }
            if (nonOwnerConnectionIds.size > 0) {
                await entityManager.delete(this.projectAccessRepository.target, {
                    projectId,
                    secretsProviderConnectionId: (0, typeorm_1.In)([...nonOwnerConnectionIds]),
                });
                await entityManager.update(this.repository.target, { id: (0, typeorm_1.In)([...nonOwnerConnectionIds]) }, { isEnabled: false });
            }
        });
        for (const providerKey of providerKeysToSync) {
            await this.externalSecretsManager.syncProviderConnection(providerKey);
        }
    }
    encryptConnectionSettings(settings) {
        return this.cipher.encrypt(settings);
    }
    async getConnectionForProject(providerKey, projectId) {
        const connection = await this.repository.findByProviderKeyAndProjectId(providerKey, projectId);
        if (!connection) {
            throw new not_found_error_1.NotFoundError(`Connection with key "${providerKey}" not found`);
        }
        return connection;
    }
    async getConnectionAccessibleFromProject(providerKey, projectId) {
        const connection = await this.repository.findAccessibleByProviderKeyAndProjectId(providerKey, projectId);
        if (!connection) {
            throw new not_found_error_1.NotFoundError(`Connection with key "${providerKey}" not found`);
        }
        return connection;
    }
    async deleteConnectionForProject(providerKey, projectId) {
        const connection = await this.repository.findByProviderKeyAndProjectId(providerKey, projectId);
        if (!connection) {
            throw new not_found_error_1.NotFoundError(`Connection with key "${providerKey}" not found`);
        }
        const connectionId = connection.id;
        await this.credentialDependencyService.deleteDependencyById({
            dependencyType: credential_dependency_service_1.EXTERNAL_SECRET_PROVIDER_DEPENDENCY_TYPE,
            dependencyId: connectionId.toString(),
        });
        await this.projectAccessRepository.deleteByConnectionId(connectionId);
        await this.repository.delete({ id: connectionId });
        await this.externalSecretsManager.syncProviderConnection(providerKey);
        return connection;
    }
    decryptConnectionSettings(encryptedSettings) {
        return (0, n8n_workflow_1.jsonParse)(this.cipher.decrypt(encryptedSettings));
    }
};
exports.SecretsProvidersConnectionsService = SecretsProvidersConnectionsService;
exports.SecretsProvidersConnectionsService = SecretsProvidersConnectionsService = __decorate([
    (0, di_1.Service)(),
    __metadata("design:paramtypes", [backend_common_1.Logger,
        db_1.SecretsProviderConnectionRepository,
        db_1.ProjectSecretsProviderAccessRepository,
        credential_dependency_service_1.CredentialDependencyService,
        provider_registry_service_1.ExternalSecretsProviderRegistry,
        n8n_core_1.Cipher,
        external_secrets_manager_ee_1.ExternalSecretsManager,
        redaction_service_ee_1.RedactionService,
        event_service_1.EventService])
], SecretsProvidersConnectionsService);
//# sourceMappingURL=secrets-providers-connections.service.ee.js.map