mirror of
https://github.com/fosrl/pangolin.git
synced 2026-01-28 22:00:51 +00:00
♻️ validate env and add remote fossorial API as an env variable
This commit is contained in:
@@ -1,14 +1,17 @@
|
|||||||
|
import { NextConfig } from "next";
|
||||||
import createNextIntlPlugin from "next-intl/plugin";
|
import createNextIntlPlugin from "next-intl/plugin";
|
||||||
|
|
||||||
|
import { pullEnv } from "./src/lib/pullEnv";
|
||||||
|
// validate env variables on build and such
|
||||||
|
pullEnv();
|
||||||
|
|
||||||
const withNextIntl = createNextIntlPlugin();
|
const withNextIntl = createNextIntlPlugin();
|
||||||
|
|
||||||
/** @type {import("next").NextConfig} */
|
const nextConfig: NextConfig = {
|
||||||
const nextConfig = {
|
|
||||||
eslint: {
|
eslint: {
|
||||||
ignoreDuringBuilds: true
|
ignoreDuringBuilds: true
|
||||||
},
|
},
|
||||||
output: "standalone",
|
output: "standalone"
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withNextIntl(nextConfig);
|
export default withNextIntl(nextConfig);
|
||||||
@@ -6,6 +6,7 @@ import { eq } from "drizzle-orm";
|
|||||||
import { configSchema, readConfigFile } from "./readConfigFile";
|
import { configSchema, readConfigFile } from "./readConfigFile";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
|
import { pullEnv } from "@app/lib/pullEnv";
|
||||||
|
|
||||||
export class Config {
|
export class Config {
|
||||||
private rawConfig!: z.infer<typeof configSchema>;
|
private rawConfig!: z.infer<typeof configSchema>;
|
||||||
@@ -149,6 +150,7 @@ export class Config {
|
|||||||
|
|
||||||
public async checkSupporterKey() {
|
public async checkSupporterKey() {
|
||||||
const [key] = await db.select().from(supporterKey).limit(1);
|
const [key] = await db.select().from(supporterKey).limit(1);
|
||||||
|
const env = pullEnv();
|
||||||
|
|
||||||
if (!key) {
|
if (!key) {
|
||||||
return;
|
return;
|
||||||
@@ -158,7 +160,7 @@ export class Config {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
"https://api.fossorial.io/api/v1/license/validate",
|
`${env.app.fossorialRemoteAPIBaseUrl}/api/v1/license/validate`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -18,11 +18,13 @@ import logger from "@server/logger";
|
|||||||
import { response as sendResponse } from "@server/lib/response";
|
import { response as sendResponse } from "@server/lib/response";
|
||||||
import privateConfig from "#private/lib/config";
|
import privateConfig from "#private/lib/config";
|
||||||
import { GenerateNewLicenseResponse } from "@server/routers/generatedLicense/types";
|
import { GenerateNewLicenseResponse } from "@server/routers/generatedLicense/types";
|
||||||
|
import { pullEnv } from "@app/lib/pullEnv";
|
||||||
|
|
||||||
async function createNewLicense(orgId: string, licenseData: any): Promise<any> {
|
async function createNewLicense(orgId: string, licenseData: any): Promise<any> {
|
||||||
try {
|
try {
|
||||||
|
const env = pullEnv();
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`https://api.fossorial.io/api/v1/license-internal/enterprise/${orgId}/create`,
|
`${env.app.fossorialRemoteAPIBaseUrl}/api/v1/license-internal/enterprise/${orgId}/create`,
|
||||||
{
|
{
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -17,12 +17,17 @@ import createHttpError from "http-errors";
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { response as sendResponse } from "@server/lib/response";
|
import { response as sendResponse } from "@server/lib/response";
|
||||||
import privateConfig from "#private/lib/config";
|
import privateConfig from "#private/lib/config";
|
||||||
import { GeneratedLicenseKey, ListGeneratedLicenseKeysResponse } from "@server/routers/generatedLicense/types";
|
import {
|
||||||
|
GeneratedLicenseKey,
|
||||||
|
ListGeneratedLicenseKeysResponse
|
||||||
|
} from "@server/routers/generatedLicense/types";
|
||||||
|
import { pullEnv } from "@app/lib/pullEnv";
|
||||||
|
|
||||||
async function fetchLicenseKeys(orgId: string): Promise<any> {
|
async function fetchLicenseKeys(orgId: string): Promise<any> {
|
||||||
try {
|
try {
|
||||||
|
const env = pullEnv();
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`https://api.fossorial.io/api/v1/license-internal/enterprise/${orgId}/list`,
|
`${env.app.fossorialRemoteAPIBaseUrl}/api/v1/license-internal/enterprise/${orgId}/list`,
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { supporterKey } from "@server/db";
|
|||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
|
import { pullEnv } from "@app/lib/pullEnv";
|
||||||
|
|
||||||
const validateSupporterKeySchema = z
|
const validateSupporterKeySchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -31,6 +32,7 @@ export async function validateSupporterKey(
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
|
const env = pullEnv();
|
||||||
const parsedBody = validateSupporterKeySchema.safeParse(req.body);
|
const parsedBody = validateSupporterKeySchema.safeParse(req.body);
|
||||||
if (!parsedBody.success) {
|
if (!parsedBody.success) {
|
||||||
return next(
|
return next(
|
||||||
@@ -44,7 +46,7 @@ export async function validateSupporterKey(
|
|||||||
const { githubUsername, key } = parsedBody.data;
|
const { githubUsername, key } = parsedBody.data;
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
"https://api.fossorial.io/api/v1/license/validate",
|
`${env.app.fossorialRemoteAPIBaseUrl}/api/v1/license/validate`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -1,105 +1,169 @@
|
|||||||
|
import z from "zod";
|
||||||
import { Env } from "./types/env";
|
import { Env } from "./types/env";
|
||||||
|
|
||||||
|
const envSchema = z.object({
|
||||||
|
// Server configuration
|
||||||
|
NEXT_PORT: z.string(),
|
||||||
|
SERVER_EXTERNAL_PORT: z.string(),
|
||||||
|
SESSION_COOKIE_NAME: z.string(),
|
||||||
|
RESOURCE_ACCESS_TOKEN_PARAM: z.string(),
|
||||||
|
RESOURCE_SESSION_REQUEST_PARAM: z.string(),
|
||||||
|
RESOURCE_ACCESS_TOKEN_HEADERS_ID: z.string(),
|
||||||
|
RESOURCE_ACCESS_TOKEN_HEADERS_TOKEN: z.string(),
|
||||||
|
REO_CLIENT_ID: z.string().optional(),
|
||||||
|
MAXMIND_DB_PATH: z.string().optional(),
|
||||||
|
|
||||||
|
// App configuration
|
||||||
|
ENVIRONMENT: z.string(),
|
||||||
|
SANDBOX_MODE: z
|
||||||
|
.string()
|
||||||
|
.default("false")
|
||||||
|
.transform((val) => val === "true"),
|
||||||
|
APP_VERSION: z.string(),
|
||||||
|
DASHBOARD_URL: z.string(),
|
||||||
|
NEXT_PUBLIC_FOSSORIAL_REMOTE_API_URL: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.default("https://api.fossorial.io")
|
||||||
|
.transform((url) => url.replace(/(.*)\/?$/, "$1")),
|
||||||
|
|
||||||
|
// Email configuration
|
||||||
|
EMAIL_ENABLED: z
|
||||||
|
.string()
|
||||||
|
.default("false")
|
||||||
|
.transform((val) => val === "true"),
|
||||||
|
|
||||||
|
// Feature flags
|
||||||
|
DISABLE_USER_CREATE_ORG: z
|
||||||
|
.string()
|
||||||
|
.default("false")
|
||||||
|
.transform((val) => val === "true"),
|
||||||
|
DISABLE_SIGNUP_WITHOUT_INVITE: z
|
||||||
|
.string()
|
||||||
|
.default("false")
|
||||||
|
.transform((val) => val === "true"),
|
||||||
|
FLAGS_EMAIL_VERIFICATION_REQUIRED: z
|
||||||
|
.string()
|
||||||
|
.default("false")
|
||||||
|
.transform((val) => val === "true"),
|
||||||
|
FLAGS_ALLOW_RAW_RESOURCES: z
|
||||||
|
.string()
|
||||||
|
.default("false")
|
||||||
|
.transform((val) => val === "true"),
|
||||||
|
FLAGS_DISABLE_LOCAL_SITES: z
|
||||||
|
.string()
|
||||||
|
.default("false")
|
||||||
|
.transform((val) => val === "true"),
|
||||||
|
FLAGS_DISABLE_BASIC_WIREGUARD_SITES: z
|
||||||
|
.string()
|
||||||
|
.default("false")
|
||||||
|
.transform((val) => val === "true"),
|
||||||
|
FLAGS_ENABLE_CLIENTS: z
|
||||||
|
.string()
|
||||||
|
.default("false")
|
||||||
|
.transform((val) => val === "true"),
|
||||||
|
HIDE_SUPPORTER_KEY: z
|
||||||
|
.string()
|
||||||
|
.default("false")
|
||||||
|
.transform((val) => val === "true"),
|
||||||
|
USE_PANGOLIN_DNS: z
|
||||||
|
.string()
|
||||||
|
.default("false")
|
||||||
|
.transform((val) => val === "true"),
|
||||||
|
|
||||||
|
// Branding configuration (all optional)
|
||||||
|
BRANDING_APP_NAME: z.string().optional(),
|
||||||
|
BACKGROUND_IMAGE_PATH: z.string().optional(),
|
||||||
|
BRANDING_LOGO_LIGHT_PATH: z.string().optional(),
|
||||||
|
BRANDING_LOGO_DARK_PATH: z.string().optional(),
|
||||||
|
BRANDING_LOGO_AUTH_WIDTH: z.coerce.number().optional(),
|
||||||
|
BRANDING_LOGO_AUTH_HEIGHT: z.coerce.number().optional(),
|
||||||
|
BRANDING_LOGO_NAVBAR_WIDTH: z.coerce.number().optional(),
|
||||||
|
BRANDING_LOGO_NAVBAR_HEIGHT: z.coerce.number().optional(),
|
||||||
|
LOGIN_PAGE_TITLE_TEXT: z.string().optional(),
|
||||||
|
LOGIN_PAGE_SUBTITLE_TEXT: z.string().optional(),
|
||||||
|
SIGNUP_PAGE_TITLE_TEXT: z.string().optional(),
|
||||||
|
SIGNUP_PAGE_SUBTITLE_TEXT: z.string().optional(),
|
||||||
|
RESOURCE_AUTH_PAGE_SHOW_LOGO: z
|
||||||
|
.string()
|
||||||
|
.transform((val) => val === "true")
|
||||||
|
.optional(),
|
||||||
|
RESOURCE_AUTH_PAGE_HIDE_POWERED_BY: z
|
||||||
|
.string()
|
||||||
|
.transform((val) => val === "true")
|
||||||
|
.optional(),
|
||||||
|
RESOURCE_AUTH_PAGE_TITLE_TEXT: z.string().optional(),
|
||||||
|
RESOURCE_AUTH_PAGE_SUBTITLE_TEXT: z.string().optional(),
|
||||||
|
BRANDING_FOOTER: z.string().optional()
|
||||||
|
});
|
||||||
|
|
||||||
export function pullEnv(): Env {
|
export function pullEnv(): Env {
|
||||||
|
const env = envSchema.parse(process.env);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
server: {
|
server: {
|
||||||
nextPort: process.env.NEXT_PORT as string,
|
nextPort: env.NEXT_PORT,
|
||||||
externalPort: process.env.SERVER_EXTERNAL_PORT as string,
|
externalPort: env.SERVER_EXTERNAL_PORT,
|
||||||
sessionCookieName: process.env.SESSION_COOKIE_NAME as string,
|
sessionCookieName: env.SESSION_COOKIE_NAME,
|
||||||
resourceAccessTokenParam: process.env
|
resourceAccessTokenParam: env.RESOURCE_ACCESS_TOKEN_PARAM,
|
||||||
.RESOURCE_ACCESS_TOKEN_PARAM as string,
|
resourceSessionRequestParam: env.RESOURCE_SESSION_REQUEST_PARAM,
|
||||||
resourceSessionRequestParam: process.env
|
resourceAccessTokenHeadersId: env.RESOURCE_ACCESS_TOKEN_HEADERS_ID,
|
||||||
.RESOURCE_SESSION_REQUEST_PARAM as string,
|
resourceAccessTokenHeadersToken:
|
||||||
resourceAccessTokenHeadersId: process.env
|
env.RESOURCE_ACCESS_TOKEN_HEADERS_TOKEN,
|
||||||
.RESOURCE_ACCESS_TOKEN_HEADERS_ID as string,
|
reoClientId: env.REO_CLIENT_ID,
|
||||||
resourceAccessTokenHeadersToken: process.env
|
maxmind_db_path: env.MAXMIND_DB_PATH
|
||||||
.RESOURCE_ACCESS_TOKEN_HEADERS_TOKEN as string,
|
|
||||||
reoClientId: process.env.REO_CLIENT_ID as string,
|
|
||||||
maxmind_db_path: process.env.MAXMIND_DB_PATH as string
|
|
||||||
},
|
},
|
||||||
app: {
|
app: {
|
||||||
environment: process.env.ENVIRONMENT as string,
|
environment: env.ENVIRONMENT,
|
||||||
sandbox_mode: process.env.SANDBOX_MODE === "true" ? true : false,
|
sandbox_mode: env.SANDBOX_MODE,
|
||||||
version: process.env.APP_VERSION as string,
|
version: env.APP_VERSION,
|
||||||
dashboardUrl: process.env.DASHBOARD_URL as string
|
dashboardUrl: env.DASHBOARD_URL,
|
||||||
|
fossorialRemoteAPIBaseUrl: env.NEXT_PUBLIC_FOSSORIAL_REMOTE_API_URL
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
emailEnabled: process.env.EMAIL_ENABLED === "true" ? true : false
|
emailEnabled: env.EMAIL_ENABLED
|
||||||
},
|
},
|
||||||
flags: {
|
flags: {
|
||||||
disableUserCreateOrg:
|
disableUserCreateOrg: env.DISABLE_USER_CREATE_ORG,
|
||||||
process.env.DISABLE_USER_CREATE_ORG === "true" ? true : false,
|
disableSignupWithoutInvite: env.DISABLE_SIGNUP_WITHOUT_INVITE,
|
||||||
disableSignupWithoutInvite:
|
emailVerificationRequired: env.FLAGS_EMAIL_VERIFICATION_REQUIRED,
|
||||||
process.env.DISABLE_SIGNUP_WITHOUT_INVITE === "true"
|
allowRawResources: env.FLAGS_ALLOW_RAW_RESOURCES,
|
||||||
? true
|
disableLocalSites: env.FLAGS_DISABLE_LOCAL_SITES,
|
||||||
: false,
|
disableBasicWireguardSites: env.FLAGS_DISABLE_BASIC_WIREGUARD_SITES,
|
||||||
emailVerificationRequired:
|
enableClients: env.FLAGS_ENABLE_CLIENTS,
|
||||||
process.env.FLAGS_EMAIL_VERIFICATION_REQUIRED === "true"
|
hideSupporterKey: env.HIDE_SUPPORTER_KEY,
|
||||||
? true
|
usePangolinDns: env.USE_PANGOLIN_DNS
|
||||||
: false,
|
|
||||||
allowRawResources:
|
|
||||||
process.env.FLAGS_ALLOW_RAW_RESOURCES === "true" ? true : false,
|
|
||||||
disableLocalSites:
|
|
||||||
process.env.FLAGS_DISABLE_LOCAL_SITES === "true" ? true : false,
|
|
||||||
disableBasicWireguardSites:
|
|
||||||
process.env.FLAGS_DISABLE_BASIC_WIREGUARD_SITES === "true"
|
|
||||||
? true
|
|
||||||
: false,
|
|
||||||
enableClients:
|
|
||||||
process.env.FLAGS_ENABLE_CLIENTS === "true" ? true : false,
|
|
||||||
hideSupporterKey:
|
|
||||||
process.env.HIDE_SUPPORTER_KEY === "true" ? true : false,
|
|
||||||
usePangolinDns:
|
|
||||||
process.env.USE_PANGOLIN_DNS === "true"
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
},
|
},
|
||||||
|
|
||||||
branding: {
|
branding: {
|
||||||
appName: process.env.BRANDING_APP_NAME as string,
|
appName: env.BRANDING_APP_NAME,
|
||||||
background_image_path: process.env.BACKGROUND_IMAGE_PATH as string,
|
background_image_path: env.BACKGROUND_IMAGE_PATH,
|
||||||
logo: {
|
logo: {
|
||||||
lightPath: process.env.BRANDING_LOGO_LIGHT_PATH as string,
|
lightPath: env.BRANDING_LOGO_LIGHT_PATH,
|
||||||
darkPath: process.env.BRANDING_LOGO_DARK_PATH as string,
|
darkPath: env.BRANDING_LOGO_DARK_PATH,
|
||||||
authPage: {
|
authPage: {
|
||||||
width: parseInt(
|
width: env.BRANDING_LOGO_AUTH_WIDTH,
|
||||||
process.env.BRANDING_LOGO_AUTH_WIDTH as string
|
height: env.BRANDING_LOGO_AUTH_HEIGHT
|
||||||
),
|
|
||||||
height: parseInt(
|
|
||||||
process.env.BRANDING_LOGO_AUTH_HEIGHT as string
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
navbar: {
|
navbar: {
|
||||||
width: parseInt(
|
width: env.BRANDING_LOGO_NAVBAR_WIDTH,
|
||||||
process.env.BRANDING_LOGO_NAVBAR_WIDTH as string
|
height: env.BRANDING_LOGO_NAVBAR_HEIGHT
|
||||||
),
|
|
||||||
height: parseInt(
|
|
||||||
process.env.BRANDING_LOGO_NAVBAR_HEIGHT as string
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
loginPage: {
|
loginPage: {
|
||||||
titleText: process.env.LOGIN_PAGE_TITLE_TEXT as string,
|
titleText: env.LOGIN_PAGE_TITLE_TEXT,
|
||||||
subtitleText: process.env.LOGIN_PAGE_SUBTITLE_TEXT as string
|
subtitleText: env.LOGIN_PAGE_SUBTITLE_TEXT
|
||||||
},
|
},
|
||||||
signupPage: {
|
signupPage: {
|
||||||
titleText: process.env.SIGNUP_PAGE_TITLE_TEXT as string,
|
titleText: env.SIGNUP_PAGE_TITLE_TEXT,
|
||||||
subtitleText: process.env.SIGNUP_PAGE_SUBTITLE_TEXT as string
|
subtitleText: env.SIGNUP_PAGE_SUBTITLE_TEXT
|
||||||
},
|
},
|
||||||
resourceAuthPage: {
|
resourceAuthPage: {
|
||||||
showLogo:
|
showLogo: env.RESOURCE_AUTH_PAGE_SHOW_LOGO,
|
||||||
process.env.RESOURCE_AUTH_PAGE_SHOW_LOGO === "true"
|
hidePoweredBy: env.RESOURCE_AUTH_PAGE_HIDE_POWERED_BY,
|
||||||
? true
|
titleText: env.RESOURCE_AUTH_PAGE_TITLE_TEXT,
|
||||||
: false,
|
subtitleText: env.RESOURCE_AUTH_PAGE_SUBTITLE_TEXT
|
||||||
hidePoweredBy:
|
|
||||||
process.env.RESOURCE_AUTH_PAGE_HIDE_POWERED_BY === "true"
|
|
||||||
? true
|
|
||||||
: false,
|
|
||||||
titleText: process.env.RESOURCE_AUTH_PAGE_TITLE_TEXT as string,
|
|
||||||
subtitleText: process.env
|
|
||||||
.RESOURCE_AUTH_PAGE_SUBTITLE_TEXT as string
|
|
||||||
},
|
},
|
||||||
footer: process.env.BRANDING_FOOTER as string
|
footer: env.BRANDING_FOOTER
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export type Env = {
|
|||||||
sandbox_mode: boolean;
|
sandbox_mode: boolean;
|
||||||
version: string;
|
version: string;
|
||||||
dashboardUrl: string;
|
dashboardUrl: string;
|
||||||
|
fossorialRemoteAPIBaseUrl: string;
|
||||||
};
|
};
|
||||||
server: {
|
server: {
|
||||||
externalPort: string;
|
externalPort: string;
|
||||||
@@ -29,11 +30,11 @@ export type Env = {
|
|||||||
enableClients: boolean;
|
enableClients: boolean;
|
||||||
hideSupporterKey: boolean;
|
hideSupporterKey: boolean;
|
||||||
usePangolinDns: boolean;
|
usePangolinDns: boolean;
|
||||||
},
|
};
|
||||||
branding: {
|
branding: {
|
||||||
appName?: string;
|
appName?: string;
|
||||||
background_image_path?: string;
|
background_image_path?: string;
|
||||||
logo?: {
|
logo: {
|
||||||
lightPath?: string;
|
lightPath?: string;
|
||||||
darkPath?: string;
|
darkPath?: string;
|
||||||
authPage?: {
|
authPage?: {
|
||||||
@@ -43,22 +44,22 @@ export type Env = {
|
|||||||
navbar?: {
|
navbar?: {
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
}
|
};
|
||||||
},
|
};
|
||||||
loginPage?: {
|
loginPage: {
|
||||||
titleText?: string;
|
titleText?: string;
|
||||||
subtitleText?: string;
|
subtitleText?: string;
|
||||||
},
|
};
|
||||||
signupPage?: {
|
signupPage: {
|
||||||
titleText?: string;
|
titleText?: string;
|
||||||
subtitleText?: string;
|
subtitleText?: string;
|
||||||
},
|
};
|
||||||
resourceAuthPage?: {
|
resourceAuthPage: {
|
||||||
showLogo?: boolean;
|
showLogo?: boolean;
|
||||||
hidePoweredBy?: boolean;
|
hidePoweredBy?: boolean;
|
||||||
titleText?: string;
|
titleText?: string;
|
||||||
subtitleText?: string;
|
subtitleText?: string;
|
||||||
},
|
};
|
||||||
footer?: string;
|
footer?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user