mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-29 14:18:26 +02:00
make config class and separate migrations script
This commit is contained in:
parent
b199595100
commit
9732098799
45 changed files with 163 additions and 156 deletions
|
@ -1,5 +1,5 @@
|
||||||
|
import { APP_PATH } from "@server/consts";
|
||||||
import { defineConfig } from "drizzle-kit";
|
import { defineConfig } from "drizzle-kit";
|
||||||
import config, { APP_PATH } from "@server/config";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:push": "npx tsx server/db/migrate.ts",
|
"db:push": "npx tsx server/db/migrate.ts",
|
||||||
"db:studio": "drizzle-kit studio",
|
"db:studio": "drizzle-kit studio",
|
||||||
"build": "mkdir -p dist && next build && node esbuild.mjs -e server/index.ts -o 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 NODE_OPTIONS=--enable-source-maps node dist/server.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"
|
"email": "email dev --dir server/emails/templates --port 3005"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { csrfProtectionMiddleware } from "./middlewares/csrfProtection";
|
||||||
import helmet from "helmet";
|
import helmet from "helmet";
|
||||||
|
|
||||||
const dev = process.env.ENVIRONMENT !== "prod";
|
const dev = process.env.ENVIRONMENT !== "prod";
|
||||||
const externalPort = config.server.external_port;
|
const externalPort = config.getRawConfig().server.external_port;
|
||||||
|
|
||||||
export function createApiServer() {
|
export function createApiServer() {
|
||||||
const apiServer = express();
|
const apiServer = express();
|
||||||
|
@ -25,13 +25,13 @@ export function createApiServer() {
|
||||||
if (dev) {
|
if (dev) {
|
||||||
apiServer.use(
|
apiServer.use(
|
||||||
cors({
|
cors({
|
||||||
origin: `http://localhost:${config.server.next_port}`,
|
origin: `http://localhost:${config.getRawConfig().server.next_port}`,
|
||||||
credentials: true
|
credentials: true
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const corsOptions = {
|
const corsOptions = {
|
||||||
origin: config.app.base_url,
|
origin: config.getRawConfig().app.base_url,
|
||||||
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
||||||
allowedHeaders: ["Content-Type", "X-CSRF-Token"]
|
allowedHeaders: ["Content-Type", "X-CSRF-Token"]
|
||||||
};
|
};
|
||||||
|
@ -47,8 +47,8 @@ export function createApiServer() {
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
apiServer.use(
|
apiServer.use(
|
||||||
rateLimitMiddleware({
|
rateLimitMiddleware({
|
||||||
windowMin: config.rate_limits.global.window_minutes,
|
windowMin: config.getRawConfig().rate_limits.global.window_minutes,
|
||||||
max: config.rate_limits.global.max_requests,
|
max: config.getRawConfig().rate_limits.global.max_requests,
|
||||||
type: "IP_AND_PATH"
|
type: "IP_AND_PATH"
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,12 +12,11 @@ import { eq } from "drizzle-orm";
|
||||||
import config from "@server/config";
|
import config from "@server/config";
|
||||||
import type { RandomReader } from "@oslojs/crypto/random";
|
import type { RandomReader } from "@oslojs/crypto/random";
|
||||||
import { generateRandomString } 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 SESSION_COOKIE_EXPIRES = 1000 * 60 * 60 * 24 * 30;
|
||||||
export const SECURE_COOKIES = config.server.secure_cookies;
|
export const SECURE_COOKIES = config.getRawConfig().server.secure_cookies;
|
||||||
export const COOKIE_DOMAIN = "." + extractBaseDomain(config.app.base_url);
|
export const COOKIE_DOMAIN = "." + config.getBaseDomain();
|
||||||
|
|
||||||
export function generateSessionToken(): string {
|
export function generateSessionToken(): string {
|
||||||
const bytes = new Uint8Array(20);
|
const bytes = new Uint8Array(20);
|
||||||
|
|
|
@ -9,12 +9,11 @@ import { Newt, newts, newtSessions, NewtSession } from "@server/db/schema";
|
||||||
import db from "@server/db";
|
import db from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import config from "@server/config";
|
import config from "@server/config";
|
||||||
import { extractBaseDomain } from "@server/utils/extractBaseDomain";
|
|
||||||
|
|
||||||
export const SESSION_COOKIE_NAME = "session";
|
export const SESSION_COOKIE_NAME = "session";
|
||||||
export const SESSION_COOKIE_EXPIRES = 1000 * 60 * 60 * 24 * 30;
|
export const SESSION_COOKIE_EXPIRES = 1000 * 60 * 60 * 24 * 30;
|
||||||
export const SECURE_COOKIES = config.server.secure_cookies;
|
export const SECURE_COOKIES = config.getRawConfig().server.secure_cookies;
|
||||||
export const COOKIE_DOMAIN = "." + extractBaseDomain(config.app.base_url);
|
export const COOKIE_DOMAIN = "." + config.getBaseDomain();
|
||||||
|
|
||||||
export async function createNewtSession(
|
export async function createNewtSession(
|
||||||
token: string,
|
token: string,
|
||||||
|
|
|
@ -8,12 +8,11 @@ import {
|
||||||
import db from "@server/db";
|
import db from "@server/db";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import config from "@server/config";
|
import config from "@server/config";
|
||||||
import { extractBaseDomain } from "@server/utils/extractBaseDomain";
|
|
||||||
|
|
||||||
export const SESSION_COOKIE_NAME = "resource_session";
|
export const SESSION_COOKIE_NAME = "resource_session";
|
||||||
export const SESSION_COOKIE_EXPIRES = 1000 * 60 * 60 * 24 * 30;
|
export const SESSION_COOKIE_EXPIRES = 1000 * 60 * 60 * 24 * 30;
|
||||||
export const SECURE_COOKIES = config.server.secure_cookies;
|
export const SECURE_COOKIES = config.getRawConfig().server.secure_cookies;
|
||||||
export const COOKIE_DOMAIN = "." + extractBaseDomain(config.app.base_url);
|
export const COOKIE_DOMAIN = "." + config.getBaseDomain();
|
||||||
|
|
||||||
export async function createResourceSession(opts: {
|
export async function createResourceSession(opts: {
|
||||||
token: string;
|
token: string;
|
||||||
|
|
|
@ -26,7 +26,7 @@ export async function sendResourceOtpEmail(
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
to: email,
|
to: email,
|
||||||
from: config.email?.no_reply,
|
from: config.getRawConfig().email?.no_reply,
|
||||||
subject: `Your one-time code to access ${resourceName}`
|
subject: `Your one-time code to access ${resourceName}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,11 +17,11 @@ export async function sendEmailVerificationCode(
|
||||||
VerifyEmail({
|
VerifyEmail({
|
||||||
username: email,
|
username: email,
|
||||||
verificationCode: code,
|
verificationCode: code,
|
||||||
verifyLink: `${config.app.base_url}/auth/verify-email`
|
verifyLink: `${config.getRawConfig().app.base_url}/auth/verify-email`
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
to: email,
|
to: email,
|
||||||
from: config.email?.no_reply,
|
from: config.getRawConfig().email?.no_reply,
|
||||||
subject: "Verify your email address"
|
subject: "Verify your email address"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import yaml from "js-yaml";
|
import yaml from "js-yaml";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { fileURLToPath } from "url";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
import { __DIRNAME, APP_PATH } from "@server/consts";
|
||||||
export const __FILENAME = fileURLToPath(import.meta.url);
|
import { loadAppVersion } from "@server/utils/loadAppVersion";
|
||||||
export const __DIRNAME = path.dirname(__FILENAME);
|
|
||||||
|
|
||||||
export const APP_PATH = path.join("config");
|
|
||||||
|
|
||||||
const portSchema = z.number().positive().gt(0).lte(65535);
|
const portSchema = z.number().positive().gt(0).lte(65535);
|
||||||
|
|
||||||
|
@ -86,10 +82,6 @@ export class Config {
|
||||||
this.loadConfig();
|
this.loadConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRawConfig() {
|
|
||||||
return this.rawConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
public loadConfig() {
|
public loadConfig() {
|
||||||
const loadConfig = (configPath: string) => {
|
const loadConfig = (configPath: string) => {
|
||||||
try {
|
try {
|
||||||
|
@ -160,16 +152,11 @@ export class Config {
|
||||||
throw new Error(`Invalid configuration file: ${errors}`);
|
throw new Error(`Invalid configuration file: ${errors}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const packageJsonPath = path.join(__DIRNAME, "..", "package.json");
|
const appVersion = loadAppVersion();
|
||||||
let packageJson: any;
|
if (!appVersion) {
|
||||||
if (fs.existsSync && fs.existsSync(packageJsonPath)) {
|
throw new Error("Could not load the application version");
|
||||||
const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8");
|
|
||||||
packageJson = JSON.parse(packageJsonContent);
|
|
||||||
|
|
||||||
if (packageJson.version) {
|
|
||||||
process.env.APP_VERSION = packageJson.version;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
process.env.APP_VERSION = appVersion;
|
||||||
|
|
||||||
process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString();
|
process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString();
|
||||||
process.env.SERVER_EXTERNAL_PORT =
|
process.env.SERVER_EXTERNAL_PORT =
|
||||||
|
@ -196,6 +183,22 @@ export class Config {
|
||||||
|
|
||||||
this.rawConfig = parsedConfig.data;
|
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();
|
export const config = new Config();
|
||||||
|
|
8
server/consts.ts
Normal file
8
server/consts.ts
Normal file
|
@ -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");
|
|
@ -1,9 +1,9 @@
|
||||||
import { drizzle } from "drizzle-orm/better-sqlite3";
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
||||||
import Database from "better-sqlite3";
|
import Database from "better-sqlite3";
|
||||||
import * as schema from "@server/db/schema";
|
import * as schema from "@server/db/schema";
|
||||||
import { APP_PATH } from "@server/config";
|
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
|
import { APP_PATH } from "@server/consts";
|
||||||
|
|
||||||
export const location = path.join(APP_PATH, "db", "db.sqlite");
|
export const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||||
export const exists = await checkFileExists(location);
|
export const exists = await checkFileExists(location);
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { readFileSync } from "fs";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { exitNodes, sites } from "./schema";
|
import { exitNodes, sites } from "./schema";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { __DIRNAME } from "@server/config";
|
import { __DIRNAME } from "@server/consts";
|
||||||
|
|
||||||
// Load the names from the names.json file
|
// Load the names from the names.json file
|
||||||
const dev = process.env.ENVIRONMENT !== "prod";
|
const dev = process.env.ENVIRONMENT !== "prod";
|
||||||
|
|
|
@ -5,25 +5,26 @@ import config from "@server/config";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
||||||
function createEmailClient() {
|
function createEmailClient() {
|
||||||
if (
|
const emailConfig = config.getRawConfig().email;
|
||||||
!config.email?.smtp_host ||
|
if (
|
||||||
!config.email?.smtp_pass ||
|
!emailConfig?.smtp_host ||
|
||||||
!config.email?.smtp_port ||
|
!emailConfig?.smtp_pass ||
|
||||||
!config.email?.smtp_user
|
!emailConfig?.smtp_port ||
|
||||||
) {
|
!emailConfig?.smtp_user
|
||||||
logger.warn(
|
) {
|
||||||
"Email SMTP configuration is missing. Emails will not be sent.",
|
logger.warn(
|
||||||
);
|
"Email SMTP configuration is missing. Emails will not be sent.",
|
||||||
return;
|
);
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return nodemailer.createTransport({
|
return nodemailer.createTransport({
|
||||||
host: config.email.smtp_host,
|
host: emailConfig.smtp_host,
|
||||||
port: config.email.smtp_port,
|
port: emailConfig.smtp_port,
|
||||||
secure: false,
|
secure: false,
|
||||||
auth: {
|
auth: {
|
||||||
user: config.email.smtp_user,
|
user: emailConfig.smtp_user,
|
||||||
pass: config.email.smtp_pass,
|
pass: emailConfig.smtp_pass,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
import { runSetupFunctions } from "./setup";
|
||||||
import { createApiServer } from "./apiServer";
|
import { createApiServer } from "./apiServer";
|
||||||
import { createNextServer } from "./nextServer";
|
import { createNextServer } from "./nextServer";
|
||||||
import { createInternalServer } from "./internalServer";
|
import { createInternalServer } from "./internalServer";
|
||||||
import { User, UserOrg } from "./db/schema";
|
import { User, UserOrg } from "./db/schema";
|
||||||
import { runSetupFunctions } from "./setup";
|
|
||||||
|
|
||||||
async function startServers() {
|
async function startServers() {
|
||||||
await runSetupFunctions();
|
await runSetupFunctions();
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
} from "@server/middlewares";
|
} from "@server/middlewares";
|
||||||
import internal from "@server/routers/internal";
|
import internal from "@server/routers/internal";
|
||||||
|
|
||||||
const internalPort = config.server.internal_port;
|
const internalPort = config.getRawConfig().server.internal_port;
|
||||||
|
|
||||||
export function createInternalServer() {
|
export function createInternalServer() {
|
||||||
const internalServer = express();
|
const internalServer = express();
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import "winston-daily-rotate-file";
|
import "winston-daily-rotate-file";
|
||||||
import config, { APP_PATH } from "@server/config";
|
import config from "@server/config";
|
||||||
import * as winston from "winston";
|
import * as winston from "winston";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import { APP_PATH } from "./consts";
|
||||||
|
|
||||||
const hformat = winston.format.printf(
|
const hformat = winston.format.printf(
|
||||||
({ level, label, message, timestamp, stack, ...metadata }) => {
|
({ level, label, message, timestamp, stack, ...metadata }) => {
|
||||||
|
@ -18,7 +19,7 @@ const hformat = winston.format.printf(
|
||||||
|
|
||||||
const transports: any = [new winston.transports.Console({})];
|
const transports: any = [new winston.transports.Console({})];
|
||||||
|
|
||||||
if (config.app.save_logs) {
|
if (config.getRawConfig().app.save_logs) {
|
||||||
transports.push(
|
transports.push(
|
||||||
new winston.transports.DailyRotateFile({
|
new winston.transports.DailyRotateFile({
|
||||||
filename: path.join(APP_PATH, "logs", "pangolin-%DATE%.log"),
|
filename: path.join(APP_PATH, "logs", "pangolin-%DATE%.log"),
|
||||||
|
@ -49,7 +50,7 @@ if (config.app.save_logs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const logger = winston.createLogger({
|
const logger = winston.createLogger({
|
||||||
level: config.app.log_level.toLowerCase(),
|
level: config.getRawConfig().app.log_level.toLowerCase(),
|
||||||
format: winston.format.combine(
|
format: winston.format.combine(
|
||||||
winston.format.errors({ stack: true }),
|
winston.format.errors({ stack: true }),
|
||||||
winston.format.colorize(),
|
winston.format.colorize(),
|
||||||
|
|
|
@ -34,7 +34,7 @@ export const verifySessionUserMiddleware = async (
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!existingUser[0].emailVerified &&
|
!existingUser[0].emailVerified &&
|
||||||
config.flags?.require_email_verification
|
config.getRawConfig().flags?.require_email_verification
|
||||||
) {
|
) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(HttpCode.BAD_REQUEST, "Email is not verified") // Might need to change the response type?
|
createHttpError(HttpCode.BAD_REQUEST, "Email is not verified") // Might need to change the response type?
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { parse } from "url";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import config from "@server/config";
|
import config from "@server/config";
|
||||||
|
|
||||||
const nextPort = config.server.next_port;
|
const nextPort = config.getRawConfig().server.next_port;
|
||||||
|
|
||||||
export async function createNextServer() {
|
export async function createNextServer() {
|
||||||
// const app = next({ dev });
|
// const app = next({ dev });
|
||||||
|
|
|
@ -99,7 +99,7 @@ export async function disable2fa(
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
to: user.email,
|
to: user.email,
|
||||||
from: config.email?.no_reply,
|
from: config.getRawConfig().email?.no_reply,
|
||||||
subject: "Two-factor authentication disabled"
|
subject: "Two-factor authentication disabled"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -127,7 +127,7 @@ export async function login(
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!existingUser.emailVerified &&
|
!existingUser.emailVerified &&
|
||||||
config.flags?.require_email_verification
|
config.getRawConfig().flags?.require_email_verification
|
||||||
) {
|
) {
|
||||||
return response<LoginResponse>(res, {
|
return response<LoginResponse>(res, {
|
||||||
data: { emailVerificationRequired: true },
|
data: { emailVerificationRequired: true },
|
||||||
|
|
|
@ -16,7 +16,7 @@ export async function requestEmailVerificationCode(
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
if (!config.flags?.require_email_verification) {
|
if (!config.getRawConfig().flags?.require_email_verification) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
|
|
|
@ -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(
|
await sendEmail(
|
||||||
ResetPasswordCode({
|
ResetPasswordCode({
|
||||||
|
@ -91,7 +91,7 @@ export async function requestPasswordReset(
|
||||||
link: url
|
link: url
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
from: config.email?.no_reply,
|
from: config.getRawConfig().email?.no_reply,
|
||||||
to: email,
|
to: email,
|
||||||
subject: "Reset your password"
|
subject: "Reset your password"
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,7 +147,7 @@ export async function resetPassword(
|
||||||
});
|
});
|
||||||
|
|
||||||
await sendEmail(ConfirmPasswordReset({ email }), {
|
await sendEmail(ConfirmPasswordReset({ email }), {
|
||||||
from: config.email?.no_reply,
|
from: config.getRawConfig().email?.no_reply,
|
||||||
to: email,
|
to: email,
|
||||||
subject: "Password Reset Confirmation"
|
subject: "Password Reset Confirmation"
|
||||||
});
|
});
|
||||||
|
|
|
@ -60,7 +60,7 @@ export async function signup(
|
||||||
const passwordHash = await hashPassword(password);
|
const passwordHash = await hashPassword(password);
|
||||||
const userId = generateId(15);
|
const userId = generateId(15);
|
||||||
|
|
||||||
if (config.flags?.disable_signup_without_invite) {
|
if (config.getRawConfig().flags?.disable_signup_without_invite) {
|
||||||
if (!inviteToken || !inviteId) {
|
if (!inviteToken || !inviteId) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
|
@ -102,7 +102,7 @@ export async function signup(
|
||||||
.where(eq(users.email, email));
|
.where(eq(users.email, email));
|
||||||
|
|
||||||
if (existing && existing.length > 0) {
|
if (existing && existing.length > 0) {
|
||||||
if (!config.flags?.require_email_verification) {
|
if (!config.getRawConfig().flags?.require_email_verification) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
|
@ -163,7 +163,7 @@ export async function signup(
|
||||||
const cookie = serializeSessionCookie(token);
|
const cookie = serializeSessionCookie(token);
|
||||||
res.appendHeader("Set-Cookie", cookie);
|
res.appendHeader("Set-Cookie", cookie);
|
||||||
|
|
||||||
if (config.flags?.require_email_verification) {
|
if (config.getRawConfig().flags?.require_email_verification) {
|
||||||
sendEmailVerificationCode(email, userId);
|
sendEmailVerificationCode(email, userId);
|
||||||
|
|
||||||
return response<SignUpResponse>(res, {
|
return response<SignUpResponse>(res, {
|
||||||
|
|
|
@ -28,7 +28,7 @@ export async function verifyEmail(
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
if (!config.flags?.require_email_verification) {
|
if (!config.getRawConfig().flags?.require_email_verification) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
|
|
|
@ -111,7 +111,7 @@ export async function verifyTotp(
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
to: user.email,
|
to: user.email,
|
||||||
from: config.email?.no_reply,
|
from: config.getRawConfig().email?.no_reply,
|
||||||
subject: "Two-factor authentication enabled"
|
subject: "Two-factor authentication enabled"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -101,13 +101,13 @@ export async function verifyResourceSession(
|
||||||
return allowed(res);
|
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) {
|
if (!sessions) {
|
||||||
return notAllowed(res);
|
return notAllowed(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionToken = sessions[config.server.session_cookie_name];
|
const sessionToken = sessions[config.getRawConfig().server.session_cookie_name];
|
||||||
|
|
||||||
// check for unified login
|
// check for unified login
|
||||||
if (sso && sessionToken) {
|
if (sso && sessionToken) {
|
||||||
|
@ -129,7 +129,7 @@ export async function verifyResourceSession(
|
||||||
|
|
||||||
const resourceSessionToken =
|
const resourceSessionToken =
|
||||||
sessions[
|
sessions[
|
||||||
`${config.server.resource_session_cookie_name}_${resource.resourceId}`
|
`${config.getRawConfig().server.resource_session_cookie_name}_${resource.resourceId}`
|
||||||
];
|
];
|
||||||
|
|
||||||
if (resourceSessionToken) {
|
if (resourceSessionToken) {
|
||||||
|
@ -213,7 +213,7 @@ async function isUserAllowedToAccessResource(
|
||||||
user: User,
|
user: User,
|
||||||
resource: Resource
|
resource: Resource
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (config.flags?.require_email_verification && !user.emailVerified) {
|
if (config.getRawConfig().flags?.require_email_verification && !user.emailVerified) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -423,11 +423,11 @@ unauthenticated.use("/auth", authRouter);
|
||||||
authRouter.use(
|
authRouter.use(
|
||||||
rateLimitMiddleware({
|
rateLimitMiddleware({
|
||||||
windowMin:
|
windowMin:
|
||||||
config.rate_limits.auth?.window_minutes ||
|
config.getRawConfig().rate_limits.auth?.window_minutes ||
|
||||||
config.rate_limits.global.window_minutes,
|
config.getRawConfig().rate_limits.global.window_minutes,
|
||||||
max:
|
max:
|
||||||
config.rate_limits.auth?.max_requests ||
|
config.getRawConfig().rate_limits.auth?.max_requests ||
|
||||||
config.rate_limits.global.max_requests,
|
config.getRawConfig().rate_limits.global.max_requests,
|
||||||
type: "IP_AND_PATH"
|
type: "IP_AND_PATH"
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -52,14 +52,14 @@ export async function getConfig(req: Request, res: Response, next: NextFunction)
|
||||||
const address = await getNextAvailableSubnet();
|
const address = await getNextAvailableSubnet();
|
||||||
const listenPort = await getNextAvailablePort();
|
const listenPort = await getNextAvailablePort();
|
||||||
let subEndpoint = "";
|
let subEndpoint = "";
|
||||||
if (config.gerbil.use_subdomain) {
|
if (config.getRawConfig().gerbil.use_subdomain) {
|
||||||
subEndpoint = await getUniqueExitNodeEndpointName();
|
subEndpoint = await getUniqueExitNodeEndpointName();
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new exit node
|
// create a new exit node
|
||||||
exitNode = await db.insert(exitNodes).values({
|
exitNode = await db.insert(exitNodes).values({
|
||||||
publicKey,
|
publicKey,
|
||||||
endpoint: `${subEndpoint}${subEndpoint != "" ? "." : ""}${config.gerbil.base_endpoint}`,
|
endpoint: `${subEndpoint}${subEndpoint != "" ? "." : ""}${config.getRawConfig().gerbil.base_endpoint}`,
|
||||||
address,
|
address,
|
||||||
listenPort,
|
listenPort,
|
||||||
reachableAt,
|
reachableAt,
|
||||||
|
@ -122,7 +122,7 @@ async function getNextAvailableSubnet(): Promise<string> {
|
||||||
}).from(exitNodes);
|
}).from(exitNodes);
|
||||||
|
|
||||||
const addresses = existingAddresses.map(a => a.address);
|
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) {
|
if (!subnet) {
|
||||||
throw new Error('No available subnets remaining in space');
|
throw new Error('No available subnets remaining in space');
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,7 @@ async function getNextAvailablePort(): Promise<number> {
|
||||||
}).from(exitNodes);
|
}).from(exitNodes);
|
||||||
|
|
||||||
// Find the first available port between 1024 and 65535
|
// 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) {
|
for (const port of existingPorts) {
|
||||||
if (port.listenPort > nextPort) {
|
if (port.listenPort > nextPort) {
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -11,7 +11,6 @@ import { createAdminRole } from "@server/setup/ensureActions";
|
||||||
import config from "@server/config";
|
import config from "@server/config";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { defaultRoleAllowedActions } from "../role";
|
import { defaultRoleAllowedActions } from "../role";
|
||||||
import { extractBaseDomain } from "@server/utils/extractBaseDomain";
|
|
||||||
|
|
||||||
const createOrgSchema = z
|
const createOrgSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
@ -30,7 +29,7 @@ export async function createOrg(
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
// should this be in a middleware?
|
// 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) {
|
if (!req.user?.serverAdmin) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
|
@ -83,8 +82,8 @@ export async function createOrg(
|
||||||
let org: Org | null = null;
|
let org: Org | null = null;
|
||||||
|
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
// create a url from config.app.base_url and get the hostname
|
// create a url from config.getRawConfig().app.base_url and get the hostname
|
||||||
const domain = extractBaseDomain(config.app.base_url);
|
const domain = config.getBaseDomain();
|
||||||
|
|
||||||
const newOrg = await trx
|
const newOrg = await trx
|
||||||
.insert(orgs)
|
.insert(orgs)
|
||||||
|
|
|
@ -134,7 +134,7 @@ export async function authWithAccessToken(
|
||||||
expiresAt: tokenItem.expiresAt,
|
expiresAt: tokenItem.expiresAt,
|
||||||
doNotExtend: tokenItem.expiresAt ? true : false
|
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);
|
const cookie = serializeResourceSessionCookie(cookieName, token);
|
||||||
res.appendHeader("Set-Cookie", cookie);
|
res.appendHeader("Set-Cookie", cookie);
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,7 @@ export async function authWithPassword(
|
||||||
token,
|
token,
|
||||||
passwordId: definedPassword.passwordId
|
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);
|
const cookie = serializeResourceSessionCookie(cookieName, token);
|
||||||
res.appendHeader("Set-Cookie", cookie);
|
res.appendHeader("Set-Cookie", cookie);
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,7 @@ export async function authWithPincode(
|
||||||
token,
|
token,
|
||||||
pincodeId: definedPincode.pincodeId
|
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);
|
const cookie = serializeResourceSessionCookie(cookieName, token);
|
||||||
res.appendHeader("Set-Cookie", cookie);
|
res.appendHeader("Set-Cookie", cookie);
|
||||||
|
|
||||||
|
|
|
@ -177,7 +177,7 @@ export async function authWithWhitelist(
|
||||||
token,
|
token,
|
||||||
whitelistId: whitelistedEmail.whitelistId
|
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);
|
const cookie = serializeResourceSessionCookie(cookieName, token);
|
||||||
res.appendHeader("Set-Cookie", cookie);
|
res.appendHeader("Set-Cookie", cookie);
|
||||||
|
|
||||||
|
|
|
@ -50,12 +50,12 @@ export async function traefikConfigProvider(
|
||||||
[badgerMiddlewareName]: {
|
[badgerMiddlewareName]: {
|
||||||
apiBaseUrl: new URL(
|
apiBaseUrl: new URL(
|
||||||
"/api/v1",
|
"/api/v1",
|
||||||
`http://${config.server.internal_hostname}:${config.server.internal_port}`,
|
`http://${config.getRawConfig().server.internal_hostname}:${config.getRawConfig().server.internal_port}`,
|
||||||
).href,
|
).href,
|
||||||
resourceSessionCookieName:
|
resourceSessionCookieName:
|
||||||
config.server.resource_session_cookie_name,
|
config.getRawConfig().server.resource_session_cookie_name,
|
||||||
userSessionCookieName:
|
userSessionCookieName:
|
||||||
config.server.session_cookie_name,
|
config.getRawConfig().server.session_cookie_name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -95,8 +95,8 @@ export async function traefikConfigProvider(
|
||||||
}
|
}
|
||||||
|
|
||||||
const tls = {
|
const tls = {
|
||||||
certResolver: config.traefik.cert_resolver,
|
certResolver: config.getRawConfig().traefik.cert_resolver,
|
||||||
...(config.traefik.prefer_wildcard_cert
|
...(config.getRawConfig().traefik.prefer_wildcard_cert
|
||||||
? {
|
? {
|
||||||
domains: [
|
domains: [
|
||||||
{
|
{
|
||||||
|
@ -110,8 +110,8 @@ export async function traefikConfigProvider(
|
||||||
http.routers![routerName] = {
|
http.routers![routerName] = {
|
||||||
entryPoints: [
|
entryPoints: [
|
||||||
resource.ssl
|
resource.ssl
|
||||||
? config.traefik.https_entrypoint
|
? config.getRawConfig().traefik.https_entrypoint
|
||||||
: config.traefik.http_entrypoint,
|
: config.getRawConfig().traefik.http_entrypoint,
|
||||||
],
|
],
|
||||||
middlewares: [badgerMiddlewareName],
|
middlewares: [badgerMiddlewareName],
|
||||||
service: serviceName,
|
service: serviceName,
|
||||||
|
@ -122,7 +122,7 @@ export async function traefikConfigProvider(
|
||||||
if (resource.ssl) {
|
if (resource.ssl) {
|
||||||
// this is a redirect router; all it does is redirect to the https version if tls is enabled
|
// this is a redirect router; all it does is redirect to the https version if tls is enabled
|
||||||
http.routers![routerName + "-redirect"] = {
|
http.routers![routerName + "-redirect"] = {
|
||||||
entryPoints: [config.traefik.http_entrypoint],
|
entryPoints: [config.getRawConfig().traefik.http_entrypoint],
|
||||||
middlewares: [redirectMiddlewareName],
|
middlewares: [redirectMiddlewareName],
|
||||||
service: serviceName,
|
service: serviceName,
|
||||||
rule: `Host(\`${fullDomain}\`)`,
|
rule: `Host(\`${fullDomain}\`)`,
|
||||||
|
|
|
@ -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) {
|
if (doEmail) {
|
||||||
await sendEmail(
|
await sendEmail(
|
||||||
|
@ -165,7 +165,7 @@ export async function inviteUser(
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
to: email,
|
to: email,
|
||||||
from: config.email?.no_reply,
|
from: config.getRawConfig().email?.no_reply,
|
||||||
subject: "You're invited to join a Fossorial organization"
|
subject: "You're invited to join a Fossorial organization"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,11 +3,10 @@ import { orgs } from "../db/schema";
|
||||||
import config from "@server/config";
|
import config from "@server/config";
|
||||||
import { ne } from "drizzle-orm";
|
import { ne } from "drizzle-orm";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { extractBaseDomain } from "@server/utils/extractBaseDomain";
|
|
||||||
|
|
||||||
export async function copyInConfig() {
|
export async function copyInConfig() {
|
||||||
// create a url from config.app.base_url and get the hostname
|
// create a url from config.getRawConfig().app.base_url and get the hostname
|
||||||
const domain = extractBaseDomain(config.app.base_url);
|
const domain = config.getBaseDomain();
|
||||||
|
|
||||||
// update the domain on all of the orgs where the domain is not equal to the new domain
|
// 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
|
// TODO: eventually each org could have a unique domain that we do not want to overwrite, so this will be unnecessary
|
||||||
|
|
|
@ -1,23 +1,15 @@
|
||||||
import { ensureActions } from "./ensureActions";
|
import { ensureActions } from "./ensureActions";
|
||||||
import { copyInConfig } from "./copyInConfig";
|
import { copyInConfig } from "./copyInConfig";
|
||||||
import { runMigrations } from "./migrations";
|
|
||||||
import { setupServerAdmin } from "./setupServerAdmin";
|
import { setupServerAdmin } from "./setupServerAdmin";
|
||||||
import { loadConfig } from "@server/config";
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export async function runSetupFunctions() {
|
export async function runSetupFunctions() {
|
||||||
try {
|
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 copyInConfig(); // copy in the config to the db as needed
|
||||||
await setupServerAdmin();
|
await setupServerAdmin();
|
||||||
await ensureActions(); // make sure all of the actions are in the db and the roles
|
await ensureActions(); // make sure all of the actions are in the db and the roles
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error running setup functions:", error);
|
logger.error("Error running setup functions:", error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import { __DIRNAME } from "@server/config";
|
|
||||||
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
||||||
import db, { exists } from "@server/db";
|
import db, { exists } from "@server/db";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
import { versionMigrations } from "@server/db/schema";
|
import { versionMigrations } from "@server/db/schema";
|
||||||
import { desc } from "drizzle-orm";
|
import { desc } from "drizzle-orm";
|
||||||
|
import { __DIRNAME } from "@server/consts";
|
||||||
// Import all migrations explicitly
|
import { loadAppVersion } from "@server/utils/loadAppVersion";
|
||||||
import m1 from "./scripts/1.0.0-beta1";
|
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
|
// Define the migration list with versions and their corresponding functions
|
||||||
const migrations = [
|
const migrations = [
|
||||||
|
@ -16,34 +17,32 @@ const migrations = [
|
||||||
// Add new migrations here as they are created
|
// Add new migrations here as they are created
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export async function runMigrations() {
|
// Run the migrations
|
||||||
if (!process.env.APP_VERSION) {
|
await runMigrations();
|
||||||
throw new Error("APP_VERSION is not set in the environment");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.ENVIRONMENT !== "prod") {
|
export async function runMigrations() {
|
||||||
console.info("Skipping migrations in non-prod environment");
|
const appVersion = loadAppVersion();
|
||||||
return;
|
if (!appVersion) {
|
||||||
|
throw new Error("APP_VERSION is not set in the environment");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
await executeScripts();
|
await executeScripts();
|
||||||
} else {
|
} else {
|
||||||
console.info("Running migrations...");
|
console.log("Running migrations...");
|
||||||
try {
|
try {
|
||||||
migrate(db, {
|
migrate(db, {
|
||||||
migrationsFolder: path.join(__DIRNAME, "init") // put here during the docker build
|
migrationsFolder: path.join(__DIRNAME, "init") // put here during the docker build
|
||||||
});
|
});
|
||||||
console.info("Migrations completed successfully.");
|
console.log("Migrations completed successfully.");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error running migrations:", error);
|
console.error("Error running migrations:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert process.env.APP_VERSION into the versionMigrations table
|
|
||||||
await db
|
await db
|
||||||
.insert(versionMigrations)
|
.insert(versionMigrations)
|
||||||
.values({
|
.values({
|
||||||
version: process.env.APP_VERSION,
|
version: appVersion,
|
||||||
executedAt: Date.now()
|
executedAt: Date.now()
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
|
@ -60,7 +59,7 @@ async function executeScripts() {
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
const startVersion = lastExecuted[0]?.version ?? "0.0.0";
|
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
|
// Filter and sort migrations
|
||||||
const pendingMigrations = migrations
|
const pendingMigrations = migrations
|
||||||
|
@ -69,7 +68,7 @@ async function executeScripts() {
|
||||||
|
|
||||||
// Run migrations in order
|
// Run migrations in order
|
||||||
for (const migration of pendingMigrations) {
|
for (const migration of pendingMigrations) {
|
||||||
console.info(`Running migration ${migration.version}`);
|
console.log(`Running migration ${migration.version}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await migration.run();
|
await migration.run();
|
||||||
|
@ -83,7 +82,7 @@ async function executeScripts() {
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
console.info(
|
console.log(
|
||||||
`Successfully completed migration ${migration.version}`
|
`Successfully completed migration ${migration.version}`
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -95,7 +94,7 @@ async function executeScripts() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.info("All migrations completed successfully");
|
console.log("All migrations completed successfully");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Migration process failed:", error);
|
console.error("Migration process failed:", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export default async function migration() {
|
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
|
// SQL operations would go here in ts format
|
||||||
logger.info("Done...");
|
console.log("Done...");
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { fromError } from "zod-validation-error";
|
||||||
export async function setupServerAdmin() {
|
export async function setupServerAdmin() {
|
||||||
const {
|
const {
|
||||||
server_admin: { email, password }
|
server_admin: { email, password }
|
||||||
} = config.users;
|
} = config.getRawConfig().users;
|
||||||
|
|
||||||
const parsed = passwordSchema.safeParse(password);
|
const parsed = passwordSchema.safeParse(password);
|
||||||
|
|
||||||
|
|
|
@ -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(".");
|
|
||||||
}
|
|
16
server/utils/loadAppVersion.ts
Normal file
16
server/utils/loadAppVersion.ts
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,6 +42,7 @@ export default async function ResourceAuthPage(props: {
|
||||||
const user = await getUser({ skipCheckVerifyEmail: true });
|
const user = await getUser({ skipCheckVerifyEmail: true });
|
||||||
|
|
||||||
if (!authInfo) {
|
if (!authInfo) {
|
||||||
|
{/* @ts-ignore */} // TODO: fix this
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-md">
|
<div className="w-full max-w-md">
|
||||||
<ResourceNotFound />
|
<ResourceNotFound />
|
||||||
|
|
|
@ -48,17 +48,19 @@ export function Header({ orgId, orgs }: HeaderProps) {
|
||||||
<div className="hidden md:block">
|
<div className="hidden md:block">
|
||||||
<div className="flex items-center gap-4 mr-4">
|
<div className="flex items-center gap-4 mr-4">
|
||||||
<Link
|
<Link
|
||||||
href="/docs"
|
href="https://docs.fossorial.io"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
className="text-muted-foreground hover:text-foreground"
|
className="text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
Documentation
|
Documentation
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<a
|
||||||
href="/support"
|
href="mailto:support@fossorial.io"
|
||||||
className="text-muted-foreground hover:text-foreground"
|
className="text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
Support
|
Support
|
||||||
</Link>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue