312 lines
12 KiB
TypeScript
312 lines
12 KiB
TypeScript
import { dirname, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { z } from 'zod/v4';
|
|
|
|
import {
|
|
DEFAULT_PLUGIN_ROUTE,
|
|
DEFAULT_POST_LIMITER_ACTIVE,
|
|
DEFAULT_PRIVATE_DIRECTORY,
|
|
DEFAULT_PUBLIC_DIRECTORY,
|
|
DEFAULT_QUERY_PARAMETER_APP,
|
|
DEFAULT_QUERY_PARAMETER_CSS,
|
|
DEFAULT_QUERY_PARAMETER_FOLLOWING,
|
|
DEFAULT_QUERY_PARAMETER_JS,
|
|
DEFAULT_QUERY_PARAMETER_LOGOUT,
|
|
DEFAULT_QUERY_PARAMETER_METADATA,
|
|
DEFAULT_QUERY_PARAMETER_TWT,
|
|
DEFAULT_QUERY_PARAMETER_TWTS,
|
|
DEFAULT_ROUTE,
|
|
DEFAULT_TWTXT_FILENAME,
|
|
DEFAULT_UPLOAD_ACTIVE,
|
|
DEFAULT_UPLOAD_ALLOWED_MIME_TYPES,
|
|
DEFAULT_UPLOAD_ENCODING,
|
|
DEFAULT_UPLOAD_HASH_ALGORITHM,
|
|
DEFAULT_UPLOAD_KEEP_EXTENSIONS,
|
|
DEFAULT_UPLOAD_ROUTE,
|
|
} from './constants.js';
|
|
|
|
/*
|
|
The following keys are expected to exist in `process.env`, either as listed, or without the
|
|
`TWTKPR_` prefix
|
|
|
|
We only have listed default values for our keys, anything for other plugins (like formidable or
|
|
express-rate-limit) fall back to their own defaults (and thus are optional).
|
|
*/
|
|
const envSchema = z.object({
|
|
NODE_ENV: z
|
|
.enum(['development', 'production', 'test'])
|
|
.default('development'),
|
|
|
|
// required vars - MUST be passed via ENV
|
|
TWTKPR_REFRESH_SECRET: z.string().default(''),
|
|
TWTKPR_ACCESS_SECRET: z.string().default(''),
|
|
|
|
// vars with default values
|
|
TWTKPR_DEFAULT_ROUTE: z.string().default(DEFAULT_ROUTE),
|
|
TWTKPR_PLUGIN_ROUTE: z.string().default(DEFAULT_PLUGIN_ROUTE),
|
|
TWTKPR_PRIVATE_DIRECTORY: z.string().default(DEFAULT_PRIVATE_DIRECTORY),
|
|
TWTKPR_PUBLIC_DIRECTORY: z.string().default(DEFAULT_PUBLIC_DIRECTORY),
|
|
TWTKPR_QUERY_PARAMETER_APP: z.string().default(DEFAULT_QUERY_PARAMETER_APP),
|
|
TWTKPR_QUERY_PARAMETER_CSS: z.string().default(DEFAULT_QUERY_PARAMETER_CSS),
|
|
TWTKPR_QUERY_PARAMETER_FOLLOWING: z
|
|
.string()
|
|
.default(DEFAULT_QUERY_PARAMETER_FOLLOWING),
|
|
TWTKPR_QUERY_PARAMETER_JS: z.string().default(DEFAULT_QUERY_PARAMETER_JS),
|
|
TWTKPR_QUERY_PARAMETER_LOGOUT: z
|
|
.string()
|
|
.default(DEFAULT_QUERY_PARAMETER_LOGOUT),
|
|
TWTKPR_QUERY_PARAMETER_METADATA: z
|
|
.string()
|
|
.default(DEFAULT_QUERY_PARAMETER_METADATA),
|
|
TWTKPR_QUERY_PARAMETER_TWT: z.string().default(DEFAULT_QUERY_PARAMETER_TWT),
|
|
TWTKPR_QUERY_PARAMETER_TWTS: z.string().default(DEFAULT_QUERY_PARAMETER_TWTS),
|
|
TWTKPR_TWTXT_FILENAME: z.string().default(DEFAULT_TWTXT_FILENAME),
|
|
|
|
/**
|
|
* Post limiter plugin
|
|
*/
|
|
|
|
// var with default value
|
|
TWTKPR_POST_LIMITER_ACTIVE: z.boolean().default(DEFAULT_POST_LIMITER_ACTIVE),
|
|
|
|
// optional vars
|
|
TWTKPR_POST_LIMITER_WINDOW_MS: z.optional(z.number()),
|
|
TWTKPR_POST_LIMITER_LIMIT: z.optional(z.union([z.number(), z.function()])),
|
|
TWTKPR_POST_LIMITER_MESSAGE: z.optional(z.any()),
|
|
TWTKPR_POST_LIMITER_STATUS_CODE: z.optional(z.number()),
|
|
TWTKPR_POST_LIMITER_HANDLER: z.optional(z.function()),
|
|
TWTKPR_POST_LIMITER_LEGACY_HEADERS: z.optional(z.boolean()),
|
|
TWTKPR_POST_LIMITER_STANDARD_HEADERS: z.optional(
|
|
z.union([z.boolean(), z.string()])
|
|
),
|
|
TWTKPR_POST_LIMITER_IDENTIFIER: z.optional(
|
|
z.union([z.string(), z.function()])
|
|
),
|
|
TWTKPR_POST_LIMITER_STORE: z.optional(z.any()),
|
|
TWTKPR_POST_LIMITER_PASS_ON_STORE_ERROR: z.optional(z.boolean()),
|
|
TWTKPR_POST_LIMITER_KEY_GENERATOR: z.optional(z.function()),
|
|
TWTKPR_POST_LIMITER_IPV6_SUBNET: z.optional(
|
|
z.union([z.number(), z.function(), z.boolean()])
|
|
),
|
|
TWTKPR_POST_LIMITER_REQUEST_PROPERTY_NAME: z.optional(z.string()),
|
|
TWTKPR_POST_LIMITER_SKIP: z.optional(z.function()),
|
|
TWTKPR_POST_LIMITER_SKIP_SUCCESSFUL_REQUESTS: z.optional(z.boolean()),
|
|
TWTKPR_POST_LIMITER_SKIP_FAILED_REQUESTS: z.optional(z.boolean()),
|
|
TWTKPR_POST_LIMITER_REQUEST_WAS_SUCCESSFUL: z.optional(z.function()),
|
|
TWTKPR_POST_LIMITER_VALIDATE: z.optional(z.union([z.boolean(), z.object()])),
|
|
|
|
/**
|
|
* Upload plugin
|
|
*/
|
|
|
|
// vars with default values
|
|
TWTKPR_UPLOAD_ACTIVE: z.boolean().default(DEFAULT_UPLOAD_ACTIVE),
|
|
TWTKPR_UPLOAD_ALLOWED_MIME_TYPES: z
|
|
.union([z.string(), z.array(z.string())])
|
|
.default(DEFAULT_UPLOAD_ALLOWED_MIME_TYPES),
|
|
TWTKPR_UPLOAD_ROUTE: z.string().default(DEFAULT_UPLOAD_ROUTE),
|
|
|
|
// optional vars
|
|
TWTKPR_UPLOAD_ALLOW_EMPTY_FILES: z.optional(z.boolean()),
|
|
TWTKPR_UPLOAD_CREATE_DIRS_FROM_UPLOADS: z.optional(z.boolean()),
|
|
TWTKPR_UPLOAD_DIRECTORY: z.optional(z.string()),
|
|
TWTKPR_UPLOAD_ENCODING: z.string().default(DEFAULT_UPLOAD_ENCODING),
|
|
TWTKPR_UPLOAD_FILE_WRITE_STREAM_HANDLER: z.optional(z.function()),
|
|
TWTKPR_UPLOAD_FILENAME: z.optional(z.function()),
|
|
TWTKPR_UPLOAD_FILTER: z.optional(z.function()),
|
|
TWTKPR_UPLOAD_HASH_ALGORITHM: z
|
|
.union([z.boolean(), z.string()])
|
|
.default(DEFAULT_UPLOAD_HASH_ALGORITHM),
|
|
TWTKPR_UPLOAD_KEEP_EXTENSIONS: z
|
|
.boolean()
|
|
.default(DEFAULT_UPLOAD_KEEP_EXTENSIONS),
|
|
TWTKPR_UPLOAD_MAX_FIELDS: z.optional(z.number()),
|
|
TWTKPR_UPLOAD_MAX_FIELDS_SIZE: z.optional(z.number()),
|
|
TWTKPR_UPLOAD_MAX_FILE_SIZE: z.optional(z.number()),
|
|
TWTKPR_UPLOAD_MAX_FILES: z.optional(z.number()),
|
|
TWTKPR_UPLOAD_MAX_TOTAL_FILE_SIZE: z.optional(z.number()),
|
|
TWTKPR_UPLOAD_MIN_FILE_SIZE: z.optional(z.number()),
|
|
});
|
|
|
|
const parseEnv = () => {
|
|
try {
|
|
/**
|
|
* there's probably an easier way to do this, spreading the keys from above and accepting either
|
|
* the app (bare) or library (`TWTKPR_`-prefixed) version of said key.
|
|
* But this should work for now.
|
|
*/
|
|
const parsedEnv = envSchema.parse({
|
|
NODE_ENV: process.env.TWTKPR_NODE_ENV || process.env.NODE_ENV,
|
|
TWTKPR_ACCESS_SECRET:
|
|
process.env.TWTKPR_ACCESS_SECRET || process.env.ACCESS_SECRET,
|
|
TWTKPR_DEFAULT_ROUTE:
|
|
process.env.TWTKPR_DEFAULT_ROUTE || process.env.DEFAULT_ROUTE,
|
|
TWTKPR_PLUGIN_ROUTE:
|
|
process.env.TWTKPR_PLUGIN_ROUTE || process.env.TWTKPR_PLUGIN_ROUTE,
|
|
TWTKPR_PRIVATE_DIRECTORY:
|
|
process.env.TWTKPR_PRIVATE_DIRECTORY || process.env.PRIVATE_DIRECTORY,
|
|
TWTKPR_PUBLIC_DIRECTORY:
|
|
process.env.TWTKPR_PUBLIC_DIRECTORY || process.env.PUBLIC_DIRECTORY,
|
|
TWTKPR_REFRESH_SECRET:
|
|
process.env.TWTKPR_REFRESH_SECRET || process.env.REFRESH_SECRET,
|
|
TWTKPR_TWTXT_FILENAME:
|
|
process.env.TWTKPR_TWTXT_FILENAME || process.env.TWTXT_FILENAME,
|
|
|
|
TWTKPR_POST_LIMITER_ACTIVE:
|
|
process.env.TWTKPR_POST_LIMITER_ACTIVE ||
|
|
process.env.POST_LIMITER_ACTIVE,
|
|
TWTKPR_POST_LIMITER_WINDOW_MS:
|
|
process.env.TWTKPR_POST_LIMITER_WINDOW_MS ||
|
|
process.env.POST_LIMITER_WINDOW_MS,
|
|
TWTKPR_POST_LIMITER_LIMIT:
|
|
process.env.TWTKPR_POST_LIMITER_LIMIT || process.env.POST_LIMITER_LIMIT,
|
|
TWTKPR_POST_LIMITER_MESSAGE:
|
|
process.env.TWTKPR_POST_LIMITER_MESSAGE ||
|
|
process.env.POST_LIMITER_MESSAGE,
|
|
TWTKPR_POST_LIMITER_STATUS_CODE:
|
|
process.env.TWTKPR_POST_LIMITER_STATUS_CODE ||
|
|
process.env.POST_LIMITER_STATUS_CODE,
|
|
TWTKPR_POST_LIMITER_HANDLER:
|
|
process.env.TWTKPR_POST_LIMITER_HANDLER ||
|
|
process.env.POST_LIMITER_HANDLER,
|
|
TWTKPR_POST_LIMITER_LEGACY_HEADERS:
|
|
process.env.TWTKPR_POST_LIMITER_LEGACY_HEADERS ||
|
|
process.env.POST_LIMITER_LEGACY_HEADERS,
|
|
TWTKPR_POST_LIMITER_STANDARD_HEADERS:
|
|
process.env.TWTKPR_POST_LIMITER_STANDARD_HEADERS ||
|
|
process.env.POST_LIMITER_STANDARD_HEADERS,
|
|
TWTKPR_POST_LIMITER_IDENTIFIER:
|
|
process.env.TWTKPR_POST_LIMITER_IDENTIFIER ||
|
|
process.env.POST_LIMITER_IDENTIFIER,
|
|
TWTKPR_POST_LIMITER_STORE:
|
|
process.env.TWTKPR_POST_LIMITER_STORE || process.env.POST_LIMITER_STORE,
|
|
TWTKPR_POST_LIMITER_PASS_ON_STORE_ERROR:
|
|
process.env.TWTKPR_POST_LIMITER_PASS_ON_STORE_ERROR ||
|
|
process.env.POST_LIMITER_PASS_ON_STORE_ERROR,
|
|
TWTKPR_POST_LIMITER_KEY_GENERATOR:
|
|
process.env.TWTKPR_POST_LIMITER_KEY_GENERATOR ||
|
|
process.env.POST_LIMITER_KEY_GENERATOR,
|
|
TWTKPR_POST_LIMITER_IPV6_SUBNET:
|
|
process.env.TWTKPR_POST_LIMITER_IPV6_SUBNET ||
|
|
process.env.POST_LIMITER_IPV6_SUBNET,
|
|
TWTKPR_POST_LIMITER_REQUEST_PROPERTY_NAME:
|
|
process.env.TWTKPR_POST_LIMITER_REQUEST_PROPERTY_NAME ||
|
|
process.env.POST_LIMITER_REQUEST_PROPERTY_NAME,
|
|
TWTKPR_POST_LIMITER_SKIP:
|
|
process.env.TWTKPR_POST_LIMITER_SKIP || process.env.POST_LIMITER_SKIP,
|
|
TWTKPR_POST_LIMITER_SKIP_SUCCESSFUL_REQUESTS:
|
|
process.env.TWTKPR_POST_LIMITER_SKIP_SUCCESSFUL_REQUESTS ||
|
|
process.env.POST_LIMITER_SKIP_SUCCESSFUL_REQUESTS,
|
|
TWTKPR_POST_LIMITER_SKIP_FAILED_REQUESTS:
|
|
process.env.TWTKPR_POST_LIMITER_SKIP_FAILED_REQUESTS ||
|
|
process.env.POST_LIMITER_SKIP_FAILED_REQUESTS,
|
|
TWTKPR_POST_LIMITER_REQUEST_WAS_SUCCESSFUL:
|
|
process.env.TWTKPR_POST_LIMITER_REQUEST_WAS_SUCCESSFUL ||
|
|
process.env.POST_LIMITER_REQUEST_WAS_SUCCESSFUL,
|
|
TWTKPR_POST_LIMITER_VALIDATE:
|
|
process.env.TWTKPR_POST_LIMITER_VALIDATE ||
|
|
process.env.POST_LIMITER_VALIDATE,
|
|
|
|
TWTKPR_QUERY_PARAMETER_APP:
|
|
process.env.TWTKPR_QUERY_PARAMETER_APP ||
|
|
process.env.QUERY_PARAMETER_APP,
|
|
TWTKPR_QUERY_PARAMETER_CSS:
|
|
process.env.TWTKPR_QUERY_PARAMETER_CSS ||
|
|
process.env.QUERY_PARAMETER_CSS,
|
|
TWTKPR_QUERY_PARAMETER_FOLLOWING:
|
|
process.env.TWTKPR_QUERY_PARAMETER_FOLLOWING ||
|
|
process.env.QUERY_PARAMETER_FOLLOWING,
|
|
TWTKPR_QUERY_PARAMETER_JS:
|
|
process.env.TWTKPR_QUERY_PARAMETER_JS || process.env.QUERY_PARAMETER_JS,
|
|
TWTKPR_QUERY_PARAMETER_LOGOUT:
|
|
process.env.TWTKPR_QUERY_PARAMETER_LOGOUT ||
|
|
process.env.QUERY_PARAMETER_LOGOUT,
|
|
TWTKPR_QUERY_PARAMETER_METADATA:
|
|
process.env.TWTKPR_QUERY_PARAMETER_METADATA ||
|
|
process.env.QUERY_PARAMETER_METADATA,
|
|
TWTKPR_QUERY_PARAMETER_TWT:
|
|
process.env.TWTKPR_QUERY_PARAMETER_TWT ||
|
|
process.env.QUERY_PARAMETER_TWT,
|
|
TWTKPR_QUERY_PARAMETER_TWTS:
|
|
process.env.TWTKPR_QUERY_PARAMETER_TWTS ||
|
|
process.env.QUERY_PARAMETER_TWTS,
|
|
|
|
TWTKPR_UPLOAD_ACTIVE:
|
|
process.env.TWTKPR_UPLOAD_ACTIVE || process.env.UPLOAD_ACTIVE,
|
|
TWTKPR_UPLOAD_ALLOW_EMPTY_FILES:
|
|
process.env.TWTKPR_UPLOAD_ALLOW_EMPTY_FILES ||
|
|
process.env.UPLOAD_ALLOW_EMPTY_FILES,
|
|
TWTKPR_UPLOAD_ALLOWED_MIME_TYPES:
|
|
process.env.TWTKPR_UPLOAD_ALLOWED_MIME_TYPES ||
|
|
process.env.UPLOAD_ALLOWED_MIME_TYPES,
|
|
TWTKPR_UPLOAD_CREATE_DIRS_FROM_UPLOADS:
|
|
process.env.TWTKPR_UPLOAD_CREATE_DIRS_FROM_UPLOADS ||
|
|
process.env.UPLOAD_CREATE_DIRS_FROM_UPLOADS,
|
|
TWTKPR_UPLOAD_DIRECTORY:
|
|
process.env.TWTKPR_UPLOAD_DIRECTORY ||
|
|
process.env.UPLOAD_DIRECTORY ||
|
|
process.env.UPLOAD_DIR,
|
|
TWTKPR_UPLOAD_ENCODING:
|
|
process.env.TWTKPR_UPLOAD_ENCODING || process.env.UPLOAD_ENCODING,
|
|
TWTKPR_UPLOAD_FILE_WRITE_STREAM_HANDLER:
|
|
process.env.TWTKPR_UPLOAD_FILE_WRITE_STREAM_HANDLER ||
|
|
process.env.UPLOAD_FILE_WRITE_STREAM_HANDLER,
|
|
TWTKPR_UPLOAD_FILENAME:
|
|
process.env.TWTKPR_UPLOAD_FILENAME || process.env.UPLOAD_FILENAME,
|
|
TWTKPR_UPLOAD_FILTER:
|
|
process.env.TWTKPR_UPLOAD_FILTER || process.env.UPLOAD_FILTER,
|
|
TWTKPR_UPLOAD_HASH_ALGORITHM:
|
|
process.env.TWTKPR_UPLOAD_HASH_ALGORITHM ||
|
|
process.env.UPLOAD_HASH_ALGORITHM,
|
|
TWTKPR_UPLOAD_KEEP_EXTENSIONS:
|
|
process.env.TWTKPR_UPLOAD_KEEP_EXTENSIONS ||
|
|
process.env.UPLOAD_KEEP_EXTENSIONS,
|
|
TWTKPR_UPLOAD_MAX_FIELDS:
|
|
process.env.TWTKPR_UPLOAD_MAX_FIELDS || process.env.UPLOAD_MAX_FIELDS,
|
|
TWTKPR_UPLOAD_MAX_FIELDS_SIZE:
|
|
process.env.TWTKPR_UPLOAD_MAX_FIELDS_SIZE ||
|
|
process.env.UPLOAD_MAX_FIELDS_SIZE,
|
|
TWTKPR_UPLOAD_MAX_FILE_SIZE:
|
|
process.env.TWTKPR_UPLOAD_MAX_FILE_SIZE ||
|
|
process.env.UPLOAD_MAX_FILE_SIZE,
|
|
TWTKPR_UPLOAD_MAX_FILES:
|
|
process.env.TWTKPR_UPLOAD_MAX_FILES || process.env.UPLOAD_MAX_FILES,
|
|
TWTKPR_UPLOAD_MAX_TOTAL_FILE_SIZE:
|
|
process.env.TWTKPR_UPLOAD_MAX_TOTAL_FILE_SIZE ||
|
|
process.env.UPLOAD_MAX_TOTAL_FILE_SIZE,
|
|
TWTKPR_UPLOAD_MIN_FILE_SIZE:
|
|
process.env.TWTKPR_UPLOAD_MIN_FILE_SIZE ||
|
|
process.env.UPLOAD_MIN_FILE_SIZE,
|
|
TWTKPR_UPLOAD_ROUTE:
|
|
process.env.TWTKPR_UPLOAD_ROUTE || process.env.UPLOAD_ROUTE,
|
|
});
|
|
|
|
if (!parsedEnv.TWTKPR_ACCESS_SECRET)
|
|
throw new Error(
|
|
'Either ACCESS_SECRET or TWTKPR_ACCESS_SECRET must be provided'
|
|
);
|
|
|
|
if (!parsedEnv.TWTKPR_REFRESH_SECRET)
|
|
throw new Error(
|
|
'Either REFRESH_SECRET or TWTKPR_REFRESH_SECRET must be provided'
|
|
);
|
|
|
|
return parsedEnv;
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
console.error(
|
|
'Missing environment variables:',
|
|
error.issues.flatMap((issue) => `${issue.path} or TWTKPR_${issue.path}`)
|
|
);
|
|
} else {
|
|
console.error(error);
|
|
}
|
|
|
|
process.exit(1);
|
|
}
|
|
};
|
|
|
|
export const env = parseEnv();
|
|
|
|
export const __dirname = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|