add credentials

This commit is contained in:
Henry
2023-07-15 14:25:52 +01:00
parent aee0a51f73
commit 413d654493
37 changed files with 1858 additions and 126 deletions
+148 -8
View File
@@ -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
}
+2 -1
View File
@@ -81,7 +81,8 @@ export function expressRequestLogger(req: Request, res: Response, next: NextFunc
GET: '⬇️',
POST: '⬆️',
PUT: '🖊',
DELETE: '❌'
DELETE: '❌',
OPTION: '🔗'
}
return requetsEmojis[method] || '?'