Feature: Option to add apikeys to the DB instead of api.json. (#2783)

* Feature: Option to add apikeys to the DB instead of api.json.

* add api storage type env variable

* code cleanup and simplification.

* md table fixes

---------

Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
Vinod Kiran
2024-07-26 22:22:12 +05:30
committed by GitHub
parent e5018d2743
commit 2dd1791ec2
26 changed files with 647 additions and 38 deletions
+206 -20
View File
@@ -1,25 +1,94 @@
import { StatusCodes } from 'http-status-codes'
import { addAPIKey, deleteAPIKey, getAPIKeys, updateAPIKey } from '../../utils/apiKey'
import {
addAPIKey as addAPIKey_json,
deleteAPIKey as deleteAPIKey_json,
generateAPIKey,
generateSecretHash,
getApiKey as getApiKey_json,
getAPIKeys as getAPIKeys_json,
updateAPIKey as updateAPIKey_json,
replaceAllAPIKeys as replaceAllAPIKeys_json,
importKeys as importKeys_json
} from '../../utils/apiKey'
import { addChatflowsCount } from '../../utils/addChatflowsCount'
import { getApiKey } from '../../utils/apiKey'
import { InternalFlowiseError } from '../../errors/internalFlowiseError'
import { getErrorMessage } from '../../errors/utils'
import { getRunningExpressApp } from '../../utils/getRunningExpressApp'
import { ApiKey } from '../../database/entities/ApiKey'
import { appConfig } from '../../AppConfig'
import { randomBytes } from 'crypto'
import { Not, IsNull } from 'typeorm'
const _apikeysStoredInJson = (): boolean => {
return appConfig.apiKeys.storageType === 'json'
}
const _apikeysStoredInDb = (): boolean => {
return appConfig.apiKeys.storageType === 'db'
}
const getAllApiKeys = async () => {
try {
const keys = await getAPIKeys()
const dbResponse = await addChatflowsCount(keys)
return dbResponse
if (_apikeysStoredInJson()) {
const keys = await getAPIKeys_json()
return await addChatflowsCount(keys)
} else if (_apikeysStoredInDb()) {
const appServer = getRunningExpressApp()
let keys = await appServer.AppDataSource.getRepository(ApiKey).find()
if (keys.length === 0) {
await createApiKey('DefaultKey')
keys = await appServer.AppDataSource.getRepository(ApiKey).find()
}
return await addChatflowsCount(keys)
} else {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `UNKNOWN APIKEY_STORAGE_TYPE`)
}
} catch (error) {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: apikeyService.getAllApiKeys - ${getErrorMessage(error)}`)
}
}
const getApiKey = async (keyName: string) => {
try {
if (_apikeysStoredInJson()) {
return getApiKey_json(keyName)
} else if (_apikeysStoredInDb()) {
const appServer = getRunningExpressApp()
const currentKey = await appServer.AppDataSource.getRepository(ApiKey).findOneBy({
keyName: keyName
})
if (!currentKey) {
return undefined
}
return currentKey
} else {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `UNKNOWN APIKEY_STORAGE_TYPE`)
}
} catch (error) {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: apikeyService.createApiKey - ${getErrorMessage(error)}`)
}
}
const createApiKey = async (keyName: string) => {
try {
const keys = await addAPIKey(keyName)
const dbResponse = await addChatflowsCount(keys)
return dbResponse
if (_apikeysStoredInJson()) {
const keys = await addAPIKey_json(keyName)
return await addChatflowsCount(keys)
} else if (_apikeysStoredInDb()) {
const apiKey = generateAPIKey()
const apiSecret = generateSecretHash(apiKey)
const appServer = getRunningExpressApp()
const newKey = new ApiKey()
newKey.id = randomBytes(16).toString('hex')
newKey.apiKey = apiKey
newKey.apiSecret = apiSecret
newKey.keyName = keyName
const key = appServer.AppDataSource.getRepository(ApiKey).create(newKey)
await appServer.AppDataSource.getRepository(ApiKey).save(key)
return getAllApiKeys()
} else {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `UNKNOWN APIKEY_STORAGE_TYPE`)
}
} catch (error) {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: apikeyService.createApiKey - ${getErrorMessage(error)}`)
}
@@ -28,9 +97,23 @@ const createApiKey = async (keyName: string) => {
// Update api key
const updateApiKey = async (id: string, keyName: string) => {
try {
const keys = await updateAPIKey(id, keyName)
const dbResponse = await addChatflowsCount(keys)
return dbResponse
if (_apikeysStoredInJson()) {
const keys = await updateAPIKey_json(id, keyName)
return await addChatflowsCount(keys)
} else if (_apikeysStoredInDb()) {
const appServer = getRunningExpressApp()
const currentKey = await appServer.AppDataSource.getRepository(ApiKey).findOneBy({
id: id
})
if (!currentKey) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `ApiKey ${currentKey} not found`)
}
currentKey.keyName = keyName
await appServer.AppDataSource.getRepository(ApiKey).save(currentKey)
return getAllApiKeys()
} else {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `UNKNOWN APIKEY_STORAGE_TYPE`)
}
} catch (error) {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: apikeyService.updateApiKey - ${getErrorMessage(error)}`)
}
@@ -38,22 +121,123 @@ const updateApiKey = async (id: string, keyName: string) => {
const deleteApiKey = async (id: string) => {
try {
const keys = await deleteAPIKey(id)
const dbResponse = await addChatflowsCount(keys)
return dbResponse
if (_apikeysStoredInJson()) {
const keys = await deleteAPIKey_json(id)
return await addChatflowsCount(keys)
} else if (_apikeysStoredInDb()) {
const appServer = getRunningExpressApp()
const dbResponse = await appServer.AppDataSource.getRepository(ApiKey).delete({ id: id })
if (!dbResponse) {
throw new InternalFlowiseError(StatusCodes.NOT_FOUND, `ApiKey ${id} not found`)
}
return getAllApiKeys()
} else {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `UNKNOWN APIKEY_STORAGE_TYPE`)
}
} catch (error) {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: apikeyService.deleteApiKey - ${getErrorMessage(error)}`)
}
}
const importKeys = async (body: any) => {
try {
const jsonFile = body.jsonFile
const splitDataURI = jsonFile.split(',')
if (splitDataURI[0] !== 'data:application/json;base64') {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Invalid dataURI`)
}
const bf = Buffer.from(splitDataURI[1] || '', 'base64')
const plain = bf.toString('utf8')
const keys = JSON.parse(plain)
if (_apikeysStoredInJson()) {
if (body.importMode === 'replaceAll') {
await replaceAllAPIKeys_json(keys)
} else {
await importKeys_json(keys, body.importMode)
}
return await addChatflowsCount(keys)
} else if (_apikeysStoredInDb()) {
const appServer = getRunningExpressApp()
const allApiKeys = await appServer.AppDataSource.getRepository(ApiKey).find()
if (body.importMode === 'replaceAll') {
await appServer.AppDataSource.getRepository(ApiKey).delete({
id: Not(IsNull())
})
}
if (body.importMode === 'errorIfExist') {
// if importMode is errorIfExist, check for existing keys and raise error before any modification to the DB
for (const key of keys) {
const keyNameExists = allApiKeys.find((k) => k.keyName === key.keyName)
if (keyNameExists) {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Key with name ${key.keyName} already exists`)
}
}
}
// iterate through the keys and add them to the database
for (const key of keys) {
const keyNameExists = allApiKeys.find((k) => k.keyName === key.keyName)
if (keyNameExists) {
const keyIndex = allApiKeys.findIndex((k) => k.keyName === key.keyName)
switch (body.importMode) {
case 'overwriteIfExist': {
const currentKey = allApiKeys[keyIndex]
currentKey.id = key.id
currentKey.apiKey = key.apiKey
currentKey.apiSecret = key.apiSecret
await appServer.AppDataSource.getRepository(ApiKey).save(currentKey)
break
}
case 'ignoreIfExist': {
// ignore this key and continue
continue
}
case 'errorIfExist': {
// should not reach here as we have already checked for existing keys
throw new Error(`Key with name ${key.keyName} already exists`)
}
default: {
throw new Error(`Unknown overwrite option ${body.importMode}`)
}
}
} else {
const newKey = new ApiKey()
newKey.id = key.id
newKey.apiKey = key.apiKey
newKey.apiSecret = key.apiSecret
newKey.keyName = key.keyName
const newKeyEntity = appServer.AppDataSource.getRepository(ApiKey).create(newKey)
await appServer.AppDataSource.getRepository(ApiKey).save(newKeyEntity)
}
}
return getAllApiKeys()
} else {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `UNKNOWN APIKEY_STORAGE_TYPE`)
}
} catch (error) {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `Error: apikeyService.importKeys - ${getErrorMessage(error)}`)
}
}
const verifyApiKey = async (paramApiKey: string): Promise<string> => {
try {
const apiKey = await getApiKey(paramApiKey)
if (!apiKey) {
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized`)
if (_apikeysStoredInJson()) {
const apiKey = await getApiKey_json(paramApiKey)
if (!apiKey) {
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized`)
}
return 'OK'
} else if (_apikeysStoredInDb()) {
const appServer = getRunningExpressApp()
const apiKey = await appServer.AppDataSource.getRepository(ApiKey).findOneBy({
apiKey: paramApiKey
})
if (!apiKey) {
throw new InternalFlowiseError(StatusCodes.UNAUTHORIZED, `Unauthorized`)
}
return 'OK'
} else {
throw new InternalFlowiseError(StatusCodes.INTERNAL_SERVER_ERROR, `UNKNOWN APIKEY_STORAGE_TYPE`)
}
const dbResponse = 'OK'
return dbResponse
} catch (error) {
if (error instanceof InternalFlowiseError && error.statusCode === StatusCodes.UNAUTHORIZED) {
throw error
@@ -71,5 +255,7 @@ export default {
deleteApiKey,
getAllApiKeys,
updateApiKey,
verifyApiKey
verifyApiKey,
getApiKey,
importKeys
}