mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 17:01:00 +03:00
add credentials
This commit is contained in:
@@ -13,18 +13,26 @@ import {
|
||||
IReactFlowNode,
|
||||
IVariableDict,
|
||||
INodeData,
|
||||
IOverrideConfig
|
||||
IOverrideConfig,
|
||||
ICredentialDataDecrypted,
|
||||
IComponentCredentials,
|
||||
ICredentialReqBody
|
||||
} from '../Interface'
|
||||
import { cloneDeep, get, omit, merge } from 'lodash'
|
||||
import { ICommonObject, getInputVariables, IDatabaseEntity } from 'flowise-components'
|
||||
import { scryptSync, randomBytes, timingSafeEqual } from 'crypto'
|
||||
import { lib, PBKDF2, AES, enc } from 'crypto-js'
|
||||
|
||||
import { ChatFlow } from '../entity/ChatFlow'
|
||||
import { ChatMessage } from '../entity/ChatMessage'
|
||||
import { Credential } from '../entity/Credential'
|
||||
import { Tool } from '../entity/Tool'
|
||||
import { DataSource } from 'typeorm'
|
||||
|
||||
const QUESTION_VAR_PREFIX = 'question'
|
||||
export const databaseEntities: IDatabaseEntity = { ChatFlow: ChatFlow, ChatMessage: ChatMessage, Tool: Tool }
|
||||
const REDACTED_CREDENTIAL_VALUE = '_FLOWISE_BLANK_07167752-1a71-43b1-bf8f-4f32252165db'
|
||||
|
||||
export const databaseEntities: IDatabaseEntity = { ChatFlow: ChatFlow, ChatMessage: ChatMessage, Tool: Tool, Credential: Credential }
|
||||
|
||||
/**
|
||||
* Returns the home folder path of the user if
|
||||
@@ -399,9 +407,8 @@ export const replaceInputsWithConfig = (flowNodeData: INodeData, overrideConfig:
|
||||
const types = 'inputs'
|
||||
|
||||
const getParamValues = (paramsObj: ICommonObject) => {
|
||||
for (const key in paramsObj) {
|
||||
const paramValue: string = paramsObj[key]
|
||||
paramsObj[key] = overrideConfig[key] ?? paramValue
|
||||
for (const config in overrideConfig) {
|
||||
paramsObj[config] = overrideConfig[config]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,11 +630,12 @@ export const mapMimeTypeToInputField = (mimeType: string) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all available inpur params config
|
||||
* Find all available input params config
|
||||
* @param {IReactFlowNode[]} reactFlowNodes
|
||||
* @returns {Promise<IOverrideConfig[]>}
|
||||
* @param {IComponentCredentials} componentCredentials
|
||||
* @returns {IOverrideConfig[]}
|
||||
*/
|
||||
export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => {
|
||||
export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[], componentCredentials: IComponentCredentials) => {
|
||||
const configs: IOverrideConfig[] = []
|
||||
|
||||
for (const flowNode of reactFlowNodes) {
|
||||
@@ -653,6 +661,23 @@ export const findAvailableConfigs = (reactFlowNodes: IReactFlowNode[]) => {
|
||||
.join(', ')
|
||||
: 'string'
|
||||
}
|
||||
} else if (inputParam.type === 'credential') {
|
||||
// get component credential inputs
|
||||
for (const name of inputParam.credentialNames ?? []) {
|
||||
if (Object.prototype.hasOwnProperty.call(componentCredentials, name)) {
|
||||
const inputs = componentCredentials[name]?.inputs ?? []
|
||||
for (const input of inputs) {
|
||||
obj = {
|
||||
node: flowNode.data.label,
|
||||
label: input.label,
|
||||
name: input.name,
|
||||
type: input.type === 'password' ? 'string' : input.type
|
||||
}
|
||||
configs.push(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
} else {
|
||||
obj = {
|
||||
node: flowNode.data.label,
|
||||
@@ -705,3 +730,118 @@ export const isFlowValidForStream = (reactFlowNodes: IReactFlowNode[], endingNod
|
||||
|
||||
return isChatOrLLMsExist && isValidChainOrAgent && !isVectorStoreFaiss(endingNodeData) && process.env.EXECUTION_MODE !== 'child'
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path of encryption key
|
||||
* @returns {string}
|
||||
*/
|
||||
export const getEncryptionKeyPath = (): string => {
|
||||
return process.env.SECRETKEY_PATH
|
||||
? path.join(process.env.SECRETKEY_PATH, 'encryption.key')
|
||||
: path.join(__dirname, '..', '..', 'encryption.key')
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an encryption key
|
||||
* @returns {string}
|
||||
*/
|
||||
export const generateEncryptKey = (): string => {
|
||||
const salt = lib.WordArray.random(128 / 8)
|
||||
const key256Bits = PBKDF2(process.env.PASSPHRASE || 'MYPASSPHRASE', salt, {
|
||||
keySize: 256 / 32,
|
||||
iterations: 1000
|
||||
})
|
||||
return key256Bits.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encryption key
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export const getEncryptionKey = async (): Promise<string> => {
|
||||
try {
|
||||
return await fs.promises.readFile(getEncryptionKeyPath(), 'utf8')
|
||||
} catch (error) {
|
||||
const encryptKey = generateEncryptKey()
|
||||
await fs.promises.writeFile(getEncryptionKeyPath(), encryptKey)
|
||||
return encryptKey
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt credential data
|
||||
* @param {ICredentialDataDecrypted} plainDataObj
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export const encryptCredentialData = async (plainDataObj: ICredentialDataDecrypted): Promise<string> => {
|
||||
const encryptKey = await getEncryptionKey()
|
||||
return AES.encrypt(JSON.stringify(plainDataObj), encryptKey).toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt credential data
|
||||
* @param {string} encryptedData
|
||||
* @param {string} componentCredentialName
|
||||
* @param {IComponentCredentials} componentCredentials
|
||||
* @returns {Promise<ICredentialDataDecrypted>}
|
||||
*/
|
||||
export const decryptCredentialData = async (
|
||||
encryptedData: string,
|
||||
componentCredentialName?: string,
|
||||
componentCredentials?: IComponentCredentials
|
||||
): Promise<ICredentialDataDecrypted> => {
|
||||
const encryptKey = await getEncryptionKey()
|
||||
const decryptedData = AES.decrypt(encryptedData, encryptKey)
|
||||
try {
|
||||
if (componentCredentialName && componentCredentials) {
|
||||
const plainDataObj = JSON.parse(decryptedData.toString(enc.Utf8))
|
||||
return redactCredentialWithPasswordType(componentCredentialName, plainDataObj, componentCredentials)
|
||||
}
|
||||
return JSON.parse(decryptedData.toString(enc.Utf8))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
throw new Error('Credentials could not be decrypted.')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform ICredentialBody from req to Credential entity
|
||||
* @param {ICredentialReqBody} body
|
||||
* @returns {Credential}
|
||||
*/
|
||||
export const transformToCredentialEntity = async (body: ICredentialReqBody): Promise<Credential> => {
|
||||
const encryptedData = await encryptCredentialData(body.plainDataObj)
|
||||
|
||||
const credentialBody = {
|
||||
name: body.name,
|
||||
credentialName: body.credentialName,
|
||||
encryptedData
|
||||
}
|
||||
|
||||
const newCredential = new Credential()
|
||||
Object.assign(newCredential, credentialBody)
|
||||
|
||||
return newCredential
|
||||
}
|
||||
|
||||
/**
|
||||
* Redact values that are of password type to avoid sending back to client
|
||||
* @param {string} componentCredentialName
|
||||
* @param {ICredentialDataDecrypted} decryptedCredentialObj
|
||||
* @param {IComponentCredentials} componentCredentials
|
||||
* @returns {ICredentialDataDecrypted}
|
||||
*/
|
||||
export const redactCredentialWithPasswordType = (
|
||||
componentCredentialName: string,
|
||||
decryptedCredentialObj: ICredentialDataDecrypted,
|
||||
componentCredentials: IComponentCredentials
|
||||
): ICredentialDataDecrypted => {
|
||||
const plainDataObj = cloneDeep(decryptedCredentialObj)
|
||||
for (const cred in plainDataObj) {
|
||||
const inputParam = componentCredentials[componentCredentialName].inputs?.find((inp) => inp.type === 'password' && inp.name === cred)
|
||||
if (inputParam) {
|
||||
plainDataObj[cred] = REDACTED_CREDENTIAL_VALUE
|
||||
}
|
||||
}
|
||||
return plainDataObj
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ export function expressRequestLogger(req: Request, res: Response, next: NextFunc
|
||||
GET: '⬇️',
|
||||
POST: '⬆️',
|
||||
PUT: '🖊',
|
||||
DELETE: '❌'
|
||||
DELETE: '❌',
|
||||
OPTION: '🔗'
|
||||
}
|
||||
|
||||
return requetsEmojis[method] || '?'
|
||||
|
||||
Reference in New Issue
Block a user