n8n-workflow-builder-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/cursor_rules.mdc +53 -0
- package/.cursor/rules/dev_workflow.mdc +219 -0
- package/.cursor/rules/mcp.mdc +430 -0
- package/.cursor/rules/self_improve.mdc +72 -0
- package/.cursor/rules/taskmaster.mdc +382 -0
- package/.cursorignore +1 -0
- package/.cursorrules +4 -0
- package/.env.example +23 -0
- package/.eslintrc.json +38 -0
- package/.github/workflows/npm-publish-github-packages.yml +55 -0
- package/.prettierrc +9 -0
- package/.roo/rules/dev_workflow.md +219 -0
- package/.roo/rules/mcp.md +430 -0
- package/.roo/rules/roo_rules.md +53 -0
- package/.roo/rules/self_improve.md +72 -0
- package/.roo/rules/taskmaster.md +382 -0
- package/.roo/rules-architect/architect-rules +93 -0
- package/.roo/rules-ask/ask-rules +89 -0
- package/.roo/rules-boomerang/boomerang-rules +181 -0
- package/.roo/rules-code/code-rules +61 -0
- package/.roo/rules-debug/debug-rules +68 -0
- package/.roo/rules-test/test-rules +61 -0
- package/.roomodes +63 -0
- package/.taskmasterconfig +31 -0
- package/.windsurfrules +2382 -0
- package/LICENSE +21 -0
- package/README.md +210 -0
- package/config/credentials/credentials.json +1 -0
- package/config/default.js +41 -0
- package/package.json +56 -0
- package/scripts/demo-n8n-integration.js +161 -0
- package/scripts/demo-workflow-generator.js +102 -0
- package/scripts/init.sh +36 -0
- package/scripts/prd.txt +197 -0
- package/src/index.ts +1440 -0
- package/src/middleware/auth.js +273 -0
- package/src/middleware/authorize.js +183 -0
- package/src/middleware/logging.js +64 -0
- package/src/middleware/mcp.js +187 -0
- package/src/middleware/rateLimiter.js +82 -0
- package/src/middleware/validation.js +241 -0
- package/src/models/credential.js +359 -0
- package/src/models/llmService.js +236 -0
- package/src/models/n8nIntegration.js +542 -0
- package/src/models/storage.js +196 -0
- package/src/models/tool.js +148 -0
- package/src/models/user.js +164 -0
- package/src/models/workflow.js +229 -0
- package/src/routes/toolDefinitions.js +62 -0
- package/src/routes/toolExecution.js +79 -0
- package/src/tools/__index.js +242 -0
- package/src/tools/connectionManagement.js +500 -0
- package/src/tools/n8nIntegration.js +370 -0
- package/src/tools/nodeDiscovery.js +488 -0
- package/src/tools/nodeManagement.js +674 -0
- package/src/tools/toolDefinitions.js +660 -0
- package/src/tools/workflowCreation.js +100 -0
- package/src/tools/workflowGenerator.js +152 -0
- package/src/tools/workflowStorage.js +113 -0
- package/src/tools/workflowTesting.js +285 -0
- package/src/utils/encryption.js +164 -0
- package/src/utils/logger.js +84 -0
- package/src/utils/mcp.js +85 -0
- package/src/utils/securityLogger.js +109 -0
- package/tests/auth.test.js +402 -0
- package/tests/authorize.test.js +208 -0
- package/tests/run-memory-tests.js +55 -0
- package/tests/run-tests.js +55 -0
- package/tests/server.test.js +203 -0
- package/tests/unit/add-ai-connections.test.js +385 -0
- package/tests/unit/connectionManagement.test.js +309 -0
- package/tests/unit/langchain-llm-format.test.js +259 -0
- package/tests/unit/memory-connection.test.js +140 -0
- package/tests/unit/memory-integration.test.js +253 -0
- package/tests/unit/n8nIntegration.test.js +291 -0
- package/tests/unit/nodeDiscovery.test.js +270 -0
- package/tests/unit/nodeManagement.test.js +522 -0
- package/tests/unit/utils/mcp-test-utils.js +94 -0
- package/tests/unit/workflowCreation.test.js +110 -0
- package/tests/unit/workflowTesting.test.js +269 -0
- package/tests/user.test.js +181 -0
- package/tsconfig.json +20 -0
- package/workflow_nodes/Brandfetch.json +85 -0
- package/workflow_nodes/WorkflowTrigger.json +28 -0
- package/workflow_nodes/actionNetwork.json +218 -0
- package/workflow_nodes/activeCampaign.json +722 -0
- package/workflow_nodes/activeCampaignTrigger.json +52 -0
- package/workflow_nodes/acuitySchedulingTrigger.json +8 -0
- package/workflow_nodes/adalo.json +123 -0
- package/workflow_nodes/affinity.json +203 -0
- package/workflow_nodes/affinityTrigger.json +124 -0
- package/workflow_nodes/aggregate.json +119 -0
- package/workflow_nodes/agileCrm.json +503 -0
- package/workflow_nodes/aiTransform.json +17 -0
- package/workflow_nodes/airtable.json +226 -0
- package/workflow_nodes/airtableTrigger.json +120 -0
- package/workflow_nodes/airtop.json +10 -0
- package/workflow_nodes/amqp.json +62 -0
- package/workflow_nodes/amqpTrigger.json +8 -0
- package/workflow_nodes/apiTemplateIo.json +147 -0
- package/workflow_nodes/asana.json +446 -0
- package/workflow_nodes/asanaTrigger.json +52 -0
- package/workflow_nodes/automizy.json +195 -0
- package/workflow_nodes/autopilot.json +287 -0
- package/workflow_nodes/autopilotTrigger.json +8 -0
- package/workflow_nodes/awsCertificateManager.json +223 -0
- package/workflow_nodes/awsComprehend.json +125 -0
- package/workflow_nodes/awsDynamoDb.json +251 -0
- package/workflow_nodes/awsElb.json +174 -0
- package/workflow_nodes/awsLambda.json +69 -0
- package/workflow_nodes/awsRekognition.json +191 -0
- package/workflow_nodes/awsS3.json +32 -0
- package/workflow_nodes/awsSes.json +302 -0
- package/workflow_nodes/awsSns.json +110 -0
- package/workflow_nodes/awsSnsTrigger.json +47 -0
- package/workflow_nodes/awsSqs.json +140 -0
- package/workflow_nodes/awsTextract.json +43 -0
- package/workflow_nodes/awsTranscribe.json +217 -0
- package/workflow_nodes/azureCosmosDb.json +8 -0
- package/workflow_nodes/azureStorage.json +17 -0
- package/workflow_nodes/bambooHr.json +8 -0
- package/workflow_nodes/bannerbear.json +126 -0
- package/workflow_nodes/baserow.json +277 -0
- package/workflow_nodes/beeminder.json +146 -0
- package/workflow_nodes/bitbucketTrigger.json +62 -0
- package/workflow_nodes/bitly.json +130 -0
- package/workflow_nodes/bitwarden.json +224 -0
- package/workflow_nodes/box.json +457 -0
- package/workflow_nodes/boxTrigger.json +8 -0
- package/workflow_nodes/brevo.json +41 -0
- package/workflow_nodes/brevoTrigger.json +145 -0
- package/workflow_nodes/bubble.json +212 -0
- package/workflow_nodes/calTrigger.json +91 -0
- package/workflow_nodes/calendlyTrigger.json +71 -0
- package/workflow_nodes/chargebee.json +217 -0
- package/workflow_nodes/chargebeeTrigger.json +187 -0
- package/workflow_nodes/circleCi.json +89 -0
- package/workflow_nodes/ciscoWebex.json +593 -0
- package/workflow_nodes/ciscoWebexTrigger.json +159 -0
- package/workflow_nodes/clearbit.json +138 -0
- package/workflow_nodes/clickUp.json +793 -0
- package/workflow_nodes/clickUpTrigger.json +188 -0
- package/workflow_nodes/clockify.json +372 -0
- package/workflow_nodes/clockifyTrigger.json +26 -0
- package/workflow_nodes/cloudflare.json +103 -0
- package/workflow_nodes/cockpit.json +161 -0
- package/workflow_nodes/coda.json +242 -0
- package/workflow_nodes/code.json +40 -0
- package/workflow_nodes/coinGecko.json +363 -0
- package/workflow_nodes/compareDatasets.json +14 -0
- package/workflow_nodes/compression.json +66 -0
- package/workflow_nodes/contentful.json +29 -0
- package/workflow_nodes/convertKit.json +159 -0
- package/workflow_nodes/convertKitTrigger.json +109 -0
- package/workflow_nodes/convertToFile.json +64 -0
- package/workflow_nodes/copper.json +239 -0
- package/workflow_nodes/copperTrigger.json +8 -0
- package/workflow_nodes/cortex.json +348 -0
- package/workflow_nodes/crateDb.json +90 -0
- package/workflow_nodes/cron.json +23 -0
- package/workflow_nodes/crowdDev.json +8 -0
- package/workflow_nodes/crowdDevTrigger.json +8 -0
- package/workflow_nodes/crypto.json +147 -0
- package/workflow_nodes/customerIo.json +206 -0
- package/workflow_nodes/customerIoTrigger.json +185 -0
- package/workflow_nodes/dateTime.json +39 -0
- package/workflow_nodes/debughelper.json +162 -0
- package/workflow_nodes/deepL.json +103 -0
- package/workflow_nodes/demio.json +187 -0
- package/workflow_nodes/dhl.json +53 -0
- package/workflow_nodes/discord.json +81 -0
- package/workflow_nodes/discourse.json +319 -0
- package/workflow_nodes/disqus.json +254 -0
- package/workflow_nodes/drift.json +112 -0
- package/workflow_nodes/dropbox.json +258 -0
- package/workflow_nodes/dropcontact.json +154 -0
- package/workflow_nodes/e2eTest.json +70 -0
- package/workflow_nodes/editImage.json +132 -0
- package/workflow_nodes/egoi.json +220 -0
- package/workflow_nodes/elasticSecurity.json +401 -0
- package/workflow_nodes/elasticsearch.json +422 -0
- package/workflow_nodes/emailReadImap.json +88 -0
- package/workflow_nodes/emailSend.json +38 -0
- package/workflow_nodes/emelia.json +201 -0
- package/workflow_nodes/emeliaTrigger.json +57 -0
- package/workflow_nodes/erpNext.json +139 -0
- package/workflow_nodes/errorTrigger.json +15 -0
- package/workflow_nodes/evaluationMetrics.json +21 -0
- package/workflow_nodes/eventbriteTrigger.json +125 -0
- package/workflow_nodes/executeCommand.json +25 -0
- package/workflow_nodes/executeWorkflow.json +102 -0
- package/workflow_nodes/executeWorkflowTrigger.json +65 -0
- package/workflow_nodes/executionData.json +35 -0
- package/workflow_nodes/extractFromFile.json +71 -0
- package/workflow_nodes/facebookGraphApi.json +234 -0
- package/workflow_nodes/facebookLeadAdsTrigger.json +8 -0
- package/workflow_nodes/facebookTrigger.json +112 -0
- package/workflow_nodes/figmaTrigger.json +8 -0
- package/workflow_nodes/filemaker.json +268 -0
- package/workflow_nodes/filter.json +24 -0
- package/workflow_nodes/flow.json +323 -0
- package/workflow_nodes/flowTrigger.json +52 -0
- package/workflow_nodes/form.json +25 -0
- package/workflow_nodes/formIoTrigger.json +59 -0
- package/workflow_nodes/formTrigger.json +8 -0
- package/workflow_nodes/formstackTrigger.json +8 -0
- package/workflow_nodes/freshdesk.json +584 -0
- package/workflow_nodes/freshservice.json +899 -0
- package/workflow_nodes/freshworksCrm.json +772 -0
- package/workflow_nodes/ftp.json +127 -0
- package/workflow_nodes/function.json +22 -0
- package/workflow_nodes/functionItem.json +22 -0
- package/workflow_nodes/gSuiteAdmin.json +562 -0
- package/workflow_nodes/getResponse.json +310 -0
- package/workflow_nodes/getResponseTrigger.json +82 -0
- package/workflow_nodes/ghost.json +290 -0
- package/workflow_nodes/git.json +184 -0
- package/workflow_nodes/github.json +732 -0
- package/workflow_nodes/githubTrigger.json +317 -0
- package/workflow_nodes/gitlab.json +544 -0
- package/workflow_nodes/gitlabTrigger.json +61 -0
- package/workflow_nodes/gmail.json +62 -0
- package/workflow_nodes/gmailTrigger.json +127 -0
- package/workflow_nodes/goToWebinar.json +430 -0
- package/workflow_nodes/gong.json +22 -0
- package/workflow_nodes/googleAds.json +116 -0
- package/workflow_nodes/googleAnalytics.json +28 -0
- package/workflow_nodes/googleBigQuery.json +38 -0
- package/workflow_nodes/googleBooks.json +154 -0
- package/workflow_nodes/googleBusinessProfile.json +277 -0
- package/workflow_nodes/googleBusinessProfileTrigger.json +55 -0
- package/workflow_nodes/googleCalendar.json +474 -0
- package/workflow_nodes/googleCalendarTrigger.json +72 -0
- package/workflow_nodes/googleChat.json +187 -0
- package/workflow_nodes/googleCloudNaturalLanguage.json +171 -0
- package/workflow_nodes/googleCloudStorage.json +466 -0
- package/workflow_nodes/googleContacts.json +481 -0
- package/workflow_nodes/googleDocs.json +312 -0
- package/workflow_nodes/googleDrive.json +920 -0
- package/workflow_nodes/googleDriveTrigger.json +181 -0
- package/workflow_nodes/googleFirebaseCloudFirestore.json +156 -0
- package/workflow_nodes/googleFirebaseRealtimeDatabase.json +75 -0
- package/workflow_nodes/googlePerspective.json +94 -0
- package/workflow_nodes/googleSheets.json +98 -0
- package/workflow_nodes/googleSheetsTrigger.json +192 -0
- package/workflow_nodes/googleSlides.json +186 -0
- package/workflow_nodes/googleTasks.json +198 -0
- package/workflow_nodes/googleTranslate.json +80 -0
- package/workflow_nodes/gotify.json +110 -0
- package/workflow_nodes/grafana.json +155 -0
- package/workflow_nodes/graphql.json +165 -0
- package/workflow_nodes/grist.json +13 -0
- package/workflow_nodes/gumroadTrigger.json +8 -0
- package/workflow_nodes/hackerNews.json +100 -0
- package/workflow_nodes/haloPSA.json +286 -0
- package/workflow_nodes/harvest.json +699 -0
- package/workflow_nodes/helpScout.json +629 -0
- package/workflow_nodes/helpScoutTrigger.json +8 -0
- package/workflow_nodes/highLevel.json +8 -0
- package/workflow_nodes/homeAssistant.json +201 -0
- package/workflow_nodes/html.json +118 -0
- package/workflow_nodes/htmlExtract.json +87 -0
- package/workflow_nodes/httpRequest.json +472 -0
- package/workflow_nodes/hubspot.json +62 -0
- package/workflow_nodes/hubspotTrigger.json +138 -0
- package/workflow_nodes/humanticAi.json +82 -0
- package/workflow_nodes/hunter.json +168 -0
- package/workflow_nodes/iCal.json +20 -0
- package/workflow_nodes/if.json +24 -0
- package/workflow_nodes/intercom.json +335 -0
- package/workflow_nodes/interval.json +8 -0
- package/workflow_nodes/invoiceNinja.json +882 -0
- package/workflow_nodes/invoiceNinjaTrigger.json +11 -0
- package/workflow_nodes/itemLists.json +313 -0
- package/workflow_nodes/iterable.json +168 -0
- package/workflow_nodes/jenkins.json +172 -0
- package/workflow_nodes/jira.json +529 -0
- package/workflow_nodes/jiraTrigger.json +308 -0
- package/workflow_nodes/jotFormTrigger.json +44 -0
- package/workflow_nodes/jwt.json +195 -0
- package/workflow_nodes/kafka.json +132 -0
- package/workflow_nodes/kafkaTrigger.json +11 -0
- package/workflow_nodes/keap.json +915 -0
- package/workflow_nodes/keapTrigger.json +37 -0
- package/workflow_nodes/kitemaker.json +153 -0
- package/workflow_nodes/koBoToolbox.json +337 -0
- package/workflow_nodes/koBoToolboxTrigger.json +8 -0
- package/workflow_nodes/langchain_Summarization Chain.json +60 -0
- package/workflow_nodes/langchain_agent.json +145 -0
- package/workflow_nodes/langchain_allowFileUploads.json +180 -0
- package/workflow_nodes/langchain_chainLlm.json +16 -0
- package/workflow_nodes/langchain_chainSummarization.json +119 -0
- package/workflow_nodes/langchain_code.json +62 -0
- package/workflow_nodes/langchain_documentBinaryInputLoader.json +8 -0
- package/workflow_nodes/langchain_documentDefaultDataLoader.json +8 -0
- package/workflow_nodes/langchain_documentGithubLoader.json +8 -0
- package/workflow_nodes/langchain_documentJsonInputLoader.json +8 -0
- package/workflow_nodes/langchain_embeddingDimensions.json +17 -0
- package/workflow_nodes/langchain_embeddingsAwsBedrock.json +8 -0
- package/workflow_nodes/langchain_embeddingsAzureOpenAi.json +151 -0
- package/workflow_nodes/langchain_embeddingsCohere.json +8 -0
- package/workflow_nodes/langchain_embeddingsGoogleGemini.json +8 -0
- package/workflow_nodes/langchain_embeddingsGoogleVertex.json +8 -0
- package/workflow_nodes/langchain_embeddingsHuggingFaceInference.json +8 -0
- package/workflow_nodes/langchain_embeddingsMistralCloud.json +8 -0
- package/workflow_nodes/langchain_embeddingsOllama.json +8 -0
- package/workflow_nodes/langchain_informationExtractor.json +81 -0
- package/workflow_nodes/langchain_lmChatAwsBedrock.json +8 -0
- package/workflow_nodes/langchain_lmChatAzureOpenAi.json +151 -0
- package/workflow_nodes/langchain_lmChatDeepSeek.json +10 -0
- package/workflow_nodes/langchain_lmChatGoogleGemini.json +31 -0
- package/workflow_nodes/langchain_lmChatGoogleVertex.json +32 -0
- package/workflow_nodes/langchain_lmChatGroq.json +8 -0
- package/workflow_nodes/langchain_lmChatMistralCloud.json +8 -0
- package/workflow_nodes/langchain_lmChatOllama.json +8 -0
- package/workflow_nodes/langchain_lmChatOpenAi.json +155 -0
- package/workflow_nodes/langchain_lmChatOpenRouter.json +10 -0
- package/workflow_nodes/langchain_lmChatXAiGrok.json +10 -0
- package/workflow_nodes/langchain_lmCohere.json +8 -0
- package/workflow_nodes/langchain_lmOllama.json +8 -0
- package/workflow_nodes/langchain_lmOpenAi.json +251 -0
- package/workflow_nodes/langchain_lmOpenHuggingFaceInference.json +8 -0
- package/workflow_nodes/langchain_manualChatTrigger.json +11 -0
- package/workflow_nodes/langchain_mcpClientTool.json +86 -0
- package/workflow_nodes/langchain_mcpTrigger.json +8 -0
- package/workflow_nodes/langchain_memoryBufferWindow.json +13 -0
- package/workflow_nodes/langchain_memoryChatRetriever.json +22 -0
- package/workflow_nodes/langchain_memoryManager.json +106 -0
- package/workflow_nodes/langchain_memoryMongoDbChat.json +10 -0
- package/workflow_nodes/langchain_memoryMotorhead.json +13 -0
- package/workflow_nodes/langchain_memoryPostgresChat.json +13 -0
- package/workflow_nodes/langchain_memoryRedisChat.json +15 -0
- package/workflow_nodes/langchain_memoryXata.json +14 -0
- package/workflow_nodes/langchain_memoryZep.json +13 -0
- package/workflow_nodes/langchain_model.json +155 -0
- package/workflow_nodes/langchain_mongoCollection.json +16 -0
- package/workflow_nodes/langchain_notice.json +22 -0
- package/workflow_nodes/langchain_openAiAssistant.json +132 -0
- package/workflow_nodes/langchain_options.json +17 -0
- package/workflow_nodes/langchain_outputParserAutofixing.json +8 -0
- package/workflow_nodes/langchain_outputParserItemList.json +8 -0
- package/workflow_nodes/langchain_outputParserStructured.json +12 -0
- package/workflow_nodes/langchain_pineconeNamespace.json +16 -0
- package/workflow_nodes/langchain_queryName.json +16 -0
- package/workflow_nodes/langchain_retrieverContextualCompression.json +8 -0
- package/workflow_nodes/langchain_retrieverMultiQuery.json +8 -0
- package/workflow_nodes/langchain_retrieverVectorStore.json +8 -0
- package/workflow_nodes/langchain_retrieverWorkflow.json +103 -0
- package/workflow_nodes/langchain_sentimentAnalysis.json +52 -0
- package/workflow_nodes/langchain_systemPromptTemplate.json +47 -0
- package/workflow_nodes/langchain_tableName.json +23 -0
- package/workflow_nodes/langchain_textClassifier.json +66 -0
- package/workflow_nodes/langchain_textSplitterCharacterTextSplitter.json +8 -0
- package/workflow_nodes/langchain_textSplitterRecursiveCharacterTextSplitter.json +8 -0
- package/workflow_nodes/langchain_textSplitterTokenSplitter.json +8 -0
- package/workflow_nodes/langchain_toolCalculator.json +8 -0
- package/workflow_nodes/langchain_toolCode.json +12 -0
- package/workflow_nodes/langchain_toolHttpRequest.json +232 -0
- package/workflow_nodes/langchain_toolSearXng.json +8 -0
- package/workflow_nodes/langchain_toolSerpApi.json +8 -0
- package/workflow_nodes/langchain_toolThink.json +8 -0
- package/workflow_nodes/langchain_toolVectorStore.json +11 -0
- package/workflow_nodes/langchain_toolWikipedia.json +8 -0
- package/workflow_nodes/langchain_toolWolframAlpha.json +8 -0
- package/workflow_nodes/langchain_toolWorkflow.json +8 -0
- package/workflow_nodes/langchain_vectorStoreInMemoryInsert.json +29 -0
- package/workflow_nodes/langchain_vectorStoreInMemoryLoad.json +8 -0
- package/workflow_nodes/langchain_vectorStorePineconeInsert.json +37 -0
- package/workflow_nodes/langchain_vectorStorePineconeLoad.json +8 -0
- package/workflow_nodes/langchain_vectorStoreSupabaseInsert.json +32 -0
- package/workflow_nodes/langchain_vectorStoreSupabaseLoad.json +8 -0
- package/workflow_nodes/langchain_vectorStoreZepInsert.json +46 -0
- package/workflow_nodes/langchain_vectorStoreZepLoad.json +8 -0
- package/workflow_nodes/ldap.json +182 -0
- package/workflow_nodes/lemlist.json +44 -0
- package/workflow_nodes/lemlistTrigger.json +45 -0
- package/workflow_nodes/limit.json +26 -0
- package/workflow_nodes/line.json +95 -0
- package/workflow_nodes/linear.json +151 -0
- package/workflow_nodes/linearTrigger.json +71 -0
- package/workflow_nodes/lingvaNex.json +66 -0
- package/workflow_nodes/linkedIn.json +142 -0
- package/workflow_nodes/localFileTrigger.json +120 -0
- package/workflow_nodes/lonescale.json +171 -0
- package/workflow_nodes/lonescaleTrigger.json +8 -0
- package/workflow_nodes/magento2.json +164 -0
- package/workflow_nodes/mailcheck.json +46 -0
- package/workflow_nodes/mailchimp.json +507 -0
- package/workflow_nodes/mailchimpTrigger.json +100 -0
- package/workflow_nodes/mailerLite.json +24 -0
- package/workflow_nodes/mailerLiteTrigger.json +74 -0
- package/workflow_nodes/mailgun.json +81 -0
- package/workflow_nodes/mailjet.json +201 -0
- package/workflow_nodes/mailjetTrigger.json +8 -0
- package/workflow_nodes/mandrill.json +372 -0
- package/workflow_nodes/manualTrigger.json +8 -0
- package/workflow_nodes/markdown.json +376 -0
- package/workflow_nodes/marketstack.json +126 -0
- package/workflow_nodes/matrix.json +264 -0
- package/workflow_nodes/mattermost.json +8 -0
- package/workflow_nodes/mautic.json +564 -0
- package/workflow_nodes/mauticTrigger.json +54 -0
- package/workflow_nodes/medium.json +209 -0
- package/workflow_nodes/merge.json +125 -0
- package/workflow_nodes/messageBird.json +182 -0
- package/workflow_nodes/metabase.json +175 -0
- package/workflow_nodes/microsoftDynamicsCrm.json +100 -0
- package/workflow_nodes/microsoftEntra.json +51 -0
- package/workflow_nodes/microsoftExcel.json +35 -0
- package/workflow_nodes/microsoftGraphSecurity.json +113 -0
- package/workflow_nodes/microsoftOneDrive.json +232 -0
- package/workflow_nodes/microsoftOneDriveTrigger.json +80 -0
- package/workflow_nodes/microsoftOutlook.json +40 -0
- package/workflow_nodes/microsoftOutlookTrigger.json +24 -0
- package/workflow_nodes/microsoftSql.json +81 -0
- package/workflow_nodes/microsoftTeams.json +36 -0
- package/workflow_nodes/microsoftToDo.json +181 -0
- package/workflow_nodes/mindee.json +86 -0
- package/workflow_nodes/misp.json +399 -0
- package/workflow_nodes/mocean.json +103 -0
- package/workflow_nodes/mondayCom.json +290 -0
- package/workflow_nodes/mongoDb.json +16 -0
- package/workflow_nodes/monicaCrm.json +543 -0
- package/workflow_nodes/moveBinaryData.json +121 -0
- package/workflow_nodes/mqtt.json +67 -0
- package/workflow_nodes/mqttTrigger.json +47 -0
- package/workflow_nodes/msg91.json +65 -0
- package/workflow_nodes/mySql.json +111 -0
- package/workflow_nodes/n8n.json +75 -0
- package/workflow_nodes/n8nTrigger.json +27 -0
- package/workflow_nodes/nasa.json +310 -0
- package/workflow_nodes/netlify.json +87 -0
- package/workflow_nodes/netlifyTrigger.json +68 -0
- package/workflow_nodes/netscalerAdc.json +243 -0
- package/workflow_nodes/nextCloud.json +312 -0
- package/workflow_nodes/noOp.json +8 -0
- package/workflow_nodes/nocoDb.json +276 -0
- package/workflow_nodes/notion.json +8 -0
- package/workflow_nodes/notionTrigger.json +75 -0
- package/workflow_nodes/npm.json +64 -0
- package/workflow_nodes/odoo.json +344 -0
- package/workflow_nodes/okta.json +97 -0
- package/workflow_nodes/oneSimpleApi.json +281 -0
- package/workflow_nodes/onfleet.json +316 -0
- package/workflow_nodes/onfleetTrigger.json +8 -0
- package/workflow_nodes/openAi.json +154 -0
- package/workflow_nodes/openThesaurus.json +81 -0
- package/workflow_nodes/openWeatherMap.json +129 -0
- package/workflow_nodes/orbit.json +375 -0
- package/workflow_nodes/oura.json +74 -0
- package/workflow_nodes/paddle.json +403 -0
- package/workflow_nodes/pagerDuty.json +351 -0
- package/workflow_nodes/payPal.json +196 -0
- package/workflow_nodes/payPalTrigger.json +40 -0
- package/workflow_nodes/peekalink.json +41 -0
- package/workflow_nodes/phantombuster.json +172 -0
- package/workflow_nodes/philipsHue.json +177 -0
- package/workflow_nodes/pipedrive.json +860 -0
- package/workflow_nodes/pipedriveTrigger.json +11 -0
- package/workflow_nodes/plivo.json +91 -0
- package/workflow_nodes/postHog.json +122 -0
- package/workflow_nodes/postbin.json +60 -0
- package/workflow_nodes/postgres.json +109 -0
- package/workflow_nodes/postgresTrigger.json +8 -0
- package/workflow_nodes/postmarkTrigger.json +72 -0
- package/workflow_nodes/profitWell.json +305 -0
- package/workflow_nodes/pushbullet.json +186 -0
- package/workflow_nodes/pushcut.json +75 -0
- package/workflow_nodes/pushcutTrigger.json +8 -0
- package/workflow_nodes/pushover.json +159 -0
- package/workflow_nodes/questDb.json +94 -0
- package/workflow_nodes/quickChart.json +188 -0
- package/workflow_nodes/quickbase.json +205 -0
- package/workflow_nodes/quickbooks.json +550 -0
- package/workflow_nodes/rabbitmq.json +165 -0
- package/workflow_nodes/rabbitmqTrigger.json +8 -0
- package/workflow_nodes/raindrop.json +216 -0
- package/workflow_nodes/readBinaryFile.json +26 -0
- package/workflow_nodes/readBinaryFiles.json +26 -0
- package/workflow_nodes/readPDF.json +31 -0
- package/workflow_nodes/readWriteFile.json +27 -0
- package/workflow_nodes/reddit.json +309 -0
- package/workflow_nodes/redis.json +183 -0
- package/workflow_nodes/redisTrigger.json +8 -0
- package/workflow_nodes/removeDuplicates.json +8 -0
- package/workflow_nodes/renameKeys.json +67 -0
- package/workflow_nodes/respondToWebhook.json +126 -0
- package/workflow_nodes/rocketchat.json +216 -0
- package/workflow_nodes/rssFeedRead.json +28 -0
- package/workflow_nodes/rssFeedReadTrigger.json +17 -0
- package/workflow_nodes/rundeck.json +79 -0
- package/workflow_nodes/s3.json +425 -0
- package/workflow_nodes/salesforce.json +1137 -0
- package/workflow_nodes/salesforceTrigger.json +122 -0
- package/workflow_nodes/salesmate.json +467 -0
- package/workflow_nodes/scheduleTrigger.json +270 -0
- package/workflow_nodes/seaTable.json +8 -0
- package/workflow_nodes/seaTableTrigger.json +87 -0
- package/workflow_nodes/securityScorecard.json +459 -0
- package/workflow_nodes/segment.json +219 -0
- package/workflow_nodes/sendGrid.json +359 -0
- package/workflow_nodes/sendy.json +225 -0
- package/workflow_nodes/sentryIo.json +426 -0
- package/workflow_nodes/serviceNow.json +544 -0
- package/workflow_nodes/set.json +124 -0
- package/workflow_nodes/shopify.json +707 -0
- package/workflow_nodes/shopifyTrigger.json +8 -0
- package/workflow_nodes/signl4.json +133 -0
- package/workflow_nodes/simulate.json +30 -0
- package/workflow_nodes/simulateTrigger.json +8 -0
- package/workflow_nodes/slack.json +62 -0
- package/workflow_nodes/slackTrigger.json +135 -0
- package/workflow_nodes/sms77.json +121 -0
- package/workflow_nodes/snowflake.json +65 -0
- package/workflow_nodes/sort.json +57 -0
- package/workflow_nodes/splitInBatches.json +30 -0
- package/workflow_nodes/splitOut.json +62 -0
- package/workflow_nodes/splunk.json +40 -0
- package/workflow_nodes/spontit.json +123 -0
- package/workflow_nodes/spotify.json +285 -0
- package/workflow_nodes/spreadsheetFile.json +8 -0
- package/workflow_nodes/sseTrigger.json +8 -0
- package/workflow_nodes/ssh.json +105 -0
- package/workflow_nodes/stackby.json +85 -0
- package/workflow_nodes/start.json +15 -0
- package/workflow_nodes/stickyNote.json +36 -0
- package/workflow_nodes/stopAndError.json +8 -0
- package/workflow_nodes/storyblok.json +138 -0
- package/workflow_nodes/strapi.json +138 -0
- package/workflow_nodes/strava.json +427 -0
- package/workflow_nodes/stravaTrigger.json +79 -0
- package/workflow_nodes/stripe.json +357 -0
- package/workflow_nodes/stripeTrigger.json +775 -0
- package/workflow_nodes/summarize.json +124 -0
- package/workflow_nodes/supabase.json +136 -0
- package/workflow_nodes/surveyMonkeyTrigger.json +160 -0
- package/workflow_nodes/switch.json +91 -0
- package/workflow_nodes/syncroMsp.json +8 -0
- package/workflow_nodes/taiga.json +340 -0
- package/workflow_nodes/taigaTrigger.json +81 -0
- package/workflow_nodes/tapfiliate.json +241 -0
- package/workflow_nodes/telegram.json +612 -0
- package/workflow_nodes/telegramTrigger.json +142 -0
- package/workflow_nodes/theHive.json +497 -0
- package/workflow_nodes/theHiveProject.json +8 -0
- package/workflow_nodes/theHiveProjectTrigger.json +162 -0
- package/workflow_nodes/theHiveTrigger.json +101 -0
- package/workflow_nodes/timescaleDb.json +95 -0
- package/workflow_nodes/todoist.json +285 -0
- package/workflow_nodes/togglTrigger.json +24 -0
- package/workflow_nodes/totp.json +86 -0
- package/workflow_nodes/travisCi.json +142 -0
- package/workflow_nodes/trello.json +609 -0
- package/workflow_nodes/trelloTrigger.json +8 -0
- package/workflow_nodes/twake.json +76 -0
- package/workflow_nodes/twilio.json +95 -0
- package/workflow_nodes/twilioTrigger.json +46 -0
- package/workflow_nodes/twist.json +376 -0
- package/workflow_nodes/twitter.json +40 -0
- package/workflow_nodes/typeformTrigger.json +62 -0
- package/workflow_nodes/unleashedSoftware.json +154 -0
- package/workflow_nodes/uplead.json +72 -0
- package/workflow_nodes/uproc.json +26 -0
- package/workflow_nodes/uptimeRobot.json +453 -0
- package/workflow_nodes/urlScanIo.json +113 -0
- package/workflow_nodes/venafiTlsProtectCloud.json +310 -0
- package/workflow_nodes/venafiTlsProtectCloudTrigger.json +38 -0
- package/workflow_nodes/venafiTlsProtectDatacenter.json +491 -0
- package/workflow_nodes/vero.json +158 -0
- package/workflow_nodes/vonage.json +125 -0
- package/workflow_nodes/wait.json +71 -0
- package/workflow_nodes/webflow.json +38 -0
- package/workflow_nodes/webflowTrigger.json +8 -0
- package/workflow_nodes/webhook.json +55 -0
- package/workflow_nodes/wekan.json +460 -0
- package/workflow_nodes/whatsApp.json +476 -0
- package/workflow_nodes/whatsAppTrigger.json +103 -0
- package/workflow_nodes/wise.json +330 -0
- package/workflow_nodes/wiseTrigger.json +8 -0
- package/workflow_nodes/wooCommerce.json +812 -0
- package/workflow_nodes/wooCommerceTrigger.json +8 -0
- package/workflow_nodes/wordpress.json +500 -0
- package/workflow_nodes/workableTrigger.json +51 -0
- package/workflow_nodes/writeBinaryFile.json +34 -0
- package/workflow_nodes/wufooTrigger.json +37 -0
- package/workflow_nodes/xero.json +530 -0
- package/workflow_nodes/xml.json +129 -0
- package/workflow_nodes/youTube.json +578 -0
- package/workflow_nodes/yourls.json +71 -0
- package/workflow_nodes/zammad.json +406 -0
- package/workflow_nodes/zendesk.json +526 -0
- package/workflow_nodes/zendeskTrigger.json +187 -0
- package/workflow_nodes/zohoCrm.json +721 -0
- package/workflow_nodes/zoom.json +507 -0
- package/workflow_nodes/zulip.json +371 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter Middleware
|
|
3
|
+
*
|
|
4
|
+
* Protects the API from excessive requests
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { logger } = require('../utils/logger');
|
|
8
|
+
const { createErrorResponse } = require('../utils/mcp');
|
|
9
|
+
const { logSecurityEvent } = require('../utils/securityLogger');
|
|
10
|
+
|
|
11
|
+
// In-memory store for rate limiting (in production, use Redis or similar)
|
|
12
|
+
const requestCounts = {};
|
|
13
|
+
|
|
14
|
+
// Constants
|
|
15
|
+
const WINDOW_MS = 60 * 1000; // 1 minute
|
|
16
|
+
const MAX_REQUESTS_PER_WINDOW = 100; // 100 requests per minute
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Simple rate limiter middleware
|
|
20
|
+
*
|
|
21
|
+
* @param {Object} req - Express request object
|
|
22
|
+
* @param {Object} res - Express response object
|
|
23
|
+
* @param {Function} next - Express next middleware function
|
|
24
|
+
*/
|
|
25
|
+
const rateLimiter = (req, res, next) => {
|
|
26
|
+
const ip = req.ip || req.connection.remoteAddress;
|
|
27
|
+
const now = Date.now();
|
|
28
|
+
|
|
29
|
+
// Initialize or reset window
|
|
30
|
+
if (!requestCounts[ip] || requestCounts[ip].windowStart < now - WINDOW_MS) {
|
|
31
|
+
requestCounts[ip] = {
|
|
32
|
+
windowStart: now,
|
|
33
|
+
count: 1
|
|
34
|
+
};
|
|
35
|
+
return next();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Increment count for existing window
|
|
39
|
+
requestCounts[ip].count++;
|
|
40
|
+
|
|
41
|
+
// Check if too many requests
|
|
42
|
+
if (requestCounts[ip].count > MAX_REQUESTS_PER_WINDOW) {
|
|
43
|
+
// Log rate limit
|
|
44
|
+
logger.warn(`Rate limit exceeded for IP: ${ip}`);
|
|
45
|
+
|
|
46
|
+
// Security log
|
|
47
|
+
logSecurityEvent({
|
|
48
|
+
level: 'warn',
|
|
49
|
+
eventType: 'rate_limit_exceeded',
|
|
50
|
+
ip,
|
|
51
|
+
details: {
|
|
52
|
+
count: requestCounts[ip].count,
|
|
53
|
+
window: WINDOW_MS,
|
|
54
|
+
limit: MAX_REQUESTS_PER_WINDOW
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Create error response
|
|
59
|
+
const errorResponse = createErrorResponse(
|
|
60
|
+
'Too many requests, please try again later',
|
|
61
|
+
'RATE_LIMIT_EXCEEDED',
|
|
62
|
+
429,
|
|
63
|
+
{
|
|
64
|
+
retryAfter: Math.ceil((requestCounts[ip].windowStart + WINDOW_MS - now) / 1000)
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Set retry header
|
|
69
|
+
res.setHeader(
|
|
70
|
+
'Retry-After',
|
|
71
|
+
Math.ceil((requestCounts[ip].windowStart + WINDOW_MS - now) / 1000)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return res.status(429).json({ error: errorResponse.error });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
next();
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
rateLimiter
|
|
82
|
+
};
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Validation Middleware
|
|
3
|
+
*
|
|
4
|
+
* Provides JSON Schema validation for API endpoints and request sanitization.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const Ajv = require('ajv');
|
|
8
|
+
const addFormats = require('ajv-formats');
|
|
9
|
+
const { createErrorResponse } = require('../utils/mcp');
|
|
10
|
+
const { logger } = require('../utils/logger');
|
|
11
|
+
const { logValidationFailure } = require('../utils/securityLogger');
|
|
12
|
+
|
|
13
|
+
// Initialize AJV with additional options
|
|
14
|
+
const ajv = new Ajv({
|
|
15
|
+
allErrors: true,
|
|
16
|
+
removeAdditional: 'all', // Remove additional properties not in schema
|
|
17
|
+
useDefaults: true, // Apply default values from schema
|
|
18
|
+
coerceTypes: true // Convert data types when possible
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Add format validators (email, date, uri, etc.)
|
|
22
|
+
addFormats(ajv);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Creates a validation middleware for a specific schema
|
|
26
|
+
*
|
|
27
|
+
* @param {Object} schema - JSON Schema object to validate against
|
|
28
|
+
* @param {string} property - Request property to validate (body, query, params)
|
|
29
|
+
* @returns {Function} Express middleware function
|
|
30
|
+
*/
|
|
31
|
+
const validateSchema = (schema, property = 'body') => {
|
|
32
|
+
const validate = ajv.compile(schema);
|
|
33
|
+
|
|
34
|
+
return (req, res, next) => {
|
|
35
|
+
const data = req[property];
|
|
36
|
+
const valid = validate(data);
|
|
37
|
+
|
|
38
|
+
if (!valid) {
|
|
39
|
+
// Format validation errors for response
|
|
40
|
+
const formattedErrors = formatValidationErrors(validate.errors);
|
|
41
|
+
|
|
42
|
+
// Regular logging
|
|
43
|
+
logger.warn(`Validation error for ${req.method} ${req.path}: ${JSON.stringify(formattedErrors)}`);
|
|
44
|
+
|
|
45
|
+
// Security logging
|
|
46
|
+
const clientIp = req.ip || req.connection.remoteAddress;
|
|
47
|
+
const userId = req.auth ? (req.auth.user ? req.auth.user.id : null) : null;
|
|
48
|
+
|
|
49
|
+
// Log each validation error separately
|
|
50
|
+
Object.keys(formattedErrors).forEach(field => {
|
|
51
|
+
logValidationFailure({
|
|
52
|
+
ip: clientIp,
|
|
53
|
+
userId,
|
|
54
|
+
endpoint: req.path,
|
|
55
|
+
method: req.method,
|
|
56
|
+
violationType: 'schema_validation',
|
|
57
|
+
inputField: field
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const errorResponse = createErrorResponse(
|
|
62
|
+
'Validation failed',
|
|
63
|
+
'VALIDATION_ERROR',
|
|
64
|
+
400,
|
|
65
|
+
formattedErrors
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return res.status(errorResponse.status).json({ error: errorResponse.error });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
next();
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Format validation errors into a more user-friendly structure
|
|
77
|
+
*
|
|
78
|
+
* @param {Array} errors - AJV validation errors
|
|
79
|
+
* @returns {Object} Formatted errors by field
|
|
80
|
+
*/
|
|
81
|
+
const formatValidationErrors = (errors) => {
|
|
82
|
+
const formattedErrors = {};
|
|
83
|
+
|
|
84
|
+
for (const error of errors) {
|
|
85
|
+
// Extract the field name from the error path
|
|
86
|
+
const path = error.instancePath.substring(1).split('/');
|
|
87
|
+
const field = path.length ? path[path.length - 1] : error.params.missingProperty || 'value';
|
|
88
|
+
|
|
89
|
+
// Initialize the field in the errors object if it doesn't exist
|
|
90
|
+
if (!formattedErrors[field]) {
|
|
91
|
+
formattedErrors[field] = [];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Add the error message for this field
|
|
95
|
+
let message = error.message;
|
|
96
|
+
|
|
97
|
+
// Customize messages for specific error types
|
|
98
|
+
if (error.keyword === 'required') {
|
|
99
|
+
message = `Missing required field: ${error.params.missingProperty}`;
|
|
100
|
+
} else if (error.keyword === 'additionalProperties') {
|
|
101
|
+
message = `Unexpected field: ${error.params.additionalProperty}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
formattedErrors[field].push(message);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return formattedErrors;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Sanitize input data to prevent injection attacks
|
|
112
|
+
*
|
|
113
|
+
* @param {*} data - Data to sanitize
|
|
114
|
+
* @returns {*} Sanitized data
|
|
115
|
+
*/
|
|
116
|
+
const sanitizeInput = (data) => {
|
|
117
|
+
if (typeof data === 'string') {
|
|
118
|
+
// Basic sanitization for strings
|
|
119
|
+
return data
|
|
120
|
+
.replace(/</g, '<')
|
|
121
|
+
.replace(/>/g, '>')
|
|
122
|
+
.replace(/"/g, '"')
|
|
123
|
+
.replace(/'/g, ''')
|
|
124
|
+
.replace(/\//g, '/')
|
|
125
|
+
.replace(/`/g, '`');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (data && typeof data === 'object') {
|
|
129
|
+
if (Array.isArray(data)) {
|
|
130
|
+
// Recursively sanitize array items
|
|
131
|
+
return data.map(item => sanitizeInput(item));
|
|
132
|
+
} else {
|
|
133
|
+
// Recursively sanitize object properties
|
|
134
|
+
const sanitized = {};
|
|
135
|
+
for (const [key, value] of Object.entries(data)) {
|
|
136
|
+
sanitized[key] = sanitizeInput(value);
|
|
137
|
+
}
|
|
138
|
+
return sanitized;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Return non-string, non-object values as is
|
|
143
|
+
return data;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Creates a sanitization middleware
|
|
148
|
+
*
|
|
149
|
+
* @param {string} property - Request property to sanitize (body, query, params)
|
|
150
|
+
* @returns {Function} Express middleware function
|
|
151
|
+
*/
|
|
152
|
+
const sanitizeRequest = (property = 'body') => {
|
|
153
|
+
return (req, res, next) => {
|
|
154
|
+
if (req[property]) {
|
|
155
|
+
req[property] = sanitizeInput(req[property]);
|
|
156
|
+
}
|
|
157
|
+
next();
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Validate workflow-specific inputs
|
|
163
|
+
*/
|
|
164
|
+
const validateWorkflow = (req, res, next) => {
|
|
165
|
+
const { name, description } = req.body;
|
|
166
|
+
const clientIp = req.ip || req.connection.remoteAddress;
|
|
167
|
+
const userId = req.auth?.userId || 'anonymous';
|
|
168
|
+
|
|
169
|
+
// Name must be provided
|
|
170
|
+
if (!name) {
|
|
171
|
+
// Log validation failure
|
|
172
|
+
logValidationFailure({
|
|
173
|
+
ip: clientIp,
|
|
174
|
+
userId,
|
|
175
|
+
endpoint: req.path,
|
|
176
|
+
method: req.method,
|
|
177
|
+
violationType: 'required_field_missing',
|
|
178
|
+
inputField: 'name'
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const errorResponse = createErrorResponse(
|
|
182
|
+
'Workflow name is required',
|
|
183
|
+
'MISSING_REQUIRED_FIELD',
|
|
184
|
+
400
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
return res.status(400).json({ error: errorResponse.error });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Name length validation
|
|
191
|
+
if (name.length > 100) {
|
|
192
|
+
// Log validation failure
|
|
193
|
+
logValidationFailure({
|
|
194
|
+
ip: clientIp,
|
|
195
|
+
userId,
|
|
196
|
+
endpoint: req.path,
|
|
197
|
+
method: req.method,
|
|
198
|
+
violationType: 'field_too_long',
|
|
199
|
+
inputField: 'name'
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const errorResponse = createErrorResponse(
|
|
203
|
+
'Workflow name cannot exceed 100 characters',
|
|
204
|
+
'FIELD_TOO_LONG',
|
|
205
|
+
400
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
return res.status(400).json({ error: errorResponse.error });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Description length validation
|
|
212
|
+
if (description && description.length > 500) {
|
|
213
|
+
// Log validation failure
|
|
214
|
+
logValidationFailure({
|
|
215
|
+
ip: clientIp,
|
|
216
|
+
userId,
|
|
217
|
+
endpoint: req.path,
|
|
218
|
+
method: req.method,
|
|
219
|
+
violationType: 'field_too_long',
|
|
220
|
+
inputField: 'description'
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const errorResponse = createErrorResponse(
|
|
224
|
+
'Workflow description cannot exceed 500 characters',
|
|
225
|
+
'FIELD_TOO_LONG',
|
|
226
|
+
400
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
return res.status(400).json({ error: errorResponse.error });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
next();
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
module.exports = {
|
|
236
|
+
validateSchema,
|
|
237
|
+
sanitizeRequest,
|
|
238
|
+
sanitizeInput,
|
|
239
|
+
formatValidationErrors,
|
|
240
|
+
validateWorkflow
|
|
241
|
+
};
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential Model
|
|
3
|
+
*
|
|
4
|
+
* Handles secure credential storage and retrieval with encryption.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs').promises;
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { encrypt, decrypt, generateKey } = require('../utils/encryption');
|
|
10
|
+
const { logger } = require('../utils/logger');
|
|
11
|
+
|
|
12
|
+
// Configuration
|
|
13
|
+
let config;
|
|
14
|
+
try {
|
|
15
|
+
config = require('../../config/default');
|
|
16
|
+
} catch (err) {
|
|
17
|
+
logger.error('Failed to load config', { error: err.message });
|
|
18
|
+
config = {
|
|
19
|
+
auth: { encryptionKey: process.env.ENCRYPTION_KEY }
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Storage location for credentials
|
|
24
|
+
const CREDENTIALS_DIR = path.join(process.cwd(), 'config', 'credentials');
|
|
25
|
+
const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'credentials.json');
|
|
26
|
+
|
|
27
|
+
// Get master encryption key from environment or generate one
|
|
28
|
+
const MASTER_KEY = process.env.ENCRYPTION_KEY ||
|
|
29
|
+
config.auth?.encryptionKey ||
|
|
30
|
+
(() => {
|
|
31
|
+
logger.warn('No encryption key found, generating one. THIS SHOULD NOT BE USED IN PRODUCTION.');
|
|
32
|
+
const key = generateKey();
|
|
33
|
+
logger.warn(`Generated encryption key: ${key}. Add this to your environment variables as ENCRYPTION_KEY.`);
|
|
34
|
+
return key;
|
|
35
|
+
})();
|
|
36
|
+
|
|
37
|
+
// Ensure credentials directory exists
|
|
38
|
+
const ensureCredentialsDir = async () => {
|
|
39
|
+
try {
|
|
40
|
+
await fs.mkdir(CREDENTIALS_DIR, { recursive: true });
|
|
41
|
+
} catch (error) {
|
|
42
|
+
logger.error('Failed to create credentials directory', { error: error.message });
|
|
43
|
+
throw new Error('Failed to create credentials directory');
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Initialize credentials storage
|
|
48
|
+
const initCredentialsStorage = async () => {
|
|
49
|
+
await ensureCredentialsDir();
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Check if credentials file exists
|
|
53
|
+
await fs.access(CREDENTIALS_FILE);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// Create an empty credentials file
|
|
56
|
+
await fs.writeFile(CREDENTIALS_FILE, JSON.stringify({ credentials: {}, meta: { version: '1.0', keyId: 'v1' } }));
|
|
57
|
+
logger.info('Created new credentials file');
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Loads credentials from storage
|
|
63
|
+
*
|
|
64
|
+
* @returns {Object} Credentials data
|
|
65
|
+
*/
|
|
66
|
+
const loadCredentials = async () => {
|
|
67
|
+
try {
|
|
68
|
+
await ensureCredentialsDir();
|
|
69
|
+
const data = await fs.readFile(CREDENTIALS_FILE, 'utf8');
|
|
70
|
+
return JSON.parse(data);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
logger.error('Failed to load credentials', { error: error.message });
|
|
73
|
+
|
|
74
|
+
// If file doesn't exist or is corrupted, create a new one
|
|
75
|
+
if (error.code === 'ENOENT' || error instanceof SyntaxError) {
|
|
76
|
+
const emptyData = { credentials: {}, meta: { version: '1.0', keyId: 'v1' } };
|
|
77
|
+
await fs.writeFile(CREDENTIALS_FILE, JSON.stringify(emptyData));
|
|
78
|
+
return emptyData;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
throw new Error('Failed to load credentials');
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Saves credentials to storage
|
|
87
|
+
*
|
|
88
|
+
* @param {Object} data - Credentials data
|
|
89
|
+
*/
|
|
90
|
+
const saveCredentials = async (data) => {
|
|
91
|
+
try {
|
|
92
|
+
await ensureCredentialsDir();
|
|
93
|
+
await fs.writeFile(CREDENTIALS_FILE, JSON.stringify(data, null, 2));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
logger.error('Failed to save credentials', { error: error.message });
|
|
96
|
+
throw new Error('Failed to save credentials');
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Stores a credential securely
|
|
102
|
+
*
|
|
103
|
+
* @param {string} name - Identifier for the credential
|
|
104
|
+
* @param {Object} data - Credential data to store
|
|
105
|
+
* @param {Object} options - Additional options
|
|
106
|
+
* @param {string} options.type - Type of credential (e.g., 'api', 'oauth')
|
|
107
|
+
* @param {string} options.keyId - Key identifier for encryption (for key rotation)
|
|
108
|
+
* @returns {Object} Status of the operation
|
|
109
|
+
*/
|
|
110
|
+
const storeCredential = async (name, data, options = {}) => {
|
|
111
|
+
if (!name || typeof name !== 'string') {
|
|
112
|
+
throw new Error('Credential name is required');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!data) {
|
|
116
|
+
throw new Error('Credential data is required');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const credentialsData = await loadCredentials();
|
|
121
|
+
const keyId = options.keyId || credentialsData.meta.keyId || 'v1';
|
|
122
|
+
|
|
123
|
+
// Encrypt the credential data
|
|
124
|
+
const encryptedData = encrypt(data, MASTER_KEY, keyId);
|
|
125
|
+
|
|
126
|
+
// Store with metadata
|
|
127
|
+
credentialsData.credentials[name] = {
|
|
128
|
+
encrypted: encryptedData,
|
|
129
|
+
type: options.type || 'generic',
|
|
130
|
+
createdAt: new Date().toISOString(),
|
|
131
|
+
updatedAt: new Date().toISOString()
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
await saveCredentials(credentialsData);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
name,
|
|
139
|
+
message: `Credential "${name}" stored successfully`
|
|
140
|
+
};
|
|
141
|
+
} catch (error) {
|
|
142
|
+
logger.error('Failed to store credential', { name, error: error.message });
|
|
143
|
+
throw new Error(`Failed to store credential "${name}": ${error.message}`);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Retrieves a stored credential
|
|
149
|
+
*
|
|
150
|
+
* @param {string} name - Name of the credential to retrieve
|
|
151
|
+
* @returns {Object} Decrypted credential data
|
|
152
|
+
*/
|
|
153
|
+
const getCredential = async (name) => {
|
|
154
|
+
if (!name || typeof name !== 'string') {
|
|
155
|
+
throw new Error('Credential name is required');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const credentialsData = await loadCredentials();
|
|
160
|
+
|
|
161
|
+
if (!credentialsData.credentials[name]) {
|
|
162
|
+
throw new Error(`Credential "${name}" not found`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const encryptedData = credentialsData.credentials[name].encrypted;
|
|
166
|
+
|
|
167
|
+
// Decrypt the credential data
|
|
168
|
+
const decryptedData = decrypt(encryptedData, MASTER_KEY);
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
data: decryptedData,
|
|
172
|
+
type: credentialsData.credentials[name].type,
|
|
173
|
+
createdAt: credentialsData.credentials[name].createdAt,
|
|
174
|
+
updatedAt: credentialsData.credentials[name].updatedAt
|
|
175
|
+
};
|
|
176
|
+
} catch (error) {
|
|
177
|
+
logger.error('Failed to retrieve credential', { name, error: error.message });
|
|
178
|
+
throw new Error(`Failed to retrieve credential "${name}": ${error.message}`);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Updates a stored credential
|
|
184
|
+
*
|
|
185
|
+
* @param {string} name - Name of the credential to update
|
|
186
|
+
* @param {Object} data - New credential data
|
|
187
|
+
* @param {Object} options - Additional options
|
|
188
|
+
* @returns {Object} Status of the operation
|
|
189
|
+
*/
|
|
190
|
+
const updateCredential = async (name, data, options = {}) => {
|
|
191
|
+
if (!name || typeof name !== 'string') {
|
|
192
|
+
throw new Error('Credential name is required');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!data) {
|
|
196
|
+
throw new Error('Credential data is required');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const credentialsData = await loadCredentials();
|
|
201
|
+
|
|
202
|
+
if (!credentialsData.credentials[name]) {
|
|
203
|
+
throw new Error(`Credential "${name}" not found`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const keyId = options.keyId || credentialsData.meta.keyId || 'v1';
|
|
207
|
+
|
|
208
|
+
// Encrypt the credential data
|
|
209
|
+
const encryptedData = encrypt(data, MASTER_KEY, keyId);
|
|
210
|
+
|
|
211
|
+
// Update the credential
|
|
212
|
+
credentialsData.credentials[name] = {
|
|
213
|
+
...credentialsData.credentials[name],
|
|
214
|
+
encrypted: encryptedData,
|
|
215
|
+
updatedAt: new Date().toISOString()
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Update type if provided
|
|
219
|
+
if (options.type) {
|
|
220
|
+
credentialsData.credentials[name].type = options.type;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
await saveCredentials(credentialsData);
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
success: true,
|
|
227
|
+
name,
|
|
228
|
+
message: `Credential "${name}" updated successfully`
|
|
229
|
+
};
|
|
230
|
+
} catch (error) {
|
|
231
|
+
logger.error('Failed to update credential', { name, error: error.message });
|
|
232
|
+
throw new Error(`Failed to update credential "${name}": ${error.message}`);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Deletes a stored credential
|
|
238
|
+
*
|
|
239
|
+
* @param {string} name - Name of the credential to delete
|
|
240
|
+
* @returns {Object} Status of the operation
|
|
241
|
+
*/
|
|
242
|
+
const deleteCredential = async (name) => {
|
|
243
|
+
if (!name || typeof name !== 'string') {
|
|
244
|
+
throw new Error('Credential name is required');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const credentialsData = await loadCredentials();
|
|
249
|
+
|
|
250
|
+
if (!credentialsData.credentials[name]) {
|
|
251
|
+
throw new Error(`Credential "${name}" not found`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Delete the credential
|
|
255
|
+
delete credentialsData.credentials[name];
|
|
256
|
+
|
|
257
|
+
await saveCredentials(credentialsData);
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
success: true,
|
|
261
|
+
name,
|
|
262
|
+
message: `Credential "${name}" deleted successfully`
|
|
263
|
+
};
|
|
264
|
+
} catch (error) {
|
|
265
|
+
logger.error('Failed to delete credential', { name, error: error.message });
|
|
266
|
+
throw new Error(`Failed to delete credential "${name}": ${error.message}`);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Lists all available credentials (without sensitive data)
|
|
272
|
+
*
|
|
273
|
+
* @returns {Object[]} List of credentials with metadata
|
|
274
|
+
*/
|
|
275
|
+
const listCredentials = async () => {
|
|
276
|
+
try {
|
|
277
|
+
const credentialsData = await loadCredentials();
|
|
278
|
+
|
|
279
|
+
return Object.entries(credentialsData.credentials).map(([name, cred]) => ({
|
|
280
|
+
name,
|
|
281
|
+
type: cred.type,
|
|
282
|
+
createdAt: cred.createdAt,
|
|
283
|
+
updatedAt: cred.updatedAt
|
|
284
|
+
}));
|
|
285
|
+
} catch (error) {
|
|
286
|
+
logger.error('Failed to list credentials', { error: error.message });
|
|
287
|
+
throw new Error('Failed to list credentials');
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Rotates the encryption key for all credentials
|
|
293
|
+
*
|
|
294
|
+
* @param {string} newKeyId - New key identifier
|
|
295
|
+
* @returns {Object} Status of the operation
|
|
296
|
+
*/
|
|
297
|
+
const rotateEncryptionKey = async (newKeyId = `v${Date.now()}`) => {
|
|
298
|
+
try {
|
|
299
|
+
const credentialsData = await loadCredentials();
|
|
300
|
+
const currentKeyId = credentialsData.meta.keyId || 'v1';
|
|
301
|
+
|
|
302
|
+
// Skip if key ID is the same
|
|
303
|
+
if (newKeyId === currentKeyId) {
|
|
304
|
+
return {
|
|
305
|
+
success: true,
|
|
306
|
+
message: 'Key already up to date, no rotation needed',
|
|
307
|
+
keyId: currentKeyId
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Re-encrypt all credentials with the new key ID
|
|
312
|
+
for (const [name, cred] of Object.entries(credentialsData.credentials)) {
|
|
313
|
+
try {
|
|
314
|
+
// Decrypt with current key
|
|
315
|
+
const decryptedData = decrypt(cred.encrypted, MASTER_KEY);
|
|
316
|
+
|
|
317
|
+
// Re-encrypt with new key ID
|
|
318
|
+
cred.encrypted = encrypt(decryptedData, MASTER_KEY, newKeyId);
|
|
319
|
+
cred.updatedAt = new Date().toISOString();
|
|
320
|
+
} catch (error) {
|
|
321
|
+
logger.error(`Failed to rotate key for credential "${name}"`, { error: error.message });
|
|
322
|
+
// Continue with other credentials
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Update metadata
|
|
327
|
+
credentialsData.meta.keyId = newKeyId;
|
|
328
|
+
credentialsData.meta.keyRotatedAt = new Date().toISOString();
|
|
329
|
+
|
|
330
|
+
await saveCredentials(credentialsData);
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
success: true,
|
|
334
|
+
message: `Encryption key rotated successfully to "${newKeyId}"`,
|
|
335
|
+
keyId: newKeyId
|
|
336
|
+
};
|
|
337
|
+
} catch (error) {
|
|
338
|
+
logger.error('Failed to rotate encryption key', { error: error.message });
|
|
339
|
+
throw new Error('Failed to rotate encryption key');
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// Initialize credentials storage on module load
|
|
344
|
+
(async () => {
|
|
345
|
+
try {
|
|
346
|
+
await initCredentialsStorage();
|
|
347
|
+
} catch (error) {
|
|
348
|
+
logger.error('Failed to initialize credentials storage', { error: error.message });
|
|
349
|
+
}
|
|
350
|
+
})();
|
|
351
|
+
|
|
352
|
+
module.exports = {
|
|
353
|
+
storeCredential,
|
|
354
|
+
getCredential,
|
|
355
|
+
updateCredential,
|
|
356
|
+
deleteCredential,
|
|
357
|
+
listCredentials,
|
|
358
|
+
rotateEncryptionKey
|
|
359
|
+
};
|