Merge branch 'dev' into distribution

This commit is contained in:
miloschwartz
2025-10-13 11:06:14 -07:00
85 changed files with 906 additions and 1639 deletions

View File

@@ -1,3 +1,16 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { rateLimitService } from "#private/lib/rateLimit";
import { cleanup as wsCleanup } from "#private/routers/ws";

View File

@@ -97,20 +97,4 @@ export async function getValidCertificatesForDomains(
});
return validCertsDecrypted;
}
export async function getValidCertificatesForDomainsHybrid(
domains: Set<string>
): Promise<
Array<{
id: number;
domain: string;
wildcard: boolean | null;
certFile: string | null;
keyFile: string | null;
expiresAt: number | null;
updatedAt?: number | null;
}>
> {
return []; // stub
}
}

View File

@@ -146,6 +146,10 @@ export class PrivateConfig {
if (parsedPrivateConfig.stripe?.s3Region) {
process.env.S3_REGION = parsedPrivateConfig.stripe.s3Region;
}
if (parsedPrivateConfig.flags?.generate_own_certificates) {
process.env.GENERATE_OWN_CERTIFICATES =
parsedPrivateConfig.flags.generate_own_certificates.toString();
}
}
this.rawPrivateConfig = parsedPrivateConfig;

View File

@@ -12,7 +12,7 @@
*/
import logger from "@server/logger";
import redisManager from "@server/private/lib/redis";
import redisManager from "#private/lib/redis";
import { build } from "@server/build";
// Rate limiting configuration

View File

@@ -17,7 +17,7 @@ import { MemoryStore, Store } from "express-rate-limit";
import RedisStore from "#private/lib/redisStore";
export function createStore(): Store {
if (build != "oss" && privateConfig.getRawPrivateConfig().flags?.enable_redis) {
if (build != "oss" && privateConfig.getRawPrivateConfig().flags.enable_redis) {
const rateLimitStore: Store = new RedisStore({
prefix: "api-rate-limit", // Optional: customize Redis key prefix
skipFailedRequests: true, // Don't count failed requests

View File

@@ -20,15 +20,18 @@ import { build } from "@server/build";
const portSchema = z.number().positive().gt(0).lte(65535);
export const privateConfigSchema = z
.object({
app: z.object({
export const privateConfigSchema = z.object({
app: z
.object({
region: z.string().optional().default("default"),
base_domain: z.string().optional()
}).optional().default({
})
.optional()
.default({
region: "default"
}),
server: z.object({
server: z
.object({
encryption_key_path: z
.string()
.optional()
@@ -37,125 +40,132 @@ export const privateConfigSchema = z
resend_api_key: z.string().optional(),
reo_client_id: z.string().optional(),
fossorial_api_key: z.string().optional()
}).optional().default({
})
.optional()
.default({
encryption_key_path: "./config/encryption.pem"
}),
redis: z
.object({
host: z.string(),
port: portSchema,
password: z.string().optional(),
db: z.number().int().nonnegative().optional().default(0),
replicas: z
.array(
z.object({
host: z.string(),
port: portSchema,
password: z.string().optional(),
db: z.number().int().nonnegative().optional().default(0)
redis: z
.object({
host: z.string(),
port: portSchema,
password: z.string().optional(),
db: z.number().int().nonnegative().optional().default(0),
replicas: z
.array(
z.object({
host: z.string(),
port: portSchema,
password: z.string().optional(),
db: z.number().int().nonnegative().optional().default(0)
})
)
.optional()
// tls: z
// .object({
// reject_unauthorized: z
// .boolean()
// .optional()
// .default(true)
// })
// .optional()
})
.optional(),
gerbil: z
.object({
local_exit_node_reachable_at: z
.string()
.optional()
.default("http://gerbil:3003")
})
.optional()
.default({}),
flags: z
.object({
enable_redis: z.boolean().optional().default(false),
generate_own_certificates: z.boolean().optional().default(false)
})
.optional()
.default({}),
branding: z
.object({
app_name: z.string().optional(),
background_image_path: z.string().optional(),
colors: z
.object({
light: colorsSchema.optional(),
dark: colorsSchema.optional()
})
.optional(),
logo: z
.object({
light_path: z.string().optional(),
dark_path: z.string().optional(),
auth_page: z
.object({
width: z.number().optional(),
height: z.number().optional()
})
)
.optional()
// tls: z
// .object({
// reject_unauthorized: z
// .boolean()
// .optional()
// .default(true)
// })
// .optional()
})
.optional(),
gerbil: z
.object({
local_exit_node_reachable_at: z.string().optional().default("http://gerbil:3003")
})
.optional()
.default({}),
flags: z
.object({
enable_redis: z.boolean().optional(),
})
.optional(),
branding: z
.object({
app_name: z.string().optional(),
background_image_path: z.string().optional(),
colors: z
.object({
light: colorsSchema.optional(),
dark: colorsSchema.optional()
})
.optional(),
logo: z
.object({
light_path: z.string().optional(),
dark_path: z.string().optional(),
auth_page: z
.object({
width: z.number().optional(),
height: z.number().optional()
})
.optional(),
navbar: z
.object({
width: z.number().optional(),
height: z.number().optional()
})
.optional()
})
.optional(),
favicon_path: z.string().optional(),
footer: z
.array(
z.object({
text: z.string(),
href: z.string().optional()
.optional(),
navbar: z
.object({
width: z.number().optional(),
height: z.number().optional()
})
)
.optional(),
login_page: z
.object({
subtitle_text: z.string().optional(),
title_text: z.string().optional()
.optional()
})
.optional(),
favicon_path: z.string().optional(),
footer: z
.array(
z.object({
text: z.string(),
href: z.string().optional()
})
.optional(),
signup_page: z
.object({
subtitle_text: z.string().optional(),
title_text: z.string().optional()
})
.optional(),
resource_auth_page: z
.object({
show_logo: z.boolean().optional(),
hide_powered_by: z.boolean().optional(),
title_text: z.string().optional(),
subtitle_text: z.string().optional()
})
.optional(),
emails: z
.object({
signature: z.string().optional(),
colors: z
.object({
primary: z.string().optional()
})
.optional()
})
.optional()
})
.optional(),
stripe: z
.object({
secret_key: z.string(),
webhook_secret: z.string(),
s3Bucket: z.string(),
s3Region: z.string().default("us-east-1"),
localFilePath: z.string()
})
.optional(),
});
)
.optional(),
login_page: z
.object({
subtitle_text: z.string().optional(),
title_text: z.string().optional()
})
.optional(),
signup_page: z
.object({
subtitle_text: z.string().optional(),
title_text: z.string().optional()
})
.optional(),
resource_auth_page: z
.object({
show_logo: z.boolean().optional(),
hide_powered_by: z.boolean().optional(),
title_text: z.string().optional(),
subtitle_text: z.string().optional()
})
.optional(),
emails: z
.object({
signature: z.string().optional(),
colors: z
.object({
primary: z.string().optional()
})
.optional()
})
.optional()
})
.optional(),
stripe: z
.object({
secret_key: z.string(),
webhook_secret: z.string(),
s3Bucket: z.string(),
s3Region: z.string().default("us-east-1"),
localFilePath: z.string()
})
.optional()
});
export function readPrivateConfigFile() {
if (build == "oss") {
@@ -186,9 +196,7 @@ export function readPrivateConfigFile() {
}
if (!environment) {
throw new Error(
"No private configuration file found."
);
throw new Error("No private configuration file found.");
}
return environment;

View File

@@ -46,7 +46,7 @@ class RedisManager {
this.isEnabled = false;
return;
}
this.isEnabled = privateConfig.getRawPrivateConfig().flags?.enable_redis || false;
this.isEnabled = privateConfig.getRawPrivateConfig().flags.enable_redis || false;
if (this.isEnabled) {
this.initializeClients();
}

View File

@@ -14,10 +14,10 @@
import Stripe from "stripe";
import privateConfig from "#private/lib/config";
import logger from "@server/logger";
import { build } from "@server/build";
import { noop } from "@server/lib/billing/usageService";
let stripe: Stripe | undefined = undefined;
if (build == "saas") {
if (!noop()) {
const stripeApiKey = privateConfig.getRawPrivateConfig().stripe?.secret_key;
if (!stripeApiKey) {
logger.error("Stripe secret key is not configured");

View File

@@ -21,11 +21,10 @@ import {
} from "@server/db";
import { and, eq, inArray, or, isNull, ne, isNotNull, desc } from "drizzle-orm";
import logger from "@server/logger";
import HttpCode from "@server/types/HttpCode";
import config from "@server/lib/config";
import { orgs, resources, sites, Target, targets } from "@server/db";
import { build } from "@server/build";
import { sanitize } from "@server/lib/traefik/utils";
import privateConfig from "#private/lib/config";
const redirectHttpsMiddlewareName = "redirect-to-https";
const redirectToRootMiddlewareName = "redirect-to-root";
@@ -79,7 +78,7 @@ export async function getTraefikConfig(
path: targets.path,
pathMatchType: targets.pathMatchType,
priority: targets.priority,
// Site fields
siteId: sites.siteId,
siteType: sites.type,
@@ -234,12 +233,13 @@ export async function getTraefikConfig(
continue;
}
if (resource.certificateStatus !== "valid") {
logger.debug(
`Resource ${resource.resourceId} has certificate stats ${resource.certificateStats}`
);
continue;
}
// TODO: for now dont filter it out because if you have multiple domain ids and one is failed it causes all of them to fail
// if (resource.certificateStatus !== "valid" && privateConfig.getRawPrivateConfig().flags.generate_own_certificates) {
// logger.debug(
// `Resource ${resource.resourceId} has certificate stats ${resource.certificateStats}`
// );
// continue;
// }
// add routers and services empty objects if they don't exist
if (!config_output.http.routers) {
@@ -264,18 +264,21 @@ export async function getTraefikConfig(
const configDomain = config.getDomain(resource.domainId);
let certResolver: string, preferWildcardCert: boolean;
if (!configDomain) {
certResolver = config.getRawConfig().traefik.cert_resolver;
preferWildcardCert =
config.getRawConfig().traefik.prefer_wildcard_cert;
} else {
certResolver = configDomain.cert_resolver;
preferWildcardCert = configDomain.prefer_wildcard_cert;
}
let tls = {};
if (build == "oss") {
if (
!privateConfig.getRawPrivateConfig().flags
.generate_own_certificates
) {
let certResolver: string, preferWildcardCert: boolean;
if (!configDomain) {
certResolver = config.getRawConfig().traefik.cert_resolver;
preferWildcardCert =
config.getRawConfig().traefik.prefer_wildcard_cert;
} else {
certResolver = configDomain.cert_resolver;
preferWildcardCert = configDomain.prefer_wildcard_cert;
}
tls = {
certResolver: certResolver,
...(preferWildcardCert
@@ -419,7 +422,7 @@ export async function getTraefikConfig(
return (
(targets as TargetWithSite[])
.filter((target: TargetWithSite) => {
.filter((target: TargetWithSite) => {
if (!target.enabled) {
return false;
}
@@ -440,7 +443,7 @@ export async function getTraefikConfig(
) {
return false;
}
} else if (target.site.type === "newt") {
} else if (target.site.type === "newt") {
if (
!target.internalPort ||
!target.method ||
@@ -448,10 +451,10 @@ export async function getTraefikConfig(
) {
return false;
}
}
return true;
})
.map((target: TargetWithSite) => {
}
return true;
})
.map((target: TargetWithSite) => {
if (
target.site.type === "local" ||
target.site.type === "wireguard"
@@ -459,14 +462,14 @@ export async function getTraefikConfig(
return {
url: `${target.method}://${target.ip}:${target.port}`
};
} else if (target.site.type === "newt") {
} else if (target.site.type === "newt") {
const ip =
target.site.subnet!.split("/")[0];
return {
url: `${target.method}://${ip}:${target.internalPort}`
};
}
})
}
})
// filter out duplicates
.filter(
(v, i, a) =>
@@ -709,4 +712,4 @@ export async function getTraefikConfig(
}
return config_output;
}
}

View File

@@ -26,6 +26,7 @@ import { sha256 } from "@oslojs/crypto/sha2";
import { serializeSessionCookie } from "@server/auth/sessions/app";
import { decrypt } from "@server/lib/crypto";
import config from "@server/lib/config";
import { TransferSessionResponse } from "@server/routers/auth/types";
const bodySchema = z.object({
token: z.string()
@@ -33,11 +34,6 @@ const bodySchema = z.object({
export type TransferSessionBodySchema = z.infer<typeof bodySchema>;
export type TransferSessionResponse = {
valid: boolean;
cookie?: string;
};
export async function transferSession(
req: Request,
res: Response,

View File

@@ -22,6 +22,8 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromZodError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { GetOrgSubscriptionResponse } from "@server/routers/billing/types";
// Import tables for billing
import {
customers,
@@ -37,11 +39,6 @@ const getOrgSchema = z
})
.strict();
export type GetOrgSubscriptionResponse = {
subscription: Subscription | null;
items: SubscriptionItem[];
};
registry.registerPath({
method: "get",
path: "/org/{orgId}/billing/subscription",

View File

@@ -25,6 +25,7 @@ import { OpenAPITags, registry } from "@server/openApi";
import { Limit, limits, Usage, usage } from "@server/db";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { GetOrgUsageResponse } from "@server/routers/billing/types";
const getOrgSchema = z
.object({
@@ -32,11 +33,6 @@ const getOrgSchema = z
})
.strict();
export type GetOrgUsageResponse = {
usage: Usage[];
limits: Limit[];
};
registry.registerPath({
method: "get",
path: "/org/{orgId}/billing/usage",

View File

@@ -19,6 +19,7 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromZodError } from "zod-validation-error";
import { getOrgTierData } from "#private/lib/billing";
import { GetOrgTierResponse } from "@server/routers/billing/types";
const getOrgSchema = z
.object({
@@ -26,11 +27,6 @@ const getOrgSchema = z
})
.strict();
export type GetOrgTierResponse = {
tier: string | null;
active: boolean;
};
export async function getOrgTier(
req: Request,
res: Response,

View File

@@ -15,15 +15,19 @@ import { Certificate, certificates, db, domains } from "@server/db";
import logger from "@server/logger";
import { Transaction } from "@server/db";
import { eq, or, and, like } from "drizzle-orm";
import { build } from "@server/build";
import privateConfig from "#private/lib/config";
/**
* Checks if a certificate exists for the given domain.
* If not, creates a new certificate in 'pending' state.
* Wildcard certs cover subdomains.
*/
export async function createCertificate(domainId: string, domain: string, trx: Transaction | typeof db) {
if (build !== "saas") {
export async function createCertificate(
domainId: string,
domain: string,
trx: Transaction | typeof db
) {
if (!privateConfig.getRawPrivateConfig().flags.generate_own_certificates) {
return;
}
@@ -39,7 +43,7 @@ export async function createCertificate(domainId: string, domain: string, trx: T
let existing: Certificate[] = [];
if (domainRecord.type == "ns") {
const domainLevelDown = domain.split('.').slice(1).join('.');
const domainLevelDown = domain.split(".").slice(1).join(".");
existing = await trx
.select()
.from(certificates)
@@ -49,7 +53,7 @@ export async function createCertificate(domainId: string, domain: string, trx: T
eq(certificates.wildcard, true), // only NS domains can have wildcard certs
or(
eq(certificates.domain, domain),
eq(certificates.domain, domainLevelDown),
eq(certificates.domain, domainLevelDown)
)
)
);
@@ -67,9 +71,7 @@ export async function createCertificate(domainId: string, domain: string, trx: T
}
if (existing.length > 0) {
logger.info(
`Certificate already exists for domain ${domain}`
);
logger.info(`Certificate already exists for domain ${domain}`);
return;
}

View File

@@ -21,6 +21,7 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { registry } from "@server/openApi";
import { GetCertificateResponse } from "@server/routers/certificates/types";
const getCertificateSchema = z
.object({
@@ -96,20 +97,6 @@ async function query(domainId: string, domain: string) {
return existing.length > 0 ? existing[0] : null;
}
export type GetCertificateResponse = {
certId: number;
domain: string;
domainId: string;
wildcard: boolean;
status: string; // pending, requested, valid, expired, failed
expiresAt: string | null;
lastRenewalAttempt: Date | null;
createdAt: string;
updatedAt: string;
errorMessage?: string | null;
renewalCount: number;
}
registry.registerPath({
method: "get",
path: "/org/{orgId}/certificate/{domainId}/{domain}",

View File

@@ -21,6 +21,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { db, domainNamespaces, resources } from "@server/db";
import { inArray } from "drizzle-orm";
import { CheckDomainAvailabilityResponse } from "@server/routers/domain/types";
const paramsSchema = z.object({}).strict();
@@ -30,15 +31,6 @@ const querySchema = z
})
.strict();
export type CheckDomainAvailabilityResponse = {
available: boolean;
options: {
domainNamespaceId: string;
domainId: string;
fullDomain: string;
}[];
};
registry.registerPath({
method: "get",
path: "/domain/check-namespace-availability",

View File

@@ -39,16 +39,17 @@ import {
import rateLimit, { ipKeyGenerator } from "express-rate-limit";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import {
unauthenticated as ua,
authenticated as a
} from "@server/routers/external";
import { verifyValidLicense } from "../middlewares/verifyValidLicense";
import { build } from "@server/build";
import {
unauthenticated as ua,
authenticated as a,
authRouter as aa
} from "@server/routers/external";
export const authenticated = a;
export const unauthenticated = ua;
export const authRouter = aa;
unauthenticated.post(
"/remote-exit-node/quick-start",
@@ -276,8 +277,6 @@ authenticated.get(
loginPage.getLoginPage
);
export const authRouter = Router();
authRouter.post(
"/remoteExitNode/get-token",
verifyValidLicense,

View File

@@ -68,10 +68,11 @@ import { decryptData } from "@server/lib/encryption";
import config from "@server/lib/config";
import privateConfig from "#private/lib/config";
import * as fs from "fs";
import { exchangeSession } from "@server/routers/badger";
import { exchangeSession } from "@server/routers/badger";
import { validateResourceSessionToken } from "@server/auth/sessions/resource";
import { checkExitNodeOrg, resolveExitNodes } from "#private/lib/exitNodes";
import { maxmindLookup } from "@server/db/maxmind";
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
// Zod schemas for request validation
const getResourceByDomainParamsSchema = z
@@ -162,6 +163,14 @@ const validateResourceSessionTokenBodySchema = z
})
.strict();
const validateResourceAccessTokenBodySchema = z
.object({
accessTokenId: z.string().optional(),
resourceId: z.number().optional(),
accessToken: z.string()
})
.strict();
// Certificates by domains query validation
const getCertificatesByDomainsQuerySchema = z
.object({
@@ -215,6 +224,33 @@ export type UserSessionWithUser = {
export const hybridRouter = Router();
hybridRouter.use(verifySessionRemoteExitNodeMiddleware);
hybridRouter.get(
"/general-config",
async (req: Request, res: Response, next: NextFunction) => {
return response(res, {
data: {
resource_session_request_param:
config.getRawConfig().server.resource_session_request_param,
resource_access_token_headers:
config.getRawConfig().server.resource_access_token_headers,
resource_access_token_param:
config.getRawConfig().server.resource_access_token_param,
session_cookie_name:
config.getRawConfig().server.session_cookie_name,
require_email_verification:
config.getRawConfig().flags?.require_email_verification ||
false,
resource_session_length_hours:
config.getRawConfig().server.resource_session_length_hours
},
success: true,
error: false,
message: "General config retrieved successfully",
status: HttpCode.OK
});
}
);
hybridRouter.get(
"/traefik-config",
async (req: Request, res: Response, next: NextFunction) => {
@@ -1101,6 +1137,52 @@ hybridRouter.post(
}
);
// Validate resource session token
hybridRouter.post(
"/resource/:resourceId/access-token/verify",
async (req: Request, res: Response, next: NextFunction) => {
try {
const parsedBody = validateResourceAccessTokenBodySchema.safeParse(
req.body
);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString()
)
);
}
const { accessToken, resourceId, accessTokenId } = parsedBody.data;
const result = await verifyResourceAccessToken({
accessTokenId,
accessToken,
resourceId
});
return response(res, {
data: result,
success: true,
error: false,
message: result.valid
? "Resource access token is valid"
: "Resource access token is invalid or expired",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Failed to validate resource session token"
)
);
}
}
);
const geoIpLookupParamsSchema = z.object({
ip: z.string().ip()
});
@@ -1489,4 +1571,4 @@ hybridRouter.post(
);
}
}
);
);

View File

@@ -33,6 +33,7 @@ import { createCertificate } from "#private/routers/certificates/createCertifica
import { getOrgTierData } from "#private/lib/billing";
import { TierId } from "@server/lib/billing/tiers";
import { build } from "@server/build";
import { CreateLoginPageResponse } from "@server/routers/loginPage/types";
const paramsSchema = z
.object({
@@ -49,8 +50,6 @@ const bodySchema = z
export type CreateLoginPageBody = z.infer<typeof bodySchema>;
export type CreateLoginPageResponse = LoginPage;
export async function createLoginPage(
req: Request,
res: Response,

View File

@@ -20,6 +20,7 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { eq, and } from "drizzle-orm";
import { DeleteLoginPageResponse } from "@server/routers/loginPage/types";
const paramsSchema = z
.object({
@@ -28,8 +29,6 @@ const paramsSchema = z
})
.strict();
export type DeleteLoginPageResponse = LoginPage;
export async function deleteLoginPage(
req: Request,
res: Response,

View File

@@ -20,6 +20,7 @@ import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { GetLoginPageResponse } from "@server/routers/loginPage/types";
const paramsSchema = z
.object({
@@ -40,10 +41,6 @@ async function query(orgId: string) {
return res?.loginPage;
}
export type GetLoginPageResponse = NonNullable<
Awaited<ReturnType<typeof query>>
>;
export async function getLoginPage(
req: Request,
res: Response,

View File

@@ -20,6 +20,7 @@ import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { LoadLoginPageResponse } from "@server/routers/loginPage/types";
const querySchema = z.object({
resourceId: z.coerce.number().int().positive().optional(),
@@ -70,10 +71,6 @@ async function query(orgId: string | undefined, fullDomain: string) {
};
}
export type LoadLoginPageResponse = NonNullable<
Awaited<ReturnType<typeof query>>
> & { orgId: string };
export async function loadLoginPage(
req: Request,
res: Response,

View File

@@ -26,6 +26,7 @@ import { createCertificate } from "#private/routers/certificates/createCertifica
import { getOrgTierData } from "#private/lib/billing";
import { TierId } from "@server/lib/billing/tiers";
import { build } from "@server/build";
import { UpdateLoginPageResponse } from "@server/routers/loginPage/types";
const paramsSchema = z
.object({
@@ -55,8 +56,6 @@ const bodySchema = z
export type UpdateLoginPageBody = z.infer<typeof bodySchema>;
export type UpdateLoginPageResponse = LoginPage;
export async function updateLoginPage(
req: Request,
res: Response,

View File

@@ -27,6 +27,7 @@ import config from "@server/lib/config";
import { build } from "@server/build";
import { getOrgTierData } from "#private/lib/billing";
import { TierId } from "@server/lib/billing/tiers";
import { CreateOrgIdpResponse } from "@server/routers/orgIdp/types";
const paramsSchema = z.object({ orgId: z.string().nonempty() }).strict();
@@ -47,11 +48,6 @@ const bodySchema = z
})
.strict();
export type CreateOrgIdpResponse = {
idpId: number;
redirectUrl: string;
};
// registry.registerPath({
// method: "put",
// path: "/idp/oidc",

View File

@@ -25,6 +25,7 @@ import { OpenAPITags, registry } from "@server/openApi";
import config from "@server/lib/config";
import { decrypt } from "@server/lib/crypto";
import { generateOidcRedirectUrl } from "@server/lib/idp/generateRedirectUrl";
import { GetOrgIdpResponse } from "@server/routers/orgIdp/types";
const paramsSchema = z
.object({
@@ -47,10 +48,6 @@ async function query(idpId: number, orgId: string) {
return res;
}
export type GetOrgIdpResponse = NonNullable<
Awaited<ReturnType<typeof query>>
> & { redirectUrl: string };
// registry.registerPath({
// method: "get",
// path: "/idp/{idpId}",

View File

@@ -22,6 +22,7 @@ import { eq, sql } from "drizzle-orm";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { ListOrgIdpsResponse } from "@server/routers/orgIdp/types";
const querySchema = z
.object({
@@ -65,15 +66,6 @@ async function query(orgId: string, limit: number, offset: number) {
return res;
}
export type ListOrgIdpsResponse = {
idps: Awaited<ReturnType<typeof query>>;
pagination: {
total: number;
limit: number;
offset: number;
};
};
// registry.registerPath({
// method: "get",
// path: "/idp",

View File

@@ -29,17 +29,12 @@ import { and, eq } from "drizzle-orm";
import { getNextAvailableSubnet } from "@server/lib/exitNodes";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { CreateRemoteExitNodeResponse } from "@server/routers/remoteExitNode/types";
export const paramsSchema = z.object({
orgId: z.string()
});
export type CreateRemoteExitNodeResponse = {
token: string;
remoteExitNodeId: string;
secret: string;
};
const bodySchema = z
.object({
remoteExitNodeId: z.string().length(15),
@@ -89,30 +84,25 @@ export async function createRemoteExitNode(
orgId,
FeatureId.REMOTE_EXIT_NODES
);
if (!usage) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
"No usage data found for this organization"
)
);
}
const rejectRemoteExitNodes = await usageService.checkLimitSet(
orgId,
false,
FeatureId.REMOTE_EXIT_NODES,
{
...usage,
instantaneousValue: (usage.instantaneousValue || 0) + 1
} // We need to add one to know if we are violating the limit
);
if (rejectRemoteExitNodes) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Remote exit node limit exceeded. Please upgrade your plan or contact us at support@fossorial.io"
)
if (usage) {
const rejectRemoteExitNodes = await usageService.checkLimitSet(
orgId,
false,
FeatureId.REMOTE_EXIT_NODES,
{
...usage,
instantaneousValue: (usage.instantaneousValue || 0) + 1
} // We need to add one to know if we are violating the limit
);
if (rejectRemoteExitNodes) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Remote exit node limit exceeded. Please upgrade your plan or contact us at support@fossorial.io"
)
);
}
}
const secretHash = await hashPassword(secret);

View File

@@ -21,6 +21,7 @@ import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { GetRemoteExitNodeResponse } from "@server/routers/remoteExitNode/types";
const getRemoteExitNodeSchema = z
.object({
@@ -52,8 +53,6 @@ async function query(remoteExitNodeId: string) {
return remoteExitNode;
}
export type GetRemoteExitNodeResponse = Awaited<ReturnType<typeof query>>;
export async function getRemoteExitNode(
req: Request,
res: Response,

View File

@@ -35,8 +35,6 @@ export const remoteExitNodeGetTokenBodySchema = z.object({
token: z.string().optional()
});
export type RemoteExitNodeGetTokenBody = z.infer<typeof remoteExitNodeGetTokenBodySchema>;
export async function getRemoteExitNodeToken(
req: Request,
res: Response,

View File

@@ -21,6 +21,7 @@ import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { ListRemoteExitNodesResponse } from "@server/routers/remoteExitNode/types";
const listRemoteExitNodesParamsSchema = z
.object({
@@ -43,7 +44,7 @@ const listRemoteExitNodesSchema = z.object({
.pipe(z.number().int().nonnegative())
});
function queryRemoteExitNodes(orgId: string) {
export function queryRemoteExitNodes(orgId: string) {
return db
.select({
remoteExitNodeId: remoteExitNodes.remoteExitNodeId,
@@ -65,11 +66,6 @@ function queryRemoteExitNodes(orgId: string) {
);
}
export type ListRemoteExitNodesResponse = {
remoteExitNodes: Awaited<ReturnType<typeof queryRemoteExitNodes>>;
pagination: { total: number; limit: number; offset: number };
};
export async function listRemoteExitNodes(
req: Request,
res: Response,

View File

@@ -19,11 +19,7 @@ import logger from "@server/logger";
import { generateId } from "@server/auth/sessions/app";
import { fromError } from "zod-validation-error";
import { z } from "zod";
export type PickRemoteExitNodeDefaultsResponse = {
remoteExitNodeId: string;
secret: string;
};
import { PickRemoteExitNodeDefaultsResponse } from "@server/routers/remoteExitNode/types";
const paramsSchema = z
.object({

View File

@@ -12,7 +12,7 @@
*/
import { NextFunction, Request, Response } from "express";
import { db, exitNodes, exitNodeOrgs } from "@server/db";
import { db } from "@server/db";
import HttpCode from "@server/types/HttpCode";
import { remoteExitNodes } from "@server/db";
import createHttpError from "http-errors";
@@ -24,11 +24,7 @@ import { hashPassword } from "@server/auth/password";
import logger from "@server/logger";
import z from "zod";
import { fromError } from "zod-validation-error";
export type QuickStartRemoteExitNodeResponse = {
remoteExitNodeId: string;
secret: string;
};
import { QuickStartRemoteExitNodeResponse } from "@server/routers/remoteExitNode/types";
const INSTALLER_KEY = "af4e4785-7e09-11f0-b93a-74563c4e2a7e";