fix: allow quoted urls in env variables (#676)
This commit is contained in:
parent
ee73f89863
commit
e39220c024
5 changed files with 49 additions and 38 deletions
|
|
@ -1,8 +1,10 @@
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import { z } from "zod";
|
import { prettifyError, z } from "zod";
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
|
import { buildAllowedHosts } from "../lib/auth/base-url";
|
||||||
|
|
||||||
|
const unquote = (str: string) => str.trim().replace(/^(['"])(.*)\1$/, "$2");
|
||||||
const getResticHostname = () => {
|
const getResticHostname = () => {
|
||||||
try {
|
try {
|
||||||
const mountinfo = readFileSync("/proc/self/mountinfo", "utf-8");
|
const mountinfo = readFileSync("/proc/self/mountinfo", "utf-8");
|
||||||
|
|
@ -41,31 +43,49 @@ const envSchema = z
|
||||||
ENABLE_DEV_PANEL: z.string().default("false"),
|
ENABLE_DEV_PANEL: z.string().default("false"),
|
||||||
PROVISIONING_PATH: z.string().optional(),
|
PROVISIONING_PATH: z.string().optional(),
|
||||||
})
|
})
|
||||||
.transform((s) => ({
|
.transform((s) => {
|
||||||
__prod__: s.NODE_ENV === "production",
|
const baseUrl = unquote(s.BASE_URL);
|
||||||
environment: s.NODE_ENV,
|
const trustedOrigins = s.TRUSTED_ORIGINS?.split(",").map(unquote).filter(Boolean).concat(baseUrl) ?? [baseUrl];
|
||||||
serverIp: s.SERVER_IP,
|
const authOrigins = [baseUrl, ...trustedOrigins];
|
||||||
serverIdleTimeout: s.SERVER_IDLE_TIMEOUT,
|
const { allowedHosts, invalidOrigins } = buildAllowedHosts(authOrigins);
|
||||||
resticHostname: s.RESTIC_HOSTNAME || getResticHostname(),
|
|
||||||
port: s.PORT,
|
|
||||||
migrationsPath: s.MIGRATIONS_PATH,
|
|
||||||
appVersion: s.APP_VERSION,
|
|
||||||
trustedOrigins: s.TRUSTED_ORIGINS?.split(",")
|
|
||||||
.map((origin) => origin.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.concat(s.BASE_URL) ?? [s.BASE_URL],
|
|
||||||
trustProxy: s.TRUST_PROXY === "true",
|
|
||||||
disableRateLimiting: s.DISABLE_RATE_LIMITING === "true" || s.NODE_ENV === "test",
|
|
||||||
appSecret: s.APP_SECRET,
|
|
||||||
baseUrl: s.BASE_URL,
|
|
||||||
isSecure: s.BASE_URL?.startsWith("https://") ?? false,
|
|
||||||
enableDevPanel: s.ENABLE_DEV_PANEL === "true",
|
|
||||||
provisioningPath: s.PROVISIONING_PATH,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const parseConfig = (env: unknown) => {
|
for (const origin of invalidOrigins) {
|
||||||
|
console.warn(
|
||||||
|
`Ignoring invalid origin in configuration: ${origin}. Make sure it is a valid URL with a protocol (e.g. https://example.com)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
__prod__: s.NODE_ENV === "production",
|
||||||
|
environment: s.NODE_ENV,
|
||||||
|
serverIp: s.SERVER_IP,
|
||||||
|
serverIdleTimeout: s.SERVER_IDLE_TIMEOUT,
|
||||||
|
resticHostname: s.RESTIC_HOSTNAME || getResticHostname(),
|
||||||
|
port: s.PORT,
|
||||||
|
migrationsPath: s.MIGRATIONS_PATH,
|
||||||
|
appVersion: s.APP_VERSION,
|
||||||
|
trustedOrigins: trustedOrigins,
|
||||||
|
trustProxy: s.TRUST_PROXY === "true",
|
||||||
|
disableRateLimiting: s.DISABLE_RATE_LIMITING === "true" || s.NODE_ENV === "test",
|
||||||
|
appSecret: s.APP_SECRET,
|
||||||
|
baseUrl,
|
||||||
|
isSecure: baseUrl.startsWith("https://"),
|
||||||
|
enableDevPanel: s.ENABLE_DEV_PANEL === "true",
|
||||||
|
provisioningPath: s.PROVISIONING_PATH,
|
||||||
|
allowedHosts,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const parseConfig = (env: unknown) => {
|
||||||
const result = envSchema.safeParse(env);
|
const result = envSchema.safeParse(env);
|
||||||
|
|
||||||
|
if (result.success && result.data.allowedHosts.length === 0) {
|
||||||
|
console.error(
|
||||||
|
`Configuration error: No valid trusted origins provided. Please check the BASE_URL and TRUSTED_ORIGINS environment variables.`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
if (!process.env.APP_SECRET) {
|
if (!process.env.APP_SECRET) {
|
||||||
const errorMessage = [
|
const errorMessage = [
|
||||||
|
|
@ -89,8 +109,8 @@ const parseConfig = (env: unknown) => {
|
||||||
console.error(errorMessage);
|
console.error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error(`Environment variable validation failed: ${result.error.message}`);
|
console.error(`Environment variable validation failed: ${prettifyError(result.error)}`);
|
||||||
throw new Error("Invalid environment variables");
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.data;
|
return result.data;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import CronExpressionParser from "cron-parser";
|
import { CronExpressionParser } from "cron-parser";
|
||||||
import { logger } from "@zerobyte/core/node";
|
import { logger } from "@zerobyte/core/node";
|
||||||
|
|
||||||
export abstract class Job {
|
export abstract class Job {
|
||||||
|
|
|
||||||
|
|
@ -12,29 +12,20 @@ import { createAuthMiddleware } from "better-auth/api";
|
||||||
import { config } from "../core/config";
|
import { config } from "../core/config";
|
||||||
import { db } from "../db/db";
|
import { db } from "../db/db";
|
||||||
import { cryptoUtils } from "../utils/crypto";
|
import { cryptoUtils } from "../utils/crypto";
|
||||||
import { logger } from "@zerobyte/core/node";
|
|
||||||
import { authService } from "../modules/auth/auth.service";
|
import { authService } from "../modules/auth/auth.service";
|
||||||
import { tanstackStartCookies } from "better-auth/tanstack-start";
|
import { tanstackStartCookies } from "better-auth/tanstack-start";
|
||||||
import { isValidUsername, normalizeUsername } from "~/lib/username";
|
import { isValidUsername, normalizeUsername } from "~/lib/username";
|
||||||
import { ensureOnlyOneUser } from "./auth/middlewares/only-one-user";
|
import { ensureOnlyOneUser } from "./auth/middlewares/only-one-user";
|
||||||
import { convertLegacyUserOnFirstLogin } from "./auth/middlewares/convert-legacy-user";
|
import { convertLegacyUserOnFirstLogin } from "./auth/middlewares/convert-legacy-user";
|
||||||
import { ensureDefaultOrg } from "./auth/helpers/create-default-org";
|
import { ensureDefaultOrg } from "./auth/helpers/create-default-org";
|
||||||
import { buildAllowedHosts } from "./auth/base-url";
|
|
||||||
import { ssoIntegration } from "../modules/sso/sso.integration";
|
import { ssoIntegration } from "../modules/sso/sso.integration";
|
||||||
|
|
||||||
export type AuthMiddlewareContext = MiddlewareContext<MiddlewareOptions, AuthContext<BetterAuthOptions>>;
|
export type AuthMiddlewareContext = MiddlewareContext<MiddlewareOptions, AuthContext<BetterAuthOptions>>;
|
||||||
|
|
||||||
const authOrigins = [config.baseUrl, ...config.trustedOrigins];
|
|
||||||
const { allowedHosts, invalidOrigins } = buildAllowedHosts(authOrigins);
|
|
||||||
|
|
||||||
for (const origin of invalidOrigins) {
|
|
||||||
logger.warn(`Ignoring invalid auth origin in configuration: ${origin}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
secret: await cryptoUtils.deriveSecret("better-auth"),
|
secret: await cryptoUtils.deriveSecret("better-auth"),
|
||||||
baseURL: {
|
baseURL: {
|
||||||
allowedHosts,
|
allowedHosts: config.allowedHosts,
|
||||||
protocol: "auto",
|
protocol: "auto",
|
||||||
},
|
},
|
||||||
trustedOrigins: config.trustedOrigins,
|
trustedOrigins: config.trustedOrigins,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import CronExpressionParser from "cron-parser";
|
import { CronExpressionParser } from "cron-parser";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { BackupSchedule } from "~/server/db/schema";
|
import type { BackupSchedule } from "~/server/db/schema";
|
||||||
import { toMessage } from "~/server/utils/errors";
|
import { toMessage } from "~/server/utils/errors";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { and, eq, inArray } from "drizzle-orm";
|
import { and, eq, inArray } from "drizzle-orm";
|
||||||
import CronExpressionParser from "cron-parser";
|
import { CronExpressionParser } from "cron-parser";
|
||||||
import { NotFoundError, BadRequestError, ConflictError } from "http-errors-enhanced";
|
import { NotFoundError, BadRequestError, ConflictError } from "http-errors-enhanced";
|
||||||
|
|
||||||
const isValidCron = (expression: string): boolean => {
|
const isValidCron = (expression: string): boolean => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue