Skip to content

Commit 63b400e

Browse files
committed
refactor: move api key utilities to seperate file
1 parent cc5bffb commit 63b400e

File tree

3 files changed

+159
-160
lines changed

3 files changed

+159
-160
lines changed

packages/server/src/index.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,11 @@ import {
3131
constructGraphs,
3232
resolveVariables,
3333
isStartNodeDependOnInput,
34-
getAPIKeys,
35-
addAPIKey,
36-
updateAPIKey,
37-
deleteAPIKey,
38-
compareKeys,
3934
mapMimeTypeToInputField,
4035
findAvailableConfigs,
4136
isSameOverrideConfig,
42-
replaceAllAPIKeys,
4337
isFlowValidForStream,
4438
databaseEntities,
45-
getApiKey,
4639
transformToCredentialEntity,
4740
decryptCredentialData,
4841
clearAllSessionMemory,
@@ -64,6 +57,7 @@ import { ChatflowPool } from './ChatflowPool'
6457
import { CachePool } from './CachePool'
6558
import { ICommonObject, INodeOptionsValue } from 'flowise-components'
6659
import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit'
60+
import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, replaceAllAPIKeys, updateAPIKey } from './utils/apiKey'
6761

6862
export class App {
6963
app: express.Application

packages/server/src/utils/apiKey.ts

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { randomBytes, scryptSync, timingSafeEqual } from 'crypto'
2+
import { ICommonObject } from 'flowise-components'
3+
import moment from 'moment'
4+
import fs from 'fs'
5+
import path from 'path'
6+
import logger from './logger'
7+
8+
/**
9+
* Returns the api key path
10+
* @returns {string}
11+
*/
12+
export const getAPIKeyPath = (): string => {
13+
return process.env.APIKEY_PATH ? path.join(process.env.APIKEY_PATH, 'api.json') : path.join(__dirname, '..', '..', 'api.json')
14+
}
15+
16+
/**
17+
* Generate the api key
18+
* @returns {string}
19+
*/
20+
export const generateAPIKey = (): string => {
21+
const buffer = randomBytes(32)
22+
return buffer.toString('base64')
23+
}
24+
25+
/**
26+
* Generate the secret key
27+
* @param {string} apiKey
28+
* @returns {string}
29+
*/
30+
export const generateSecretHash = (apiKey: string): string => {
31+
const salt = randomBytes(8).toString('hex')
32+
const buffer = scryptSync(apiKey, salt, 64) as Buffer
33+
return `${buffer.toString('hex')}.${salt}`
34+
}
35+
36+
/**
37+
* Verify valid keys
38+
* @param {string} storedKey
39+
* @param {string} suppliedKey
40+
* @returns {boolean}
41+
*/
42+
export const compareKeys = (storedKey: string, suppliedKey: string): boolean => {
43+
const [hashedPassword, salt] = storedKey.split('.')
44+
const buffer = scryptSync(suppliedKey, salt, 64) as Buffer
45+
return timingSafeEqual(Buffer.from(hashedPassword, 'hex'), buffer)
46+
}
47+
48+
/**
49+
* Get API keys
50+
* @returns {Promise<ICommonObject[]>}
51+
*/
52+
export const getAPIKeys = async (): Promise<ICommonObject[]> => {
53+
try {
54+
const content = await fs.promises.readFile(getAPIKeyPath(), 'utf8')
55+
return JSON.parse(content)
56+
} catch (error) {
57+
const keyName = 'DefaultKey'
58+
const apiKey = generateAPIKey()
59+
const apiSecret = generateSecretHash(apiKey)
60+
const content = [
61+
{
62+
keyName,
63+
apiKey,
64+
apiSecret,
65+
createdAt: moment().format('DD-MMM-YY'),
66+
id: randomBytes(16).toString('hex')
67+
}
68+
]
69+
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8')
70+
return content
71+
}
72+
}
73+
74+
/**
75+
* Add new API key
76+
* @param {string} keyName
77+
* @returns {Promise<ICommonObject[]>}
78+
*/
79+
export const addAPIKey = async (keyName: string): Promise<ICommonObject[]> => {
80+
const existingAPIKeys = await getAPIKeys()
81+
const apiKey = generateAPIKey()
82+
const apiSecret = generateSecretHash(apiKey)
83+
const content = [
84+
...existingAPIKeys,
85+
{
86+
keyName,
87+
apiKey,
88+
apiSecret,
89+
createdAt: moment().format('DD-MMM-YY'),
90+
id: randomBytes(16).toString('hex')
91+
}
92+
]
93+
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8')
94+
return content
95+
}
96+
97+
/**
98+
* Get API Key details
99+
* @param {string} apiKey
100+
* @returns {Promise<ICommonObject[]>}
101+
*/
102+
export const getApiKey = async (apiKey: string) => {
103+
const existingAPIKeys = await getAPIKeys()
104+
const keyIndex = existingAPIKeys.findIndex((key) => key.apiKey === apiKey)
105+
if (keyIndex < 0) return undefined
106+
return existingAPIKeys[keyIndex]
107+
}
108+
109+
/**
110+
* Update existing API key
111+
* @param {string} keyIdToUpdate
112+
* @param {string} newKeyName
113+
* @returns {Promise<ICommonObject[]>}
114+
*/
115+
export const updateAPIKey = async (keyIdToUpdate: string, newKeyName: string): Promise<ICommonObject[]> => {
116+
const existingAPIKeys = await getAPIKeys()
117+
const keyIndex = existingAPIKeys.findIndex((key) => key.id === keyIdToUpdate)
118+
if (keyIndex < 0) return []
119+
existingAPIKeys[keyIndex].keyName = newKeyName
120+
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(existingAPIKeys), 'utf8')
121+
return existingAPIKeys
122+
}
123+
124+
/**
125+
* Delete API key
126+
* @param {string} keyIdToDelete
127+
* @returns {Promise<ICommonObject[]>}
128+
*/
129+
export const deleteAPIKey = async (keyIdToDelete: string): Promise<ICommonObject[]> => {
130+
const existingAPIKeys = await getAPIKeys()
131+
const result = existingAPIKeys.filter((key) => key.id !== keyIdToDelete)
132+
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(result), 'utf8')
133+
return result
134+
}
135+
136+
/**
137+
* Replace all api keys
138+
* @param {ICommonObject[]} content
139+
* @returns {Promise<void>}
140+
*/
141+
export const replaceAllAPIKeys = async (content: ICommonObject[]): Promise<void> => {
142+
try {
143+
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8')
144+
} catch (error) {
145+
logger.error(error)
146+
}
147+
}

packages/server/src/utils/index.ts

+11-153
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,32 @@
11
import path from 'path'
22
import fs from 'fs'
3-
import moment from 'moment'
43
import logger from './logger'
54
import {
5+
IComponentCredentials,
66
IComponentNodes,
7+
ICredentialDataDecrypted,
8+
ICredentialReqBody,
79
IDepthQueue,
810
IExploredNode,
11+
INodeData,
912
INodeDependencies,
1013
INodeDirectedGraph,
1114
INodeQueue,
15+
IOverrideConfig,
1216
IReactFlowEdge,
1317
IReactFlowNode,
14-
IVariableDict,
15-
INodeData,
16-
IOverrideConfig,
17-
ICredentialDataDecrypted,
18-
IComponentCredentials,
19-
ICredentialReqBody
18+
IVariableDict
2019
} from '../Interface'
2120
import { cloneDeep, get, isEqual } from 'lodash'
2221
import {
23-
ICommonObject,
22+
convertChatHistoryToText,
2423
getInputVariables,
25-
IDatabaseEntity,
2624
handleEscapeCharacters,
27-
IMessage,
28-
convertChatHistoryToText
25+
ICommonObject,
26+
IDatabaseEntity,
27+
IMessage
2928
} from 'flowise-components'
30-
import { scryptSync, randomBytes, timingSafeEqual } from 'crypto'
29+
import { randomBytes } from 'crypto'
3130
import { AES, enc } from 'crypto-js'
3231

3332
import { ChatFlow } from '../database/entities/ChatFlow'
@@ -574,147 +573,6 @@ export const isSameOverrideConfig = (
574573
return false
575574
}
576575

577-
/**
578-
* Returns the api key path
579-
* @returns {string}
580-
*/
581-
export const getAPIKeyPath = (): string => {
582-
return process.env.APIKEY_PATH ? path.join(process.env.APIKEY_PATH, 'api.json') : path.join(__dirname, '..', '..', 'api.json')
583-
}
584-
585-
/**
586-
* Generate the api key
587-
* @returns {string}
588-
*/
589-
export const generateAPIKey = (): string => {
590-
const buffer = randomBytes(32)
591-
return buffer.toString('base64')
592-
}
593-
594-
/**
595-
* Generate the secret key
596-
* @param {string} apiKey
597-
* @returns {string}
598-
*/
599-
export const generateSecretHash = (apiKey: string): string => {
600-
const salt = randomBytes(8).toString('hex')
601-
const buffer = scryptSync(apiKey, salt, 64) as Buffer
602-
return `${buffer.toString('hex')}.${salt}`
603-
}
604-
605-
/**
606-
* Verify valid keys
607-
* @param {string} storedKey
608-
* @param {string} suppliedKey
609-
* @returns {boolean}
610-
*/
611-
export const compareKeys = (storedKey: string, suppliedKey: string): boolean => {
612-
const [hashedPassword, salt] = storedKey.split('.')
613-
const buffer = scryptSync(suppliedKey, salt, 64) as Buffer
614-
return timingSafeEqual(Buffer.from(hashedPassword, 'hex'), buffer)
615-
}
616-
617-
/**
618-
* Get API keys
619-
* @returns {Promise<ICommonObject[]>}
620-
*/
621-
export const getAPIKeys = async (): Promise<ICommonObject[]> => {
622-
try {
623-
const content = await fs.promises.readFile(getAPIKeyPath(), 'utf8')
624-
return JSON.parse(content)
625-
} catch (error) {
626-
const keyName = 'DefaultKey'
627-
const apiKey = generateAPIKey()
628-
const apiSecret = generateSecretHash(apiKey)
629-
const content = [
630-
{
631-
keyName,
632-
apiKey,
633-
apiSecret,
634-
createdAt: moment().format('DD-MMM-YY'),
635-
id: randomBytes(16).toString('hex')
636-
}
637-
]
638-
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8')
639-
return content
640-
}
641-
}
642-
643-
/**
644-
* Add new API key
645-
* @param {string} keyName
646-
* @returns {Promise<ICommonObject[]>}
647-
*/
648-
export const addAPIKey = async (keyName: string): Promise<ICommonObject[]> => {
649-
const existingAPIKeys = await getAPIKeys()
650-
const apiKey = generateAPIKey()
651-
const apiSecret = generateSecretHash(apiKey)
652-
const content = [
653-
...existingAPIKeys,
654-
{
655-
keyName,
656-
apiKey,
657-
apiSecret,
658-
createdAt: moment().format('DD-MMM-YY'),
659-
id: randomBytes(16).toString('hex')
660-
}
661-
]
662-
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8')
663-
return content
664-
}
665-
666-
/**
667-
* Get API Key details
668-
* @param {string} apiKey
669-
* @returns {Promise<ICommonObject[]>}
670-
*/
671-
export const getApiKey = async (apiKey: string) => {
672-
const existingAPIKeys = await getAPIKeys()
673-
const keyIndex = existingAPIKeys.findIndex((key) => key.apiKey === apiKey)
674-
if (keyIndex < 0) return undefined
675-
return existingAPIKeys[keyIndex]
676-
}
677-
678-
/**
679-
* Update existing API key
680-
* @param {string} keyIdToUpdate
681-
* @param {string} newKeyName
682-
* @returns {Promise<ICommonObject[]>}
683-
*/
684-
export const updateAPIKey = async (keyIdToUpdate: string, newKeyName: string): Promise<ICommonObject[]> => {
685-
const existingAPIKeys = await getAPIKeys()
686-
const keyIndex = existingAPIKeys.findIndex((key) => key.id === keyIdToUpdate)
687-
if (keyIndex < 0) return []
688-
existingAPIKeys[keyIndex].keyName = newKeyName
689-
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(existingAPIKeys), 'utf8')
690-
return existingAPIKeys
691-
}
692-
693-
/**
694-
* Delete API key
695-
* @param {string} keyIdToDelete
696-
* @returns {Promise<ICommonObject[]>}
697-
*/
698-
export const deleteAPIKey = async (keyIdToDelete: string): Promise<ICommonObject[]> => {
699-
const existingAPIKeys = await getAPIKeys()
700-
const result = existingAPIKeys.filter((key) => key.id !== keyIdToDelete)
701-
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(result), 'utf8')
702-
return result
703-
}
704-
705-
/**
706-
* Replace all api keys
707-
* @param {ICommonObject[]} content
708-
* @returns {Promise<void>}
709-
*/
710-
export const replaceAllAPIKeys = async (content: ICommonObject[]): Promise<void> => {
711-
try {
712-
await fs.promises.writeFile(getAPIKeyPath(), JSON.stringify(content), 'utf8')
713-
} catch (error) {
714-
logger.error(error)
715-
}
716-
}
717-
718576
/**
719577
* Map MimeType to InputField
720578
* @param {string} mimeType

0 commit comments

Comments
 (0)