diff --git a/drizzle.config.ts b/drizzle.config.ts index 7ef7ee96..8cf04a69 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,5 +1,5 @@ +import { APP_PATH } from "@server/consts"; import { defineConfig } from "drizzle-kit"; -import config, { APP_PATH } from "@server/config"; import path from "path"; export default defineConfig({ diff --git a/package.json b/package.json index 7426c951..0b5efeef 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,8 @@ "db:generate": "drizzle-kit generate", "db:push": "npx tsx server/db/migrate.ts", "db:studio": "drizzle-kit studio", - "build": "mkdir -p dist && next build && node esbuild.mjs -e server/index.ts -o dist/server.mjs", - "start": "NODE_ENV=development ENVIRONMENT=prod NODE_OPTIONS=--enable-source-maps node dist/server.mjs", + "build": "mkdir -p dist && next build && node esbuild.mjs -e server/index.ts -o dist/server.mjs && node esbuild.mjs -e server/setup/migrations.ts -o dist/migrations.mjs", + "start": "NODE_ENV=development ENVIRONMENT=prod sh -c 'node dist/migrations.mjs && node dist/server.mjs'", "email": "email dev --dir server/emails/templates --port 3005" }, "dependencies": { diff --git a/server/apiServer.ts b/server/apiServer.ts index 1de0329b..2e568f2e 100644 --- a/server/apiServer.ts +++ b/server/apiServer.ts @@ -15,7 +15,7 @@ import { csrfProtectionMiddleware } from "./middlewares/csrfProtection"; import helmet from "helmet"; const dev = process.env.ENVIRONMENT !== "prod"; -const externalPort = config.server.external_port; +const externalPort = config.getRawConfig().server.external_port; export function createApiServer() { const apiServer = express(); @@ -25,13 +25,13 @@ export function createApiServer() { if (dev) { apiServer.use( cors({ - origin: `http://localhost:${config.server.next_port}`, + origin: `http://localhost:${config.getRawConfig().server.next_port}`, credentials: true }) ); } else { const corsOptions = { - origin: config.app.base_url, + origin: config.getRawConfig().app.base_url, methods: ["GET", "POST", "PUT", "DELETE", "PATCH"], allowedHeaders: ["Content-Type", "X-CSRF-Token"] }; @@ -47,8 +47,8 @@ export function createApiServer() { if (!dev) { apiServer.use( rateLimitMiddleware({ - windowMin: config.rate_limits.global.window_minutes, - max: config.rate_limits.global.max_requests, + windowMin: config.getRawConfig().rate_limits.global.window_minutes, + max: config.getRawConfig().rate_limits.global.max_requests, type: "IP_AND_PATH" }) ); diff --git a/server/auth/index.ts b/server/auth/index.ts index e8c86a01..56e3fc29 100644 --- a/server/auth/index.ts +++ b/server/auth/index.ts @@ -12,12 +12,11 @@ import { eq } from "drizzle-orm"; import config from "@server/config"; import type { RandomReader } from "@oslojs/crypto/random"; import { generateRandomString } from "@oslojs/crypto/random"; -import { extractBaseDomain } from "@server/utils/extractBaseDomain"; -export const SESSION_COOKIE_NAME = config.server.session_cookie_name; +export const SESSION_COOKIE_NAME = config.getRawConfig().server.session_cookie_name; export const SESSION_COOKIE_EXPIRES = 1000 * 60 * 60 * 24 * 30; -export const SECURE_COOKIES = config.server.secure_cookies; -export const COOKIE_DOMAIN = "." + extractBaseDomain(config.app.base_url); +export const SECURE_COOKIES = config.getRawConfig().server.secure_cookies; +export const COOKIE_DOMAIN = "." + config.getBaseDomain(); export function generateSessionToken(): string { const bytes = new Uint8Array(20); diff --git a/server/auth/newt.ts b/server/auth/newt.ts index 97a0b4e2..f9aefe3b 100644 --- a/server/auth/newt.ts +++ b/server/auth/newt.ts @@ -9,12 +9,11 @@ import { Newt, newts, newtSessions, NewtSession } from "@server/db/schema"; import db from "@server/db"; import { eq } from "drizzle-orm"; import config from "@server/config"; -import { extractBaseDomain } from "@server/utils/extractBaseDomain"; export const SESSION_COOKIE_NAME = "session"; export const SESSION_COOKIE_EXPIRES = 1000 * 60 * 60 * 24 * 30; -export const SECURE_COOKIES = config.server.secure_cookies; -export const COOKIE_DOMAIN = "." + extractBaseDomain(config.app.base_url); +export const SECURE_COOKIES = config.getRawConfig().server.secure_cookies; +export const COOKIE_DOMAIN = "." + config.getBaseDomain(); export async function createNewtSession( token: string, diff --git a/server/auth/resource.ts b/server/auth/resource.ts index ef45d4b8..e325080a 100644 --- a/server/auth/resource.ts +++ b/server/auth/resource.ts @@ -8,12 +8,11 @@ import { import db from "@server/db"; import { eq, and } from "drizzle-orm"; import config from "@server/config"; -import { extractBaseDomain } from "@server/utils/extractBaseDomain"; export const SESSION_COOKIE_NAME = "resource_session"; export const SESSION_COOKIE_EXPIRES = 1000 * 60 * 60 * 24 * 30; -export const SECURE_COOKIES = config.server.secure_cookies; -export const COOKIE_DOMAIN = "." + extractBaseDomain(config.app.base_url); +export const SECURE_COOKIES = config.getRawConfig().server.secure_cookies; +export const COOKIE_DOMAIN = "." + config.getBaseDomain(); export async function createResourceSession(opts: { token: string; diff --git a/server/auth/resourceOtp.ts b/server/auth/resourceOtp.ts index 43d25776..420602f2 100644 --- a/server/auth/resourceOtp.ts +++ b/server/auth/resourceOtp.ts @@ -26,7 +26,7 @@ export async function sendResourceOtpEmail( }), { to: email, - from: config.email?.no_reply, + from: config.getRawConfig().email?.no_reply, subject: `Your one-time code to access ${resourceName}` } ); diff --git a/server/auth/sendEmailVerificationCode.ts b/server/auth/sendEmailVerificationCode.ts index 4d961d25..52d56f8a 100644 --- a/server/auth/sendEmailVerificationCode.ts +++ b/server/auth/sendEmailVerificationCode.ts @@ -17,11 +17,11 @@ export async function sendEmailVerificationCode( VerifyEmail({ username: email, verificationCode: code, - verifyLink: `${config.app.base_url}/auth/verify-email` + verifyLink: `${config.getRawConfig().app.base_url}/auth/verify-email` }), { to: email, - from: config.email?.no_reply, + from: config.getRawConfig().email?.no_reply, subject: "Verify your email address" } ); diff --git a/server/config.ts b/server/config.ts index b2c99971..435c57c9 100644 --- a/server/config.ts +++ b/server/config.ts @@ -1,14 +1,10 @@ import fs from "fs"; import yaml from "js-yaml"; import path from "path"; -import { fileURLToPath } from "url"; import { z } from "zod"; import { fromError } from "zod-validation-error"; - -export const __FILENAME = fileURLToPath(import.meta.url); -export const __DIRNAME = path.dirname(__FILENAME); - -export const APP_PATH = path.join("config"); +import { __DIRNAME, APP_PATH } from "@server/consts"; +import { loadAppVersion } from "@server/utils/loadAppVersion"; const portSchema = z.number().positive().gt(0).lte(65535); @@ -86,10 +82,6 @@ export class Config { this.loadConfig(); } - public getRawConfig() { - return this.rawConfig; - } - public loadConfig() { const loadConfig = (configPath: string) => { try { @@ -160,16 +152,11 @@ export class Config { throw new Error(`Invalid configuration file: ${errors}`); } - const packageJsonPath = path.join(__DIRNAME, "..", "package.json"); - let packageJson: any; - if (fs.existsSync && fs.existsSync(packageJsonPath)) { - const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8"); - packageJson = JSON.parse(packageJsonContent); - - if (packageJson.version) { - process.env.APP_VERSION = packageJson.version; - } + const appVersion = loadAppVersion(); + if (!appVersion) { + throw new Error("Could not load the application version"); } + process.env.APP_VERSION = appVersion; process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString(); process.env.SERVER_EXTERNAL_PORT = @@ -196,6 +183,22 @@ export class Config { this.rawConfig = parsedConfig.data; } + + public getRawConfig() { + return this.rawConfig; + } + + public getBaseDomain(): string { + const newUrl = new URL(this.rawConfig.app.base_url); + const hostname = newUrl.hostname; + const parts = hostname.split("."); + + if (parts.length <= 2) { + return parts.join("."); + } + + return parts.slice(1).join("."); + } } export const config = new Config(); diff --git a/server/consts.ts b/server/consts.ts new file mode 100644 index 00000000..156b334d --- /dev/null +++ b/server/consts.ts @@ -0,0 +1,8 @@ +import path from "path"; +import { fileURLToPath } from "url"; +import { existsSync } from "fs"; + +export const __FILENAME = fileURLToPath(import.meta.url); +export const __DIRNAME = path.dirname(__FILENAME); + +export const APP_PATH = path.join("config"); diff --git a/server/db/index.ts b/server/db/index.ts index ce164f97..586934c8 100644 --- a/server/db/index.ts +++ b/server/db/index.ts @@ -1,9 +1,9 @@ import { drizzle } from "drizzle-orm/better-sqlite3"; import Database from "better-sqlite3"; import * as schema from "@server/db/schema"; -import { APP_PATH } from "@server/config"; import path from "path"; import fs from "fs/promises"; +import { APP_PATH } from "@server/consts"; export const location = path.join(APP_PATH, "db", "db.sqlite"); export const exists = await checkFileExists(location); @@ -20,4 +20,4 @@ async function checkFileExists(filePath: string): Promise { } catch { return false; } -} \ No newline at end of file +} diff --git a/server/db/names.ts b/server/db/names.ts index 440895f8..d7d2d474 100644 --- a/server/db/names.ts +++ b/server/db/names.ts @@ -3,7 +3,7 @@ import { readFileSync } from "fs"; import { db } from "@server/db"; import { exitNodes, sites } from "./schema"; import { eq, and } from "drizzle-orm"; -import { __DIRNAME } from "@server/config"; +import { __DIRNAME } from "@server/consts"; // Load the names from the names.json file const dev = process.env.ENVIRONMENT !== "prod"; diff --git a/server/emails/index.ts b/server/emails/index.ts index d2ce5495..13b37c6f 100644 --- a/server/emails/index.ts +++ b/server/emails/index.ts @@ -5,25 +5,26 @@ import config from "@server/config"; import logger from "@server/logger"; function createEmailClient() { - if ( - !config.email?.smtp_host || - !config.email?.smtp_pass || - !config.email?.smtp_port || - !config.email?.smtp_user - ) { - logger.warn( - "Email SMTP configuration is missing. Emails will not be sent.", - ); - return; - } + const emailConfig = config.getRawConfig().email; +if ( + !emailConfig?.smtp_host || + !emailConfig?.smtp_pass || + !emailConfig?.smtp_port || + !emailConfig?.smtp_user +) { + logger.warn( + "Email SMTP configuration is missing. Emails will not be sent.", + ); + return; +} return nodemailer.createTransport({ - host: config.email.smtp_host, - port: config.email.smtp_port, + host: emailConfig.smtp_host, + port: emailConfig.smtp_port, secure: false, auth: { - user: config.email.smtp_user, - pass: config.email.smtp_pass, + user: emailConfig.smtp_user, + pass: emailConfig.smtp_pass, }, }); } diff --git a/server/index.ts b/server/index.ts index 73384d7b..364db07a 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1,8 +1,8 @@ +import { runSetupFunctions } from "./setup"; import { createApiServer } from "./apiServer"; import { createNextServer } from "./nextServer"; import { createInternalServer } from "./internalServer"; import { User, UserOrg } from "./db/schema"; -import { runSetupFunctions } from "./setup"; async function startServers() { await runSetupFunctions(); diff --git a/server/internalServer.ts b/server/internalServer.ts index 57b51278..c1bd6e10 100644 --- a/server/internalServer.ts +++ b/server/internalServer.ts @@ -10,7 +10,7 @@ import { } from "@server/middlewares"; import internal from "@server/routers/internal"; -const internalPort = config.server.internal_port; +const internalPort = config.getRawConfig().server.internal_port; export function createInternalServer() { const internalServer = express(); diff --git a/server/logger.ts b/server/logger.ts index 530a8105..9895f413 100644 --- a/server/logger.ts +++ b/server/logger.ts @@ -1,7 +1,8 @@ import "winston-daily-rotate-file"; -import config, { APP_PATH } from "@server/config"; +import config from "@server/config"; import * as winston from "winston"; import path from "path"; +import { APP_PATH } from "./consts"; const hformat = winston.format.printf( ({ level, label, message, timestamp, stack, ...metadata }) => { @@ -18,7 +19,7 @@ const hformat = winston.format.printf( const transports: any = [new winston.transports.Console({})]; -if (config.app.save_logs) { +if (config.getRawConfig().app.save_logs) { transports.push( new winston.transports.DailyRotateFile({ filename: path.join(APP_PATH, "logs", "pangolin-%DATE%.log"), @@ -49,7 +50,7 @@ if (config.app.save_logs) { } const logger = winston.createLogger({ - level: config.app.log_level.toLowerCase(), + level: config.getRawConfig().app.log_level.toLowerCase(), format: winston.format.combine( winston.format.errors({ stack: true }), winston.format.colorize(), diff --git a/server/middlewares/verifyUser.ts b/server/middlewares/verifyUser.ts index 8657b779..551c0c9e 100644 --- a/server/middlewares/verifyUser.ts +++ b/server/middlewares/verifyUser.ts @@ -34,7 +34,7 @@ export const verifySessionUserMiddleware = async ( if ( !existingUser[0].emailVerified && - config.flags?.require_email_verification + config.getRawConfig().flags?.require_email_verification ) { return next( createHttpError(HttpCode.BAD_REQUEST, "Email is not verified") // Might need to change the response type? diff --git a/server/nextServer.ts b/server/nextServer.ts index 11c1e57f..3a65eb27 100644 --- a/server/nextServer.ts +++ b/server/nextServer.ts @@ -4,7 +4,7 @@ import { parse } from "url"; import logger from "@server/logger"; import config from "@server/config"; -const nextPort = config.server.next_port; +const nextPort = config.getRawConfig().server.next_port; export async function createNextServer() { // const app = next({ dev }); diff --git a/server/routers/auth/disable2fa.ts b/server/routers/auth/disable2fa.ts index fd3213ad..2a8bbf82 100644 --- a/server/routers/auth/disable2fa.ts +++ b/server/routers/auth/disable2fa.ts @@ -99,7 +99,7 @@ export async function disable2fa( }), { to: user.email, - from: config.email?.no_reply, + from: config.getRawConfig().email?.no_reply, subject: "Two-factor authentication disabled" } ); diff --git a/server/routers/auth/login.ts b/server/routers/auth/login.ts index 41bd83ce..9a2dd72f 100644 --- a/server/routers/auth/login.ts +++ b/server/routers/auth/login.ts @@ -127,7 +127,7 @@ export async function login( if ( !existingUser.emailVerified && - config.flags?.require_email_verification + config.getRawConfig().flags?.require_email_verification ) { return response(res, { data: { emailVerificationRequired: true }, diff --git a/server/routers/auth/requestEmailVerificationCode.ts b/server/routers/auth/requestEmailVerificationCode.ts index a5b19b5e..06ded317 100644 --- a/server/routers/auth/requestEmailVerificationCode.ts +++ b/server/routers/auth/requestEmailVerificationCode.ts @@ -16,7 +16,7 @@ export async function requestEmailVerificationCode( res: Response, next: NextFunction ): Promise { - if (!config.flags?.require_email_verification) { + if (!config.getRawConfig().flags?.require_email_verification) { return next( createHttpError( HttpCode.BAD_REQUEST, diff --git a/server/routers/auth/requestPasswordReset.ts b/server/routers/auth/requestPasswordReset.ts index df57ec4b..f6bdfde5 100644 --- a/server/routers/auth/requestPasswordReset.ts +++ b/server/routers/auth/requestPasswordReset.ts @@ -82,7 +82,7 @@ export async function requestPasswordReset( }); }); - const url = `${config.app.base_url}/auth/reset-password?email=${email}&token=${token}`; + const url = `${config.getRawConfig().app.base_url}/auth/reset-password?email=${email}&token=${token}`; await sendEmail( ResetPasswordCode({ @@ -91,7 +91,7 @@ export async function requestPasswordReset( link: url }), { - from: config.email?.no_reply, + from: config.getRawConfig().email?.no_reply, to: email, subject: "Reset your password" } diff --git a/server/routers/auth/resetPassword.ts b/server/routers/auth/resetPassword.ts index 259e4e28..2e46a0ea 100644 --- a/server/routers/auth/resetPassword.ts +++ b/server/routers/auth/resetPassword.ts @@ -147,7 +147,7 @@ export async function resetPassword( }); await sendEmail(ConfirmPasswordReset({ email }), { - from: config.email?.no_reply, + from: config.getRawConfig().email?.no_reply, to: email, subject: "Password Reset Confirmation" }); diff --git a/server/routers/auth/signup.ts b/server/routers/auth/signup.ts index 11984961..63a9ce6c 100644 --- a/server/routers/auth/signup.ts +++ b/server/routers/auth/signup.ts @@ -60,7 +60,7 @@ export async function signup( const passwordHash = await hashPassword(password); const userId = generateId(15); - if (config.flags?.disable_signup_without_invite) { + if (config.getRawConfig().flags?.disable_signup_without_invite) { if (!inviteToken || !inviteId) { return next( createHttpError( @@ -102,7 +102,7 @@ export async function signup( .where(eq(users.email, email)); if (existing && existing.length > 0) { - if (!config.flags?.require_email_verification) { + if (!config.getRawConfig().flags?.require_email_verification) { return next( createHttpError( HttpCode.BAD_REQUEST, @@ -163,7 +163,7 @@ export async function signup( const cookie = serializeSessionCookie(token); res.appendHeader("Set-Cookie", cookie); - if (config.flags?.require_email_verification) { + if (config.getRawConfig().flags?.require_email_verification) { sendEmailVerificationCode(email, userId); return response(res, { diff --git a/server/routers/auth/verifyEmail.ts b/server/routers/auth/verifyEmail.ts index a73983ce..b7a85cfd 100644 --- a/server/routers/auth/verifyEmail.ts +++ b/server/routers/auth/verifyEmail.ts @@ -28,7 +28,7 @@ export async function verifyEmail( res: Response, next: NextFunction ): Promise { - if (!config.flags?.require_email_verification) { + if (!config.getRawConfig().flags?.require_email_verification) { return next( createHttpError( HttpCode.BAD_REQUEST, diff --git a/server/routers/auth/verifyTotp.ts b/server/routers/auth/verifyTotp.ts index 311042ee..79d0e899 100644 --- a/server/routers/auth/verifyTotp.ts +++ b/server/routers/auth/verifyTotp.ts @@ -111,7 +111,7 @@ export async function verifyTotp( }), { to: user.email, - from: config.email?.no_reply, + from: config.getRawConfig().email?.no_reply, subject: "Two-factor authentication enabled" } ); diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index 734a2339..58033587 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -101,13 +101,13 @@ export async function verifyResourceSession( return allowed(res); } - const redirectUrl = `${config.app.base_url}/auth/resource/${encodeURIComponent(resource.resourceId)}?redirect=${encodeURIComponent(originalRequestURL)}`; + const redirectUrl = `${config.getRawConfig().app.base_url}/auth/resource/${encodeURIComponent(resource.resourceId)}?redirect=${encodeURIComponent(originalRequestURL)}`; if (!sessions) { return notAllowed(res); } - const sessionToken = sessions[config.server.session_cookie_name]; + const sessionToken = sessions[config.getRawConfig().server.session_cookie_name]; // check for unified login if (sso && sessionToken) { @@ -129,7 +129,7 @@ export async function verifyResourceSession( const resourceSessionToken = sessions[ - `${config.server.resource_session_cookie_name}_${resource.resourceId}` + `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}` ]; if (resourceSessionToken) { @@ -213,7 +213,7 @@ async function isUserAllowedToAccessResource( user: User, resource: Resource ): Promise { - if (config.flags?.require_email_verification && !user.emailVerified) { + if (config.getRawConfig().flags?.require_email_verification && !user.emailVerified) { return false; } diff --git a/server/routers/external.ts b/server/routers/external.ts index f9d3e62d..5736a76f 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -423,11 +423,11 @@ unauthenticated.use("/auth", authRouter); authRouter.use( rateLimitMiddleware({ windowMin: - config.rate_limits.auth?.window_minutes || - config.rate_limits.global.window_minutes, + config.getRawConfig().rate_limits.auth?.window_minutes || + config.getRawConfig().rate_limits.global.window_minutes, max: - config.rate_limits.auth?.max_requests || - config.rate_limits.global.max_requests, + config.getRawConfig().rate_limits.auth?.max_requests || + config.getRawConfig().rate_limits.global.max_requests, type: "IP_AND_PATH" }) ); diff --git a/server/routers/gerbil/getConfig.ts b/server/routers/gerbil/getConfig.ts index c6663fc2..b30eee26 100644 --- a/server/routers/gerbil/getConfig.ts +++ b/server/routers/gerbil/getConfig.ts @@ -52,14 +52,14 @@ export async function getConfig(req: Request, res: Response, next: NextFunction) const address = await getNextAvailableSubnet(); const listenPort = await getNextAvailablePort(); let subEndpoint = ""; - if (config.gerbil.use_subdomain) { + if (config.getRawConfig().gerbil.use_subdomain) { subEndpoint = await getUniqueExitNodeEndpointName(); } // create a new exit node exitNode = await db.insert(exitNodes).values({ publicKey, - endpoint: `${subEndpoint}${subEndpoint != "" ? "." : ""}${config.gerbil.base_endpoint}`, + endpoint: `${subEndpoint}${subEndpoint != "" ? "." : ""}${config.getRawConfig().gerbil.base_endpoint}`, address, listenPort, reachableAt, @@ -122,7 +122,7 @@ async function getNextAvailableSubnet(): Promise { }).from(exitNodes); const addresses = existingAddresses.map(a => a.address); - let subnet = findNextAvailableCidr(addresses, config.gerbil.block_size, config.gerbil.subnet_group); + let subnet = findNextAvailableCidr(addresses, config.getRawConfig().gerbil.block_size, config.getRawConfig().gerbil.subnet_group); if (!subnet) { throw new Error('No available subnets remaining in space'); } @@ -139,7 +139,7 @@ async function getNextAvailablePort(): Promise { }).from(exitNodes); // Find the first available port between 1024 and 65535 - let nextPort = config.gerbil.start_port; + let nextPort = config.getRawConfig().gerbil.start_port; for (const port of existingPorts) { if (port.listenPort > nextPort) { break; diff --git a/server/routers/org/createOrg.ts b/server/routers/org/createOrg.ts index ea0b01d5..a471be39 100644 --- a/server/routers/org/createOrg.ts +++ b/server/routers/org/createOrg.ts @@ -11,7 +11,6 @@ import { createAdminRole } from "@server/setup/ensureActions"; import config from "@server/config"; import { fromError } from "zod-validation-error"; import { defaultRoleAllowedActions } from "../role"; -import { extractBaseDomain } from "@server/utils/extractBaseDomain"; const createOrgSchema = z .object({ @@ -30,7 +29,7 @@ export async function createOrg( ): Promise { try { // should this be in a middleware? - if (config.flags?.disable_user_create_org) { + if (config.getRawConfig().flags?.disable_user_create_org) { if (!req.user?.serverAdmin) { return next( createHttpError( @@ -83,8 +82,8 @@ export async function createOrg( let org: Org | null = null; await db.transaction(async (trx) => { - // create a url from config.app.base_url and get the hostname - const domain = extractBaseDomain(config.app.base_url); + // create a url from config.getRawConfig().app.base_url and get the hostname + const domain = config.getBaseDomain(); const newOrg = await trx .insert(orgs) diff --git a/server/routers/resource/authWithAccessToken.ts b/server/routers/resource/authWithAccessToken.ts index 5256020a..e79bfef7 100644 --- a/server/routers/resource/authWithAccessToken.ts +++ b/server/routers/resource/authWithAccessToken.ts @@ -134,7 +134,7 @@ export async function authWithAccessToken( expiresAt: tokenItem.expiresAt, doNotExtend: tokenItem.expiresAt ? true : false }); - const cookieName = `${config.server.resource_session_cookie_name}_${resource.resourceId}`; + const cookieName = `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}`; const cookie = serializeResourceSessionCookie(cookieName, token); res.appendHeader("Set-Cookie", cookie); diff --git a/server/routers/resource/authWithPassword.ts b/server/routers/resource/authWithPassword.ts index 47c8c050..bbcfff84 100644 --- a/server/routers/resource/authWithPassword.ts +++ b/server/routers/resource/authWithPassword.ts @@ -122,7 +122,7 @@ export async function authWithPassword( token, passwordId: definedPassword.passwordId }); - const cookieName = `${config.server.resource_session_cookie_name}_${resource.resourceId}`; + const cookieName = `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}`; const cookie = serializeResourceSessionCookie(cookieName, token); res.appendHeader("Set-Cookie", cookie); diff --git a/server/routers/resource/authWithPincode.ts b/server/routers/resource/authWithPincode.ts index 773049a3..a0ac53d2 100644 --- a/server/routers/resource/authWithPincode.ts +++ b/server/routers/resource/authWithPincode.ts @@ -133,7 +133,7 @@ export async function authWithPincode( token, pincodeId: definedPincode.pincodeId }); - const cookieName = `${config.server.resource_session_cookie_name}_${resource.resourceId}`; + const cookieName = `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}`; const cookie = serializeResourceSessionCookie(cookieName, token); res.appendHeader("Set-Cookie", cookie); diff --git a/server/routers/resource/authWithWhitelist.ts b/server/routers/resource/authWithWhitelist.ts index 3f490ee2..252c9eb2 100644 --- a/server/routers/resource/authWithWhitelist.ts +++ b/server/routers/resource/authWithWhitelist.ts @@ -177,7 +177,7 @@ export async function authWithWhitelist( token, whitelistId: whitelistedEmail.whitelistId }); - const cookieName = `${config.server.resource_session_cookie_name}_${resource.resourceId}`; + const cookieName = `${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}`; const cookie = serializeResourceSessionCookie(cookieName, token); res.appendHeader("Set-Cookie", cookie); diff --git a/server/routers/traefik/getTraefikConfig.ts b/server/routers/traefik/getTraefikConfig.ts index d8624c82..fa4ba157 100644 --- a/server/routers/traefik/getTraefikConfig.ts +++ b/server/routers/traefik/getTraefikConfig.ts @@ -50,12 +50,12 @@ export async function traefikConfigProvider( [badgerMiddlewareName]: { apiBaseUrl: new URL( "/api/v1", - `http://${config.server.internal_hostname}:${config.server.internal_port}`, + `http://${config.getRawConfig().server.internal_hostname}:${config.getRawConfig().server.internal_port}`, ).href, resourceSessionCookieName: - config.server.resource_session_cookie_name, + config.getRawConfig().server.resource_session_cookie_name, userSessionCookieName: - config.server.session_cookie_name, + config.getRawConfig().server.session_cookie_name, }, }, }, @@ -95,8 +95,8 @@ export async function traefikConfigProvider( } const tls = { - certResolver: config.traefik.cert_resolver, - ...(config.traefik.prefer_wildcard_cert + certResolver: config.getRawConfig().traefik.cert_resolver, + ...(config.getRawConfig().traefik.prefer_wildcard_cert ? { domains: [ { @@ -110,8 +110,8 @@ export async function traefikConfigProvider( http.routers![routerName] = { entryPoints: [ resource.ssl - ? config.traefik.https_entrypoint - : config.traefik.http_entrypoint, + ? config.getRawConfig().traefik.https_entrypoint + : config.getRawConfig().traefik.http_entrypoint, ], middlewares: [badgerMiddlewareName], service: serviceName, @@ -122,7 +122,7 @@ export async function traefikConfigProvider( if (resource.ssl) { // this is a redirect router; all it does is redirect to the https version if tls is enabled http.routers![routerName + "-redirect"] = { - entryPoints: [config.traefik.http_entrypoint], + entryPoints: [config.getRawConfig().traefik.http_entrypoint], middlewares: [redirectMiddlewareName], service: serviceName, rule: `Host(\`${fullDomain}\`)`, diff --git a/server/routers/user/inviteUser.ts b/server/routers/user/inviteUser.ts index 2073142f..e44838bc 100644 --- a/server/routers/user/inviteUser.ts +++ b/server/routers/user/inviteUser.ts @@ -152,7 +152,7 @@ export async function inviteUser( }); }); - const inviteLink = `${config.app.base_url}/invite?token=${inviteId}-${token}`; + const inviteLink = `${config.getRawConfig().app.base_url}/invite?token=${inviteId}-${token}`; if (doEmail) { await sendEmail( @@ -165,7 +165,7 @@ export async function inviteUser( }), { to: email, - from: config.email?.no_reply, + from: config.getRawConfig().email?.no_reply, subject: "You're invited to join a Fossorial organization" } ); diff --git a/server/setup/copyInConfig.ts b/server/setup/copyInConfig.ts index 68e44c4a..3eab20ba 100644 --- a/server/setup/copyInConfig.ts +++ b/server/setup/copyInConfig.ts @@ -3,11 +3,10 @@ import { orgs } from "../db/schema"; import config from "@server/config"; import { ne } from "drizzle-orm"; import logger from "@server/logger"; -import { extractBaseDomain } from "@server/utils/extractBaseDomain"; export async function copyInConfig() { - // create a url from config.app.base_url and get the hostname - const domain = extractBaseDomain(config.app.base_url); + // create a url from config.getRawConfig().app.base_url and get the hostname + const domain = config.getBaseDomain(); // update the domain on all of the orgs where the domain is not equal to the new domain // TODO: eventually each org could have a unique domain that we do not want to overwrite, so this will be unnecessary diff --git a/server/setup/index.ts b/server/setup/index.ts index ebc40c6e..51d87283 100644 --- a/server/setup/index.ts +++ b/server/setup/index.ts @@ -1,23 +1,15 @@ import { ensureActions } from "./ensureActions"; import { copyInConfig } from "./copyInConfig"; -import { runMigrations } from "./migrations"; import { setupServerAdmin } from "./setupServerAdmin"; -import { loadConfig } from "@server/config"; +import logger from "@server/logger"; export async function runSetupFunctions() { try { - await runMigrations(); // run the migrations - - console.log("Migrations completed successfully.") - - // ANYTHING BEFORE THIS LINE CANNOT USE THE CONFIG - loadConfig(); - await copyInConfig(); // copy in the config to the db as needed await setupServerAdmin(); await ensureActions(); // make sure all of the actions are in the db and the roles } catch (error) { - console.error("Error running setup functions:", error); + logger.error("Error running setup functions:", error); process.exit(1); } } diff --git a/server/setup/migrations.ts b/server/setup/migrations.ts index 2883ed27..f6ea3504 100644 --- a/server/setup/migrations.ts +++ b/server/setup/migrations.ts @@ -1,14 +1,15 @@ -import { __DIRNAME } from "@server/config"; import { migrate } from "drizzle-orm/better-sqlite3/migrator"; import db, { exists } from "@server/db"; import path from "path"; import semver from "semver"; import { versionMigrations } from "@server/db/schema"; import { desc } from "drizzle-orm"; - -// Import all migrations explicitly +import { __DIRNAME } from "@server/consts"; +import { loadAppVersion } from "@server/utils/loadAppVersion"; import m1 from "./scripts/1.0.0-beta1"; -// Add new migration imports here as they are created + +// THIS CANNOT IMPORT ANYTHING FROM THE SERVER +// EXCEPT FOR THE DATABASE AND THE SCHEMA // Define the migration list with versions and their corresponding functions const migrations = [ @@ -16,34 +17,32 @@ const migrations = [ // Add new migrations here as they are created ] as const; -export async function runMigrations() { - if (!process.env.APP_VERSION) { - throw new Error("APP_VERSION is not set in the environment"); - } +// Run the migrations +await runMigrations(); - if (process.env.ENVIRONMENT !== "prod") { - console.info("Skipping migrations in non-prod environment"); - return; +export async function runMigrations() { + const appVersion = loadAppVersion(); + if (!appVersion) { + throw new Error("APP_VERSION is not set in the environment"); } if (exists) { await executeScripts(); } else { - console.info("Running migrations..."); + console.log("Running migrations..."); try { migrate(db, { migrationsFolder: path.join(__DIRNAME, "init") // put here during the docker build }); - console.info("Migrations completed successfully."); + console.log("Migrations completed successfully."); } catch (error) { console.error("Error running migrations:", error); } - // insert process.env.APP_VERSION into the versionMigrations table await db .insert(versionMigrations) .values({ - version: process.env.APP_VERSION, + version: appVersion, executedAt: Date.now() }) .execute(); @@ -60,7 +59,7 @@ async function executeScripts() { .limit(1); const startVersion = lastExecuted[0]?.version ?? "0.0.0"; - console.info(`Starting migrations from version ${startVersion}`); + console.log(`Starting migrations from version ${startVersion}`); // Filter and sort migrations const pendingMigrations = migrations @@ -69,7 +68,7 @@ async function executeScripts() { // Run migrations in order for (const migration of pendingMigrations) { - console.info(`Running migration ${migration.version}`); + console.log(`Running migration ${migration.version}`); try { await migration.run(); @@ -83,7 +82,7 @@ async function executeScripts() { }) .execute(); - console.info( + console.log( `Successfully completed migration ${migration.version}` ); } catch (error) { @@ -95,7 +94,7 @@ async function executeScripts() { } } - console.info("All migrations completed successfully"); + console.log("All migrations completed successfully"); } catch (error) { console.error("Migration process failed:", error); throw error; diff --git a/server/setup/scripts/1.0.0-beta1.ts b/server/setup/scripts/1.0.0-beta1.ts index 70f2db71..1a564836 100644 --- a/server/setup/scripts/1.0.0-beta1.ts +++ b/server/setup/scripts/1.0.0-beta1.ts @@ -1,7 +1,7 @@ import logger from "@server/logger"; export default async function migration() { - logger.info("Running setup script 1.0.0-beta.1"); + console.log("Running setup script 1.0.0-beta.1"); // SQL operations would go here in ts format - logger.info("Done..."); + console.log("Done..."); } diff --git a/server/setup/setupServerAdmin.ts b/server/setup/setupServerAdmin.ts index f5291903..d4482158 100644 --- a/server/setup/setupServerAdmin.ts +++ b/server/setup/setupServerAdmin.ts @@ -12,7 +12,7 @@ import { fromError } from "zod-validation-error"; export async function setupServerAdmin() { const { server_admin: { email, password } - } = config.users; + } = config.getRawConfig().users; const parsed = passwordSchema.safeParse(password); diff --git a/server/utils/extractBaseDomain.ts b/server/utils/extractBaseDomain.ts deleted file mode 100644 index cbb12381..00000000 --- a/server/utils/extractBaseDomain.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function extractBaseDomain(url: string): string { - const newUrl = new URL(url); - const hostname = newUrl.hostname; - const parts = hostname.split("."); - - if (parts.length <= 2) { - return parts.join("."); - } - - return parts.slice(1).join("."); -} diff --git a/server/utils/loadAppVersion.ts b/server/utils/loadAppVersion.ts new file mode 100644 index 00000000..425eae60 --- /dev/null +++ b/server/utils/loadAppVersion.ts @@ -0,0 +1,16 @@ +import path from "path"; +import { __DIRNAME } from "@server/consts"; +import fs from "fs"; + +export function loadAppVersion() { + const packageJsonPath = path.join(__DIRNAME, "..", "package.json"); + let packageJson: any; + if (fs.existsSync && fs.existsSync(packageJsonPath)) { + const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8"); + packageJson = JSON.parse(packageJsonContent); + + if (packageJson.version) { + return packageJson.version; + } + } +} diff --git a/src/app/auth/resource/[resourceId]/page.tsx b/src/app/auth/resource/[resourceId]/page.tsx index aee798cd..4365de84 100644 --- a/src/app/auth/resource/[resourceId]/page.tsx +++ b/src/app/auth/resource/[resourceId]/page.tsx @@ -42,6 +42,7 @@ export default async function ResourceAuthPage(props: { const user = await getUser({ skipCheckVerifyEmail: true }); if (!authInfo) { + {/* @ts-ignore */} // TODO: fix this return (
diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 19b3340c..e05b9c1e 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -48,17 +48,19 @@ export function Header({ orgId, orgs }: HeaderProps) {
Documentation - Support - +