Updates to environment and initialization logic (#5683)

* fix: make TOKEN_HASH_SECRET required and add check on server start

* fix: make EXPRESS_SESSION_SECRET required and add check on server start

* fix: make jwt variables required and validate on server start

* fix: formatting

* fix: cypress tests

* fix: remove env file for cypress and refactor

* revert: changes to workflow and env examples

* update: how missing/weak default secrets are handled

* update: how auth secrets are setup and read

* fix: update env examples for docker

* fix: weak defaults list

* fix: weak defaults list

---------

Co-authored-by: yau-wd <[email protected]>
Co-authored-by: yau-wd <[email protected]>
This commit is contained in:
Ilango
2026-02-25 21:40:12 +08:00
committed by GitHub
co-authored by yau-wd yau-wd
parent 66374bfcc9
commit 75ce181a68
8 changed files with 277 additions and 53 deletions
+29 -11
View File
@@ -4,7 +4,7 @@ PORT=3000
############################################## DATABASE ####################################################
############################################################################################################
DATABASE_PATH=/root/.flowise
# DATABASE_PATH=/your_database_path/.flowise
# DATABASE_TYPE=postgres
# DATABASE_PORT=5432
# DATABASE_HOST=""
@@ -21,12 +21,14 @@ DATABASE_PATH=/root/.flowise
############################################################################################################
# SECRETKEY_STORAGE_TYPE=local #(local | aws)
SECRETKEY_PATH=/root/.flowise
# SECRETKEY_PATH=/your_secret_path/.flowise
# FLOWISE_SECRETKEY_OVERWRITE=myencryptionkey # (if you want to overwrite the secret key)
# SECRETKEY_AWS_ACCESS_KEY=<your-access-key>
# SECRETKEY_AWS_SECRET_KEY=<your-secret-key>
# SECRETKEY_AWS_REGION=us-west-2
# SECRETKEY_AWS_NAME=FlowiseEncryptionKey
# SECRETKEY_AWS_AUTH_PREFIX=Flowise # (when SECRETKEY_STORAGE_TYPE=aws, prefix for auth secret names e.g. FlowiseTokenHashSecret)
# Auth secrets (TOKEN_HASH_SECRET, EXPRESS_SESSION_SECRET, JWT_*) can be set here or left unset to use file/AWS storage
############################################################################################################
@@ -34,7 +36,7 @@ SECRETKEY_PATH=/root/.flowise
############################################################################################################
# DEBUG=true
LOG_PATH=/root/.flowise/logs
# LOG_PATH=/your_log_path/.flowise/logs
# LOG_LEVEL=info #(error | warn | info | verbose | debug)
# LOG_SANITIZE_BODY_FIELDS=password,pwd,pass,secret,token,apikey,api_key,accesstoken,access_token,refreshtoken,refresh_token,clientsecret,client_secret,privatekey,private_key,secretkey,secret_key,auth,authorization,credential,credentials
# LOG_SANITIZE_HEADER_FIELDS=authorization,x-api-key,x-auth-token,cookie
@@ -48,7 +50,7 @@ LOG_PATH=/root/.flowise/logs
############################################################################################################
# STORAGE_TYPE=local (local | s3 | gcs)
BLOB_STORAGE_PATH=/root/.flowise/storage
# BLOB_STORAGE_PATH=/your_storage_path/.flowise/storage
# S3_STORAGE_BUCKET_NAME=flowise
# S3_STORAGE_ACCESS_KEY_ID=<your-access-key>
# S3_STORAGE_SECRET_ACCESS_KEY=<your-secret-key>
@@ -91,20 +93,29 @@ BLOB_STORAGE_PATH=/root/.flowise/storage
# ALLOW_UNAUTHORIZED_CERTS=false
# [email protected]
JWT_AUTH_TOKEN_SECRET='AABBCCDDAABBCCDDAABBCCDDAABBCCDDAABBCCDD'
JWT_REFRESH_TOKEN_SECRET='AABBCCDDAABBCCDDAABBCCDDAABBCCDDAABBCCDD'
JWT_ISSUER='ISSUER'
JWT_AUDIENCE='AUDIENCE'
# Auth secrets: set via env (backwards compat) or leave unset to use file/AWS storage (SECRETKEY_PATH or SECRETKEY_STORAGE_TYPE=aws)
# Generate a secure 32-byte secret using: openssl rand -hex 32
# JWT_AUTH_TOKEN_SECRET=
# JWT_REFRESH_TOKEN_SECRET=
JWT_ISSUER=Flowise
JWT_AUDIENCE=Flowise
JWT_TOKEN_EXPIRY_IN_MINUTES=360
JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
# EXPIRE_AUTH_TOKENS_ON_RESTART=true # (if you need to expire all tokens on app restart)
# EXPRESS_SESSION_SECRET=flowise
# Generate a secure 32-byte secret using: openssl rand -hex 32 (or leave unset for file/AWS storage)
# EXPRESS_SESSION_SECRET=
# SECURE_COOKIES=
# INVITE_TOKEN_EXPIRY_IN_HOURS=24
# PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=15
# PASSWORD_SALT_HASH_ROUNDS=10
# TOKEN_HASH_SECRET='popcorn'
# Generate a secure 32-byte secret using: openssl rand -hex 32 (or leave unset for file/AWS storage)
# TOKEN_HASH_SECRET=
# WORKSPACE_INVITE_TEMPLATE_PATH=/path/to/custom/workspace_invite.hbs
@@ -167,7 +178,6 @@ JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
# REDIS_KEEP_ALIVE=
# ENABLE_BULLMQ_DASHBOARD=
############################################################################################################
############################################## SECURITY ####################################################
############################################################################################################
@@ -176,3 +186,11 @@ JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
# CUSTOM_MCP_SECURITY_CHECK=true
# CUSTOM_MCP_PROTOCOL=sse #(stdio | sse)
# TRUST_PROXY=true #(true | false | 1 | loopback| linklocal | uniquelocal | IP addresses | loopback, IP addresses)
############################################################################################################
########################################### DOCUMENT LOADERS ###############################################
############################################################################################################
# PUPPETEER_EXECUTABLE_FILE_PATH='C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
# PLAYWRIGHT_EXECUTABLE_FILE_PATH='C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
+29 -11
View File
@@ -4,7 +4,7 @@ WORKER_PORT=5566
############################################## DATABASE ####################################################
############################################################################################################
DATABASE_PATH=/root/.flowise
# DATABASE_PATH=/your_database_path/.flowise
# DATABASE_TYPE=postgres
# DATABASE_PORT=5432
# DATABASE_HOST=""
@@ -21,12 +21,14 @@ DATABASE_PATH=/root/.flowise
############################################################################################################
# SECRETKEY_STORAGE_TYPE=local #(local | aws)
SECRETKEY_PATH=/root/.flowise
# SECRETKEY_PATH=/your_secret_path/.flowise
# FLOWISE_SECRETKEY_OVERWRITE=myencryptionkey # (if you want to overwrite the secret key)
# SECRETKEY_AWS_ACCESS_KEY=<your-access-key>
# SECRETKEY_AWS_SECRET_KEY=<your-secret-key>
# SECRETKEY_AWS_REGION=us-west-2
# SECRETKEY_AWS_NAME=FlowiseEncryptionKey
# SECRETKEY_AWS_AUTH_PREFIX=Flowise # (when SECRETKEY_STORAGE_TYPE=aws, prefix for auth secret names e.g. FlowiseTokenHashSecret)
# Auth secrets (TOKEN_HASH_SECRET, EXPRESS_SESSION_SECRET, JWT_*) can be set here or left unset to use file/AWS storage
############################################################################################################
@@ -34,7 +36,7 @@ SECRETKEY_PATH=/root/.flowise
############################################################################################################
# DEBUG=true
LOG_PATH=/root/.flowise/logs
# LOG_PATH=/your_log_path/.flowise/logs
# LOG_LEVEL=info #(error | warn | info | verbose | debug)
# LOG_SANITIZE_BODY_FIELDS=password,pwd,pass,secret,token,apikey,api_key,accesstoken,access_token,refreshtoken,refresh_token,clientsecret,client_secret,privatekey,private_key,secretkey,secret_key,auth,authorization,credential,credentials
# LOG_SANITIZE_HEADER_FIELDS=authorization,x-api-key,x-auth-token,cookie
@@ -48,7 +50,7 @@ LOG_PATH=/root/.flowise/logs
############################################################################################################
# STORAGE_TYPE=local (local | s3 | gcs)
BLOB_STORAGE_PATH=/root/.flowise/storage
# BLOB_STORAGE_PATH=/your_storage_path/.flowise/storage
# S3_STORAGE_BUCKET_NAME=flowise
# S3_STORAGE_ACCESS_KEY_ID=<your-access-key>
# S3_STORAGE_SECRET_ACCESS_KEY=<your-secret-key>
@@ -91,20 +93,29 @@ BLOB_STORAGE_PATH=/root/.flowise/storage
# ALLOW_UNAUTHORIZED_CERTS=false
# [email protected]
JWT_AUTH_TOKEN_SECRET='AABBCCDDAABBCCDDAABBCCDDAABBCCDDAABBCCDD'
JWT_REFRESH_TOKEN_SECRET='AABBCCDDAABBCCDDAABBCCDDAABBCCDDAABBCCDD'
JWT_ISSUER='ISSUER'
JWT_AUDIENCE='AUDIENCE'
# Auth secrets: set via env (backwards compat) or leave unset to use file/AWS storage (SECRETKEY_PATH or SECRETKEY_STORAGE_TYPE=aws)
# Generate a secure 32-byte secret using: openssl rand -hex 32
# JWT_AUTH_TOKEN_SECRET=
# JWT_REFRESH_TOKEN_SECRET=
JWT_ISSUER=Flowise
JWT_AUDIENCE=Flowise
JWT_TOKEN_EXPIRY_IN_MINUTES=360
JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
# EXPIRE_AUTH_TOKENS_ON_RESTART=true # (if you need to expire all tokens on app restart)
# EXPRESS_SESSION_SECRET=flowise
# Generate a secure 32-byte secret using: openssl rand -hex 32 (or leave unset for file/AWS storage)
# EXPRESS_SESSION_SECRET=
# SECURE_COOKIES=
# INVITE_TOKEN_EXPIRY_IN_HOURS=24
# PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=15
# PASSWORD_SALT_HASH_ROUNDS=10
# TOKEN_HASH_SECRET='popcorn'
# Generate a secure 32-byte secret using: openssl rand -hex 32 (or leave unset for file/AWS storage)
# TOKEN_HASH_SECRET=
# WORKSPACE_INVITE_TEMPLATE_PATH=/path/to/custom/workspace_invite.hbs
@@ -167,7 +178,6 @@ JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
# REDIS_KEEP_ALIVE=
# ENABLE_BULLMQ_DASHBOARD=
############################################################################################################
############################################## SECURITY ####################################################
############################################################################################################
@@ -176,3 +186,11 @@ JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
# CUSTOM_MCP_SECURITY_CHECK=true
# CUSTOM_MCP_PROTOCOL=sse #(stdio | sse)
# TRUST_PROXY=true #(true | false | 1 | loopback| linklocal | uniquelocal | IP addresses | loopback, IP addresses)
############################################################################################################
########################################### DOCUMENT LOADERS ###############################################
############################################################################################################
# PUPPETEER_EXECUTABLE_FILE_PATH='C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
# PLAYWRIGHT_EXECUTABLE_FILE_PATH='C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
+17 -6
View File
@@ -27,6 +27,8 @@ PORT=3000
# SECRETKEY_AWS_SECRET_KEY=<your-secret-key>
# SECRETKEY_AWS_REGION=us-west-2
# SECRETKEY_AWS_NAME=FlowiseEncryptionKey
# SECRETKEY_AWS_AUTH_PREFIX=Flowise # (when SECRETKEY_STORAGE_TYPE=aws, prefix for auth secret names e.g. FlowiseTokenHashSecret)
# Auth secrets (TOKEN_HASH_SECRET, EXPRESS_SESSION_SECRET, JWT_*) can be set here or left unset to use file/AWS storage
############################################################################################################
@@ -91,20 +93,29 @@ PORT=3000
# ALLOW_UNAUTHORIZED_CERTS=false
# [email protected]
JWT_AUTH_TOKEN_SECRET='AABBCCDDAABBCCDDAABBCCDDAABBCCDDAABBCCDD'
JWT_REFRESH_TOKEN_SECRET='AABBCCDDAABBCCDDAABBCCDDAABBCCDDAABBCCDD'
JWT_ISSUER='ISSUER'
JWT_AUDIENCE='AUDIENCE'
# Auth secrets: set via env (backwards compat) or leave unset to use file/AWS storage (SECRETKEY_PATH or SECRETKEY_STORAGE_TYPE=aws)
# Generate a secure 32-byte secret using: openssl rand -hex 32
# JWT_AUTH_TOKEN_SECRET=
# JWT_REFRESH_TOKEN_SECRET=
JWT_ISSUER=Flowise
JWT_AUDIENCE=Flowise
JWT_TOKEN_EXPIRY_IN_MINUTES=360
JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES=43200
# EXPIRE_AUTH_TOKENS_ON_RESTART=true # (if you need to expire all tokens on app restart)
# EXPRESS_SESSION_SECRET=flowise
# Generate a secure 32-byte secret using: openssl rand -hex 32 (or leave unset for file/AWS storage)
# EXPRESS_SESSION_SECRET=
# SECURE_COOKIES=
# INVITE_TOKEN_EXPIRY_IN_HOURS=24
# PASSWORD_RESET_TOKEN_EXPIRY_IN_MINS=15
# PASSWORD_SALT_HASH_ROUNDS=10
# TOKEN_HASH_SECRET='popcorn'
# Generate a secure 32-byte secret using: openssl rand -hex 32 (or leave unset for file/AWS storage)
# TOKEN_HASH_SECRET=
# WORKSPACE_INVITE_TEMPLATE_PATH=/path/to/custom/workspace_invite.hbs
@@ -19,6 +19,13 @@ import { OrganizationUserErrorMessage, OrganizationUserService } from '../../ser
import { OrganizationService } from '../../services/organization.service'
import { RoleErrorMessage, RoleService } from '../../services/role.service'
import { WorkspaceUserService } from '../../services/workspace-user.service'
import {
getExpressSessionSecret,
getJWTAudience,
getJWTAuthTokenSecret,
getJWTIssuer,
getJWTRefreshTokenSecret
} from '../../utils/authSecrets'
import { decryptToken, encryptToken, generateSafeCopy } from '../../utils/tempTokenUtils'
import { getAuthStrategy } from './AuthStrategy'
import { initializeDBClientAndStore, initializeRedisClientAndStore } from './SessionPersistance'
@@ -26,12 +33,7 @@ import { v4 as uuidv4 } from 'uuid'
const localStrategy = require('passport-local').Strategy
const jwtAudience = process.env.JWT_AUDIENCE || 'AUDIENCE'
const jwtIssuer = process.env.JWT_ISSUER || 'ISSUER'
const expireAuthTokensOnRestart = process.env.EXPIRE_AUTH_TOKENS_ON_RESTART === 'true'
const jwtAuthTokenSecret = process.env.JWT_AUTH_TOKEN_SECRET || 'auth_token'
const jwtRefreshSecret = process.env.JWT_REFRESH_TOKEN_SECRET || process.env.JWT_AUTH_TOKEN_SECRET || 'refresh_token'
// Allow explicit override of cookie security settings
// This is useful when running behind a reverse proxy/load balancer that terminates SSL
@@ -46,16 +48,11 @@ const secureCookie =
: process.env.APP_URL?.startsWith('https')
? true
: false
const jwtOptions = {
secretOrKey: jwtAuthTokenSecret,
audience: jwtAudience,
issuer: jwtIssuer
}
const _initializePassportMiddleware = async (app: express.Application) => {
// Configure session middleware
let options: any = {
secret: process.env.EXPRESS_SESSION_SECRET || 'flowise',
secret: getExpressSessionSecret(),
resave: false,
saveUninitialized: false,
cookie: {
@@ -101,6 +98,11 @@ const _initializePassportMiddleware = async (app: express.Application) => {
export const initializeJwtCookieMiddleware = async (app: express.Application, identityManager: IdentityManager) => {
await _initializePassportMiddleware(app)
const jwtOptions = {
secretOrKey: getJWTAuthTokenSecret(),
audience: getJWTAudience(),
issuer: getJWTIssuer()
}
const strategy = getAuthStrategy(jwtOptions)
passport.use(strategy)
passport.use(
@@ -234,7 +236,7 @@ export const initializeJwtCookieMiddleware = async (app: express.Application, id
const refreshToken = req.cookies.refreshToken
if (!refreshToken) return res.sendStatus(401)
jwt.verify(refreshToken, jwtRefreshSecret, async (err: any, payload: any) => {
jwt.verify(refreshToken, getJWTRefreshTokenSecret(), async (err: any, payload: any) => {
if (err || !payload) return res.status(401).json({ message: ErrorMessage.REFRESH_TOKEN_EXPIRED })
// @ts-ignore
const loggedInUser = req.user as LoggedInUser
@@ -368,7 +370,7 @@ export const generateJwtAuthToken = (user: any) => {
if (expiryInMinutes === -1) {
expiryInMinutes = process.env.JWT_TOKEN_EXPIRY_IN_MINUTES ? parseInt(process.env.JWT_TOKEN_EXPIRY_IN_MINUTES) : 60
}
return _generateJwtToken(user, expiryInMinutes, jwtAuthTokenSecret)
return _generateJwtToken(user, expiryInMinutes, getJWTAuthTokenSecret())
}
export const generateJwtRefreshToken = (user: any) => {
@@ -390,17 +392,17 @@ export const generateJwtRefreshToken = (user: any) => {
? parseInt(process.env.JWT_REFRESH_TOKEN_EXPIRY_IN_MINUTES)
: 129600 // 90 days
}
return _generateJwtToken(user, expiryInMinutes, jwtRefreshSecret)
return _generateJwtToken(user, expiryInMinutes, getJWTRefreshTokenSecret())
}
const _generateJwtToken = (user: Partial<LoggedInUser>, expiryInMinutes: number, secret: string) => {
const encryptedUserInfo = encryptToken(user?.id + ':' + user?.activeWorkspaceId)
return sign({ id: user?.id, username: user?.name, meta: encryptedUserInfo }, secret!, {
return sign({ id: user?.id, username: user?.name, meta: encryptedUserInfo }, secret, {
expiresIn: expiryInMinutes + 'm', // Expiry in minutes
notBefore: '0', // Cannot use before now, can be configured to be deferred.
algorithm: 'HS256', // HMAC using SHA-256 hash algorithm
audience: jwtAudience, // The audience of the token
issuer: jwtIssuer // The issuer of the token
audience: getJWTAudience(),
issuer: getJWTIssuer()
})
}
@@ -0,0 +1,100 @@
import { getOrCreateStoredSecret } from '../../utils'
/**
* Weak default values that were previously hardcoded when env vars were not set.
* If the user has set a var to one of these, we treat it as "not set" and use file/AWS storage instead.
*/
const WEAK_DEFAULTS: Record<string, string> = {
JWT_AUTH_TOKEN_SECRET: 'AABBCCDDAABBCCDDAABBCCDDAABBCCDDAABBCCDD',
JWT_REFRESH_TOKEN_SECRET: 'AABBCCDDAABBCCDDAABBCCDDAABBCCDDAABBCCDD',
EXPRESS_SESSION_SECRET: 'flowise',
TOKEN_HASH_SECRET: 'popcorn'
}
let tokenHashSecret: string | undefined
let expressSessionSecret: string | undefined
let jwtAuthTokenSecret: string | undefined
let jwtRefreshTokenSecret: string | undefined
let jwtIssuer: string | undefined
let jwtAudience: string | undefined
const NOT_INITIALIZED = 'Auth secrets not initialized. Call initAuthSecrets() first.'
/**
* Initialize auth secrets from env (backwards compat) → AWS Secrets Manager → filesystem.
* Each secret is generated with crypto.randomBytes(32) when created (or 'flowise' for JWT_ISSUER/JWT_AUDIENCE).
* Call once after getEncryptionKey() in initDatabase().
*/
export async function initAuthSecrets(): Promise<void> {
tokenHashSecret = await getOrCreateStoredSecret({
envKey: 'TOKEN_HASH_SECRET',
fileName: 'token_hash_secret.key',
awsSecretIdSuffix: 'TokenHashSecret',
weakDefault: WEAK_DEFAULTS.TOKEN_HASH_SECRET
})
expressSessionSecret = await getOrCreateStoredSecret({
envKey: 'EXPRESS_SESSION_SECRET',
fileName: 'express_session_secret.key',
awsSecretIdSuffix: 'ExpressSessionSecret',
weakDefault: WEAK_DEFAULTS.EXPRESS_SESSION_SECRET
})
jwtAuthTokenSecret = await getOrCreateStoredSecret({
envKey: 'JWT_AUTH_TOKEN_SECRET',
fileName: 'jwt_auth_token_secret.key',
awsSecretIdSuffix: 'JWTAuthTokenSecret',
weakDefault: WEAK_DEFAULTS.JWT_AUTH_TOKEN_SECRET
})
jwtRefreshTokenSecret = await getOrCreateStoredSecret({
envKey: 'JWT_REFRESH_TOKEN_SECRET',
fileName: 'jwt_refresh_token_secret.key',
awsSecretIdSuffix: 'JWTRefreshTokenSecret',
weakDefault: WEAK_DEFAULTS.JWT_REFRESH_TOKEN_SECRET
})
jwtIssuer = await getOrCreateStoredSecret({
envKey: 'JWT_ISSUER',
fileName: 'jwt_issuer.key',
awsSecretIdSuffix: 'JWTIssuer',
defaultValueForNew: 'flowise'
})
jwtAudience = await getOrCreateStoredSecret({
envKey: 'JWT_AUDIENCE',
fileName: 'jwt_audience.key',
awsSecretIdSuffix: 'JWTAudience',
defaultValueForNew: 'flowise'
})
}
export function getTokenHashSecret(): string {
if (tokenHashSecret === undefined) throw new Error(NOT_INITIALIZED)
return tokenHashSecret
}
export function getExpressSessionSecret(): string {
if (expressSessionSecret === undefined) throw new Error(NOT_INITIALIZED)
return expressSessionSecret
}
export function getJWTAuthTokenSecret(): string {
if (jwtAuthTokenSecret === undefined) throw new Error(NOT_INITIALIZED)
return jwtAuthTokenSecret
}
export function getJWTRefreshTokenSecret(): string {
if (jwtRefreshTokenSecret === undefined) throw new Error(NOT_INITIALIZED)
return jwtRefreshTokenSecret
}
export function getJWTIssuer(): string {
if (jwtIssuer === undefined) throw new Error(NOT_INITIALIZED)
return jwtIssuer
}
export function getJWTAudience(): string {
if (jwtAudience === undefined) throw new Error(NOT_INITIALIZED)
return jwtAudience
}
@@ -2,6 +2,7 @@ import { LoggedInUser } from '../Interface.Enterprise'
import * as crypto from 'crypto'
import moment from 'moment'
import { customAlphabet } from 'nanoid'
import { getTokenHashSecret } from './authSecrets'
const nanoid = customAlphabet('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 64)
@@ -28,10 +29,7 @@ export const generateTempToken = () => {
// Encrypt token with password using crypto.Cipheriv
export const encryptToken = (stringToEncrypt: string) => {
const key = crypto
.createHash('sha256')
.update(process.env.TOKEN_HASH_SECRET || 'Secre$t')
.digest()
const key = crypto.createHash('sha256').update(getTokenHashSecret()).digest()
const IV_LENGTH = 16
const iv = crypto.randomBytes(IV_LENGTH)
@@ -47,10 +45,7 @@ export const encryptToken = (stringToEncrypt: string) => {
// Decrypt token using the inverse of encryption crypto algorithm
export const decryptToken = (stringToDecrypt: string): string | undefined => {
try {
const key = crypto
.createHash('sha256')
.update(process.env.TOKEN_HASH_SECRET || 'Secre$t')
.digest()
const key = crypto.createHash('sha256').update(getTokenHashSecret()).digest()
let textParts = stringToDecrypt.split(':')
let iv = Buffer.from(textParts.shift() as string, 'hex')
+5
View File
@@ -14,6 +14,7 @@ import { Organization } from './enterprise/database/entities/organization.entity
import { Workspace } from './enterprise/database/entities/workspace.entity'
import { LoggedInUser } from './enterprise/Interface.Enterprise'
import { initializeJwtCookieMiddleware, verifyToken, verifyTokenForBullMQDashboard } from './enterprise/middleware/passport'
import { initAuthSecrets } from './enterprise/utils/authSecrets'
import { IdentityManager } from './IdentityManager'
import { MODE, Platform } from './Interface'
import { IMetricsProvider } from './Interface.Metrics'
@@ -104,6 +105,10 @@ export class App {
await getEncryptionKey()
logger.info('🔑 [server]: Encryption key initialized successfully')
// Initialize auth secrets (env → AWS Secrets Manager → filesystem)
await initAuthSecrets()
logger.info('🔐 [server]: Auth initialized successfully')
// Initialize Rate Limit
this.rateLimiterManager = RateLimiterManager.getInstance()
await this.rateLimiterManager.initializeRateLimiters(await getDataSource().getRepository(ChatFlow).find())
+75
View File
@@ -1661,6 +1661,81 @@ export const generateEncryptKey = (): string => {
return randomBytes(24).toString('base64')
}
/**
* Returns the directory where auth secrets are stored (same as encryption key directory).
* Used for file-based storage of TOKEN_HASH_SECRET, EXPRESS_SESSION_SECRET, JWT_*, etc.
*/
export const getAuthSecretsDirectory = (): string => {
return process.env.SECRETKEY_PATH ? process.env.SECRETKEY_PATH : path.join(getUserHome(), '.flowise')
}
/**
* Generate a 32-byte auth secret (OWASP recommendation).
* Used for auth secrets only; encryption key remains 24 bytes.
*/
export const generateAuthSecret = (): string => {
return randomBytes(32).toString('base64')
}
export interface GetOrCreateStoredSecretOptions {
envKey: string
fileName: string
awsSecretIdSuffix: string
/** When generating a new secret, use this value instead of random (e.g. 'flowise' for JWT_ISSUER/JWT_AUDIENCE) */
defaultValueForNew?: string
/** If env is set to this value, treat as unset (backwards compat for weak defaults) */
weakDefault?: string
}
/**
* Resolve an auth secret: env (backwards compat) → AWS Secrets Manager → filesystem (read or generate+write).
* Used by initAuthSecrets() for each of the six auth secrets.
*/
export const getOrCreateStoredSecret = async (options: GetOrCreateStoredSecretOptions): Promise<string> => {
const { envKey, fileName, awsSecretIdSuffix, defaultValueForNew, weakDefault } = options
const envVal = process.env[envKey]
const useEnv = envVal && envVal.trim() !== '' && (weakDefault === undefined || envVal !== weakDefault)
if (useEnv) {
return envVal!.trim()
}
if (USE_AWS_SECRETS_MANAGER && secretsManagerClient) {
const prefix = process.env.SECRETKEY_AWS_AUTH_PREFIX || 'Flowise'
const secretId = prefix + awsSecretIdSuffix
try {
const command = new GetSecretValueCommand({ SecretId: secretId })
const response = await secretsManagerClient.send(command)
if (response.SecretString) {
return response.SecretString
}
} catch (error: any) {
if (error.name === 'ResourceNotFoundException') {
const newValue = defaultValueForNew !== undefined ? defaultValueForNew : generateAuthSecret()
const createCommand = new CreateSecretCommand({
Name: secretId,
SecretString: newValue
})
await secretsManagerClient.send(createCommand)
return newValue
}
throw error
}
}
const dir = getAuthSecretsDirectory()
const filePath = path.join(dir, fileName)
try {
return await fs.promises.readFile(filePath, 'utf8')
} catch {
const value = defaultValueForNew !== undefined ? defaultValueForNew : generateAuthSecret()
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true })
}
await fs.promises.writeFile(filePath, value)
return value
}
}
/**
* Transform ICredentialBody from req to Credential entity
* @param {ICredentialReqBody} body