mirror of
https://github.com/FlowiseAI/Flowise.git
synced 2026-10-12 02:02:17 +00:00
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:
+29
-11
@@ -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
@@ -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'
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user