From e475c1ea50b51d7c6f951ef33cd0c2671dd81e3d Mon Sep 17 00:00:00 2001 From: Milo Schwartz Date: Mon, 3 Feb 2025 21:18:16 -0500 Subject: [PATCH] all resources at the base domain closes #137 --- server/db/schema.ts | 3 +- server/lib/config.ts | 14 +- server/routers/resource/createResource.ts | 33 +++- server/routers/resource/updateResource.ts | 62 ++++++- server/routers/traefik/getTraefikConfig.ts | 7 +- server/setup/copyInConfig.ts | 7 +- server/setup/migrations.ts | 44 ++++- server/setup/scripts/1.0.0-beta12.ts | 62 +++++++ .../settings/resources/CreateResourceForm.tsx | 149 ++++++++++++----- .../[resourceId]/ResourceInfoBox.tsx | 19 ++- .../[resourceId]/connectivity/page.tsx | 3 +- .../resources/[resourceId]/general/page.tsx | 154 +++++++++++++----- src/components/ui/checkbox.tsx | 69 +++++--- src/lib/pullEnv.ts | 10 +- src/lib/types/env.ts | 1 + 15 files changed, 496 insertions(+), 141 deletions(-) create mode 100644 server/setup/scripts/1.0.0-beta12.ts diff --git a/server/db/schema.ts b/server/db/schema.ts index b87acd9..f44873d 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -53,7 +53,8 @@ export const resources = sqliteTable("resources", { proxyPort: integer("proxyPort"), emailWhitelistEnabled: integer("emailWhitelistEnabled", { mode: "boolean" }) .notNull() - .default(false) + .default(false), + isBaseDomain: integer("isBaseDomain", { mode: "boolean" }) }); export const targets = sqliteTable("targets", { diff --git a/server/lib/config.ts b/server/lib/config.ts index f2d8a16..b8e6161 100644 --- a/server/lib/config.ts +++ b/server/lib/config.ts @@ -10,7 +10,6 @@ import { configFilePath1, configFilePath2 } from "@server/lib/consts"; -import { loadAppVersion } from "@server/lib/loadAppVersion"; import { passwordSchema } from "@server/auth/passwordSchema"; import stoi from "./stoi"; @@ -152,7 +151,8 @@ const configSchema = z.object({ require_email_verification: z.boolean().optional(), disable_signup_without_invite: z.boolean().optional(), disable_user_create_org: z.boolean().optional(), - allow_raw_resources: z.boolean().optional() + allow_raw_resources: z.boolean().optional(), + allow_base_domain_resources: z.boolean().optional() }) .optional() }); @@ -252,9 +252,9 @@ export class Config { ? "true" : "false"; process.env.FLAGS_ALLOW_RAW_RESOURCES = parsedConfig.data.flags - ?.allow_raw_resources - ? "true" - : "false"; + ?.allow_raw_resources + ? "true" + : "false"; process.env.SESSION_COOKIE_NAME = parsedConfig.data.server.session_cookie_name; process.env.EMAIL_ENABLED = parsedConfig.data.email ? "true" : "false"; @@ -270,6 +270,10 @@ export class Config { parsedConfig.data.server.resource_access_token_param; process.env.RESOURCE_SESSION_REQUEST_PARAM = parsedConfig.data.server.resource_session_request_param; + process.env.FLAGS_ALLOW_BASE_DOMAIN_RESOURCES = parsedConfig.data.flags + ?.allow_base_domain_resources + ? "true" + : "false"; this.rawConfig = parsedConfig.data; } diff --git a/server/routers/resource/createResource.ts b/server/routers/resource/createResource.ts index a5669a1..01d6ee2 100644 --- a/server/routers/resource/createResource.ts +++ b/server/routers/resource/createResource.ts @@ -34,7 +34,8 @@ const createResourceSchema = z siteId: z.number(), http: z.boolean(), protocol: z.string(), - proxyPort: z.number().optional() + proxyPort: z.number().optional(), + isBaseDomain: z.boolean().optional() }) .refine( (data) => { @@ -55,7 +56,7 @@ const createResourceSchema = z ) .refine( (data) => { - if (data.http) { + if (data.http && !data.isBaseDomain) { return subdomainSchema.safeParse(data.subdomain).success; } return true; @@ -75,7 +76,7 @@ const createResourceSchema = z return true; }, { - message: "Cannot update proxyPort" + message: "Proxy port cannot be set" } ) .refine( @@ -88,6 +89,19 @@ const createResourceSchema = z { message: "Port 80 and 443 are reserved for http and https resources" } + ) + .refine( + (data) => { + if (!config.getRawConfig().flags?.allow_base_domain_resources) { + if (data.isBaseDomain) { + return false; + } + } + return true; + }, + { + message: "Base domain resources are not allowed" + } ); export type CreateResourceResponse = Resource; @@ -108,7 +122,7 @@ export async function createResource( ); } - let { name, subdomain, protocol, proxyPort, http } = parsedBody.data; + let { name, subdomain, protocol, proxyPort, http, isBaseDomain } = parsedBody.data; // Validate request params const parsedParams = createResourceParamsSchema.safeParse(req.params); @@ -145,7 +159,13 @@ export async function createResource( ); } - const fullDomain = `${subdomain}.${org[0].domain}`; + let fullDomain = ""; + if (isBaseDomain) { + fullDomain = org[0].domain; + } else { + fullDomain = `${subdomain}.${org[0].domain}`; + } + // if http is false check to see if there is already a resource with the same port and protocol if (!http) { const existingResource = await db @@ -195,7 +215,8 @@ export async function createResource( http, protocol, proxyPort, - ssl: true + ssl: true, + isBaseDomain }) .returning(); diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 6910bd7..60b75f8 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -28,7 +28,8 @@ const updateResourceBodySchema = z sso: z.boolean().optional(), blockAccess: z.boolean().optional(), proxyPort: z.number().int().min(1).max(65535).optional(), - emailWhitelistEnabled: z.boolean().optional() + emailWhitelistEnabled: z.boolean().optional(), + isBaseDomain: z.boolean().optional() }) .strict() .refine((data) => Object.keys(data).length > 0, { @@ -55,6 +56,19 @@ const updateResourceBodySchema = z { message: "Port 80 and 443 are reserved for http and https resources" } + ) + .refine( + (data) => { + if (!config.getRawConfig().flags?.allow_base_domain_resources) { + if (data.isBaseDomain) { + return false; + } + } + return true; + }, + { + message: "Base domain resources are not allowed" + } ); export async function updateResource( @@ -104,6 +118,29 @@ export async function updateResource( ); } + if (updateData.subdomain) { + if (!resource.http) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Cannot update subdomain for non-http resource" + ) + ); + } + + const valid = subdomainSchema.safeParse( + updateData.subdomain + ).success; + if (!valid) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + "Invalid subdomain provided" + ) + ); + } + } + if (updateData.proxyPort) { const proxyPort = updateData.proxyPort; const existingResource = await db @@ -138,15 +175,32 @@ export async function updateResource( ); } - const fullDomain = updateData.subdomain - ? `${updateData.subdomain}.${org.domain}` - : undefined; + let fullDomain = ""; + if (updateData.isBaseDomain) { + fullDomain = org.domain; + } else { + fullDomain = `${updateData.subdomain}.${org.domain}`; + } const updatePayload = { ...updateData, ...(fullDomain && { fullDomain }) }; + const [existingDomain] = await db + .select() + .from(resources) + .where(eq(resources.fullDomain, fullDomain)); + + if (existingDomain && existingDomain.resourceId !== resourceId) { + return next( + createHttpError( + HttpCode.CONFLICT, + "Resource with that domain already exists" + ) + ); + } + const updatedResource = await db .update(resources) .set(updatePayload) diff --git a/server/routers/traefik/getTraefikConfig.ts b/server/routers/traefik/getTraefikConfig.ts index 7c12cdb..98702aa 100644 --- a/server/routers/traefik/getTraefikConfig.ts +++ b/server/routers/traefik/getTraefikConfig.ts @@ -25,6 +25,7 @@ export async function traefikConfigProvider( http: resources.http, proxyPort: resources.proxyPort, protocol: resources.protocol, + isBaseDomain: resources.isBaseDomain, // Site fields site: { siteId: sites.siteId, @@ -110,11 +111,11 @@ export async function traefikConfigProvider( const routerName = `${resource.resourceId}-router`; const serviceName = `${resource.resourceId}-service`; - const fullDomain = `${resource.subdomain}.${org.domain}`; + const fullDomain = `${resource.fullDomain}`; if (resource.http) { // HTTP configuration remains the same - if (!resource.subdomain) { + if (!resource.subdomain && !resource.isBaseDomain) { continue; } @@ -148,6 +149,8 @@ export async function traefikConfigProvider( : {}) }; + logger.debug(config.getRawConfig().traefik.prefer_wildcard_cert) + const additionalMiddlewares = config.getRawConfig().traefik.additional_middlewares || []; diff --git a/server/setup/copyInConfig.ts b/server/setup/copyInConfig.ts index 5a5e671..8f3af8d 100644 --- a/server/setup/copyInConfig.ts +++ b/server/setup/copyInConfig.ts @@ -23,7 +23,12 @@ export async function copyInConfig() { const allResources = await trx.select().from(resources); for (const resource of allResources) { - const fullDomain = `${resource.subdomain}.${domain}`; + let fullDomain = ""; + if (resource.isBaseDomain) { + fullDomain = domain; + } else { + fullDomain = `${resource.subdomain}.${domain}`; + } await trx .update(resources) .set({ fullDomain }) diff --git a/server/setup/migrations.ts b/server/setup/migrations.ts index e0e25f1..5581fc2 100644 --- a/server/setup/migrations.ts +++ b/server/setup/migrations.ts @@ -3,8 +3,9 @@ import db, { exists } from "@server/db"; import path from "path"; import semver from "semver"; import { versionMigrations } from "@server/db/schema"; -import { __DIRNAME, APP_VERSION } from "@server/lib/consts"; +import { __DIRNAME, APP_PATH, APP_VERSION } from "@server/lib/consts"; import { SqliteError } from "better-sqlite3"; +import fs from "fs"; import m1 from "./scripts/1.0.0-beta1"; import m2 from "./scripts/1.0.0-beta2"; import m3 from "./scripts/1.0.0-beta3"; @@ -12,6 +13,7 @@ import m4 from "./scripts/1.0.0-beta5"; import m5 from "./scripts/1.0.0-beta6"; import m6 from "./scripts/1.0.0-beta9"; import m7 from "./scripts/1.0.0-beta10"; +import m8 from "./scripts/1.0.0-beta12"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -24,12 +26,41 @@ const migrations = [ { version: "1.0.0-beta.5", run: m4 }, { version: "1.0.0-beta.6", run: m5 }, { version: "1.0.0-beta.9", run: m6 }, - { version: "1.0.0-beta.10", run: m7 } + { version: "1.0.0-beta.10", run: m7 }, + { version: "1.0.0-beta.12", run: m8 } // Add new migrations here as they are created ] as const; -// Run the migrations -await runMigrations(); +await run(); + +async function run() { + // backup the database + backupDb(); + + // run the migrations + await runMigrations(); +} + +function backupDb() { + // make dir config/db/backups + const appPath = APP_PATH; + const dbDir = path.join(appPath, "db"); + + const backupsDir = path.join(dbDir, "backups"); + + // check if the backups directory exists and create it if it doesn't + if (!fs.existsSync(backupsDir)) { + fs.mkdirSync(backupsDir, { recursive: true }); + } + + // copy the db.sqlite file to backups + // add the date to the filename + const date = new Date(); + const dateString = `${date.getFullYear()}-${date.getMonth()}-${date.getDate()}_${date.getHours()}-${date.getMinutes()}-${date.getSeconds()}`; + const dbPath = path.join(dbDir, "db.sqlite"); + const backupPath = path.join(backupsDir, `db_${dateString}.sqlite`); + fs.copyFileSync(dbPath, backupPath); +} export async function runMigrations() { try { @@ -105,7 +136,10 @@ async function executeScripts() { `Successfully completed migration ${migration.version}` ); } catch (e) { - if (e instanceof SqliteError && e.code === "SQLITE_CONSTRAINT_UNIQUE") { + if ( + e instanceof SqliteError && + e.code === "SQLITE_CONSTRAINT_UNIQUE" + ) { console.error("Migration has already run! Skipping..."); continue; } diff --git a/server/setup/scripts/1.0.0-beta12.ts b/server/setup/scripts/1.0.0-beta12.ts new file mode 100644 index 0000000..0632b5e --- /dev/null +++ b/server/setup/scripts/1.0.0-beta12.ts @@ -0,0 +1,62 @@ +import db from "@server/db"; +import { configFilePath1, configFilePath2 } from "@server/lib/consts"; +import { sql } from "drizzle-orm"; +import fs from "fs"; +import yaml from "js-yaml"; + +export default async function migration() { + console.log("Running setup script 1.0.0-beta.12..."); + + try { + // Determine which config file exists + const filePaths = [configFilePath1, configFilePath2]; + let filePath = ""; + for (const path of filePaths) { + if (fs.existsSync(path)) { + filePath = path; + break; + } + } + + if (!filePath) { + throw new Error( + `No config file found (expected config.yml or config.yaml).` + ); + } + + // Read and parse the YAML file + let rawConfig: any; + const fileContents = fs.readFileSync(filePath, "utf8"); + rawConfig = yaml.load(fileContents); + + if (!rawConfig.flags) { + rawConfig.flags = {}; + } + + rawConfig.flags.allow_base_domain_resources = true; + + // Write the updated YAML back to the file + const updatedYaml = yaml.dump(rawConfig); + fs.writeFileSync(filePath, updatedYaml, "utf8"); + + console.log(`Added new config option: allow_base_domain_resources`); + } catch (e) { + console.log( + `Unable to add new config option: allow_base_domain_resources. This is not critical.` + ); + console.error(e); + } + + try { + db.transaction((trx) => { + trx.run(sql`ALTER TABLE 'resources' ADD 'isBaseDomain' integer;`); + }); + + console.log(`Added new column: isBaseDomain`); + } catch (e) { + console.log("Unable to add new column: isBaseDomain"); + throw e; + } + + console.log("Done."); +} diff --git a/src/app/[orgId]/settings/resources/CreateResourceForm.tsx b/src/app/[orgId]/settings/resources/CreateResourceForm.tsx index 4add304..6e33ec7 100644 --- a/src/app/[orgId]/settings/resources/CreateResourceForm.tsx +++ b/src/app/[orgId]/settings/resources/CreateResourceForm.tsx @@ -63,6 +63,8 @@ import { subdomainSchema } from "@server/schemas/subdomainSchema"; import Link from "next/link"; import { SquareArrowOutUpRight } from "lucide-react"; import CopyTextBox from "@app/components/CopyTextBox"; +import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group"; +import { Label } from "@app/components/ui/label"; const createResourceFormSchema = z .object({ @@ -71,7 +73,8 @@ const createResourceFormSchema = z siteId: z.number(), http: z.boolean(), protocol: z.string(), - proxyPort: z.number().optional() + proxyPort: z.number().optional(), + isBaseDomain: z.boolean().optional() }) .refine( (data) => { @@ -92,7 +95,7 @@ const createResourceFormSchema = z ) .refine( (data) => { - if (data.http) { + if (data.http && !data.isBaseDomain) { return subdomainSchema.safeParse(data.subdomain).success; } return true; @@ -131,12 +134,15 @@ export default function CreateResourceForm({ const [domainSuffix, setDomainSuffix] = useState(org.org.domain); const [showSnippets, setShowSnippets] = useState(false); const [resourceId, setResourceId] = useState(null); + const [domainType, setDomainType] = useState<"subdomain" | "basedomain">( + "subdomain" + ); const form = useForm({ resolver: zodResolver(createResourceFormSchema), defaultValues: { subdomain: "", - name: "My Resource", + name: "", http: true, protocol: "tcp" } @@ -180,7 +186,8 @@ export default function CreateResourceForm({ http: data.http, protocol: data.protocol, proxyPort: data.http ? undefined : data.proxyPort, - siteId: data.siteId + siteId: data.siteId, + isBaseDomain: data.isBaseDomain } ) .catch((e) => { @@ -246,7 +253,7 @@ export default function CreateResourceForm({ Name @@ -291,33 +298,89 @@ export default function CreateResourceForm({ /> )} + {form.watch("http") && + env.flags.allowBaseDomainResources && ( +
+ { + setDomainType( + val as any + ); + form.setValue( + "isBaseDomain", + val === "basedomain" + ); + }} + > +
+ + +
+
+ + +
+
+
+ )} + {form.watch("http") && ( ( - - Subdomain - - - - form.setValue( - "subdomain", + {!env.flags + .allowBaseDomainResources && ( + + Subdomain + + )} + {domainType === + "subdomain" ? ( + + - + ) => + form.setValue( + "subdomain", + value + ) + } + /> + + ) : ( + + + + )} This is the fully qualified domain name @@ -471,9 +534,7 @@ export default function CreateResourceForm({ site ) => ( - {!showSnippets && } + {!showSnippets && ( + + )} - {showSnippets && } + {showSnippets && ( + + )} diff --git a/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx b/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx index c7f5162..38f3c84 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx @@ -2,11 +2,7 @@ import { useState } from "react"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { - InfoIcon, - ShieldCheck, - ShieldOff -} from "lucide-react"; +import { InfoIcon, ShieldCheck, ShieldOff } from "lucide-react"; import { useOrgContext } from "@app/hooks/useOrgContext"; import { useResourceContext } from "@app/hooks/useResourceContext"; import { Separator } from "@app/components/ui/separator"; @@ -26,9 +22,12 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) { const { org } = useOrgContext(); const { resource, authInfo } = useResourceContext(); - const fullUrl = `${resource.ssl ? "https" : "http"}://${ - resource.subdomain - }.${org.org.domain}`; + let fullUrl = `${resource.ssl ? "https" : "http"}://`; + if (resource.isBaseDomain) { + fullUrl = fullUrl + org.org.domain; + } else { + fullUrl = fullUrl + `${resource.subdomain}.${org.org.domain}`; + } return ( @@ -82,7 +81,9 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) { Protocol - {resource.protocol.toUpperCase()} + + {resource.protocol.toUpperCase()} + diff --git a/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx index cf13ea9..7cc2191 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx @@ -132,9 +132,8 @@ export default function ReverseProxyTargets(props: { defaultValues: { ip: "", method: resource.http ? "http" : null, - port: "" // protocol: "TCP", - } + } as z.infer }); useEffect(() => { diff --git a/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx index a6987e9..848115f 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx @@ -51,13 +51,17 @@ import { createApiClient } from "@app/lib/api"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { subdomainSchema } from "@server/schemas/subdomainSchema"; import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"; +import { pullEnv } from "@app/lib/pullEnv"; +import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group"; +import { Label } from "@app/components/ui/label"; const GeneralFormSchema = z .object({ subdomain: z.string().optional(), name: z.string().min(1).max(255), proxyPort: z.number().optional(), - http: z.boolean() + http: z.boolean(), + isBaseDomain: z.boolean().optional() }) .refine( (data) => { @@ -78,7 +82,7 @@ const GeneralFormSchema = z ) .refine( (data) => { - if (data.http) { + if (data.http && !data.isBaseDomain) { return subdomainSchema.safeParse(data.subdomain).success; } return true; @@ -103,9 +107,11 @@ export default function GeneralForm() { const { org } = useOrgContext(); const router = useRouter(); + const { env } = useEnvContext(); + const orgId = params.orgId; - const api = createApiClient(useEnvContext()); + const api = createApiClient({ env }); const [sites, setSites] = useState([]); const [saveLoading, setSaveLoading] = useState(false); @@ -113,13 +119,18 @@ export default function GeneralForm() { const [transferLoading, setTransferLoading] = useState(false); const [open, setOpen] = useState(false); + const [domainType, setDomainType] = useState<"subdomain" | "basedomain">( + resource.isBaseDomain ? "basedomain" : "subdomain" + ); + const form = useForm({ resolver: zodResolver(GeneralFormSchema), defaultValues: { name: resource.name, subdomain: resource.subdomain ? resource.subdomain : undefined, proxyPort: resource.proxyPort ? resource.proxyPort : undefined, - http: resource.http + http: resource.http, + isBaseDomain: resource.isBaseDomain ? true : false }, mode: "onChange" }); @@ -148,7 +159,8 @@ export default function GeneralForm() { .post(`resource/${resource?.resourceId}`, { name: data.name, subdomain: data.subdomain, - proxyPort: data.proxyPort + proxyPort: data.proxyPort, + isBaseDomain: data.isBaseDomain }) .catch((e) => { toast({ @@ -170,7 +182,8 @@ export default function GeneralForm() { updateResource({ name: data.name, subdomain: data.subdomain, - proxyPort: data.proxyPort + proxyPort: data.proxyPort, + isBaseDomain: data.isBaseDomain }); } setSaveLoading(false); @@ -242,40 +255,103 @@ export default function GeneralForm() { )} /> - {resource.http ? ( - ( - - Subdomain - - - form.setValue( - "subdomain", - value - ) - } - /> - - - This is the subdomain that - will be used to access the - resource. - - - + {resource.http && ( + <> + {env.flags.allowBaseDomainResources && ( +
+ { + setDomainType( + val as any + ); + form.setValue( + "isBaseDomain", + val === "basedomain" + ); + }} + > +
+ + +
+
+ + +
+
+
)} - /> - ) : ( + + ( + + {!env.flags + .allowBaseDomainResources && ( + + Subdomain + + )} + + {domainType === + "subdomain" ? ( + + + form.setValue( + "subdomain", + value + ) + } + /> + + ) : ( + + + + )} + + This is the subdomain + that will be used to + access the resource. + + + + )} + /> + + )} + + {!resource.http && ( , - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - - - - - -)) -Checkbox.displayName = CheckboxPrimitive.Root.displayName + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; -export { Checkbox } +interface CheckboxWithLabelProps + extends React.ComponentPropsWithoutRef { + label: string; +} + +const CheckboxWithLabel = React.forwardRef< + React.ElementRef, + CheckboxWithLabelProps +>(({ className, label, id, ...props }, ref) => { + return ( +
+ + +
+ ); +}); +CheckboxWithLabel.displayName = "CheckboxWithLabel"; + +export { Checkbox, CheckboxWithLabel }; diff --git a/src/lib/pullEnv.ts b/src/lib/pullEnv.ts index 368df44..e189db5 100644 --- a/src/lib/pullEnv.ts +++ b/src/lib/pullEnv.ts @@ -6,8 +6,10 @@ export function pullEnv(): Env { nextPort: process.env.NEXT_PORT as string, externalPort: process.env.SERVER_EXTERNAL_PORT as string, sessionCookieName: process.env.SESSION_COOKIE_NAME as string, - resourceAccessTokenParam: process.env.RESOURCE_ACCESS_TOKEN_PARAM as string, - resourceSessionRequestParam: process.env.RESOURCE_SESSION_REQUEST_PARAM as string + resourceAccessTokenParam: process.env + .RESOURCE_ACCESS_TOKEN_PARAM as string, + resourceSessionRequestParam: process.env + .RESOURCE_SESSION_REQUEST_PARAM as string }, app: { environment: process.env.ENVIRONMENT as string, @@ -29,6 +31,10 @@ export function pullEnv(): Env { : false, allowRawResources: process.env.FLAGS_ALLOW_RAW_RESOURCES === "true" ? true : false, + allowBaseDomainResources: + process.env.FLAGS_ALLOW_BASE_DOMAIN_RESOURCES === "true" + ? true + : false } }; } diff --git a/src/lib/types/env.ts b/src/lib/types/env.ts index 14efd1b..7080d46 100644 --- a/src/lib/types/env.ts +++ b/src/lib/types/env.ts @@ -18,5 +18,6 @@ export type Env = { disableUserCreateOrg: boolean; emailVerificationRequired: boolean; allowRawResources: boolean; + allowBaseDomainResources: boolean; } };