mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-30 06:29:23 +02:00
rename auth and start work separating config
This commit is contained in:
parent
deac897037
commit
d3bfac20a0
15 changed files with 153 additions and 120 deletions
|
@ -12,12 +12,12 @@ 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_EXPIRES = 1000 * 60 * 60 * 24 * 30;
|
||||
export const SECURE_COOKIES = config.server.secure_cookies;
|
||||
export const COOKIE_DOMAIN =
|
||||
"." + new URL(config.app.base_url).hostname.split(".").slice(-2).join(".");
|
||||
export const COOKIE_DOMAIN = "." + extractBaseDomain(config.app.base_url);
|
||||
|
||||
export function generateSessionToken(): string {
|
||||
const bytes = new Uint8Array(20);
|
||||
|
|
|
@ -9,12 +9,12 @@ 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 =
|
||||
"." + new URL(config.app.base_url).hostname.split(".").slice(-2).join(".");
|
||||
export const COOKIE_DOMAIN = "." + extractBaseDomain(config.app.base_url);
|
||||
|
||||
export async function createNewtSession(
|
||||
token: string,
|
||||
|
|
|
@ -8,12 +8,12 @@ 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 =
|
||||
"." + new URL(config.app.base_url).hostname.split(".").slice(-2).join(".");
|
||||
export const COOKIE_DOMAIN = "." + extractBaseDomain(config.app.base_url);
|
||||
|
||||
export async function createResourceSession(opts: {
|
||||
token: string;
|
||||
|
|
193
server/config.ts
193
server/config.ts
|
@ -1,9 +1,9 @@
|
|||
import { z } from "zod";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import path from "path";
|
||||
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);
|
||||
|
@ -79,102 +79,125 @@ const environmentSchema = z.object({
|
|||
.optional()
|
||||
});
|
||||
|
||||
export function getConfig() {
|
||||
const loadConfig = (configPath: string) => {
|
||||
try {
|
||||
const yamlContent = fs.readFileSync(configPath, "utf8");
|
||||
const config = yaml.load(yamlContent);
|
||||
return config;
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(
|
||||
`Error loading configuration file: ${error.message}`
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
export class Config {
|
||||
private rawConfig!: z.infer<typeof environmentSchema>;
|
||||
|
||||
const configFilePath1 = path.join(APP_PATH, "config.yml");
|
||||
const configFilePath2 = path.join(APP_PATH, "config.yaml");
|
||||
|
||||
let environment: any;
|
||||
if (fs.existsSync(configFilePath1)) {
|
||||
environment = loadConfig(configFilePath1);
|
||||
} else if (fs.existsSync(configFilePath2)) {
|
||||
environment = loadConfig(configFilePath2);
|
||||
constructor() {
|
||||
this.loadConfig();
|
||||
}
|
||||
if (!environment) {
|
||||
const exampleConfigPath = path.join(__DIRNAME, "config.example.yml");
|
||||
if (fs.existsSync(exampleConfigPath)) {
|
||||
|
||||
public getRawConfig() {
|
||||
return this.rawConfig;
|
||||
}
|
||||
|
||||
public loadConfig() {
|
||||
const loadConfig = (configPath: string) => {
|
||||
try {
|
||||
const exampleConfigContent = fs.readFileSync(
|
||||
exampleConfigPath,
|
||||
"utf8"
|
||||
);
|
||||
fs.writeFileSync(configFilePath1, exampleConfigContent, "utf8");
|
||||
environment = loadConfig(configFilePath1);
|
||||
const yamlContent = fs.readFileSync(configPath, "utf8");
|
||||
const config = yaml.load(yamlContent);
|
||||
return config;
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(
|
||||
`Error creating configuration file from example: ${error.message}`
|
||||
`Error loading configuration file: ${error.message}`
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
"No configuration file found and no example configuration available"
|
||||
};
|
||||
|
||||
const configFilePath1 = path.join(APP_PATH, "config.yml");
|
||||
const configFilePath2 = path.join(APP_PATH, "config.yaml");
|
||||
|
||||
let environment: any;
|
||||
if (fs.existsSync(configFilePath1)) {
|
||||
environment = loadConfig(configFilePath1);
|
||||
} else if (fs.existsSync(configFilePath2)) {
|
||||
environment = loadConfig(configFilePath2);
|
||||
}
|
||||
if (!environment) {
|
||||
const exampleConfigPath = path.join(
|
||||
__DIRNAME,
|
||||
"config.example.yml"
|
||||
);
|
||||
if (fs.existsSync(exampleConfigPath)) {
|
||||
try {
|
||||
const exampleConfigContent = fs.readFileSync(
|
||||
exampleConfigPath,
|
||||
"utf8"
|
||||
);
|
||||
fs.writeFileSync(
|
||||
configFilePath1,
|
||||
exampleConfigContent,
|
||||
"utf8"
|
||||
);
|
||||
environment = loadConfig(configFilePath1);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
throw new Error(
|
||||
`Error creating configuration file from example: ${
|
||||
error.message
|
||||
}`
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
"No configuration file found and no example configuration available"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!environment) {
|
||||
throw new Error("No configuration file found");
|
||||
}
|
||||
|
||||
const parsedConfig = environmentSchema.safeParse(environment);
|
||||
|
||||
if (!parsedConfig.success) {
|
||||
const errors = fromError(parsedConfig.error);
|
||||
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;
|
||||
if (!environment) {
|
||||
throw new Error("No configuration file found");
|
||||
}
|
||||
|
||||
const parsedConfig = environmentSchema.safeParse(environment);
|
||||
|
||||
if (!parsedConfig.success) {
|
||||
const errors = fromError(parsedConfig.error);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString();
|
||||
process.env.SERVER_EXTERNAL_PORT =
|
||||
parsedConfig.data.server.external_port.toString();
|
||||
process.env.SERVER_INTERNAL_PORT =
|
||||
parsedConfig.data.server.internal_port.toString();
|
||||
process.env.FLAGS_EMAIL_VERIFICATION_REQUIRED = parsedConfig.data.flags
|
||||
?.require_email_verification
|
||||
? "true"
|
||||
: "false";
|
||||
process.env.SESSION_COOKIE_NAME =
|
||||
parsedConfig.data.server.session_cookie_name;
|
||||
process.env.RESOURCE_SESSION_COOKIE_NAME =
|
||||
parsedConfig.data.server.resource_session_cookie_name;
|
||||
process.env.EMAIL_ENABLED = parsedConfig.data.email ? "true" : "false";
|
||||
process.env.DISABLE_SIGNUP_WITHOUT_INVITE = parsedConfig.data.flags
|
||||
?.disable_signup_without_invite
|
||||
? "true"
|
||||
: "false";
|
||||
process.env.DISABLE_USER_CREATE_ORG = parsedConfig.data.flags
|
||||
?.disable_user_create_org
|
||||
? "true"
|
||||
: "false";
|
||||
|
||||
this.rawConfig = parsedConfig.data;
|
||||
}
|
||||
|
||||
process.env.NEXT_PORT = parsedConfig.data.server.next_port.toString();
|
||||
process.env.SERVER_EXTERNAL_PORT =
|
||||
parsedConfig.data.server.external_port.toString();
|
||||
process.env.SERVER_INTERNAL_PORT =
|
||||
parsedConfig.data.server.internal_port.toString();
|
||||
process.env.FLAGS_EMAIL_VERIFICATION_REQUIRED = parsedConfig.data.flags
|
||||
?.require_email_verification
|
||||
? "true"
|
||||
: "false";
|
||||
process.env.SESSION_COOKIE_NAME =
|
||||
parsedConfig.data.server.session_cookie_name;
|
||||
process.env.RESOURCE_SESSION_COOKIE_NAME =
|
||||
parsedConfig.data.server.resource_session_cookie_name;
|
||||
process.env.EMAIL_ENABLED = parsedConfig.data.email ? "true" : "false";
|
||||
process.env.DISABLE_SIGNUP_WITHOUT_INVITE = parsedConfig.data.flags
|
||||
?.disable_signup_without_invite
|
||||
? "true"
|
||||
: "false";
|
||||
process.env.DISABLE_USER_CREATE_ORG = parsedConfig.data.flags
|
||||
?.disable_user_create_org
|
||||
? "true"
|
||||
: "false";
|
||||
|
||||
return parsedConfig.data;
|
||||
}
|
||||
|
||||
export default getConfig();
|
||||
export const config = new Config();
|
||||
|
||||
export default config;
|
||||
|
|
|
@ -6,7 +6,7 @@ import logger from "@server/logger";
|
|||
export async function sendEmail(
|
||||
template: ReactElement,
|
||||
opts: {
|
||||
name: string | undefined;
|
||||
name?: string;
|
||||
from: string | undefined;
|
||||
to: string | undefined;
|
||||
subject: string;
|
||||
|
|
|
@ -11,6 +11,7 @@ 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({
|
||||
|
@ -83,7 +84,7 @@ export async function createOrg(
|
|||
|
||||
await db.transaction(async (trx) => {
|
||||
// create a url from config.app.base_url and get the hostname
|
||||
const domain = new URL(config.app.base_url).hostname;
|
||||
const domain = extractBaseDomain(config.app.base_url);
|
||||
|
||||
const newOrg = await trx
|
||||
.insert(orgs)
|
||||
|
|
|
@ -53,8 +53,6 @@ export async function createResource(
|
|||
|
||||
let { name, subdomain } = parsedBody.data;
|
||||
|
||||
subdomain = subdomain.toLowerCase(); // always to lower case
|
||||
|
||||
// Validate request params
|
||||
const parsedParams = createResourceParamsSchema.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
|
|
|
@ -41,8 +41,6 @@ export async function traefikConfigProvider(
|
|||
const badgerMiddlewareName = "badger";
|
||||
const redirectMiddlewareName = "redirect-to-https";
|
||||
|
||||
// const baseDomain = new URL(config.app.base_url).hostname;
|
||||
|
||||
const http: any = {
|
||||
routers: {},
|
||||
services: {},
|
||||
|
|
|
@ -3,10 +3,11 @@ 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 = new URL(config.app.base_url).hostname;
|
||||
const domain = extractBaseDomain(config.app.base_url);
|
||||
|
||||
// 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
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
import { ensureActions } from "./ensureActions";
|
||||
import { copyInConfig } from "./copyInConfig";
|
||||
import logger from "@server/logger";
|
||||
import { runMigrations } from "./migrations";
|
||||
import { setupServerAdmin } from "./setupServerAdmin";
|
||||
import { loadConfig } from "@server/config";
|
||||
|
||||
export async function runSetupFunctions() {
|
||||
try {
|
||||
logger.info(`Setup for version ${process.env.APP_VERSION}`);
|
||||
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) {
|
||||
logger.error("Error running setup functions", error);
|
||||
console.error("Error running setup functions:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import logger from "@server/logger";
|
||||
import { __DIRNAME } from "@server/config";
|
||||
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
||||
import db, { exists } from "@server/db";
|
||||
|
@ -8,12 +7,12 @@ import { versionMigrations } from "@server/db/schema";
|
|||
import { desc } from "drizzle-orm";
|
||||
|
||||
// Import all migrations explicitly
|
||||
import migration100beta1 from "./scripts/1.0.0-beta1";
|
||||
import m1 from "./scripts/1.0.0-beta1";
|
||||
// Add new migration imports here as they are created
|
||||
|
||||
// Define the migration list with versions and their corresponding functions
|
||||
const migrations = [
|
||||
{ version: "1.0.0-beta.1", run: migration100beta1 }
|
||||
{ version: "1.0.0-beta.1", run: m1 }
|
||||
// Add new migrations here as they are created
|
||||
] as const;
|
||||
|
||||
|
@ -23,21 +22,21 @@ export async function runMigrations() {
|
|||
}
|
||||
|
||||
if (process.env.ENVIRONMENT !== "prod") {
|
||||
logger.info("Skipping migrations in non-prod environment");
|
||||
console.info("Skipping migrations in non-prod environment");
|
||||
return;
|
||||
}
|
||||
|
||||
if (exists) {
|
||||
await executeScripts();
|
||||
} else {
|
||||
logger.info("Running migrations...");
|
||||
console.info("Running migrations...");
|
||||
try {
|
||||
migrate(db, {
|
||||
migrationsFolder: path.join(__DIRNAME, "init") // put here during the docker build
|
||||
});
|
||||
logger.info("Migrations completed successfully.");
|
||||
console.info("Migrations completed successfully.");
|
||||
} catch (error) {
|
||||
logger.error("Error running migrations:", error);
|
||||
console.error("Error running migrations:", error);
|
||||
}
|
||||
|
||||
// insert process.env.APP_VERSION into the versionMigrations table
|
||||
|
@ -61,7 +60,7 @@ async function executeScripts() {
|
|||
.limit(1);
|
||||
|
||||
const startVersion = lastExecuted[0]?.version ?? "0.0.0";
|
||||
logger.info(`Starting migrations from version ${startVersion}`);
|
||||
console.info(`Starting migrations from version ${startVersion}`);
|
||||
|
||||
// Filter and sort migrations
|
||||
const pendingMigrations = migrations
|
||||
|
@ -70,7 +69,7 @@ async function executeScripts() {
|
|||
|
||||
// Run migrations in order
|
||||
for (const migration of pendingMigrations) {
|
||||
logger.info(`Running migration ${migration.version}`);
|
||||
console.info(`Running migration ${migration.version}`);
|
||||
|
||||
try {
|
||||
await migration.run();
|
||||
|
@ -84,11 +83,11 @@ async function executeScripts() {
|
|||
})
|
||||
.execute();
|
||||
|
||||
logger.info(
|
||||
console.info(
|
||||
`Successfully completed migration ${migration.version}`
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
console.error(
|
||||
`Failed to run migration ${migration.version}:`,
|
||||
error
|
||||
);
|
||||
|
@ -96,9 +95,9 @@ async function executeScripts() {
|
|||
}
|
||||
}
|
||||
|
||||
logger.info("All migrations completed successfully");
|
||||
console.info("All migrations completed successfully");
|
||||
} catch (error) {
|
||||
logger.error("Migration process failed:", error);
|
||||
console.error("Migration process failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import logger from "@server/logger";
|
||||
|
||||
export default async function migration100beta1() {
|
||||
export default async function migration() {
|
||||
logger.info("Running setup script 1.0.0-beta.1");
|
||||
// SQL operations would go here in ts format
|
||||
logger.info("Done...");
|
||||
}
|
||||
}
|
||||
|
|
11
server/utils/extractBaseDomain.ts
Normal file
11
server/utils/extractBaseDomain.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
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(".");
|
||||
}
|
|
@ -398,7 +398,7 @@ export default function ResourceAuthenticationPage() {
|
|||
onCheckedChange={(val) => setSsoEnabled(val)}
|
||||
/>
|
||||
<Label htmlFor="sso-toggle">
|
||||
Allow Unified Login
|
||||
Use Platform SSO
|
||||
</Label>
|
||||
</div>
|
||||
<span className="text-muted-foreground text-sm">
|
||||
|
|
|
@ -44,7 +44,6 @@ export default async function InvitePage(props: {
|
|||
await authCookieHeader()
|
||||
)
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
error = formatAxiosError(e);
|
||||
});
|
||||
|
||||
|
@ -68,8 +67,6 @@ export default async function InvitePage(props: {
|
|||
|
||||
const type = cardType();
|
||||
|
||||
console.log("card type is", type, error)
|
||||
|
||||
if (!user && type === "user_does_not_exist") {
|
||||
redirect(`/auth/signup?redirect=/invite?token=${params.token}`);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue