Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 45 additions & 25 deletions app/server/core/config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { readFileSync } from "node:fs";
import os from "node:os";
import { z } from "zod";
import { prettifyError, z } from "zod";
import "dotenv/config";
import { buildAllowedHosts } from "../lib/auth/base-url";

const unquote = (str: string) => str.trim().replace(/^(['"])(.*)\1$/, "$2");
const getResticHostname = () => {
try {
const mountinfo = readFileSync("/proc/self/mountinfo", "utf-8");
Expand Down Expand Up @@ -41,31 +43,49 @@ const envSchema = z
ENABLE_DEV_PANEL: z.string().default("false"),
PROVISIONING_PATH: z.string().optional(),
})
.transform((s) => ({
__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: 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,
}));
.transform((s) => {
const baseUrl = unquote(s.BASE_URL);
const trustedOrigins = s.TRUSTED_ORIGINS?.split(",").map(unquote).filter(Boolean).concat(baseUrl) ?? [baseUrl];
const authOrigins = [baseUrl, ...trustedOrigins];
const { allowedHosts, invalidOrigins } = buildAllowedHosts(authOrigins);

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);

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 (!process.env.APP_SECRET) {
const errorMessage = [
Expand All @@ -89,8 +109,8 @@ const parseConfig = (env: unknown) => {
console.error(errorMessage);
}

console.error(`Environment variable validation failed: ${result.error.message}`);
throw new Error("Invalid environment variables");
console.error(`Environment variable validation failed: ${prettifyError(result.error)}`);
process.exit(1);
}

return result.data;
Expand Down
2 changes: 1 addition & 1 deletion app/server/core/scheduler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import CronExpressionParser from "cron-parser";
import { CronExpressionParser } from "cron-parser";
import { logger } from "@zerobyte/core/node";

export abstract class Job {
Expand Down
11 changes: 1 addition & 10 deletions app/server/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,20 @@ import { createAuthMiddleware } from "better-auth/api";
import { config } from "../core/config";
import { db } from "../db/db";
import { cryptoUtils } from "../utils/crypto";
import { logger } from "@zerobyte/core/node";
import { authService } from "../modules/auth/auth.service";
import { tanstackStartCookies } from "better-auth/tanstack-start";
import { isValidUsername, normalizeUsername } from "~/lib/username";
import { ensureOnlyOneUser } from "./auth/middlewares/only-one-user";
import { convertLegacyUserOnFirstLogin } from "./auth/middlewares/convert-legacy-user";
import { ensureDefaultOrg } from "./auth/helpers/create-default-org";
import { buildAllowedHosts } from "./auth/base-url";
import { ssoIntegration } from "../modules/sso/sso.integration";

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({
secret: await cryptoUtils.deriveSecret("better-auth"),
baseURL: {
allowedHosts,
allowedHosts: config.allowedHosts,
protocol: "auto",
},
trustedOrigins: config.trustedOrigins,
Expand Down
2 changes: 1 addition & 1 deletion app/server/modules/backups/backup.helpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import CronExpressionParser from "cron-parser";
import { CronExpressionParser } from "cron-parser";
import path from "node:path";
import type { BackupSchedule } from "~/server/db/schema";
import { toMessage } from "~/server/utils/errors";
Expand Down
2 changes: 1 addition & 1 deletion app/server/modules/backups/backups.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
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";

const isValidCron = (expression: string): boolean => {
Expand Down
Loading