Add support for _FILE environment variables (#6101)

* Add support for _FILE environment variables

* Enhance processing of _FILE env vars

* Same processing as with other env vars (do not simply treat as string)
- tested successfully
* Warn if both variables are set (EXAMPLE and EXAMPLE_FILE)
* Add comments to make it easier to understand the code

* Set newKey only after file read was successful

* Don't convert value > MAX_SAFE_INTEGER to number

Thanks to @skizer!

As stated by @skizer (from #6119):
  Altho it seems that we do have a numerical value
  it can happen that its outside of Number.MAX_SAFE_INTEGER
  thus resulting in a change of the original intended value
  e.g oauth -> discord -> client_id

* Fix recursive logger-env import

Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
This commit is contained in:
Pascal Jufer
2021-06-09 20:08:26 +02:00
committed by GitHub
parent 1a867f17d1
commit a2a35aaab5
4 changed files with 80 additions and 31 deletions

View File

@@ -7,11 +7,10 @@ import dotenv from 'dotenv';
import fs from 'fs';
import { clone, toNumber, toString } from 'lodash';
import path from 'path';
import logger from './logger';
import { requireYAML } from './utils/require-yaml';
import { toArray } from './utils/to-array';
const acceptableEnvTypes = ['string', 'number', 'regex', 'array'];
const acceptedEnvTypes = ['string', 'number', 'regex', 'array'];
const defaults: Record<string, any> = {
CONFIG_PATH: path.resolve(process.cwd(), '.env'),
@@ -125,7 +124,7 @@ function getEnv() {
return exported;
}
logger.warn(
throw new Error(
`Invalid JS configuration file export type. Requires one of "function", "object", received: "${typeof exported}"`
);
}
@@ -141,11 +140,11 @@ function getEnv() {
return data as Record<string, string>;
}
logger.warn('Invalid YAML configuration. Root has to ben an object.');
throw new Error('Invalid YAML configuration. Root has to be an object.');
}
// Default to env vars plain text files
return dotenv.parse(fs.readFileSync(configPath).toString());
return dotenv.parse(fs.readFileSync(configPath, { encoding: 'utf8' }));
}
function getVariableType(variable: string) {
@@ -175,12 +174,33 @@ function getEnvironmentValueByType(envVariableString: string) {
function processValues(env: Record<string, any>) {
env = clone(env);
for (const [key, value] of Object.entries(env)) {
if (typeof value === 'string' && acceptableEnvTypes.some((envType) => value.includes(`${envType}:`))) {
for (let [key, value] of Object.entries(env)) {
// If key ends with '_FILE', try to get the value from the file defined in this variable
// and store it in the variable with the same name but without '_FILE' at the end
let newKey;
if (key.length > 5 && key.endsWith('_FILE')) {
try {
value = fs.readFileSync(value, { encoding: 'utf8' });
newKey = key.slice(0, -5);
if (newKey in env) {
throw new Error(
`Duplicate environment variable encountered: you can't use "${key}" and "${newKey}" simultaneously.`
);
}
key = newKey;
} catch {
throw new Error(`Failed to read value from file "${value}", defined in environment variable "${key}".`);
}
}
// Convert values with a type prefix
// (see https://docs.directus.io/reference/environment-variables/#environment-syntax-prefix)
if (typeof value === 'string' && acceptedEnvTypes.some((envType) => value.includes(`${envType}:`))) {
env[key] = getEnvironmentValueByType(value);
continue;
}
// Convert values where the key is defined in typeMap
if (typeMap[key]) {
switch (typeMap[key]) {
case 'number':
@@ -193,20 +213,34 @@ function processValues(env: Record<string, any>) {
env[key] = toArray(value);
break;
}
continue;
}
if (value === 'true') env[key] = true;
else if (value === 'false') env[key] = false;
else if (value === 'null') env[key] = null;
else if (
// Try to convert remaining values:
// - boolean values to boolean
// - 'null' to null
// - number values (> 0 <= Number.MAX_SAFE_INTEGER) to number
if (value === 'true' || value === 'false') {
env[key] = !!value;
continue;
}
if (value === 'null') {
env[key] = null;
continue;
}
if (
String(value).startsWith('0') === false &&
isNaN(value) === false &&
value.length > 0 &&
value < Number.MAX_SAFE_INTEGER // Make sure we don't treat long numeric ID strings as numbers
value <= Number.MAX_SAFE_INTEGER
) {
env[key] = Number(value);
continue;
}
// If '_FILE' variable hasn't been processed yet, store it as it is (string)
if (newKey) {
env[key] = value;
}
}