From 1d5d438502b5474e1b44856ebd437d30205dc965 Mon Sep 17 00:00:00 2001 From: Peter Baus Date: Mon, 26 Aug 2024 08:50:25 +0200 Subject: [PATCH 01/21] api:API variables validation --- api/src/config.ts | 245 ++++++++++++++++++++++++++++++---------------- 1 file changed, 163 insertions(+), 82 deletions(-) diff --git a/api/src/config.ts b/api/src/config.ts index 116788586..8abc0257e 100644 --- a/api/src/config.ts +++ b/api/src/config.ts @@ -1,3 +1,4 @@ +import Joi from "joi"; import logger from "./lib/logger"; import { randomString } from "./service/hash"; @@ -121,6 +122,127 @@ interface Config { silenceLoggingOnFrequentRoutes: boolean; } +const envVarsSchema = Joi.object({ + ORGANIZATION: Joi.string() + .min(1) + .max(100) + .required() + .note( + "In the blockchain network, each node is represented by its organization name. This environment variable sets this organization name. It is used to create the organization stream on the blockchain and is also displayed in the frontend's top right corner.", + ), + PORT: Joi.number() + .min(0) + .max(65535) + .default(8091) + .note( + "The port used to expose the API for your installation.
Example: If you run TruBudget locally and set API_PORT to `8080`, you can reach the API via `localhost:8080/api`.", + ), + ORGANIZATION_VAULT_SECRET: Joi.string() + .invalid("secret") + .required() + .note( + "This is the key to en-/decrypt user data of an organization. If you want to add a new node for your organization, you want users to be able to log in on either node.
**Caution:** If you want to run TruBudget in production, make sure NOT to use the default value from the `.env_example` file!", + ), + ROOT_SECRET: Joi.string() + .min(8) + .required() + .default(randomString(32)) + .note( + "The root secret is the password for the root user. If you start with an empty blockchain, the root user is needed to add other users, approve new nodes,.. If you don't set a value via the environment variable, the API generates one randomly and prints it to the console
**Caution:** If you want to run TruBudget in production, make sure to set a secure root secret.", + ), + MULTICHAIN_RPC_HOST: Joi.string() + .default("localhost") + .note( + "The IP address of the blockchain (not multichain daemon,but they are usally the same) you want to connect to.", + ), + MULTICHAIN_RPC_PORT: Joi.number() + .default(8000) + .note( + "The Port of the blockchain where the server is available for incoming http connections (e.g. readiness, versions, backup and restore)", + ), + MULTICHAIN_RPC_USER: Joi.string() + .default("multichainrpc") + .note("The user used to connect to the multichain daemon."), + MULTICHAIN_RPC_PASSWORD: Joi.string() + .default("s750SiJnj50yIrmwxPnEdSzpfGlTAHzhaUwgqKeb0G1j") + .note( + "Password used by the API to connect to the blockchain. The password is set by the origin node upon start. Every beta node needs to use the same RPC password in order to be able to connect to the blockchain.
**Hint:** Although the MULTICHAIN_RPC_PASSWORD is not required it is highly recommended to set an own secure one.", + ), + BLOCKCHAIN_PORT: Joi.number() + .default(8085) + .note( + "The port used to expose the multichain daemon of your Trubudget blockchain installation(bc). The port used to connect to the multichain daemon(api). This will be used internally for the communication between the API and the multichain daemon.", + ), + SWAGGER_BASEPATH: Joi.string() + .example("/") + .forbidden() + .note( + "deprecated", + "This variable was used to choose which environment (prod or test) is used for testing the requests. The variable is deprecated now, as the Swagger documentation can be used for the prod and test environment separately.", + ), + JWT_ALGORITHM: Joi.string() + .default("HS256") + .valid("HS256", "RS256") + .note("Algorithm used for signing and verifying JWTs."), + JWT_SECRET: Joi.string() + .min(32) + .default(randomString(32)) + .when("JWT_ALGORITHM", { + is: "RS256", + then: Joi.string().base64().required(), + }) + .note( + "A string that is used to sign JWT which are created by the authenticate endpoint of the api. If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded private key for RSA.", + ), + JWT_PUBLIC_KEY: Joi.string() + .default("") + .when("JWT_ALGORITHM", { + is: "RS256", + then: Joi.string().base64().required(), + }) + .note( + "If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded public key for RSA.", + ), + DOCUMENT_FEATURE_ENABLED: Joi.boolean().default(false), + DOCUMENT_EXTERNAL_LINKS_ENABLED: Joi.boolean().default(false), + STORAGE_SERVICE_HOST: Joi.string().default("localhost"), + STORAGE_SERVICE_PORT: Joi.number().default(8090), + STORAGE_SERVICE_EXTERNAL_URL: Joi.string().default("").when("DOCUMENT_FEATURE_ENABLED", { + is: true, + then: Joi.required(), + }), + EMAIL_HOST: Joi.string().default("localhost"), + EMAIL_PORT: Joi.number().default(8089), + ACCESS_CONTROL_ALLOW_ORIGIN: Joi.string().default("*"), + NODE_ENV: Joi.string().default("production"), + ENCRYPTION_PASSWORD: Joi.string(), + SIGNING_METHOD: Joi.string().default("node"), + RATE_LIMIT: Joi.number(), + AUTHPROXY_ENABLED: Joi.boolean().default(false), + AUTHPROXY_JWS_SIGNATURE: Joi.string(), + DB_TYPE: Joi.string().default("pg"), + SQL_DEBUG: Joi.boolean().default(false), + API_DB_USER: Joi.string().default("postgres"), + API_DB_PASSWORD: Joi.string().default("test"), + API_DB_HOST: Joi.string().default("localhost"), + API_DB_NAME: Joi.string().default("trubudget_email_service"), + API_DB_PORT: Joi.number().default(5432), + API_DB_SSL: Joi.boolean().default(false), + API_DB_SCHEMA: Joi.string().default("public"), + API_REFRESH_TOKENS_TABLE: Joi.string().default("refresh_token"), + REFRESH_TOKEN_STORAGE: Joi.string().allow("db", "memory"), + SNAPSHOT_EVENT_INTERVAL: Joi.number().default(3), + SILENCE_LOGGING_ON_FREQUENT_ROUTES: Joi.boolean().default(false), + APPLICATIONINSIGHTS_CONNECTION_STRING: Joi.string().default(""), +}) + .unknown() + .required(); + +const { error, value: envVars } = envVarsSchema.validate(process.env); +if (error) { + throw new Error(`Config validation error: ${error.message}`); +} + /** * environment variables which are required by the API * @notExported @@ -128,79 +250,69 @@ interface Config { const requiredEnvVars = ["ORGANIZATION", "ORGANIZATION_VAULT_SECRET"]; export const config: Config = { - organization: process.env.ORGANIZATION || "", - organizationVaultSecret: process.env.ORGANIZATION_VAULT_SECRET || "", - port: Number(process.env.PORT) || 8080, - rootSecret: process.env.ROOT_SECRET || randomString(32), + organization: envVars.ORGANIZATION, + organizationVaultSecret: envVars.ORGANIZATION_VAULT_SECRET, + port: envVars.PORT, + rootSecret: envVars.ROOT_SECRET, // RPC is the mutlichain daemon rpc: { - host: process.env.MULTICHAIN_RPC_HOST || "localhost", - port: Number(process.env.MULTICHAIN_RPC_PORT) || 8000, - user: process.env.MULTICHAIN_RPC_USER || "multichainrpc", - password: process.env.MULTICHAIN_RPC_PASSWORD || "s750SiJnj50yIrmwxPnEdSzpfGlTAHzhaUwgqKeb0G1j", + host: envVars.MULTICHAIN_RPC_HOST, + port: envVars.MULTICHAIN_RPC_PORT, + user: envVars.MULTICHAIN_RPC_USER, + password: envVars.MULTICHAIN_RPC_PASSWORD, }, // Blockchain is the blockchain component of Trubudget // It serves e.g. backup or version endpoints blockchain: { - host: process.env.MULTICHAIN_RPC_HOST || "localhost", - port: Number(process.env.BLOCKCHAIN_PORT) || 8085, + host: envVars.MULTICHAIN_RPC_HOST, + port: envVars.BLOCKCHAIN_PORT, }, jwt: { - secretOrPrivateKey: process.env.JWT_SECRET || randomString(32), - publicKey: process.env.JWT_PUBLIC_KEY || "", - algorithm: process.env.JWT_ALGORITHM === "RS256" ? "RS256" : "HS256", + secretOrPrivateKey: envVars.JWT_SECRET, + publicKey: envVars.JWT_PUBLIC_KEY, + algorithm: envVars.JWT_ALGORITHM, }, npmPackageVersion: process.env.npm_package_version || "", // Continues Integration ciCommitSha: process.env.CI_COMMIT_SHA || "", buildTimeStamp: process.env.BUILDTIMESTAMP || "", - documentFeatureEnabled: process.env.DOCUMENT_FEATURE_ENABLED === "true" ? true : false, - documentExternalLinksEnabled: - process.env.DOCUMENT_EXTERNAL_LINKS_ENABLED === "true" ? true : false, + documentFeatureEnabled: envVars.DOCUMENT_FEATURE_ENABLED, + documentExternalLinksEnabled: envVars.DOCUMENT_EXTERNAL_LINKS_ENABLED, storageService: { - host: process.env.STORAGE_SERVICE_HOST || "localhost", - port: Number(process.env.STORAGE_SERVICE_PORT) || 8090, - externalUrl: process.env.STORAGE_SERVICE_EXTERNAL_URL || "", + host: envVars.STORAGE_SERVICE_HOST, + port: envVars.STORAGE_SERVICE_PORT, + externalUrl: envVars.STORAGE_SERVICE_EXTERNAL_URL, }, emailService: { - host: process.env.EMAIL_HOST || "localhost", - port: Number(process.env.EMAIL_PORT) || 8089, + host: envVars.EMAIL_HOST, + port: envVars.EMAIL_PORT, }, - encryptionPassword: - process.env.ENCRYPTION_PASSWORD === "" ? undefined : process.env.ENCRYPTION_PASSWORD, - signingMethod: process.env.SIGNING_METHOD || "node", - nodeEnv: process.env.NODE_ENV || "production", - accessControlAllowOrigin: process.env.ACCESS_CONTROL_ALLOW_ORIGIN || "*", - rateLimit: - process.env.RATE_LIMIT === "" || isNaN(Number(process.env.RATE_LIMIT)) - ? undefined - : Number(process.env.RATE_LIMIT), + encryptionPassword: envVars.ENCRYPTION_PASSWORD, + signingMethod: envVars.SIGNING_METHOD, + nodeEnv: envVars.NODE_ENV, + accessControlAllowOrigin: envVars.ACCESS_CONTROL_ALLOW_ORIGIN, + rateLimit: envVars.RATE_LIMIT, authProxy: { - enabled: process.env.AUTHPROXY_ENABLED === "true" || false, + enabled: envVars.AUTHPROXY_ENABLED, authProxyCookie: "authorizationToken", - jwsSignature: process.env.AUTHPROXY_JWS_SIGNATURE || undefined, + jwsSignature: envVars.AUTHPROXY_JWS_SIGNATURE, }, db: { - user: process.env.API_DB_USER || "postgres", - password: process.env.API_DB_PASSWORD || "test", - host: process.env.API_DB_HOST || "localhost", - database: process.env.API_DB_NAME || "trubudget_email_service", - port: Number(process.env.API_DB_PORT) || 5432, - ssl: process.env.API_DB_SSL === "true", - schema: process.env.API_DB_SCHEMA || "public", + user: envVars.API_DB_USER, + password: envVars.API_DB_PASSWORD, + host: envVars.API_DB_HOST, + database: envVars.API_DB_NAME, + port: envVars.API_DB_PORT, + ssl: envVars.API_DB_SSL, + schema: envVars.API_DB_SCHEMA, }, - dbType: process.env.DB_TYPE || "pg", - sqlDebug: Boolean(process.env.SQL_DEBUG) || false, - refreshTokensTable: process.env.API_REFRESH_TOKENS_TABLE || "refresh_token", - refreshTokenStorage: - process.env.REFRESH_TOKEN_STORAGE && - ["db", "memory"].includes(process.env.REFRESH_TOKEN_STORAGE) - ? process.env.REFRESH_TOKEN_STORAGE - : undefined, - snapshotEventInterval: Number(process.env.SNAPSHOT_EVENT_INTERVAL) || 3, - azureMonitorConnectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING || "", - silenceLoggingOnFrequentRoutes: - process.env.SILENCE_LOGGING_ON_FREQUENT_ROUTES === "true" || false, + dbType: envVars.DB_TYPE, + sqlDebug: envVars.SQL_DEBUG, + refreshTokensTable: envVars.API_REFRESH_TOKENS_TABLE, + refreshTokenStorage: envVars.REFRESH_TOKEN_STORAGE, + snapshotEventInterval: envVars.SNAPSHOT_EVENT_INTERVAL, + azureMonitorConnectionString: envVars.APPLICATIONINSIGHTS_CONNECTION_STRING, + silenceLoggingOnFrequentRoutes: envVars.SILENCE_LOGGING_ON_FREQUENT_ROUTES, }; /** @@ -257,37 +369,6 @@ const envExists = ( * @notExported */ const getValidConfig = (): Config => { - exitIfMissing(requiredEnvVars); - - // Environment Validation - const jwtSecret: string = process.env.JWT_SECRET || randomString(32); - if (jwtSecret.length < 32) { - logger.warn("Warning: the JWT secret key should be at least 32 characters long."); - } - const rootSecret: string = process.env.ROOT_SECRET || randomString(32); - if (!process.env.ROOT_SECRET) { - logger.warn(`Warning: root password not set; autogenerated to ${rootSecret}`); - } - - // Document feature enabled - if (process.env.DOCUMENT_FEATURE_ENABLED === "true") { - const requiredDocEnvVars = ["STORAGE_SERVICE_EXTERNAL_URL"]; - exitIfMissing(requiredDocEnvVars); - } - - const jwtAlgorithm: string = process.env.JWT_ALGORITHM; - if ( - !( - jwtAlgorithm === "HS256" || - jwtAlgorithm === "RS256" || - jwtAlgorithm === undefined || - jwtAlgorithm === "" - ) - ) { - logger.fatal("JWT_ALGORITHM must be either HS256 or RS256 or empty (defaults to HS256)"); - process.exit(1); - } - return config; }; /** From 442a0de8c91eff1aeb75b228f39bac153243d608 Mon Sep 17 00:00:00 2001 From: Peter Baus Date: Mon, 26 Aug 2024 15:59:19 +0200 Subject: [PATCH 02/21] api:Env variables validator --- api/package.json | 1 + api/src/config.ts | 58 +---------------------------- api/src/scripts/envVarsValidator.ts | 9 +++++ 3 files changed, 12 insertions(+), 56 deletions(-) create mode 100644 api/src/scripts/envVarsValidator.ts diff --git a/api/package.json b/api/package.json index 095ca1c15..2054540e7 100644 --- a/api/package.json +++ b/api/package.json @@ -47,6 +47,7 @@ "tsc": "tsc", "test:here": "node ./runTest.js", "generate-joi": "node dist/scripts/joiGenerator.js", + "validate-env": "node dist/scripts/envVarsValidator.js", "build-docs": "bash ./buildDocs.sh" }, "nyc": { diff --git a/api/src/config.ts b/api/src/config.ts index 8abc0257e..f778e4e14 100644 --- a/api/src/config.ts +++ b/api/src/config.ts @@ -1,5 +1,4 @@ -import Joi from "joi"; -import logger from "./lib/logger"; +import * as Joi from "joi"; import { randomString } from "./service/hash"; export interface JwtConfig { @@ -121,7 +120,7 @@ interface Config { azureMonitorConnectionString: string; silenceLoggingOnFrequentRoutes: boolean; } - +console.log(Joi); const envVarsSchema = Joi.object({ ORGANIZATION: Joi.string() .min(1) @@ -243,12 +242,6 @@ if (error) { throw new Error(`Config validation error: ${error.message}`); } -/** - * environment variables which are required by the API - * @notExported - */ -const requiredEnvVars = ["ORGANIZATION", "ORGANIZATION_VAULT_SECRET"]; - export const config: Config = { organization: envVars.ORGANIZATION, organizationVaultSecret: envVars.ORGANIZATION_VAULT_SECRET, @@ -315,53 +308,6 @@ export const config: Config = { silenceLoggingOnFrequentRoutes: envVars.SILENCE_LOGGING_ON_FREQUENT_ROUTES, }; -/** - * Checks if required environment variables are set, stops the process otherwise - * @notExported - * - * @param requiredEnvVars environment variables required for the API to run - */ -function exitIfMissing(requiredEnvVars): void { - let envVarMissing = false; - requiredEnvVars.forEach((env) => { - if (!envExists(process.env, env)) envVarMissing = true; - }); - if (envVarMissing) process.exit(1); -} - -/** - * Checks if an environment variable is attached to the current process - * @notExported - * - * @param processEnv environment variables attached to the current process - * @param prop environment variable to check - * @param msg optional message to print out - * @returns a boolean indicating if an environment variable is attached to the current process - */ -const envExists = ( - processEnv: Partial, - prop: K, - msg?: string, -): boolean => { - if (processEnv[prop] === undefined || processEnv[prop] === null) { - switch (prop) { - case "ORGANIZATION": - msg = "Please set ORGANIZATION to the organization this node belongs to."; - break; - case "ORGANIZATION_VAULT_SECRET": - msg = - "Please set ORGANIZATION_VAULT_SECRET to the secret key used to encrypt the organization's vault."; - break; - default: - break; - } - logger.fatal(msg || `Environment is missing required variable ${String(prop)}`); - return false; - } else { - return true; - } -}; - /** * Gets the configuration used to start the API * diff --git a/api/src/scripts/envVarsValidator.ts b/api/src/scripts/envVarsValidator.ts new file mode 100644 index 000000000..776d561d2 --- /dev/null +++ b/api/src/scripts/envVarsValidator.ts @@ -0,0 +1,9 @@ +import * as dotenv from "dotenv"; +// Load environment variables +dotenv.config(); + +import config from "../config"; + +if (config()) { + console.log("Environment variables are valid"); +} From a7b5ca61c8e0aa69f8db2e424031d4a568007a9e Mon Sep 17 00:00:00 2001 From: Peter Baus Date: Mon, 2 Sep 2024 12:57:52 +0200 Subject: [PATCH 03/21] api:Env validation in dev start script --- api/src/config.ts | 6 +++--- api/src/scripts/envVarsValidator.ts | 11 ++++++----- scripts/development/start-dev.sh | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/api/src/config.ts b/api/src/config.ts index f778e4e14..0b0d838d1 100644 --- a/api/src/config.ts +++ b/api/src/config.ts @@ -120,7 +120,7 @@ interface Config { azureMonitorConnectionString: string; silenceLoggingOnFrequentRoutes: boolean; } -console.log(Joi); + const envVarsSchema = Joi.object({ ORGANIZATION: Joi.string() .min(1) @@ -216,7 +216,7 @@ const envVarsSchema = Joi.object({ NODE_ENV: Joi.string().default("production"), ENCRYPTION_PASSWORD: Joi.string(), SIGNING_METHOD: Joi.string().default("node"), - RATE_LIMIT: Joi.number(), + RATE_LIMIT: Joi.number().allow("").empty(""), AUTHPROXY_ENABLED: Joi.boolean().default(false), AUTHPROXY_JWS_SIGNATURE: Joi.string(), DB_TYPE: Joi.string().default("pg"), @@ -232,7 +232,7 @@ const envVarsSchema = Joi.object({ REFRESH_TOKEN_STORAGE: Joi.string().allow("db", "memory"), SNAPSHOT_EVENT_INTERVAL: Joi.number().default(3), SILENCE_LOGGING_ON_FREQUENT_ROUTES: Joi.boolean().default(false), - APPLICATIONINSIGHTS_CONNECTION_STRING: Joi.string().default(""), + APPLICATIONINSIGHTS_CONNECTION_STRING: Joi.string().allow(""), }) .unknown() .required(); diff --git a/api/src/scripts/envVarsValidator.ts b/api/src/scripts/envVarsValidator.ts index 776d561d2..89dbea44e 100644 --- a/api/src/scripts/envVarsValidator.ts +++ b/api/src/scripts/envVarsValidator.ts @@ -1,9 +1,10 @@ -import * as dotenv from "dotenv"; -// Load environment variables -dotenv.config(); - import config from "../config"; -if (config()) { +try { + config(); console.log("Environment variables are valid"); +} catch (error) { + console.error("Environment variables are not valid"); + console.error(error); + process.exit(1); } diff --git a/scripts/development/start-dev.sh b/scripts/development/start-dev.sh index 6a551a5bc..3f3b3b825 100755 --- a/scripts/development/start-dev.sh +++ b/scripts/development/start-dev.sh @@ -396,6 +396,23 @@ if [ "$IS_PARTLY_REBUILDING" = true ]; then $COMPOSE build $BUILD_SERVICES fi +# Read space sepparated services to array +read -a ALL_SERVICES_ARRAY <<< "$COMPOSE_SERVICES $ENABLED_SERVICES" + +# loop through the services array +for service in "${ALL_SERVICES_ARRAY[@]}" +do + echo "INFO: Validating environment variables for $service service ..." + # Run environenment variables check + OUTPUT=$(docker run --env-file $SCRIPT_DIR/.env trubudget-dev-$service npm run validate-env 2>&1) + + if [[ $OUTPUT =~ "Config validation error" ]]; then + echo "${red}ERROR: The .env file is not valid for the $service service. Please check the .env file.${colorReset}" + exit 1 + fi +done + + if [ "$IS_LOG_ENABLED" = false ]; then echo "INFO: Docker container are started without logging" fi From 96888b2141c799ca1b0ab07bd14d1100ef14ef7d Mon Sep 17 00:00:00 2001 From: Peter Baus Date: Thu, 12 Sep 2024 07:16:24 +0200 Subject: [PATCH 04/21] api:Restructure config files.Docker compose validate joi after start --- api/package.json | 3 +- api/src/config.ts | 120 +--------------------------- api/src/envVarsSchema.ts | 118 +++++++++++++++++++++++++++ api/src/scripts/envVarsValidator.ts | 14 ++-- scripts/development/start-dev.sh | 30 ++++--- 5 files changed, 143 insertions(+), 142 deletions(-) create mode 100644 api/src/envVarsSchema.ts diff --git a/api/package.json b/api/package.json index 2054540e7..61d4a272c 100644 --- a/api/package.json +++ b/api/package.json @@ -48,7 +48,8 @@ "test:here": "node ./runTest.js", "generate-joi": "node dist/scripts/joiGenerator.js", "validate-env": "node dist/scripts/envVarsValidator.js", - "build-docs": "bash ./buildDocs.sh" + "build-docs": "bash ./buildDocs.sh", + "validate-env-variables": "node dist/scripts/envVarsValidator.js" }, "nyc": { "include": [ diff --git a/api/src/config.ts b/api/src/config.ts index 0b0d838d1..7bc3f86cd 100644 --- a/api/src/config.ts +++ b/api/src/config.ts @@ -1,5 +1,5 @@ -import * as Joi from "joi"; -import { randomString } from "./service/hash"; + +import { envVarsSchema } from "envVarsSchema"; export interface JwtConfig { secretOrPrivateKey: string; @@ -121,121 +121,7 @@ interface Config { silenceLoggingOnFrequentRoutes: boolean; } -const envVarsSchema = Joi.object({ - ORGANIZATION: Joi.string() - .min(1) - .max(100) - .required() - .note( - "In the blockchain network, each node is represented by its organization name. This environment variable sets this organization name. It is used to create the organization stream on the blockchain and is also displayed in the frontend's top right corner.", - ), - PORT: Joi.number() - .min(0) - .max(65535) - .default(8091) - .note( - "The port used to expose the API for your installation.
Example: If you run TruBudget locally and set API_PORT to `8080`, you can reach the API via `localhost:8080/api`.", - ), - ORGANIZATION_VAULT_SECRET: Joi.string() - .invalid("secret") - .required() - .note( - "This is the key to en-/decrypt user data of an organization. If you want to add a new node for your organization, you want users to be able to log in on either node.
**Caution:** If you want to run TruBudget in production, make sure NOT to use the default value from the `.env_example` file!", - ), - ROOT_SECRET: Joi.string() - .min(8) - .required() - .default(randomString(32)) - .note( - "The root secret is the password for the root user. If you start with an empty blockchain, the root user is needed to add other users, approve new nodes,.. If you don't set a value via the environment variable, the API generates one randomly and prints it to the console
**Caution:** If you want to run TruBudget in production, make sure to set a secure root secret.", - ), - MULTICHAIN_RPC_HOST: Joi.string() - .default("localhost") - .note( - "The IP address of the blockchain (not multichain daemon,but they are usally the same) you want to connect to.", - ), - MULTICHAIN_RPC_PORT: Joi.number() - .default(8000) - .note( - "The Port of the blockchain where the server is available for incoming http connections (e.g. readiness, versions, backup and restore)", - ), - MULTICHAIN_RPC_USER: Joi.string() - .default("multichainrpc") - .note("The user used to connect to the multichain daemon."), - MULTICHAIN_RPC_PASSWORD: Joi.string() - .default("s750SiJnj50yIrmwxPnEdSzpfGlTAHzhaUwgqKeb0G1j") - .note( - "Password used by the API to connect to the blockchain. The password is set by the origin node upon start. Every beta node needs to use the same RPC password in order to be able to connect to the blockchain.
**Hint:** Although the MULTICHAIN_RPC_PASSWORD is not required it is highly recommended to set an own secure one.", - ), - BLOCKCHAIN_PORT: Joi.number() - .default(8085) - .note( - "The port used to expose the multichain daemon of your Trubudget blockchain installation(bc). The port used to connect to the multichain daemon(api). This will be used internally for the communication between the API and the multichain daemon.", - ), - SWAGGER_BASEPATH: Joi.string() - .example("/") - .forbidden() - .note( - "deprecated", - "This variable was used to choose which environment (prod or test) is used for testing the requests. The variable is deprecated now, as the Swagger documentation can be used for the prod and test environment separately.", - ), - JWT_ALGORITHM: Joi.string() - .default("HS256") - .valid("HS256", "RS256") - .note("Algorithm used for signing and verifying JWTs."), - JWT_SECRET: Joi.string() - .min(32) - .default(randomString(32)) - .when("JWT_ALGORITHM", { - is: "RS256", - then: Joi.string().base64().required(), - }) - .note( - "A string that is used to sign JWT which are created by the authenticate endpoint of the api. If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded private key for RSA.", - ), - JWT_PUBLIC_KEY: Joi.string() - .default("") - .when("JWT_ALGORITHM", { - is: "RS256", - then: Joi.string().base64().required(), - }) - .note( - "If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded public key for RSA.", - ), - DOCUMENT_FEATURE_ENABLED: Joi.boolean().default(false), - DOCUMENT_EXTERNAL_LINKS_ENABLED: Joi.boolean().default(false), - STORAGE_SERVICE_HOST: Joi.string().default("localhost"), - STORAGE_SERVICE_PORT: Joi.number().default(8090), - STORAGE_SERVICE_EXTERNAL_URL: Joi.string().default("").when("DOCUMENT_FEATURE_ENABLED", { - is: true, - then: Joi.required(), - }), - EMAIL_HOST: Joi.string().default("localhost"), - EMAIL_PORT: Joi.number().default(8089), - ACCESS_CONTROL_ALLOW_ORIGIN: Joi.string().default("*"), - NODE_ENV: Joi.string().default("production"), - ENCRYPTION_PASSWORD: Joi.string(), - SIGNING_METHOD: Joi.string().default("node"), - RATE_LIMIT: Joi.number().allow("").empty(""), - AUTHPROXY_ENABLED: Joi.boolean().default(false), - AUTHPROXY_JWS_SIGNATURE: Joi.string(), - DB_TYPE: Joi.string().default("pg"), - SQL_DEBUG: Joi.boolean().default(false), - API_DB_USER: Joi.string().default("postgres"), - API_DB_PASSWORD: Joi.string().default("test"), - API_DB_HOST: Joi.string().default("localhost"), - API_DB_NAME: Joi.string().default("trubudget_email_service"), - API_DB_PORT: Joi.number().default(5432), - API_DB_SSL: Joi.boolean().default(false), - API_DB_SCHEMA: Joi.string().default("public"), - API_REFRESH_TOKENS_TABLE: Joi.string().default("refresh_token"), - REFRESH_TOKEN_STORAGE: Joi.string().allow("db", "memory"), - SNAPSHOT_EVENT_INTERVAL: Joi.number().default(3), - SILENCE_LOGGING_ON_FREQUENT_ROUTES: Joi.boolean().default(false), - APPLICATIONINSIGHTS_CONNECTION_STRING: Joi.string().allow(""), -}) - .unknown() - .required(); + const { error, value: envVars } = envVarsSchema.validate(process.env); if (error) { diff --git a/api/src/envVarsSchema.ts b/api/src/envVarsSchema.ts new file mode 100644 index 000000000..ef34f265a --- /dev/null +++ b/api/src/envVarsSchema.ts @@ -0,0 +1,118 @@ +import * as Joi from "joi"; +import { randomString } from "./service/hash"; + +export const envVarsSchema = Joi.object({ + ORGANIZATION: Joi.string() + .min(1) + .max(100) + .required() + .note( + "In the blockchain network, each node is represented by its organization name. This environment variable sets this organization name. It is used to create the organization stream on the blockchain and is also displayed in the frontend's top right corner.", + ), + PORT: Joi.number() + .min(0) + .max(65535) + .default(8091) + .note( + "The port used to expose the API for your installation.
Example: If you run TruBudget locally and set API_PORT to `8080`, you can reach the API via `localhost:8080/api`.", + ), + ORGANIZATION_VAULT_SECRET: Joi.string() + .invalid("secret") + .required() + .note( + "This is the key to en-/decrypt user data of an organization. If you want to add a new node for your organization, you want users to be able to log in on either node.
**Caution:** If you want to run TruBudget in production, make sure NOT to use the default value from the `.env_example` file!", + ), + ROOT_SECRET: Joi.string() + .min(8) + .required() + .default(randomString(32)) + .note( + "The root secret is the password for the root user. If you start with an empty blockchain, the root user is needed to add other users, approve new nodes,.. If you don't set a value via the environment variable, the API generates one randomly and prints it to the console
**Caution:** If you want to run TruBudget in production, make sure to set a secure root secret.", + ), + MULTICHAIN_RPC_HOST: Joi.string() + .default("localhost") + .note( + "The IP address of the blockchain (not multichain daemon,but they are usally the same) you want to connect to.", + ), + MULTICHAIN_RPC_PORT: Joi.number() + .default(8000) + .note( + "The Port of the blockchain where the server is available for incoming http connections (e.g. readiness, versions, backup and restore)", + ), + MULTICHAIN_RPC_USER: Joi.string() + .default("multichainrpc") + .note("The user used to connect to the multichain daemon."), + MULTICHAIN_RPC_PASSWORD: Joi.string() + .default("s750SiJnj50yIrmwxPnEdSzpfGlTAHzhaUwgqKeb0G1j") + .note( + "Password used by the API to connect to the blockchain. The password is set by the origin node upon start. Every beta node needs to use the same RPC password in order to be able to connect to the blockchain.
**Hint:** Although the MULTICHAIN_RPC_PASSWORD is not required it is highly recommended to set an own secure one.", + ), + BLOCKCHAIN_PORT: Joi.number() + .default(8085) + .note( + "The port used to expose the multichain daemon of your Trubudget blockchain installation(bc). The port used to connect to the multichain daemon(api). This will be used internally for the communication between the API and the multichain daemon.", + ), + SWAGGER_BASEPATH: Joi.string() + .example("/") + .forbidden() + .note( + "deprecated", + "This variable was used to choose which environment (prod or test) is used for testing the requests. The variable is deprecated now, as the Swagger documentation can be used for the prod and test environment separately.", + ), + JWT_ALGORITHM: Joi.string() + .default("HS256") + .valid("HS256", "RS256") + .note("Algorithm used for signing and verifying JWTs."), + JWT_SECRET: Joi.string() + .min(32) + .default(randomString(32)) + .when("JWT_ALGORITHM", { + is: "RS256", + then: Joi.string().base64().required(), + }) + .note( + "A string that is used to sign JWT which are created by the authenticate endpoint of the api. If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded private key for RSA.", + ), + JWT_PUBLIC_KEY: Joi.string() + .default("") + .when("JWT_ALGORITHM", { + is: "RS256", + then: Joi.string().base64().required(), + }) + .note( + "If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded public key for RSA.", + ), + DOCUMENT_FEATURE_ENABLED: Joi.boolean().default(false), + DOCUMENT_EXTERNAL_LINKS_ENABLED: Joi.boolean().default(false), + STORAGE_SERVICE_HOST: Joi.string().default("localhost"), + STORAGE_SERVICE_PORT: Joi.number().default(8090), + STORAGE_SERVICE_EXTERNAL_URL: Joi.string().default("").when("DOCUMENT_FEATURE_ENABLED", { + is: true, + then: Joi.required(), + }), + EMAIL_HOST: Joi.string().default("localhost"), + EMAIL_PORT: Joi.number().default(8089), + ACCESS_CONTROL_ALLOW_ORIGIN: Joi.string().default("*"), + NODE_ENV: Joi.string().default("production"), + ENCRYPTION_PASSWORD: Joi.string(), + SIGNING_METHOD: Joi.string().default("node"), + RATE_LIMIT: Joi.number().allow("").empty(""), + AUTHPROXY_ENABLED: Joi.boolean().default(false), + AUTHPROXY_JWS_SIGNATURE: Joi.string(), + DB_TYPE: Joi.string().default("pg"), + SQL_DEBUG: Joi.boolean().default(false), + API_DB_USER: Joi.string().default("postgres"), + API_DB_PASSWORD: Joi.string().default("test"), + API_DB_HOST: Joi.string().default("localhost"), + API_DB_NAME: Joi.string().default("trubudget_email_service"), + API_DB_PORT: Joi.number().default(5432), + API_DB_SSL: Joi.boolean().default(false), + API_DB_SCHEMA: Joi.string().default("public"), + API_REFRESH_TOKENS_TABLE: Joi.string().default("refresh_token"), + REFRESH_TOKEN_STORAGE: Joi.string().allow("db", "memory"), + SNAPSHOT_EVENT_INTERVAL: Joi.number().default(3), + SILENCE_LOGGING_ON_FREQUENT_ROUTES: Joi.boolean().default(false), + APPLICATIONINSIGHTS_CONNECTION_STRING: Joi.string().allow(""), +}) + .unknown() + .required(); diff --git a/api/src/scripts/envVarsValidator.ts b/api/src/scripts/envVarsValidator.ts index 89dbea44e..fff9121b1 100644 --- a/api/src/scripts/envVarsValidator.ts +++ b/api/src/scripts/envVarsValidator.ts @@ -1,10 +1,8 @@ -import config from "../config"; +import { envVarsSchema } from "../envVarsSchema"; -try { - config(); - console.log("Environment variables are valid"); -} catch (error) { - console.error("Environment variables are not valid"); - console.error(error); - process.exit(1); +const { error } = envVarsSchema.validate(process.env, { abortEarly: false }); +if (error) { + console.log(error.message); +} else { + console.log("[]"); } diff --git a/scripts/development/start-dev.sh b/scripts/development/start-dev.sh index 3f3b3b825..720f7a032 100755 --- a/scripts/development/start-dev.sh +++ b/scripts/development/start-dev.sh @@ -396,7 +396,19 @@ if [ "$IS_PARTLY_REBUILDING" = true ]; then $COMPOSE build $BUILD_SERVICES fi -# Read space sepparated services to array +if [ "$IS_LOG_ENABLED" = false ]; then + echo "INFO: Docker container are started without logging" +fi + +if [ "$START_FRONTEND_IN_CONTAINER" = false ]; then + echo "INFO: Docker container are started without frontend. You have to start it in separate window." +fi + +# Start docker containers +echo "INFO: Executing command: $COMPOSE up $LOG_OPTION $COMPOSE_SERVICES $ENABLED_SERVICES $BETA_SERVICES" +$COMPOSE up $LOG_OPTION $COMPOSE_SERVICES $ENABLED_SERVICES $BETA_SERVICES + +# Read logs from servises to check if there is any error. Stop the environment if there is any error. read -a ALL_SERVICES_ARRAY <<< "$COMPOSE_SERVICES $ENABLED_SERVICES" # loop through the services array @@ -404,24 +416,10 @@ for service in "${ALL_SERVICES_ARRAY[@]}" do echo "INFO: Validating environment variables for $service service ..." # Run environenment variables check - OUTPUT=$(docker run --env-file $SCRIPT_DIR/.env trubudget-dev-$service npm run validate-env 2>&1) + OUTPUT=$(docker logs trubudget-dev-$service 2>&1 | grep "Config validation error") if [[ $OUTPUT =~ "Config validation error" ]]; then echo "${red}ERROR: The .env file is not valid for the $service service. Please check the .env file.${colorReset}" exit 1 fi done - - -if [ "$IS_LOG_ENABLED" = false ]; then - echo "INFO: Docker container are started without logging" -fi - -if [ "$START_FRONTEND_IN_CONTAINER" = false ]; then - echo "INFO: Docker container are started without frontend. You have to start it in separate window." -fi - -# Start docker containers -echo "INFO: Executing command: $COMPOSE up $LOG_OPTION $COMPOSE_SERVICES $ENABLED_SERVICES $BETA_SERVICES" -$COMPOSE up $LOG_OPTION $COMPOSE_SERVICES $ENABLED_SERVICES $BETA_SERVICES - From 6f99bf1ec25999d912847487a6bb26246fd6bc44 Mon Sep 17 00:00:00 2001 From: Peter Baus Date: Thu, 12 Sep 2024 13:27:41 +0200 Subject: [PATCH 05/21] logs script --- scripts/development/start-dev.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/scripts/development/start-dev.sh b/scripts/development/start-dev.sh index 720f7a032..a4a86d8ca 100755 --- a/scripts/development/start-dev.sh +++ b/scripts/development/start-dev.sh @@ -410,16 +410,23 @@ $COMPOSE up $LOG_OPTION $COMPOSE_SERVICES $ENABLED_SERVICES $BETA_SERVICES # Read logs from servises to check if there is any error. Stop the environment if there is any error. read -a ALL_SERVICES_ARRAY <<< "$COMPOSE_SERVICES $ENABLED_SERVICES" +# Read running docker containers into array +read -a RUNNING_CONTAINERS <<< $(docker ps --format '{{.Names}}') # loop through the services array -for service in "${ALL_SERVICES_ARRAY[@]}" +for container in "${RUNNING_CONTAINERS[@]}" do - echo "INFO: Validating environment variables for $service service ..." + # skip the container if it is not in the services array + if [[ ! " ${ALL_SERVICES_ARRAY[@]} " =~ " ${container%-*} " ]]; then + echo "Skipping $container container ..." + continue + fi + echo "INFO: Validating environment variables for $container container ..." # Run environenment variables check - OUTPUT=$(docker logs trubudget-dev-$service 2>&1 | grep "Config validation error") + OUTPUT=$(docker logs $container 2>&1 | grep "Config validation error") if [[ $OUTPUT =~ "Config validation error" ]]; then - echo "${red}ERROR: The .env file is not valid for the $service service. Please check the .env file.${colorReset}" + echo "${red}ERROR: The .env file is not valid for the $container service. Please check the .env file.${colorReset}" exit 1 fi done From 5f8a6ed319e5cfd5a7afe24be2357f78292755a0 Mon Sep 17 00:00:00 2001 From: Peter Baus Date: Mon, 16 Sep 2024 09:30:10 +0200 Subject: [PATCH 06/21] temp --- scripts/development/extract-variables.sh | 46 ++++++++++++++++++++++++ scripts/development/start-dev.sh | 43 +++++++++++----------- 2 files changed, 68 insertions(+), 21 deletions(-) create mode 100644 scripts/development/extract-variables.sh diff --git a/scripts/development/extract-variables.sh b/scripts/development/extract-variables.sh new file mode 100644 index 000000000..66ca783e7 --- /dev/null +++ b/scripts/development/extract-variables.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Path to the docker-compose.yml file +COMPOSE_FILE="docker-compose.yml" + +# Function to parse environment variables for each service and distinguish values from environment variables +parse_services_and_environment_variables() { + service="" + awk ' + # Find a new service block (starts at the same indentation level as "services") + /^[[:space:]]{2}[a-zA-Z0-9_-]+:/ { + service=$1; + gsub(":", "", service); + } + + # Find the environment block for the current service + /environment:/ { + in_env_block=1; + next; + } + + # Extract key-value pairs inside the environment block + in_env_block && /^[[:space:]]*[A-Z_]+:/ { + gsub(/[[:space:]]*/, "", $0); # Remove spaces + split($0, kv, ":"); # Split on ":" + key=kv[1]; # Key is before ":" + value=kv[2]; # Value is after ":" + + # Check if the value is a hardcoded value or an environment variable reference + if (value ~ /^\$\{.*\}$/) { + gsub(/[\$\{\}]/, "", value); # Clean up variable placeholders + print "Service: " service ", key:\"" key "\", value:\"" value "\", type:environment variable"; + } else { + print "Service: " service ", key:\"" key "\", value:\"" value "\", type:value"; + } + } + + # Reset in_env_block when we leave the environment block or hit a new service + /^[[:space:]]{2}[a-zA-Z0-9_-]+:/ || /^[[:space:]]{2}$/ { + in_env_block=0; + } + ' "$COMPOSE_FILE" +} + +# Run the function to extract variables and service names +parse_services_and_environment_variables diff --git a/scripts/development/start-dev.sh b/scripts/development/start-dev.sh index a4a86d8ca..1a00b8475 100755 --- a/scripts/development/start-dev.sh +++ b/scripts/development/start-dev.sh @@ -384,6 +384,7 @@ if [ "$IS_RESTARTING_ONLY" = false ]; then fi echo "INFO: Pull images from https://hub.docker.com/ ..." +echo $COMPOSE_SERVICES $ENABLED_SERVICES $BETA_SERVICES $COMPOSE pull $COMPOSE_SERVICES $ENABLED_SERVICES $BETA_SERVICES if [ "$IS_REBUILDING" = true ]; then @@ -396,6 +397,27 @@ if [ "$IS_PARTLY_REBUILDING" = true ]; then $COMPOSE build $BUILD_SERVICES fi +# Read logs from servises to check if there is any error. Stop the environment if there is any error. +read -a ALL_SERVICES_ARRAY <<< "$COMPOSE_SERVICES $ENABLED_SERVICES" + +# loop through the services array +for service_to_be_started in "${ALL_SERVICES_ARRAY[@]}" +do + # # skip the container if it is not in the services array + # if [[ ! " ${ALL_SERVICES_ARRAY[@]} " =~ " ${container%-*} " ]]; then + # echo "Skipping $container container ..." + # continue + # fi + echo "INFO: Validating environment variables for $service_to_be_started service ..." + # Run environenment variables check + OUTPUT=$(docker run $container validate-env-variables 2>&1 | grep "Config validation error") + + if [[ $OUTPUT =~ "Config validation error" ]]; then + echo "${red}ERROR: The .env file is not valid for the $container service. Please check the .env file.${colorReset}" + exit 1 + fi +done + if [ "$IS_LOG_ENABLED" = false ]; then echo "INFO: Docker container are started without logging" fi @@ -408,25 +430,4 @@ fi echo "INFO: Executing command: $COMPOSE up $LOG_OPTION $COMPOSE_SERVICES $ENABLED_SERVICES $BETA_SERVICES" $COMPOSE up $LOG_OPTION $COMPOSE_SERVICES $ENABLED_SERVICES $BETA_SERVICES -# Read logs from servises to check if there is any error. Stop the environment if there is any error. -read -a ALL_SERVICES_ARRAY <<< "$COMPOSE_SERVICES $ENABLED_SERVICES" -# Read running docker containers into array -read -a RUNNING_CONTAINERS <<< $(docker ps --format '{{.Names}}') - -# loop through the services array -for container in "${RUNNING_CONTAINERS[@]}" -do - # skip the container if it is not in the services array - if [[ ! " ${ALL_SERVICES_ARRAY[@]} " =~ " ${container%-*} " ]]; then - echo "Skipping $container container ..." - continue - fi - echo "INFO: Validating environment variables for $container container ..." - # Run environenment variables check - OUTPUT=$(docker logs $container 2>&1 | grep "Config validation error") - if [[ $OUTPUT =~ "Config validation error" ]]; then - echo "${red}ERROR: The .env file is not valid for the $container service. Please check the .env file.${colorReset}" - exit 1 - fi -done From 38b6166f40b08acc2f1b2c846d9981bb0833087d Mon Sep 17 00:00:00 2001 From: Peter Baus Date: Tue, 17 Sep 2024 08:18:31 +0200 Subject: [PATCH 07/21] scripts:Check for env vars, without setting them for now. --- api/package.json | 3 +-- api/src/scripts/envVarsValidator.ts | 2 +- .../extract-variables.sh | 8 ++------ scripts/development/start-dev.sh | 17 +++++++---------- scripts/operation/start-trubudget.sh | 18 ++++++++++++++++++ 5 files changed, 29 insertions(+), 19 deletions(-) rename scripts/{development => common}/extract-variables.sh (89%) diff --git a/api/package.json b/api/package.json index 61d4a272c..34bc7cfec 100644 --- a/api/package.json +++ b/api/package.json @@ -47,9 +47,8 @@ "tsc": "tsc", "test:here": "node ./runTest.js", "generate-joi": "node dist/scripts/joiGenerator.js", - "validate-env": "node dist/scripts/envVarsValidator.js", "build-docs": "bash ./buildDocs.sh", - "validate-env-variables": "node dist/scripts/envVarsValidator.js" + "validate-env-variables": "node ./dist/scripts/envVarsValidator.js" }, "nyc": { "include": [ diff --git a/api/src/scripts/envVarsValidator.ts b/api/src/scripts/envVarsValidator.ts index fff9121b1..73c6cf651 100644 --- a/api/src/scripts/envVarsValidator.ts +++ b/api/src/scripts/envVarsValidator.ts @@ -2,7 +2,7 @@ import { envVarsSchema } from "../envVarsSchema"; const { error } = envVarsSchema.validate(process.env, { abortEarly: false }); if (error) { - console.log(error.message); + console.log(`Config validation error: ${error.message}`); } else { console.log("[]"); } diff --git a/scripts/development/extract-variables.sh b/scripts/common/extract-variables.sh similarity index 89% rename from scripts/development/extract-variables.sh rename to scripts/common/extract-variables.sh index 66ca783e7..4672b25fd 100644 --- a/scripts/development/extract-variables.sh +++ b/scripts/common/extract-variables.sh @@ -1,10 +1,9 @@ #!/bin/bash -# Path to the docker-compose.yml file -COMPOSE_FILE="docker-compose.yml" - # Function to parse environment variables for each service and distinguish values from environment variables parse_services_and_environment_variables() { + # Path to the docker-compose.yml file + COMPOSE_FILE=$1 service="" awk ' # Find a new service block (starts at the same indentation level as "services") @@ -41,6 +40,3 @@ parse_services_and_environment_variables() { } ' "$COMPOSE_FILE" } - -# Run the function to extract variables and service names -parse_services_and_environment_variables diff --git a/scripts/development/start-dev.sh b/scripts/development/start-dev.sh index 1a00b8475..0ca2e0ba7 100755 --- a/scripts/development/start-dev.sh +++ b/scripts/development/start-dev.sh @@ -64,6 +64,7 @@ IS_SKIPPING=false SKIPPED_SERVICE="" IS_RESTARTING_ONLY=false RESTART_ONLY_SERVICE="" +CONTAINERS_PREFIX="trubudget-dev" while [ "$1" != "" ]; do case $1 in @@ -154,7 +155,7 @@ while [ "$1" != "" ]; do ;; --down) - docker compose -p trubudget-dev down + docker compose -p $CONTAINERS_PREFIX down exit 1 ;; @@ -377,14 +378,13 @@ if [[ $COMPOSE_SERVICES =~ "minio" || $COMPOSE_SERVICES =~ "azure-storage" ]]; t COMPOSE="$COMPOSE -f $SCRIPT_DIR/docker-compose.azure-storage.yml" fi fi -COMPOSE="$COMPOSE -p trubudget-dev --env-file $SCRIPT_DIR/.env" +COMPOSE="$COMPOSE -p $CONTAINERS_PREFIX --env-file $SCRIPT_DIR/.env" if [ "$IS_RESTARTING_ONLY" = false ]; then $COMPOSE down fi echo "INFO: Pull images from https://hub.docker.com/ ..." -echo $COMPOSE_SERVICES $ENABLED_SERVICES $BETA_SERVICES $COMPOSE pull $COMPOSE_SERVICES $ENABLED_SERVICES $BETA_SERVICES if [ "$IS_REBUILDING" = true ]; then @@ -398,22 +398,19 @@ if [ "$IS_PARTLY_REBUILDING" = true ]; then fi # Read logs from servises to check if there is any error. Stop the environment if there is any error. +# e.g. alpha-node emaildb minio alpha-api email-service excel-export-service storage-service provisioning frontend read -a ALL_SERVICES_ARRAY <<< "$COMPOSE_SERVICES $ENABLED_SERVICES" # loop through the services array for service_to_be_started in "${ALL_SERVICES_ARRAY[@]}" do - # # skip the container if it is not in the services array - # if [[ ! " ${ALL_SERVICES_ARRAY[@]} " =~ " ${container%-*} " ]]; then - # echo "Skipping $container container ..." - # continue - # fi echo "INFO: Validating environment variables for $service_to_be_started service ..." # Run environenment variables check - OUTPUT=$(docker run $container validate-env-variables 2>&1 | grep "Config validation error") + OUTPUT=$(docker run ${CONTAINERS_PREFIX}-${service_to_be_started} npm run validate-env-variables 2>&1) if [[ $OUTPUT =~ "Config validation error" ]]; then - echo "${red}ERROR: The .env file is not valid for the $container service. Please check the .env file.${colorReset}" + echo "${red}ERROR: The .env file is not valid for the $service_to_be_started service. Please check the .env file.${colorReset}" + echo $OUTPUT exit 1 fi done diff --git a/scripts/operation/start-trubudget.sh b/scripts/operation/start-trubudget.sh index cabf649e3..a89504a7e 100644 --- a/scripts/operation/start-trubudget.sh +++ b/scripts/operation/start-trubudget.sh @@ -267,6 +267,24 @@ $COMPOSE pull $COMPOSE_SERVICES $ENABLED_SERVICES $BETA_SERVICES echo "INFO: Since images are used, building is not necessary and will be skipped." #$COMPOSE build +# Read logs from servises to check if there is any error. Stop the environment if there is any error. +# e.g. alpha-node emaildb minio alpha-api email-service excel-export-service storage-service provisioning frontend +read -a ALL_SERVICES_ARRAY <<< "$COMPOSE_SERVICES $ENABLED_SERVICES" + +# loop through the services array +for service_to_be_started in "${ALL_SERVICES_ARRAY[@]}" +do + echo "INFO: Validating environment variables for $service_to_be_started service ..." + # Run environenment variables check + OUTPUT=$(docker run ${CONTAINERS_PREFIX}-${service_to_be_started} npm run validate-env-variables 2>&1) + + if [[ $OUTPUT =~ "Config validation error" ]]; then + echo "${red}ERROR: The .env file is not valid for the $service_to_be_started service. Please check the .env file.${colorReset}" + echo $OUTPUT + exit 1 + fi +done + # Start docker containers echo "INFO: Executing command: $COMPOSE up $LOG_OPTION $COMPOSE_SERVICES $ENABLED_SERVICES $BETA_SERVICES" $COMPOSE up $LOG_OPTION $COMPOSE_SERVICES $ENABLED_SERVICES $BETA_SERVICES From 05856f51f4300aa80cb5b6d8dafc1163e31edd56 Mon Sep 17 00:00:00 2001 From: Peter Baus Date: Tue, 17 Sep 2024 14:10:36 +0200 Subject: [PATCH 08/21] api:Create env docs generator --- api/src/scripts/envVarsDocsGenerator.ts | 72 +++++++++++++++++++++++++ api/src/scripts/envVarsValidator.ts | 2 +- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 api/src/scripts/envVarsDocsGenerator.ts diff --git a/api/src/scripts/envVarsDocsGenerator.ts b/api/src/scripts/envVarsDocsGenerator.ts new file mode 100644 index 000000000..2c42e64c2 --- /dev/null +++ b/api/src/scripts/envVarsDocsGenerator.ts @@ -0,0 +1,72 @@ +import Joi from "joi"; +import { envVarsSchema } from "../envVarsSchema"; + +interface EnvVariable { + name: string; + required: string; + default: string; + description: string; +} + +// Helper function to extract Joi schema information +export const extractSchemaInfo = (schema: Joi.ObjectSchema) => { + const envVariables: EnvVariable[] = []; + + const schemaDescribe = schema.describe(); + // Iterate over schema keys + if (schemaDescribe.keys) { + Object.keys(schemaDescribe.keys).forEach((key) => { + const item = schemaDescribe.keys[key]; + + const isRequired = item.flags && item.flags.presence === "required" ? "yes" : "no"; + const defaultValue = item.flags && item.flags.default ? item.flags.default : "-"; + const description = item.notes && item.notes.length ? item.notes.join(" ") : "-"; + const additionalEntries: string[] = []; + if (item.min) { + additionalEntries.push(`Minimal value: ${item.min}.`); + } + if (item.max) { + additionalEntries.push(`Maximal value: ${item.max}.`); + } + if (item.invalid) { + additionalEntries.push(`Invalid values: ${item.invalid}.`); + } + if (item.example) { + additionalEntries.push(`Example values: ${item.example}.`); + } + if (item.valid) { + additionalEntries.push(`Allowed values: ${item.valid}.`); + } + + envVariables.push({ + name: key, + required: isRequired, + default: defaultValue, + description: description.replace(//g, " ") + ` ${additionalEntries.join(" ")}`, + }); + }); + } + + return envVariables; +}; + +// Generate Markdown table +const generateMarkdown = (envVariables: EnvVariable[]) => { + const header = + "| Env Variable name | Required | Default Value | Description |\n|------------------|----------------------|---------------|-------------|\n"; + const rows = envVariables + .map( + (varInfo: EnvVariable) => + `| ${varInfo.name} | ${varInfo.required} | ${varInfo.default} | ${varInfo.description} |`, + ) + .join("\n"); + + return header + rows; +}; + +const updateMarkdownFile = () => { + const schema = extractSchemaInfo(envVarsSchema); + const mdTable = generateMarkdown(schema); + + console.log(mdTable); +}; diff --git a/api/src/scripts/envVarsValidator.ts b/api/src/scripts/envVarsValidator.ts index 73c6cf651..e0b609791 100644 --- a/api/src/scripts/envVarsValidator.ts +++ b/api/src/scripts/envVarsValidator.ts @@ -4,5 +4,5 @@ const { error } = envVarsSchema.validate(process.env, { abortEarly: false }); if (error) { console.log(`Config validation error: ${error.message}`); } else { - console.log("[]"); + console.log("Environment variables are valid."); } From 1630d13985f5e88dd423ce73990db6f1c9b1d503 Mon Sep 17 00:00:00 2001 From: Peter Baus Date: Fri, 20 Sep 2024 15:34:25 +0200 Subject: [PATCH 09/21] api:Include also additinal vars validation in docs --- api/environment-variables.md | 2 +- api/environment-variables2.md | 43 +++++++++++++++++++++++++ api/package.json | 3 +- api/src/scripts/envVarsDocsGenerator.ts | 39 +++++++++++++++------- 4 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 api/environment-variables2.md diff --git a/api/environment-variables.md b/api/environment-variables.md index c3a183283..3cf215ab8 100644 --- a/api/environment-variables.md +++ b/api/environment-variables.md @@ -4,7 +4,7 @@ | Env Variable | Required | Default Value | Description | | ------------------------------------- | ---------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| LOG_LEVEL | no | info | Defines the log output. Supported levels are `trace`, `debug`, `info`, `warn`, `error`, `fatal` | +| **LOG_LEVEL** | no | info | Defines the log output. Supported levels are `trace`, `debug`, `info`, `warn`, `error`, `fatal` | | ORGANIZATION | yes | - | In the blockchain network, each node is represented by its organization name. This environment variable sets this organization name. It is used to create the organization stream on the blockchain and is also displayed in the frontend's top right corner. | | ORGANIZATION_VAULT_SECRET | yes | - | This is the key to en-/decrypt user data of an organization. If you want to add a new node for your organization, you want users to be able to log in on either node.
**Caution:** If you want to run TruBudget in production, make sure NOT to use the default value from the `.env_example` file! | | PORT | no | 8080 | The port used to expose the API for your installation.
Example: If you run TruBudget locally and set API_PORT to `8080`, you can reach the API via `localhost:8080/api`. | diff --git a/api/environment-variables2.md b/api/environment-variables2.md new file mode 100644 index 000000000..392119805 --- /dev/null +++ b/api/environment-variables2.md @@ -0,0 +1,43 @@ +| Env Variable name | Required | Default Value | Description | +|------------------|----------------------|---------------|-------------| +| ORGANIZATION | yes | - | In the blockchain network, each node is represented by its organization name. This environment variable sets this organization name. It is used to create the organization stream on the blockchain and is also displayed in the frontend's top right corner. | +| PORT | no | 8091 | The port used to expose the API for your installation. Example: If you run TruBudget locally and set API_PORT to `8080`, you can reach the API via `localhost:8080/api`. | +| ORGANIZATION_VAULT_SECRET | yes | - | This is the key to en-/decrypt user data of an organization. If you want to add a new node for your organization, you want users to be able to log in on either node. **Caution:** If you want to run TruBudget in production, make sure NOT to use the default value from the `.env_example` file! Invalid values: secret. | +| ROOT_SECRET | yes | aaba5fcbe677fc0d952c29d8928d283601a6e26ae84149185feb45fd81785f63 | The root secret is the password for the root user. If you start with an empty blockchain, the root user is needed to add other users, approve new nodes,.. If you don't set a value via the environment variable, the API generates one randomly and prints it to the console **Caution:** If you want to run TruBudget in production, make sure to set a secure root secret. | +| MULTICHAIN_RPC_HOST | no | localhost | The IP address of the blockchain (not multichain daemon,but they are usally the same) you want to connect to. | +| MULTICHAIN_RPC_PORT | no | 8000 | The Port of the blockchain where the server is available for incoming http connections (e.g. readiness, versions, backup and restore) | +| MULTICHAIN_RPC_USER | no | multichainrpc | The user used to connect to the multichain daemon. | +| MULTICHAIN_RPC_PASSWORD | no | s750SiJnj50yIrmwxPnEdSzpfGlTAHzhaUwgqKeb0G1j | Password used by the API to connect to the blockchain. The password is set by the origin node upon start. Every beta node needs to use the same RPC password in order to be able to connect to the blockchain. **Hint:** Although the MULTICHAIN_RPC_PASSWORD is not required it is highly recommended to set an own secure one. | +| BLOCKCHAIN_PORT | no | 8085 | The port used to expose the multichain daemon of your Trubudget blockchain installation(bc). The port used to connect to the multichain daemon(api). This will be used internally for the communication between the API and the multichain daemon. | +| SWAGGER_BASEPATH`deprecated` | no | - | deprecated This variable was used to choose which environment (prod or test) is used for testing the requests. The variable is deprecated now, as the Swagger documentation can be used for the prod and test environment separately. Example values: /. | +| JWT_ALGORITHM | no | HS256 | Algorithm used for signing and verifying JWTs. Allowed values: HS256, RS256. | +| JWT_SECRET | no | 59cc214d8a8128f72f8f130f71bd4a2c5f6c223ae9ea8b9484055150d1807fbd | A string that is used to sign JWT which are created by the authenticate endpoint of the api. If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded private key for RSA. | +| JWT_PUBLIC_KEY | no | - | If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded public key for RSA. | +| DOCUMENT_FEATURE_ENABLED | no | - | - | +| DOCUMENT_EXTERNAL_LINKS_ENABLED | no | - | - | +| STORAGE_SERVICE_HOST | no | localhost | - | +| STORAGE_SERVICE_PORT | no | 8090 | - | +| STORAGE_SERVICE_EXTERNAL_URL | no | - | - | +| EMAIL_HOST | no | localhost | - | +| EMAIL_PORT | no | 8089 | - | +| ACCESS_CONTROL_ALLOW_ORIGIN | no | * | - | +| NODE_ENV | no | production | - | +| ENCRYPTION_PASSWORD | no | - | - | +| SIGNING_METHOD | no | node | - | +| RATE_LIMIT | no | - | - Allowed values: . | +| AUTHPROXY_ENABLED | no | - | - | +| AUTHPROXY_JWS_SIGNATURE | no | - | - | +| DB_TYPE | no | pg | - | +| SQL_DEBUG | no | - | - | +| API_DB_USER | no | postgres | - | +| API_DB_PASSWORD | no | test | - | +| API_DB_HOST | no | localhost | - | +| API_DB_NAME | no | trubudget_email_service | - | +| API_DB_PORT | no | 5432 | - | +| API_DB_SSL | no | - | - | +| API_DB_SCHEMA | no | public | - | +| API_REFRESH_TOKENS_TABLE | no | refresh_token | - | +| REFRESH_TOKEN_STORAGE | no | - | - Allowed values: db, memory. | +| SNAPSHOT_EVENT_INTERVAL | no | 3 | - | +| SILENCE_LOGGING_ON_FREQUENT_ROUTES | no | - | - | +| APPLICATIONINSIGHTS_CONNECTION_STRING | no | - | - Allowed values: . | diff --git a/api/package.json b/api/package.json index 34bc7cfec..d64fa6f8c 100644 --- a/api/package.json +++ b/api/package.json @@ -48,7 +48,8 @@ "test:here": "node ./runTest.js", "generate-joi": "node dist/scripts/joiGenerator.js", "build-docs": "bash ./buildDocs.sh", - "validate-env-variables": "node ./dist/scripts/envVarsValidator.js" + "validate-env-variables": "node ./dist/scripts/envVarsValidator.js", + "generate-env-vars-docs": "node ./dist/scripts/envVarsDocsGenerator.js" }, "nyc": { "include": [ diff --git a/api/src/scripts/envVarsDocsGenerator.ts b/api/src/scripts/envVarsDocsGenerator.ts index 2c42e64c2..5b925437a 100644 --- a/api/src/scripts/envVarsDocsGenerator.ts +++ b/api/src/scripts/envVarsDocsGenerator.ts @@ -1,10 +1,12 @@ import Joi from "joi"; import { envVarsSchema } from "../envVarsSchema"; +import { writeFileSync } from "fs"; interface EnvVariable { name: string; required: string; default: string; + deprecated: boolean; description: string; } @@ -22,26 +24,35 @@ export const extractSchemaInfo = (schema: Joi.ObjectSchema) => { const defaultValue = item.flags && item.flags.default ? item.flags.default : "-"; const description = item.notes && item.notes.length ? item.notes.join(" ") : "-"; const additionalEntries: string[] = []; - if (item.min) { - additionalEntries.push(`Minimal value: ${item.min}.`); + + const min = item.rules && item.rules.find((rule) => rule.name === "min"); + const max = item.rules && item.rules.find((rule) => rule.name === "max"); + const invalid = item.invalid; + const deprecated = item.notes && item.notes.find((note) => note === "deprecated"); + const examples = item.examples; + const valid = item.allow; + + if (min) { + additionalEntries.push(`Minimal value: ${min?.args?.limit}.`); } - if (item.max) { - additionalEntries.push(`Maximal value: ${item.max}.`); + if (max) { + additionalEntries.push(`Maximal value: ${max?.args?.limit}.`); } - if (item.invalid) { - additionalEntries.push(`Invalid values: ${item.invalid}.`); + if (invalid) { + additionalEntries.push(`Invalid values: ${invalid.join(", ")}.`); } - if (item.example) { - additionalEntries.push(`Example values: ${item.example}.`); + if (examples) { + additionalEntries.push(`Example values: ${examples.join(", ")}.`); } - if (item.valid) { - additionalEntries.push(`Allowed values: ${item.valid}.`); + if (valid) { + additionalEntries.push(`Allowed values: ${valid.join(", ")}.`); } envVariables.push({ name: key, required: isRequired, default: defaultValue, + deprecated: !!deprecated, description: description.replace(//g, " ") + ` ${additionalEntries.join(" ")}`, }); }); @@ -57,7 +68,9 @@ const generateMarkdown = (envVariables: EnvVariable[]) => { const rows = envVariables .map( (varInfo: EnvVariable) => - `| ${varInfo.name} | ${varInfo.required} | ${varInfo.default} | ${varInfo.description} |`, + `| **${varInfo.name}**${varInfo.deprecated ? " `deprecated`" : ""} | ${ + varInfo.required + } | ${varInfo.default} | ${varInfo.description} |`, ) .join("\n"); @@ -68,5 +81,7 @@ const updateMarkdownFile = () => { const schema = extractSchemaInfo(envVarsSchema); const mdTable = generateMarkdown(schema); - console.log(mdTable); + writeFileSync("../../environment-variables2.md", mdTable, "utf-8"); }; + +updateMarkdownFile(); From 4e1ee6d17f0378bbd03c372afd2a7d1c87db8508 Mon Sep 17 00:00:00 2001 From: Peter Baus Date: Fri, 20 Sep 2024 15:34:58 +0200 Subject: [PATCH 10/21] api:Include also additinal vars validation in docs --- scripts/common/envVarsGenerator/dist/index.js | 65 ++ .../common/envVarsGenerator/dist/index.js.map | 1 + .../common/envVarsGenerator/package-lock.json | 993 ++++++++++++++++++ scripts/common/envVarsGenerator/package.json | 18 + scripts/common/envVarsGenerator/src/index.ts | 85 ++ scripts/common/envVarsGenerator/tsconfig.json | 18 + 6 files changed, 1180 insertions(+) create mode 100644 scripts/common/envVarsGenerator/dist/index.js create mode 100644 scripts/common/envVarsGenerator/dist/index.js.map create mode 100644 scripts/common/envVarsGenerator/package-lock.json create mode 100644 scripts/common/envVarsGenerator/package.json create mode 100644 scripts/common/envVarsGenerator/src/index.ts create mode 100644 scripts/common/envVarsGenerator/tsconfig.json diff --git a/scripts/common/envVarsGenerator/dist/index.js b/scripts/common/envVarsGenerator/dist/index.js new file mode 100644 index 000000000..df49d9b0e --- /dev/null +++ b/scripts/common/envVarsGenerator/dist/index.js @@ -0,0 +1,65 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.updateMarkdownFile = exports.extractSchemaInfo = void 0; +const fs_1 = require("fs"); +// Helper function to extract Joi schema information +const extractSchemaInfo = (schema) => { + const envVariables = []; + const schemaDescribe = schema.describe(); + // Iterate over schema keys + if (schemaDescribe.keys) { + Object.keys(schemaDescribe.keys).forEach((key) => { + var _a, _b; + const item = schemaDescribe.keys[key]; + const isRequired = item.flags && item.flags.presence === "required" ? "yes" : "no"; + const defaultValue = item.flags && item.flags.default ? item.flags.default : "-"; + const description = item.notes && item.notes.length ? item.notes.join(" ") : "-"; + const additionalEntries = []; + const min = item.rules && item.rules.find((rule) => rule.name === "min"); + const max = item.rules && item.rules.find((rule) => rule.name === "max"); + const invalid = item.invalid; + const deprecated = item.notes && item.notes.find((note) => note === "deprecated"); + const examples = item.examples; + const valid = item.allow; + if (min) { + additionalEntries.push(`Minimal value: ${(_a = min === null || min === void 0 ? void 0 : min.args) === null || _a === void 0 ? void 0 : _a.limit}.`); + } + if (max) { + additionalEntries.push(`Maximal value: ${(_b = max === null || max === void 0 ? void 0 : max.args) === null || _b === void 0 ? void 0 : _b.limit}.`); + } + if (invalid) { + additionalEntries.push(`Invalid values: ${invalid.join(", ")}.`); + } + if (examples) { + additionalEntries.push(`Example values: ${examples.join(", ")}.`); + } + if (valid) { + additionalEntries.push(`Allowed values: ${valid.join(", ")}.`); + } + envVariables.push({ + name: key, + required: isRequired, + default: defaultValue, + deprecated: !!deprecated, + description: description.replace(//g, " ") + ` ${additionalEntries.join(" ")}`, + }); + }); + } + return envVariables; +}; +exports.extractSchemaInfo = extractSchemaInfo; +// Generate Markdown table +const generateMarkdown = (envVariables) => { + const header = "| Env Variable name | Required | Default Value | Description |\n|------------------|----------------------|---------------|-------------|\n"; + const rows = envVariables + .map((varInfo) => `| **${varInfo.name}**${varInfo.deprecated ? " `deprecated`" : ""} | ${varInfo.required} | ${varInfo.default} | ${varInfo.description} |`) + .join("\n"); + return header + rows; +}; +const updateMarkdownFile = (envVarsSchema) => { + const schema = (0, exports.extractSchemaInfo)(envVarsSchema); + const mdTable = generateMarkdown(schema); + (0, fs_1.writeFileSync)("../../environment-variables2.md", mdTable, "utf-8"); +}; +exports.updateMarkdownFile = updateMarkdownFile; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/scripts/common/envVarsGenerator/dist/index.js.map b/scripts/common/envVarsGenerator/dist/index.js.map new file mode 100644 index 000000000..157bed855 --- /dev/null +++ b/scripts/common/envVarsGenerator/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/scripts/common/envVarsGenerator/package-lock.json b/scripts/common/envVarsGenerator/package-lock.json new file mode 100644 index 000000000..ed80d9f9d --- /dev/null +++ b/scripts/common/envVarsGenerator/package-lock.json @@ -0,0 +1,993 @@ +{ + "name": "envvarsgenerator", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "envvarsgenerator", + "version": "1.0.0", + "dependencies": { + "joi": "^17.13.3" + }, + "devDependencies": { + "diff": "^7.0.0", + "rimraf": "^6.0.1", + "typescript": "^5.6.2" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jackspeak": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/lru-cache": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", + "dev": true, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "dependencies": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/typescript": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + } + }, + "dependencies": { + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + } + }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true + }, + "@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, + "ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true + }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + } + }, + "glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "jackspeak": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, + "joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "requires": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "lru-cache": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", + "dev": true + }, + "minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true + }, + "package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "requires": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + } + }, + "rimraf": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.0.1.tgz", + "integrity": "sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==", + "dev": true, + "requires": { + "glob": "^11.0.0", + "package-json-from-dist": "^1.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + } + } + }, + "typescript": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + } + } +} diff --git a/scripts/common/envVarsGenerator/package.json b/scripts/common/envVarsGenerator/package.json new file mode 100644 index 000000000..ccb19a145 --- /dev/null +++ b/scripts/common/envVarsGenerator/package.json @@ -0,0 +1,18 @@ +{ + "name": "envvarsgenerator", + "version": "1.0.0", + "description": "", + "main": "index.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "rimraf dist && tsc" + }, + "devDependencies": { + "diff": "^7.0.0", + "rimraf": "^6.0.1", + "typescript": "^5.6.2" + }, + "dependencies": { + "joi": "^17.13.3" + } +} diff --git a/scripts/common/envVarsGenerator/src/index.ts b/scripts/common/envVarsGenerator/src/index.ts new file mode 100644 index 000000000..fbb5f16e0 --- /dev/null +++ b/scripts/common/envVarsGenerator/src/index.ts @@ -0,0 +1,85 @@ +import * as Joi from "joi"; +import { writeFileSync } from "fs"; + +interface EnvVariable { + name: string; + required: string; + default: string; + deprecated: boolean; + description: string; +} + +// Helper function to extract Joi schema information +export const extractSchemaInfo = (schema: Joi.ObjectSchema) => { + const envVariables: EnvVariable[] = []; + + const schemaDescribe = schema.describe(); + // Iterate over schema keys + if (schemaDescribe.keys) { + Object.keys(schemaDescribe.keys).forEach((key) => { + const item = schemaDescribe.keys[key]; + + const isRequired = item.flags && item.flags.presence === "required" ? "yes" : "no"; + const defaultValue = item.flags && item.flags.default ? item.flags.default : "-"; + const description = item.notes && item.notes.length ? item.notes.join(" ") : "-"; + const additionalEntries: string[] = []; + + const min = item.rules && item.rules.find((rule) => rule.name === "min"); + const max = item.rules && item.rules.find((rule) => rule.name === "max"); + const invalid = item.invalid; + const deprecated = item.notes && item.notes.find((note) => note === "deprecated"); + const examples = item.examples; + const valid = item.allow; + + if (min) { + additionalEntries.push(`Minimal value: ${min?.args?.limit}.`); + } + if (max) { + additionalEntries.push(`Maximal value: ${max?.args?.limit}.`); + } + if (invalid) { + additionalEntries.push(`Invalid values: ${invalid.join(", ")}.`); + } + if (examples) { + additionalEntries.push(`Example values: ${examples.join(", ")}.`); + } + if (valid) { + additionalEntries.push(`Allowed values: ${valid.join(", ")}.`); + } + + envVariables.push({ + name: key, + required: isRequired, + default: defaultValue, + deprecated: !!deprecated, + description: description.replace(//g, " ") + ` ${additionalEntries.join(" ")}`, + }); + }); + } + + return envVariables; +}; + +// Generate Markdown table +const generateMarkdown = (envVariables: EnvVariable[]) => { + const header = + "| Env Variable name | Required | Default Value | Description |\n|------------------|----------------------|---------------|-------------|\n"; + const rows = envVariables + .map( + (varInfo: EnvVariable) => + `| **${varInfo.name}**${varInfo.deprecated ? " `deprecated`" : ""} | ${ + varInfo.required + } | ${varInfo.default} | ${varInfo.description} |`, + ) + .join("\n"); + + return header + rows; +}; + +export const updateMarkdownFile = (envVarsSchema) => { + const schema = extractSchemaInfo(envVarsSchema); + const mdTable = generateMarkdown(schema); + + writeFileSync("../../environment-variables2.md", mdTable, "utf-8"); +}; + diff --git a/scripts/common/envVarsGenerator/tsconfig.json b/scripts/common/envVarsGenerator/tsconfig.json new file mode 100644 index 000000000..2ab225ca6 --- /dev/null +++ b/scripts/common/envVarsGenerator/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2017", + "module": "commonjs", + "outDir": "dist", + "sourceMap": true, + "forceConsistentCasingInFileNames": true, + "strictNullChecks": true, + "strict": false, + "noUnusedLocals": false, + "noUnusedParameters": false + }, + "include": ["src/**/*.ts"], + "exclude": [ + "node_modules", + "coverage" + ] +} From 8570adf5d166ddc55d245952b94a00975f13e55d Mon Sep 17 00:00:00 2001 From: Peter Baus Date: Tue, 24 Sep 2024 11:53:52 +0200 Subject: [PATCH 11/21] api:Script for generating variables docs --- api/environment-variables.md | 87 +++++----- api/environment-variables2.md | 43 ----- api/package-lock.json | 18 ++ api/package.json | 1 + api/src/envVarsSchema.ts | 154 +++++++++++++++--- api/src/scripts/envVarsDocsGenerator.ts | 90 ++-------- scripts/common/envVarsGenerator/dist/index.js | 50 ++++-- .../common/envVarsGenerator/dist/index.js.map | 2 +- scripts/common/envVarsGenerator/src/index.ts | 58 +++++-- 9 files changed, 286 insertions(+), 217 deletions(-) delete mode 100644 api/environment-variables2.md diff --git a/api/environment-variables.md b/api/environment-variables.md index 3cf215ab8..6114e3479 100644 --- a/api/environment-variables.md +++ b/api/environment-variables.md @@ -1,45 +1,48 @@ # TruBudget-API -## Environment Variables +## Environment variables -| Env Variable | Required | Default Value | Description | -| ------------------------------------- | ---------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **LOG_LEVEL** | no | info | Defines the log output. Supported levels are `trace`, `debug`, `info`, `warn`, `error`, `fatal` | -| ORGANIZATION | yes | - | In the blockchain network, each node is represented by its organization name. This environment variable sets this organization name. It is used to create the organization stream on the blockchain and is also displayed in the frontend's top right corner. | -| ORGANIZATION_VAULT_SECRET | yes | - | This is the key to en-/decrypt user data of an organization. If you want to add a new node for your organization, you want users to be able to log in on either node.
**Caution:** If you want to run TruBudget in production, make sure NOT to use the default value from the `.env_example` file! | -| PORT | no | 8080 | The port used to expose the API for your installation.
Example: If you run TruBudget locally and set API_PORT to `8080`, you can reach the API via `localhost:8080/api`. | -| PRETTY_PRINT | no | false | Decides whether the logs printed by the API are pretty printed or not. Pretty printed logs are easier to read while non-pretty printed logs are easier to store and use e.g. in the ELK (Elasticsearch-Logstash-Kabana) stack. | -| ROOT_SECRET | no | [random] | The root secret is the password for the root user. If you start with an empty blockchain, the root user is needed to add other users, approve new nodes,.. If you don't set a value via the environment variable, the API generates one randomly and prints it to the console
**Caution:** If you want to run TruBudget in production, make sure to set a secure root secret. | -| MULTICHAIN_RPC_HOST | no | localhost | The IP address of the blockchain (not multichain daemon,but they are usally the same) you want to connect to. | -| BLOCKCHAIN_PORT | no | 8085 | The Port of the blockchain where the server is available for incoming http connections (e.g. readiness, versions, backup and restore) | -| MULTICHAIN_RPC_USER | no | multichainrpc | The user used to connect to the multichain daemon. | -| MULTICHAIN_RPC_PASSWORD | no | [hardcoded] | Password used by the API to connect to the blockchain. The password is set by the origin node upon start. Every beta node needs to use the same RPC password in order to be able to connect to the blockchain.
**Hint:** Although the MULTICHAIN_RPC_PASSWORD is not required it is highly recommended to set an own secure one | -| MULTICHAIN_RPC_PORT | no | 8000 | The port used to expose the multichain daemon of your Trubudget blockchain installation(bc). The port used to connect to the multichain daemon(api). This will be used internally for the communication between the API and the multichain daemon. | -| SWAGGER_BASEPATH `deprecated` | no | / | This variable was used to choose which environment (prod or test) is used for testing the requests. The variable is deprecated now, as the Swagger documentation can be used for the prod and test environment separately. | -| JWT_ALGORITHM | no | `HS256` | Algorithm used for signing and verifying JWTs. Currently `HS256` or `RS256` are supported. | -| JWT_SECRET | no | [random] | A string that is used to sign JWT which are created by the authenticate endpoint of the api. If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded private key for RSA. | -| JWT_PUBLIC_KEY | no | | If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded public key for RSA. | -| DOCUMENT_FEATURE_ENABLED | no | false | If true, all uploaded documents are stored using trubudget's storage-service. If false, the document feature of TruBudget is disabled, and trying to upload a document will result in an error. | -| DOCUMENT_EXTERNAL_LINKS_ENABLED | no | false | If true, it is possible to use external documents links also without TruBudget's storage-service. If false, the external documents links feature of TruBudget is still possible to use in case DOCUMENT_FEATURE_ENABLED equals "true". | -| STORAGE_SERVICE_HOST | no | localhost | IP of connected storage service | -| STORAGE_SERVICE_PORT | no | 8090 | Port of connected storage service | -| STORAGE_SERVICE_EXTERNAL_URL | no | - | IP and port of own connected storage service accessible externally | -| ENCRYPTION_PASSWORD | no | - | If set, all data that is send to the MultiChain node and external storage will be symmetrically encrypted by the ENCRYPTION_PASSWORD | -| SIGNING_METHOD | no | node | Possible signing methods are: `node` and `user`. Transactions on the chain will be signed using either the address of the node or the address of the specific user publishing that transaction. | -| NODE_ENV | no | production | If set to `development` api will allow any string as password. If set to `production` passwords must satisfy safePasswordSchema, see lib/joiValidation-.ts & -.spec.ts files | -| ACCESS_CONTROL_ALLOW_ORIGIN | no | "\*" | Since the service uses CORS, the domain by which it can be called needs to be set. Setting this value to `"*"` means that it can be called from any domain. Read more about this topic [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). | -| RATE_LIMIT | no | - | If set, API will limit the number of requests from any individual IP address to the set number per minute. Can be set to any `number`, but shouldn't be set too low. | -| AUTHPROXY_ENABLED | no | false | Enables logging in using the authorization token from authentication proxy | -| AUTHPROXY_JWS_SIGNATURE | no / yes if AUTHPROXY_ENABLED=true | - | secret/public key/certificate for verifying auth proxy token signature | -| APPLICATIONINSIGHTS_CONNECTION_STRING | no | - | Azure Application Insights Connection String | -| SILENCE_LOGGING_ON_FREQUENT_ROUTES | no | - | Set to "true" if you want to hide route logging on frequent and technical endpoints like `/readiness`, `/version`, etc. | -| REFRESH_TOKEN_STORAGE | no | - | Determining the type of storage for refresh tokens. Allowed values are "db" or "memory" | -| API_DB_USER | yes (if REFRESH_TOKEN_STORAGE=db) | - | Database user for database connection, e.g. postgres | -| API_DB_PASSWORD | yes (if REFRESH_TOKEN_STORAGE=db) | - | Database password for database connection | -| API_DB_TYPE | yes (if REFRESH_TOKEN_STORAGE=db) | - | Database type. Allowed values: "pg" | -| API_DB_HOST | yes (if REFRESH_TOKEN_STORAGE=db) | - | Database host | -| API_DB_DATABASE | yes (if REFRESH_TOKEN_STORAGE=db) | - | Name of the used database | -| API_DB_PORT | yes (if REFRESH_TOKEN_STORAGE=db) | - | Database port, e.g. 5432 | -| API_DB_SSL | yes (if REFRESH_TOKEN_STORAGE=db) | - | Database SSL connection. Allowed values: "true" or "false". | -| API_DB_SCHEMA | yes (if REFRESH_TOKEN_STORAGE=db) | - | Database schema, e.g. "public" | -| API_REFRESH_TOKENS_TABLE | yes (if REFRESH_TOKEN_STORAGE=db) | - | Name of table where refresh tokens will be stored, e.g. "refresh_token" | +| Env Variable name | Required | Default Value | Description | +| ----------------------------------------- | -------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **LOG_LEVEL** | no | info | Defines the log output. Allowed values: trace, debug, info, warn, error, fatal. | +| **ORGANIZATION** | yes | - | In the blockchain network, each node is represented by its organization name. This environment variable sets this organization name. It is used to create the organization stream on the blockchain and is also displayed in the frontend's top right corner. Minimal value: 1. Maximal value: 100. | +| **PORT** | no | 8091 | The port used to expose the API for your installation. Example: If you run TruBudget locally and set API_PORT to `8080`, you can reach the API via `localhost:8080/api`. Minimal value: 0. Maximal value: 65535. | +| **ORGANIZATION_VAULT_SECRET** | yes | - | This is the key to en-/decrypt user data of an organization. If you want to add a new node for your organization, you want users to be able to log in on either node. **Caution:** If you want to run TruBudget in production, make sure NOT to use the default value from the `.env_example` file! Invalid values: secret. | +| **ROOT_SECRET** | yes | 8f15b15fd8b18da27870b6776fa88b45de8377cc2485b55731984d250f3143c2 | The root secret is the password for the root user. If you start with an empty blockchain, the root user is needed to add other users, approve new nodes,.. If you don't set a value via the environment variable, the API generates one randomly and prints it to the console **Caution:** If you want to run TruBudget in production, make sure to set a secure root secret. Minimal value: 8. | +| **MULTICHAIN_RPC_HOST** | no | localhost | The IP address of the blockchain (not multichain daemon,but they are usally the same) you want to connect to. | +| **MULTICHAIN_RPC_PORT** | no | 8000 | The Port of the blockchain where the server is available for incoming http connections (e.g. readiness, versions, backup and restore) | +| **MULTICHAIN_RPC_USER** | no | multichainrpc | The user used to connect to the multichain daemon. | +| **MULTICHAIN_RPC_PASSWORD** | no | s750SiJnj50yIrmwxPnEdSzpfGlTAHzhaUwgqKeb0G1j | Password used by the API to connect to the blockchain. The password is set by the origin node upon start. Every beta node needs to use the same RPC password in order to be able to connect to the blockchain. **Hint:** Although the MULTICHAIN_RPC_PASSWORD is not required it is highly recommended to set an own secure one. | +| **BLOCKCHAIN_PORT** | no | 8085 | The port used to expose the multichain daemon of your Trubudget blockchain installation(bc). The port used to connect to the multichain daemon(api). This will be used internally for the communication between the API and the multichain daemon. | +| **SWAGGER_BASEPATH** `deprecated` | no | - | deprecated This variable was used to choose which environment (prod or test) is used for testing the requests. The variable is deprecated now, as the Swagger documentation can be used for the prod and test environment separately. Example values: /. | +| **JWT_ALGORITHM** | no | HS256 | Algorithm used for signing and verifying JWTs. Allowed values: HS256, RS256. | +| **JWT_SECRET** | yes (if JWT_ALGORITHM=RS256) | 5cbe27d6635c09a3a3a2678c4721f570914177e32bcb4a55c7f57786823f5807 | A string that is used to sign JWT which are created by the authenticate endpoint of the api. If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded private key for RSA. Minimal value: 32. | +| **JWT_PUBLIC_KEY** | yes (if JWT_ALGORITHM=RS256) | - | If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded public key for RSA. | +| **DOCUMENT_FEATURE_ENABLED** | no | - | If true, all uploaded documents are stored using trubudget's storage-service. If false, the document feature of TruBudget is disabled, and trying to upload a document will result in an error. | +| **DOCUMENT_EXTERNAL_LINKS_ENABLED** | no | - | If true, it is possible to use external documents links also without TruBudget's storage-service. If false, the external documents links feature of TruBudget is still possible to use in case DOCUMENT_FEATURE_ENABLED equals "true". | +| **STORAGE_SERVICE_HOST** | no | localhost | IP of connected storage service | +| **STORAGE_SERVICE_PORT** | no | 8090 | Port of connected storage service | +| **STORAGE_SERVICE_EXTERNAL_URL** | yes (if DOCUMENT_FEATURE_ENABLED=true) | - | IP and port of own connected storage service accessible externally | +| **EMAIL_HOST** | no | localhost | - | +| **EMAIL_PORT** | no | 8089 | - | +| **ACCESS_CONTROL_ALLOW_ORIGIN** | no | * | Since the service uses CORS, the domain by which it can be called needs to be set. Setting this value to `*` means that it can be called from any domain. Read more about this topic [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). | +| **NODE_ENV** | no | production | If set to `development` api will allow any string as password. If set to `production` passwords must satisfy safePasswordSchema, see lib/joiValidation-.ts & -.spec.ts files Allowed values: production, development, testing. | +| **ENCRYPTION_PASSWORD** | no | - | If set, all data that is send to the MultiChain node and external storage will be symmetrically encrypted by the ENCRYPTION_PASSWORD | +| **SIGNING_METHOD** | no | node | Possible signing methods are: `node` and `user`. Transactions on the chain will be signed using either the address of the node or the address of the specific user publishing that transaction. Allowed values: node, user. | +| **RATE_LIMIT** | no | - | If set, API will limit the number of requests from any individual IP address to the set number per minute. Can be set to any `number`, but shouldn't be set too low. Allowed values: . | +| **AUTHPROXY_ENABLED** | no | - | Enables logging in using the authorization token from authentication proxy | +| **AUTHPROXY_JWS_SIGNATURE** | yes (if AUTHPROXY_ENABLED=true) | - | secret/public key/certificate for verifying auth proxy token signature | +| **DB_TYPE** | no | pg | - | +| **SQL_DEBUG** | no | - | - | +| **REFRESH_TOKEN_STORAGE** | no | - | Determining the type of storage for refresh tokens. Allowed values are "db" or "memory" or blank to disable refresh token functionality. Allowed values: db, memory, . | +| **API_DB_USER** | yes (if REFRESH_TOKEN_STORAGE=db) | postgres | Database user for database connection, e.g. postgres | +| **API_DB_PASSWORD** | yes (if REFRESH_TOKEN_STORAGE=db) | test | Database password for database connection | +| **API_DB_HOST** | yes (if REFRESH_TOKEN_STORAGE=db) | localhost | Database host | +| **API_DB_NAME** | yes (if REFRESH_TOKEN_STORAGE=db) | trubudget_email_service | Name of the used database Example values: trubudget-db. | +| **API_DB_PORT** | yes (if REFRESH_TOKEN_STORAGE=db) | 5432 | Database port, e.g. 5432 | +| **API_DB_SSL** | yes (if REFRESH_TOKEN_STORAGE=db) | - | Database SSL connection. Allowed values: "true" or "false". | +| **API_DB_SCHEMA** | yes (if REFRESH_TOKEN_STORAGE=db) | public | Database schema, e.g. "public". | +| **API_REFRESH_TOKENS_TABLE** | yes (if REFRESH_TOKEN_STORAGE=db) | refresh_token | Name of table where refresh tokens will be stored, e.g. "refresh_token". | +| **SNAPSHOT_EVENT_INTERVAL** | no | 3 | - | +| **SILENCE_LOGGING_ON_FREQUENT_ROUTES** | no | - | Set to "true" if you want to hide route logging on frequent and technical endpoints like `/readiness`, `/version`, etc. | +| **APPLICATIONINSIGHTS_CONNECTION_STRING** | no | - | Azure Application Insights Connection String Allowed values: . | \ No newline at end of file diff --git a/api/environment-variables2.md b/api/environment-variables2.md deleted file mode 100644 index 392119805..000000000 --- a/api/environment-variables2.md +++ /dev/null @@ -1,43 +0,0 @@ -| Env Variable name | Required | Default Value | Description | -|------------------|----------------------|---------------|-------------| -| ORGANIZATION | yes | - | In the blockchain network, each node is represented by its organization name. This environment variable sets this organization name. It is used to create the organization stream on the blockchain and is also displayed in the frontend's top right corner. | -| PORT | no | 8091 | The port used to expose the API for your installation. Example: If you run TruBudget locally and set API_PORT to `8080`, you can reach the API via `localhost:8080/api`. | -| ORGANIZATION_VAULT_SECRET | yes | - | This is the key to en-/decrypt user data of an organization. If you want to add a new node for your organization, you want users to be able to log in on either node. **Caution:** If you want to run TruBudget in production, make sure NOT to use the default value from the `.env_example` file! Invalid values: secret. | -| ROOT_SECRET | yes | aaba5fcbe677fc0d952c29d8928d283601a6e26ae84149185feb45fd81785f63 | The root secret is the password for the root user. If you start with an empty blockchain, the root user is needed to add other users, approve new nodes,.. If you don't set a value via the environment variable, the API generates one randomly and prints it to the console **Caution:** If you want to run TruBudget in production, make sure to set a secure root secret. | -| MULTICHAIN_RPC_HOST | no | localhost | The IP address of the blockchain (not multichain daemon,but they are usally the same) you want to connect to. | -| MULTICHAIN_RPC_PORT | no | 8000 | The Port of the blockchain where the server is available for incoming http connections (e.g. readiness, versions, backup and restore) | -| MULTICHAIN_RPC_USER | no | multichainrpc | The user used to connect to the multichain daemon. | -| MULTICHAIN_RPC_PASSWORD | no | s750SiJnj50yIrmwxPnEdSzpfGlTAHzhaUwgqKeb0G1j | Password used by the API to connect to the blockchain. The password is set by the origin node upon start. Every beta node needs to use the same RPC password in order to be able to connect to the blockchain. **Hint:** Although the MULTICHAIN_RPC_PASSWORD is not required it is highly recommended to set an own secure one. | -| BLOCKCHAIN_PORT | no | 8085 | The port used to expose the multichain daemon of your Trubudget blockchain installation(bc). The port used to connect to the multichain daemon(api). This will be used internally for the communication between the API and the multichain daemon. | -| SWAGGER_BASEPATH`deprecated` | no | - | deprecated This variable was used to choose which environment (prod or test) is used for testing the requests. The variable is deprecated now, as the Swagger documentation can be used for the prod and test environment separately. Example values: /. | -| JWT_ALGORITHM | no | HS256 | Algorithm used for signing and verifying JWTs. Allowed values: HS256, RS256. | -| JWT_SECRET | no | 59cc214d8a8128f72f8f130f71bd4a2c5f6c223ae9ea8b9484055150d1807fbd | A string that is used to sign JWT which are created by the authenticate endpoint of the api. If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded private key for RSA. | -| JWT_PUBLIC_KEY | no | - | If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded public key for RSA. | -| DOCUMENT_FEATURE_ENABLED | no | - | - | -| DOCUMENT_EXTERNAL_LINKS_ENABLED | no | - | - | -| STORAGE_SERVICE_HOST | no | localhost | - | -| STORAGE_SERVICE_PORT | no | 8090 | - | -| STORAGE_SERVICE_EXTERNAL_URL | no | - | - | -| EMAIL_HOST | no | localhost | - | -| EMAIL_PORT | no | 8089 | - | -| ACCESS_CONTROL_ALLOW_ORIGIN | no | * | - | -| NODE_ENV | no | production | - | -| ENCRYPTION_PASSWORD | no | - | - | -| SIGNING_METHOD | no | node | - | -| RATE_LIMIT | no | - | - Allowed values: . | -| AUTHPROXY_ENABLED | no | - | - | -| AUTHPROXY_JWS_SIGNATURE | no | - | - | -| DB_TYPE | no | pg | - | -| SQL_DEBUG | no | - | - | -| API_DB_USER | no | postgres | - | -| API_DB_PASSWORD | no | test | - | -| API_DB_HOST | no | localhost | - | -| API_DB_NAME | no | trubudget_email_service | - | -| API_DB_PORT | no | 5432 | - | -| API_DB_SSL | no | - | - | -| API_DB_SCHEMA | no | public | - | -| API_REFRESH_TOKENS_TABLE | no | refresh_token | - | -| REFRESH_TOKEN_STORAGE | no | - | - Allowed values: db, memory. | -| SNAPSHOT_EVENT_INTERVAL | no | 3 | - | -| SILENCE_LOGGING_ON_FREQUENT_ROUTES | no | - | - | -| APPLICATIONINSIGHTS_CONNECTION_STRING | no | - | - Allowed values: . | diff --git a/api/package-lock.json b/api/package-lock.json index 04e121061..0c9f5ffdb 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -61,6 +61,7 @@ "chai": "^4.3.7", "colors": "^1.4.0", "diff": "^5.1.0", + "env-vars-generator": "file:../scripts/common/envVarsGenerator", "eslint": "^8.40.0", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-prettier": "^8.8.0", @@ -84,6 +85,19 @@ "typescript": "^5.0.4" } }, + "../scripts/common/envVarsGenerator": { + "name": "envvarsgenerator", + "version": "1.0.0", + "dev": true, + "dependencies": { + "joi": "^17.13.3" + }, + "devDependencies": { + "diff": "^7.0.0", + "rimraf": "^6.0.1", + "typescript": "^5.6.2" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -3810,6 +3824,10 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-vars-generator": { + "resolved": "../scripts/common/envVarsGenerator", + "link": true + }, "node_modules/es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", diff --git a/api/package.json b/api/package.json index d64fa6f8c..cee055b09 100644 --- a/api/package.json +++ b/api/package.json @@ -125,6 +125,7 @@ "chai": "^4.3.7", "colors": "^1.4.0", "diff": "^5.1.0", + "env-vars-generator": "file:../scripts/common/envVarsGenerator", "eslint": "^8.40.0", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-prettier": "^8.8.0", diff --git a/api/src/envVarsSchema.ts b/api/src/envVarsSchema.ts index ef34f265a..869b48525 100644 --- a/api/src/envVarsSchema.ts +++ b/api/src/envVarsSchema.ts @@ -2,6 +2,10 @@ import * as Joi from "joi"; import { randomString } from "./service/hash"; export const envVarsSchema = Joi.object({ + LOG_LEVEL: Joi.string() + .default("info") + .allow("trace", "debug", "info", "warn", "error", "fatal") + .note("Defines the log output."), ORGANIZATION: Joi.string() .min(1) .max(100) @@ -82,37 +86,135 @@ export const envVarsSchema = Joi.object({ .note( "If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded public key for RSA.", ), - DOCUMENT_FEATURE_ENABLED: Joi.boolean().default(false), - DOCUMENT_EXTERNAL_LINKS_ENABLED: Joi.boolean().default(false), - STORAGE_SERVICE_HOST: Joi.string().default("localhost"), - STORAGE_SERVICE_PORT: Joi.number().default(8090), - STORAGE_SERVICE_EXTERNAL_URL: Joi.string().default("").when("DOCUMENT_FEATURE_ENABLED", { - is: true, - then: Joi.required(), - }), + DOCUMENT_FEATURE_ENABLED: Joi.boolean() + .default(false) + .note( + "If true, all uploaded documents are stored using trubudget's storage-service. If false, the document feature of TruBudget is disabled, and trying to upload a document will result in an error.", + ), + DOCUMENT_EXTERNAL_LINKS_ENABLED: Joi.boolean() + .default(false) + .note( + 'If true, it is possible to use external documents links also without TruBudget\'s storage-service. If false, the external documents links feature of TruBudget is still possible to use in case DOCUMENT_FEATURE_ENABLED equals "true".', + ), + STORAGE_SERVICE_HOST: Joi.string().default("localhost").note("IP of connected storage service"), + STORAGE_SERVICE_PORT: Joi.number().default(8090).note("Port of connected storage service"), + STORAGE_SERVICE_EXTERNAL_URL: Joi.string() + .default("") + .when("DOCUMENT_FEATURE_ENABLED", { + is: true, + then: Joi.required(), + }) + .note("IP and port of own connected storage service accessible externally"), EMAIL_HOST: Joi.string().default("localhost"), EMAIL_PORT: Joi.number().default(8089), - ACCESS_CONTROL_ALLOW_ORIGIN: Joi.string().default("*"), - NODE_ENV: Joi.string().default("production"), - ENCRYPTION_PASSWORD: Joi.string(), - SIGNING_METHOD: Joi.string().default("node"), - RATE_LIMIT: Joi.number().allow("").empty(""), - AUTHPROXY_ENABLED: Joi.boolean().default(false), - AUTHPROXY_JWS_SIGNATURE: Joi.string(), + ACCESS_CONTROL_ALLOW_ORIGIN: Joi.string() + .default("*") + .note( + "Since the service uses CORS, the domain by which it can be called needs to be set. Setting this value to `*` means that it can be called from any domain. Read more about this topic [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS).", + ), + NODE_ENV: Joi.string() + .default("production") + .allow("production", "development", "testing") + .note( + "If set to `development` api will allow any string as password. If set to `production` passwords must satisfy safePasswordSchema, see lib/joiValidation-.ts & -.spec.ts files", + ), + ENCRYPTION_PASSWORD: Joi.string().note( + "If set, all data that is send to the MultiChain node and external storage will be symmetrically encrypted by the ENCRYPTION_PASSWORD", + ), + SIGNING_METHOD: Joi.string() + .default("node") + .allow("node", "user") + .note( + "Possible signing methods are: `node` and `user`. Transactions on the chain will be signed using either the address of the node or the address of the specific user publishing that transaction.", + ), + RATE_LIMIT: Joi.number() + .allow("") + .empty("") + .note( + "If set, API will limit the number of requests from any individual IP address to the set number per minute. Can be set to any `number`, but shouldn't be set too low.", + ), + AUTHPROXY_ENABLED: Joi.boolean() + .default(false) + .note("Enables logging in using the authorization token from authentication proxy"), + AUTHPROXY_JWS_SIGNATURE: Joi.string() + .when("AUTHPROXY_ENABLED", { + is: true, + then: Joi.required(), + }) + .note("secret/public key/certificate for verifying auth proxy token signature"), DB_TYPE: Joi.string().default("pg"), SQL_DEBUG: Joi.boolean().default(false), - API_DB_USER: Joi.string().default("postgres"), - API_DB_PASSWORD: Joi.string().default("test"), - API_DB_HOST: Joi.string().default("localhost"), - API_DB_NAME: Joi.string().default("trubudget_email_service"), - API_DB_PORT: Joi.number().default(5432), - API_DB_SSL: Joi.boolean().default(false), - API_DB_SCHEMA: Joi.string().default("public"), - API_REFRESH_TOKENS_TABLE: Joi.string().default("refresh_token"), - REFRESH_TOKEN_STORAGE: Joi.string().allow("db", "memory"), + API_DB_USER: Joi.string() + .default("postgres") + .when("REFRESH_TOKEN_STORAGE", { + is: "db", + then: Joi.required(), + }) + .note("Database user for database connection, e.g. postgres"), + API_DB_PASSWORD: Joi.string() + .when("REFRESH_TOKEN_STORAGE", { + is: "db", + then: Joi.required(), + }) + .default("test") + .note("Database password for database connection"), + API_DB_HOST: Joi.string() + .when("REFRESH_TOKEN_STORAGE", { + is: "db", + then: Joi.required(), + }) + .default("localhost") + .note("Database host"), + API_DB_NAME: Joi.string() + .when("REFRESH_TOKEN_STORAGE", { + is: "db", + then: Joi.required(), + }) + .default("trubudget_email_service") + .example("trubudget-db") + .note("Name of the used database"), + API_DB_PORT: Joi.number() + .when("REFRESH_TOKEN_STORAGE", { + is: "db", + then: Joi.required(), + }) + .default(5432) + .note("Database port, e.g. 5432"), + API_DB_SSL: Joi.boolean() + .when("REFRESH_TOKEN_STORAGE", { + is: "db", + then: Joi.required(), + }) + .default(false) + .note('Database SSL connection. Allowed values: "true" or "false".'), + API_DB_SCHEMA: Joi.string() + .when("REFRESH_TOKEN_STORAGE", { + is: "db", + then: Joi.required(), + }) + .default("public") + .note('Database schema, e.g. "public".'), + API_REFRESH_TOKENS_TABLE: Joi.string() + .default("refresh_token") + .when("REFRESH_TOKEN_STORAGE", { + is: "db", + then: Joi.required(), + }) + .note('Name of table where refresh tokens will be stored, e.g. "refresh_token".'), + REFRESH_TOKEN_STORAGE: Joi.string() + .allow("db", "memory", "") + .note( + 'Determining the type of storage for refresh tokens. Allowed values are "db" or "memory" or blank to disable refresh token functionality.', + ), SNAPSHOT_EVENT_INTERVAL: Joi.number().default(3), - SILENCE_LOGGING_ON_FREQUENT_ROUTES: Joi.boolean().default(false), - APPLICATIONINSIGHTS_CONNECTION_STRING: Joi.string().allow(""), + SILENCE_LOGGING_ON_FREQUENT_ROUTES: Joi.boolean() + .default(false) + .note( + 'Set to "true" if you want to hide route logging on frequent and technical endpoints like `/readiness`, `/version`, etc.', + ), + APPLICATIONINSIGHTS_CONNECTION_STRING: Joi.string() + .allow("") + .note("Azure Application Insights Connection String"), }) .unknown() .required(); diff --git a/api/src/scripts/envVarsDocsGenerator.ts b/api/src/scripts/envVarsDocsGenerator.ts index 5b925437a..7d0a271b8 100644 --- a/api/src/scripts/envVarsDocsGenerator.ts +++ b/api/src/scripts/envVarsDocsGenerator.ts @@ -1,87 +1,17 @@ -import Joi from "joi"; -import { envVarsSchema } from "../envVarsSchema"; import { writeFileSync } from "fs"; +import { generateMarkdownFile } from "env-vars-generator/dist"; +import { envVarsSchema } from "../envVarsSchema"; -interface EnvVariable { - name: string; - required: string; - default: string; - deprecated: boolean; - description: string; -} - -// Helper function to extract Joi schema information -export const extractSchemaInfo = (schema: Joi.ObjectSchema) => { - const envVariables: EnvVariable[] = []; - - const schemaDescribe = schema.describe(); - // Iterate over schema keys - if (schemaDescribe.keys) { - Object.keys(schemaDescribe.keys).forEach((key) => { - const item = schemaDescribe.keys[key]; - - const isRequired = item.flags && item.flags.presence === "required" ? "yes" : "no"; - const defaultValue = item.flags && item.flags.default ? item.flags.default : "-"; - const description = item.notes && item.notes.length ? item.notes.join(" ") : "-"; - const additionalEntries: string[] = []; - - const min = item.rules && item.rules.find((rule) => rule.name === "min"); - const max = item.rules && item.rules.find((rule) => rule.name === "max"); - const invalid = item.invalid; - const deprecated = item.notes && item.notes.find((note) => note === "deprecated"); - const examples = item.examples; - const valid = item.allow; - - if (min) { - additionalEntries.push(`Minimal value: ${min?.args?.limit}.`); - } - if (max) { - additionalEntries.push(`Maximal value: ${max?.args?.limit}.`); - } - if (invalid) { - additionalEntries.push(`Invalid values: ${invalid.join(", ")}.`); - } - if (examples) { - additionalEntries.push(`Example values: ${examples.join(", ")}.`); - } - if (valid) { - additionalEntries.push(`Allowed values: ${valid.join(", ")}.`); - } - - envVariables.push({ - name: key, - required: isRequired, - default: defaultValue, - deprecated: !!deprecated, - description: description.replace(//g, " ") + ` ${additionalEntries.join(" ")}`, - }); - }); - } - - return envVariables; -}; +function updateReadme(): void { + const mdTable = generateMarkdownFile(envVarsSchema); -// Generate Markdown table -const generateMarkdown = (envVariables: EnvVariable[]) => { - const header = - "| Env Variable name | Required | Default Value | Description |\n|------------------|----------------------|---------------|-------------|\n"; - const rows = envVariables - .map( - (varInfo: EnvVariable) => - `| **${varInfo.name}**${varInfo.deprecated ? " `deprecated`" : ""} | ${ - varInfo.required - } | ${varInfo.default} | ${varInfo.description} |`, - ) - .join("\n"); + const md = `# TruBudget-API - return header + rows; -}; +## Environment variables -const updateMarkdownFile = () => { - const schema = extractSchemaInfo(envVarsSchema); - const mdTable = generateMarkdown(schema); +${mdTable}`; - writeFileSync("../../environment-variables2.md", mdTable, "utf-8"); -}; + writeFileSync("./environment-variables.md", md, "utf-8"); +} -updateMarkdownFile(); +updateReadme(); diff --git a/scripts/common/envVarsGenerator/dist/index.js b/scripts/common/envVarsGenerator/dist/index.js index df49d9b0e..e53d2c6c2 100644 --- a/scripts/common/envVarsGenerator/dist/index.js +++ b/scripts/common/envVarsGenerator/dist/index.js @@ -1,7 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.updateMarkdownFile = exports.extractSchemaInfo = void 0; -const fs_1 = require("fs"); +exports.generateMarkdownFile = exports.extractSchemaInfo = void 0; // Helper function to extract Joi schema information const extractSchemaInfo = (schema) => { const envVariables = []; @@ -11,7 +10,7 @@ const extractSchemaInfo = (schema) => { Object.keys(schemaDescribe.keys).forEach((key) => { var _a, _b; const item = schemaDescribe.keys[key]; - const isRequired = item.flags && item.flags.presence === "required" ? "yes" : "no"; + let isRequired = item.flags && item.flags.presence === "required" ? "yes" : "no"; const defaultValue = item.flags && item.flags.default ? item.flags.default : "-"; const description = item.notes && item.notes.length ? item.notes.join(" ") : "-"; const additionalEntries = []; @@ -21,6 +20,16 @@ const extractSchemaInfo = (schema) => { const deprecated = item.notes && item.notes.find((note) => note === "deprecated"); const examples = item.examples; const valid = item.allow; + if (item.whens) { + item.whens.forEach(when => { + var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; + // conditional required + if (isRequired !== "yes" && ((_b = (_a = when.then) === null || _a === void 0 ? void 0 : _a.flags) === null || _b === void 0 ? void 0 : _b.presence) === "required") { + const relationSign = ((_d = (_c = when.is) === null || _c === void 0 ? void 0 : _c.flags) === null || _d === void 0 ? void 0 : _d.only) === true && ((_f = (_e = when.is) === null || _e === void 0 ? void 0 : _e.flags) === null || _f === void 0 ? void 0 : _f.presence) === "required" ? "=" : " "; + isRequired = `yes (if ${(_h = (_g = when.ref) === null || _g === void 0 ? void 0 : _g.path) === null || _h === void 0 ? void 0 : _h[0]}${relationSign}${(_k = (_j = when.is) === null || _j === void 0 ? void 0 : _j.allow) === null || _k === void 0 ? void 0 : _k[1]})`; + } + }); + } if (min) { additionalEntries.push(`Minimal value: ${(_a = min === null || min === void 0 ? void 0 : min.args) === null || _a === void 0 ? void 0 : _a.limit}.`); } @@ -50,16 +59,35 @@ const extractSchemaInfo = (schema) => { exports.extractSchemaInfo = extractSchemaInfo; // Generate Markdown table const generateMarkdown = (envVariables) => { - const header = "| Env Variable name | Required | Default Value | Description |\n|------------------|----------------------|---------------|-------------|\n"; - const rows = envVariables - .map((varInfo) => `| **${varInfo.name}**${varInfo.deprecated ? " `deprecated`" : ""} | ${varInfo.required} | ${varInfo.default} | ${varInfo.description} |`) - .join("\n"); - return header + rows; + const table = []; + table.push(["Env Variable name", "Required", "Default Value", "Description"]); + table.push(["---", "---", "---", "---"]); + envVariables + .forEach((varInfo) => { + table.push([`**${varInfo.name}**${varInfo.deprecated ? " `deprecated`" : ""}`, `${varInfo.required}`, `${varInfo.default}`, `${varInfo.description}`]); + }); + // get max column length for each column + const tableColumnsLength = []; + table.forEach((row) => row.forEach((column, columnIndex) => { + if (!tableColumnsLength[columnIndex] || tableColumnsLength[columnIndex] < column.length) { + tableColumnsLength[columnIndex] = column.length; + } + })); + const finalMarkdownTable = table.map(row => { + const finalMarkdownRow = row.map((column, columnIndex) => { + if (column === "---") { + return "-".repeat(tableColumnsLength[columnIndex]); + } + return column + " ".repeat(tableColumnsLength[columnIndex] - column.length); + }); + return `| ${finalMarkdownRow.join(" | ")} |`; + }); + return finalMarkdownTable.join("\n"); }; -const updateMarkdownFile = (envVarsSchema) => { +const generateMarkdownFile = (envVarsSchema) => { const schema = (0, exports.extractSchemaInfo)(envVarsSchema); const mdTable = generateMarkdown(schema); - (0, fs_1.writeFileSync)("../../environment-variables2.md", mdTable, "utf-8"); + return mdTable; }; -exports.updateMarkdownFile = updateMarkdownFile; +exports.generateMarkdownFile = generateMarkdownFile; //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/scripts/common/envVarsGenerator/dist/index.js.map b/scripts/common/envVarsGenerator/dist/index.js.map index 157bed855..123414545 100644 --- a/scripts/common/envVarsGenerator/dist/index.js.map +++ b/scripts/common/envVarsGenerator/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAUA,oDAAoD;AAC7C,MAAM,iBAAiB,GAAG,CAAC,MAAwB,EAAE,EAAE;IAC5D,MAAM,YAAY,GAAkB,EAAE,CAAC;IAEvC,MAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IACzC,2BAA2B;IAC3B,IAAI,cAAc,CAAC,IAAI,EAAE,CAAC;QACxB,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;;YAC/C,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAEtC,IAAI,UAAU,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;YAEjF,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;YACjF,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;YACjF,MAAM,iBAAiB,GAAa,EAAE,CAAC;YAEvC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC;YACzE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC;YACzE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;YAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC;YAClF,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;YAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;YAEzB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;;oBACxB,uBAAuB;oBACzB,IAAI,UAAU,KAAK,KAAK,IAAI,CAAA,MAAA,MAAA,IAAI,CAAC,IAAI,0CAAE,KAAK,0CAAE,QAAQ,MAAK,UAAU,EAAE,CAAC;wBACtE,MAAM,YAAY,GAAG,CAAA,MAAA,MAAA,IAAI,CAAC,EAAE,0CAAE,KAAK,0CAAE,IAAI,MAAK,IAAI,IAAI,CAAA,MAAA,MAAA,IAAI,CAAC,EAAE,0CAAE,KAAK,0CAAE,QAAQ,MAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;wBAC1G,UAAU,GAAG,WAAW,MAAA,MAAA,IAAI,CAAC,GAAG,0CAAE,IAAI,0CAAG,CAAC,CAAC,GAAG,YAAY,GAAG,MAAA,MAAA,IAAI,CAAC,EAAE,0CAAE,KAAK,0CAAG,CAAC,CAAC,GAAG,CAAA;oBACrF,CAAC;gBACD,CAAC,CAAC,CAAC;YAEL,CAAC;YAED,IAAI,GAAG,EAAE,CAAC;gBACR,iBAAiB,CAAC,IAAI,CAAC,kBAAkB,MAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,IAAI,0CAAE,KAAK,GAAG,CAAC,CAAC;YAChE,CAAC;YACD,IAAI,GAAG,EAAE,CAAC;gBACR,iBAAiB,CAAC,IAAI,CAAC,kBAAkB,MAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,IAAI,0CAAE,KAAK,GAAG,CAAC,CAAC;YAChE,CAAC;YACD,IAAI,OAAO,EAAE,CAAC;gBACZ,iBAAiB,CAAC,IAAI,CAAC,mBAAmB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACnE,CAAC;YACD,IAAI,QAAQ,EAAE,CAAC;gBACb,iBAAiB,CAAC,IAAI,CAAC,mBAAmB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACpE,CAAC;YACD,IAAI,KAAK,EAAE,CAAC;gBACV,iBAAiB,CAAC,IAAI,CAAC,mBAAmB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACjE,CAAC;YAED,YAAY,CAAC,IAAI,CAAC;gBAChB,IAAI,EAAE,GAAG;gBACT,QAAQ,EAAE,UAAU;gBACpB,OAAO,EAAE,YAAY;gBACrB,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,WAAW,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,GAAG,IAAI,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;aACrF,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC,CAAC;AA5DW,QAAA,iBAAiB,qBA4D5B;AAEF,0BAA0B;AAC1B,MAAM,gBAAgB,GAAG,CAAC,YAA2B,EAAE,EAAE;IACvD,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,CAAC,IAAI,CAAC,CAAC,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC,CAAC;IAC9E,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,EAAC,KAAK,EAAC,KAAK,EAAC,KAAK,CAAC,CAAC,CAAC;IACtC,YAAY;SACT,OAAO,CACN,CAAC,OAAoB,EAAE,EAAE;QACvB,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,GAC7E,OAAO,CAAC,QACV,EAAE,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;IACtD,CAAC,CAAC,CAAC;IAEP,wCAAwC;IACxC,MAAM,kBAAkB,GAAa,EAAE,CAAC;IACxC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE;QACzD,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,IAAI,kBAAkB,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;YACxF,kBAAkB,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;QAClD,CAAC;IACH,CAAC,CAAC,CAAC,CAAC;IAEJ,MAAM,kBAAkB,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QACzC,MAAM,gBAAgB,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE;YACvD,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;gBACrB,OAAO,GAAG,CAAC,MAAM,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC,CAAC;YACrD,CAAC;YACD,OAAO,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,kBAAkB,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9E,CAAC,CAAC,CAAC;QAEH,OAAO,KAAK,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,OAAO,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACvC,CAAC,CAAC;AAEK,MAAM,oBAAoB,GAAG,CAAC,aAAa,EAAE,EAAE;IACpD,MAAM,MAAM,GAAG,IAAA,yBAAiB,EAAC,aAAa,CAAC,CAAC;IAChD,MAAM,OAAO,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAEzC,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AALW,QAAA,oBAAoB,wBAK/B"} \ No newline at end of file diff --git a/scripts/common/envVarsGenerator/src/index.ts b/scripts/common/envVarsGenerator/src/index.ts index fbb5f16e0..cd6973e65 100644 --- a/scripts/common/envVarsGenerator/src/index.ts +++ b/scripts/common/envVarsGenerator/src/index.ts @@ -1,5 +1,4 @@ import * as Joi from "joi"; -import { writeFileSync } from "fs"; interface EnvVariable { name: string; @@ -19,7 +18,8 @@ export const extractSchemaInfo = (schema: Joi.ObjectSchema) => { Object.keys(schemaDescribe.keys).forEach((key) => { const item = schemaDescribe.keys[key]; - const isRequired = item.flags && item.flags.presence === "required" ? "yes" : "no"; + let isRequired = item.flags && item.flags.presence === "required" ? "yes" : "no"; + const defaultValue = item.flags && item.flags.default ? item.flags.default : "-"; const description = item.notes && item.notes.length ? item.notes.join(" ") : "-"; const additionalEntries: string[] = []; @@ -31,6 +31,17 @@ export const extractSchemaInfo = (schema: Joi.ObjectSchema) => { const examples = item.examples; const valid = item.allow; + if (item.whens) { + item.whens.forEach(when => { + // conditional required + if (isRequired !== "yes" && when.then?.flags?.presence === "required") { + const relationSign = when.is?.flags?.only === true && when.is?.flags?.presence === "required" ? "=" : " "; + isRequired = `yes (if ${when.ref?.path?.[0]}${relationSign}${when.is?.allow?.[1]})` + } + }); + + } + if (min) { additionalEntries.push(`Minimal value: ${min?.args?.limit}.`); } @@ -62,24 +73,43 @@ export const extractSchemaInfo = (schema: Joi.ObjectSchema) => { // Generate Markdown table const generateMarkdown = (envVariables: EnvVariable[]) => { - const header = - "| Env Variable name | Required | Default Value | Description |\n|------------------|----------------------|---------------|-------------|\n"; - const rows = envVariables - .map( - (varInfo: EnvVariable) => - `| **${varInfo.name}**${varInfo.deprecated ? " `deprecated`" : ""} | ${ + const table: string[][] = []; + table.push(["Env Variable name", "Required", "Default Value", "Description"]); + table.push(["---","---","---","---"]); + envVariables + .forEach( + (varInfo: EnvVariable) => { + table.push([`**${varInfo.name}**${varInfo.deprecated ? " `deprecated`" : ""}`, `${ varInfo.required - } | ${varInfo.default} | ${varInfo.description} |`, - ) - .join("\n"); + }`, `${varInfo.default}`, `${varInfo.description}`]) + }); + + // get max column length for each column + const tableColumnsLength: number[] = []; + table.forEach((row) => row.forEach((column, columnIndex) => { + if (!tableColumnsLength[columnIndex] || tableColumnsLength[columnIndex] < column.length) { + tableColumnsLength[columnIndex] = column.length; + } + })); + + const finalMarkdownTable = table.map(row => { + const finalMarkdownRow = row.map((column, columnIndex) => { + if (column === "---") { + return "-".repeat(tableColumnsLength[columnIndex]); + } + return column + " ".repeat(tableColumnsLength[columnIndex] - column.length); + }); + + return `| ${finalMarkdownRow.join(" | ")} |`; + }); - return header + rows; + return finalMarkdownTable.join("\n"); }; -export const updateMarkdownFile = (envVarsSchema) => { +export const generateMarkdownFile = (envVarsSchema) => { const schema = extractSchemaInfo(envVarsSchema); const mdTable = generateMarkdown(schema); - writeFileSync("../../environment-variables2.md", mdTable, "utf-8"); + return mdTable; }; From 0cffe59564bab3ad4866f5ecd576339b83366f64 Mon Sep 17 00:00:00 2001 From: Peter Baus Date: Tue, 24 Sep 2024 14:40:51 +0200 Subject: [PATCH 12/21] api:Add protocols to env vars --- api/src/envVarsSchema.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/api/src/envVarsSchema.ts b/api/src/envVarsSchema.ts index 869b48525..a1a7f6404 100644 --- a/api/src/envVarsSchema.ts +++ b/api/src/envVarsSchema.ts @@ -43,6 +43,12 @@ export const envVarsSchema = Joi.object({ .note( "The Port of the blockchain where the server is available for incoming http connections (e.g. readiness, versions, backup and restore)", ), + MULTICHAIN_PROTOCOL: Joi.string() + .default("http") + .allow("http", "https") + .note( + "The protocol used to expose the multichain daemon of your Trubudget blockchain installation(bc). The protocol used to connect to the multichain daemon(api). This will be used internally for the communication between the API and the multichain daemon.", + ), MULTICHAIN_RPC_USER: Joi.string() .default("multichainrpc") .note("The user used to connect to the multichain daemon."), @@ -56,6 +62,12 @@ export const envVarsSchema = Joi.object({ .note( "The port used to expose the multichain daemon of your Trubudget blockchain installation(bc). The port used to connect to the multichain daemon(api). This will be used internally for the communication between the API and the multichain daemon.", ), + BLOCKCHAIN_PROTOCOL: Joi.string() + .default("http") + .allow("http", "https") + .note( + "The Protocol of the blockchain where the server is available for incoming http connections.", + ), SWAGGER_BASEPATH: Joi.string() .example("/") .forbidden() @@ -98,6 +110,10 @@ export const envVarsSchema = Joi.object({ ), STORAGE_SERVICE_HOST: Joi.string().default("localhost").note("IP of connected storage service"), STORAGE_SERVICE_PORT: Joi.number().default(8090).note("Port of connected storage service"), + STORAGE_SERVICE_PROTOCOL: Joi.string() + .default("http") + .allow("http", "https") + .note("Protocol of connected storage service."), STORAGE_SERVICE_EXTERNAL_URL: Joi.string() .default("") .when("DOCUMENT_FEATURE_ENABLED", { @@ -107,6 +123,10 @@ export const envVarsSchema = Joi.object({ .note("IP and port of own connected storage service accessible externally"), EMAIL_HOST: Joi.string().default("localhost"), EMAIL_PORT: Joi.number().default(8089), + EMAIL_PROTOCOL: Joi.string() + .default("http") + .allow("http", "https") + .note("Protocol of connected storage service."), ACCESS_CONTROL_ALLOW_ORIGIN: Joi.string() .default("*") .note( From 8de584b5650359fa7181fc1be2bf862456f6a158 Mon Sep 17 00:00:00 2001 From: Peter Baus Date: Wed, 25 Sep 2024 07:14:52 +0200 Subject: [PATCH 13/21] api:Fix run of tests --- api/environment-variables.md | 12 ++++++++---- api/package.json | 2 +- api/src/config.ts | 2 +- api/src/envVarsSchema.ts | 3 +-- api/src/lib/joiValidation.spec.ts | 12 ++++++------ api/src/lib/joiValidation.ts | 14 ++++++++------ scripts/common/envVarsGenerator/dist/index.js | 4 ++++ scripts/common/envVarsGenerator/dist/index.js.map | 2 +- scripts/common/envVarsGenerator/src/index.ts | 4 ++++ 9 files changed, 34 insertions(+), 21 deletions(-) diff --git a/api/environment-variables.md b/api/environment-variables.md index d9eab6e29..219c14bf0 100644 --- a/api/environment-variables.md +++ b/api/environment-variables.md @@ -6,25 +6,29 @@ | ----------------------------------------- | -------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **LOG_LEVEL** | no | info | Defines the log output. Allowed values: trace, debug, info, warn, error, fatal. | | **ORGANIZATION** | yes | - | In the blockchain network, each node is represented by its organization name. This environment variable sets this organization name. It is used to create the organization stream on the blockchain and is also displayed in the frontend's top right corner. Minimal value: 1. Maximal value: 100. | -| **PORT** | no | 8091 | The port used to expose the API for your installation. Example: If you run TruBudget locally and set API_PORT to `8080`, you can reach the API via `localhost:8080/api`. Minimal value: 0. Maximal value: 65535. | +| **PORT** | no | 8091 | The port used to expose the API for your installation. Example: If you run TruBudget locally and set API_PORT to `8080`, you can reach the API via `localhost:8080/api`. Value is a port with minimal value 0 and maximal value 65535 | | **ORGANIZATION_VAULT_SECRET** | yes | - | This is the key to en-/decrypt user data of an organization. If you want to add a new node for your organization, you want users to be able to log in on either node. **Caution:** If you want to run TruBudget in production, make sure NOT to use the default value from the `.env_example` file! Invalid values: secret. | -| **ROOT_SECRET** | yes | 8f15b15fd8b18da27870b6776fa88b45de8377cc2485b55731984d250f3143c2 | The root secret is the password for the root user. If you start with an empty blockchain, the root user is needed to add other users, approve new nodes,.. If you don't set a value via the environment variable, the API generates one randomly and prints it to the console **Caution:** If you want to run TruBudget in production, make sure to set a secure root secret. Minimal value: 8. | +| **ROOT_SECRET** | yes | fe42ff0b3d2b95d091d499374f5165fda53c4df7d49c4d139aea9b1e04f99d6a | The root secret is the password for the root user. If you start with an empty blockchain, the root user is needed to add other users, approve new nodes,.. If you don't set a value via the environment variable, the API generates one randomly and prints it to the console **Caution:** If you want to run TruBudget in production, make sure to set a secure root secret. Minimal value: 8. | | **MULTICHAIN_RPC_HOST** | no | localhost | The IP address of the blockchain (not multichain daemon,but they are usally the same) you want to connect to. | | **MULTICHAIN_RPC_PORT** | no | 8000 | The Port of the blockchain where the server is available for incoming http connections (e.g. readiness, versions, backup and restore) | +| **MULTICHAIN_PROTOCOL** | no | http | The protocol used to expose the multichain daemon of your Trubudget blockchain installation(bc). The protocol used to connect to the multichain daemon(api). This will be used internally for the communication between the API and the multichain daemon. Allowed values: http, https. | | **MULTICHAIN_RPC_USER** | no | multichainrpc | The user used to connect to the multichain daemon. | | **MULTICHAIN_RPC_PASSWORD** | no | s750SiJnj50yIrmwxPnEdSzpfGlTAHzhaUwgqKeb0G1j | Password used by the API to connect to the blockchain. The password is set by the origin node upon start. Every beta node needs to use the same RPC password in order to be able to connect to the blockchain. **Hint:** Although the MULTICHAIN_RPC_PASSWORD is not required it is highly recommended to set an own secure one. | | **BLOCKCHAIN_PORT** | no | 8085 | The port used to expose the multichain daemon of your Trubudget blockchain installation(bc). The port used to connect to the multichain daemon(api). This will be used internally for the communication between the API and the multichain daemon. | +| **BLOCKCHAIN_PROTOCOL** | no | http | The Protocol of the blockchain where the server is available for incoming http connections. Allowed values: http, https. | | **SWAGGER_BASEPATH** `deprecated` | no | - | deprecated This variable was used to choose which environment (prod or test) is used for testing the requests. The variable is deprecated now, as the Swagger documentation can be used for the prod and test environment separately. Example values: /. | | **JWT_ALGORITHM** | no | HS256 | Algorithm used for signing and verifying JWTs. Allowed values: HS256, RS256. | -| **JWT_SECRET** | yes (if JWT_ALGORITHM=RS256) | 5cbe27d6635c09a3a3a2678c4721f570914177e32bcb4a55c7f57786823f5807 | A string that is used to sign JWT which are created by the authenticate endpoint of the api. If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded private key for RSA. Minimal value: 32. | +| **JWT_SECRET** | yes (if JWT_ALGORITHM=RS256) | aa16a897f182cacb44af89c7bd80a0ad74f13dd6592bdf1d31164ef51d819762 | A string that is used to sign JWT which are created by the authenticate endpoint of the api. If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded private key for RSA. Minimal value: 32. | | **JWT_PUBLIC_KEY** | yes (if JWT_ALGORITHM=RS256) | - | If JWT_ALGORITHM is set to `RS256`, this is required and holds BASE64 encoded PEM encoded public key for RSA. | | **DOCUMENT_FEATURE_ENABLED** | no | - | If true, all uploaded documents are stored using trubudget's storage-service. If false, the document feature of TruBudget is disabled, and trying to upload a document will result in an error. | | **DOCUMENT_EXTERNAL_LINKS_ENABLED** | no | - | If true, it is possible to use external documents links also without TruBudget's storage-service. If false, the external documents links feature of TruBudget is still possible to use in case DOCUMENT_FEATURE_ENABLED equals "true". | | **STORAGE_SERVICE_HOST** | no | localhost | IP of connected storage service | | **STORAGE_SERVICE_PORT** | no | 8090 | Port of connected storage service | +| **STORAGE_SERVICE_PROTOCOL** | no | http | Protocol of connected storage service. Allowed values: http, https. | | **STORAGE_SERVICE_EXTERNAL_URL** | yes (if DOCUMENT_FEATURE_ENABLED=true) | - | IP and port of own connected storage service accessible externally | | **EMAIL_HOST** | no | localhost | - | | **EMAIL_PORT** | no | 8089 | - | +| **EMAIL_PROTOCOL** | no | http | Protocol of connected storage service. Allowed values: http, https. | | **ACCESS_CONTROL_ALLOW_ORIGIN** | no | * | Since the service uses CORS, the domain by which it can be called needs to be set. Setting this value to `*` means that it can be called from any domain. Read more about this topic [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS). | | **NODE_ENV** | no | production | If set to `development` api will allow any string as password. If set to `production` passwords must satisfy safePasswordSchema, see lib/joiValidation-.ts & -.spec.ts files Allowed values: production, development, testing. | | **ENCRYPTION_PASSWORD** | no | - | If set, all data that is send to the MultiChain node and external storage will be symmetrically encrypted by the ENCRYPTION_PASSWORD | @@ -45,4 +49,4 @@ | **API_REFRESH_TOKENS_TABLE** | yes (if REFRESH_TOKEN_STORAGE=db) | refresh_token | Name of table where refresh tokens will be stored, e.g. "refresh_token". | | **SNAPSHOT_EVENT_INTERVAL** | no | 3 | - | | **SILENCE_LOGGING_ON_FREQUENT_ROUTES** | no | - | Set to "true" if you want to hide route logging on frequent and technical endpoints like `/readiness`, `/version`, etc. | -| **APPLICATIONINSIGHTS_CONNECTION_STRING** | no | - | Azure Application Insights Connection String Allowed values: . | +| **APPLICATIONINSIGHTS_CONNECTION_STRING** | no | - | Azure Application Insights Connection String Allowed values: . | \ No newline at end of file diff --git a/api/package.json b/api/package.json index c7f2ffd38..865395323 100644 --- a/api/package.json +++ b/api/package.json @@ -39,7 +39,7 @@ "build": "rimraf dist && tsc", "dev": "ts-node-dev --poll --respawn -r tsconfig-paths/register src/index.ts -- --inspect=0.0.0.0:9229", "watch": "ts-node-dev --inspect=0.0.0.0:9229 --poll --respawn -r tsconfig-paths/register src/index.ts", - "test": "nyc mocha --require ts-node/register -r tsconfig-paths/register \"src/**/*.spec.ts\"", + "test": "NODE_ENV=testing nyc mocha --require ts-node/register -r tsconfig-paths/register \"src/**/*.spec.ts\"", "coverage": "nyc report --reporter=text-lcov", "generate-report": "nyc report --reporter=html", "lint": "eslint src --ext ts", diff --git a/api/src/config.ts b/api/src/config.ts index a65526f0f..66da44e64 100644 --- a/api/src/config.ts +++ b/api/src/config.ts @@ -130,7 +130,7 @@ interface Config { } const { error, value: envVars } = envVarsSchema.validate(process.env); -if (error) { +if (error && process.env.NODE_ENV !== "testing") { throw new Error(`Config validation error: ${error.message}`); } diff --git a/api/src/envVarsSchema.ts b/api/src/envVarsSchema.ts index a1a7f6404..7aede2b33 100644 --- a/api/src/envVarsSchema.ts +++ b/api/src/envVarsSchema.ts @@ -14,8 +14,7 @@ export const envVarsSchema = Joi.object({ "In the blockchain network, each node is represented by its organization name. This environment variable sets this organization name. It is used to create the organization stream on the blockchain and is also displayed in the frontend's top right corner.", ), PORT: Joi.number() - .min(0) - .max(65535) + .port() .default(8091) .note( "The port used to expose the API for your installation.
Example: If you run TruBudget locally and set API_PORT to `8080`, you can reach the API via `localhost:8080/api`.", diff --git a/api/src/lib/joiValidation.spec.ts b/api/src/lib/joiValidation.spec.ts index 514b436c9..4d0a54169 100644 --- a/api/src/lib/joiValidation.spec.ts +++ b/api/src/lib/joiValidation.spec.ts @@ -1,17 +1,17 @@ import { expect } from "chai"; -import { safeIdSchema, safePasswordSchema, safeStringSchema } from "./joiValidation"; +import { safeIdSchema, safePasswordSchemaProduction, safeStringSchema } from "./joiValidation"; import Joi = require("joi"); const safeStringSchemaSchema = Joi.alternatives([safeStringSchema]); const safeIdSchemaSchema = Joi.alternatives([safeIdSchema]); -const safePasswordSchemaSchema = Joi.alternatives([safePasswordSchema]); +const safePasswordSchemaSchemaProduction = Joi.alternatives([safePasswordSchemaProduction]); describe("JoiValidation: Password", () => { it("Should accept user creation with a correct Password", async () => { const pass = "Test1234"; const controlValue = "Test1234"; - const { value, error } = safePasswordSchemaSchema.validate(pass); + const { value, error } = safePasswordSchemaSchemaProduction.validate(pass); expect(value).to.equal(controlValue); expect(error).to.equal(undefined); @@ -20,7 +20,7 @@ describe("JoiValidation: Password", () => { it("Should not accept a weak Password (only in Production)", async () => { const pass = "asdfasdf"; - const { value, error } = safePasswordSchemaSchema.validate(pass); + const { value, error } = safePasswordSchemaSchemaProduction.validate(pass); expect(value).to.equal(undefined); expect(error?.message || "").to.contain("fails to match the required pattern:"); @@ -29,7 +29,7 @@ describe("JoiValidation: Password", () => { it("Should not accept a short Password", async () => { const pass = "Test123"; - const { value, error } = safePasswordSchemaSchema.validate(pass); + const { value, error } = safePasswordSchemaSchemaProduction.validate(pass); expect(value).to.equal(undefined); expect(error?.message || "").to.contain("length must be at least 8 characters long"); @@ -38,7 +38,7 @@ describe("JoiValidation: Password", () => { it("Should not accept a Password with malicious code", async () => { const pass = "Test1234