Compare commits

..

4 Commits

Author SHA1 Message Date
miloschwartz
5fd78817a8 fix image 2026-04-13 20:28:05 -07:00
Owen Schwartz
72bc125f84 Merge pull request #2854 from fosrl/dev
Rename script
2026-04-13 16:23:39 -07:00
Owen Schwartz
b18ea66def Merge pull request #2853 from fosrl/dev
1.17.1-s.1
2026-04-13 12:28:08 -07:00
Owen Schwartz
41f541a531 Merge pull request #2852 from fosrl/dev
1.17.1-s.0
2026-04-13 11:36:36 -07:00
224 changed files with 889 additions and 2007 deletions

View File

@@ -1 +0,0 @@
*-journal

View File

@@ -6,7 +6,7 @@ import sys
HEADER_TEXT = """/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
@@ -17,109 +17,87 @@ HEADER_TEXT = """/*
*/
"""
HEADER_NORMALIZED = HEADER_TEXT.strip()
def extract_leading_block_comment(content):
"""
If the file content begins with a /* ... */ block comment, return the
full text of that comment (including the delimiters) and the index at
which the rest of the file starts (after any trailing newlines).
Returns (None, 0) when no such comment is found.
"""
stripped = content.lstrip()
if not stripped.startswith('/*'):
return None, 0
# Account for any leading whitespace before the comment
comment_start = content.index('/*')
end_marker = content.find('*/', comment_start + 2)
if end_marker == -1:
return None, 0
comment_end = end_marker + 2 # position just after '*/'
comment_text = content[comment_start:comment_end].strip()
# Advance past any whitespace / newlines that follow the closing */
rest_start = comment_end
while rest_start < len(content) and content[rest_start] in '\n\r':
rest_start += 1
return comment_text, rest_start
def should_add_header(file_path):
"""
Checks if a file should receive the commercial license header.
Returns True if 'server/private' is in the path.
Returns True if 'private' is in the path or file content.
"""
# Check if 'private' is in the file path (case-insensitive)
if 'server/private' in file_path.lower():
return True
return False
# Check if 'private' is in the file content (case-insensitive)
# try:
# with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
# content = f.read()
# if 'private' in content.lower():
# return True
# except Exception as e:
# print(f"Could not read file {file_path}: {e}")
return False
def process_directory(root_dir):
"""
Recursively scans a directory and adds/replaces/removes headers in
qualifying .ts or .tsx files, skipping any 'node_modules' directories.
Recursively scans a directory and adds headers to qualifying .ts or .tsx files,
skipping any 'node_modules' directories.
"""
print(f"Scanning directory: {root_dir}")
files_processed = 0
files_modified = 0
headers_added = 0
for root, dirs, files in os.walk(root_dir):
# Exclude 'node_modules' directories from the scan.
# --- MODIFICATION ---
# Exclude 'node_modules' directories from the scan to improve performance.
if 'node_modules' in dirs:
dirs.remove('node_modules')
for file in files:
if not (file.endswith('.ts') or file.endswith('.tsx')):
continue
if file.endswith('.ts') or file.endswith('.tsx'):
file_path = os.path.join(root, file)
files_processed += 1
file_path = os.path.join(root, file)
files_processed += 1
try:
with open(file_path, 'r+', encoding='utf-8') as f:
original_content = f.read()
has_header = original_content.startswith(HEADER_TEXT.strip())
if should_add_header(file_path):
# Add header only if it's not already there
if not has_header:
f.seek(0, 0) # Go to the beginning of the file
f.write(HEADER_TEXT.strip() + '\n\n' + original_content)
print(f"Added header to: {file_path}")
headers_added += 1
else:
print(f"Header already exists in: {file_path}")
else:
# Remove header if it exists but shouldn't be there
if has_header:
# Find the end of the header and remove it (including following newlines)
header_with_newlines = HEADER_TEXT.strip() + '\n\n'
if original_content.startswith(header_with_newlines):
content_without_header = original_content[len(header_with_newlines):]
else:
# Handle case where there might be different newline patterns
header_end = len(HEADER_TEXT.strip())
# Skip any newlines after the header
while header_end < len(original_content) and original_content[header_end] in '\n\r':
header_end += 1
content_without_header = original_content[header_end:]
f.seek(0)
f.write(content_without_header)
f.truncate()
print(f"Removed header from: {file_path}")
headers_added += 1 # Reusing counter for modifications
try:
with open(file_path, 'r', encoding='utf-8') as f:
original_content = f.read()
existing_comment, body_start = extract_leading_block_comment(
original_content
)
has_any_header = existing_comment is not None
has_correct_header = existing_comment == HEADER_NORMALIZED
body = original_content[body_start:] if has_any_header else original_content
if should_add_header(file_path):
if has_correct_header:
print(f"Header up-to-date: {file_path}")
else:
# Either no header exists or the header is outdated — write
# the correct one.
action = "Replaced header in" if has_any_header else "Added header to"
new_content = HEADER_NORMALIZED + '\n\n' + body
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f"{action}: {file_path}")
files_modified += 1
else:
if has_any_header:
# Remove the header — it shouldn't be here.
with open(file_path, 'w', encoding='utf-8') as f:
f.write(body)
print(f"Removed header from: {file_path}")
files_modified += 1
else:
print(f"No header needed: {file_path}")
except Exception as e:
print(f"Error processing file {file_path}: {e}")
except Exception as e:
print(f"Error processing file {file_path}: {e}")
print("\n--- Scan Complete ---")
print(f"Total .ts or .tsx files found: {files_processed}")
print(f"Files modified (added/replaced/removed): {files_modified}")
print(f"Total .ts or .tsx files found: {files_processed}")
print(f"Files modified (headers added/removed): {headers_added}")
if __name__ == "__main__":
@@ -128,10 +106,10 @@ if __name__ == "__main__":
if len(sys.argv) > 1:
target_directory = sys.argv[1]
else:
target_directory = '.' # Default to current directory
target_directory = '.' # Default to current directory
if not os.path.isdir(target_directory):
print(f"Error: Directory '{target_directory}' not found.")
sys.exit(1)
process_directory(os.path.abspath(target_directory))
process_directory(os.path.abspath(target_directory))

View File

@@ -2351,7 +2351,7 @@
},
"scale": {
"title": "Скала",
"description": "Функции за корпоративни клиенти, 50 потребители, 100 сайта и приоритетна поддръжка."
"description": "Предприятие, 50 потребители, 50 сайта и приоритетна поддръжка."
}
},
"personalUseOnly": "Само за лична употреба (безплатен лиценз - без проверка)",

View File

@@ -2351,7 +2351,7 @@
},
"scale": {
"title": "Měřítko",
"description": "Podnikové funkce, 50 uživatelů, 100 stránek a prioritní podpora."
"description": "Podnikové funkce, 50 uživatelů, 50 míst a prioritní podpory."
}
},
"personalUseOnly": "Pouze pro osobní použití (zdarma licence - bez ověření)",

View File

@@ -2351,7 +2351,7 @@
},
"scale": {
"title": "Maßstab",
"description": "Unternehmensmerkmale, 50 Benutzer, 100 Standorte und prioritärer Support."
"description": "Enterprise Features, 50 Benutzer, 50 Sites und Prioritätsunterstützung."
}
},
"personalUseOnly": "Nur persönliche Nutzung (kostenlose Lizenz - kein Checkout)",

View File

@@ -898,7 +898,6 @@
"idpDisplayName": "A display name for this identity provider",
"idpAutoProvisionUsers": "Auto Provision Users",
"idpAutoProvisionUsersDescription": "When enabled, users will be automatically created in the system upon first login with the ability to map users to roles and organizations.",
"idpAutoProvisionConfigureAfterCreate": "You can configure auto provision settings once the identity provider is created.",
"licenseBadge": "EE",
"idpType": "Provider Type",
"idpTypeDescription": "Select the type of identity provider you want to configure",
@@ -950,7 +949,7 @@
"defaultMappingsRole": "Default Role Mapping",
"defaultMappingsRoleDescription": "The result of this expression must return the role name as defined in the organization as a string.",
"defaultMappingsOrg": "Default Organization Mapping",
"defaultMappingsOrgDescription": "When set, this expression must return the organization ID or true for the user to access that organization. When unset, defining a role mapping is enough: the user is allowed in as long as a valid role mapping can be resolved for them within the organization.",
"defaultMappingsOrgDescription": "When set, this expression must return the organization ID or true for the user to access that organization. When unset, defining an organization policy for that org is enough: the user is allowed in as long as a valid role mapping can be resolved for them within the organization.",
"defaultMappingsSubmit": "Save Default Mappings",
"orgPoliciesEdit": "Edit Organization Policy",
"org": "Organization",
@@ -2027,7 +2026,7 @@
},
"internationaldomaindetected": "International Domain Detected",
"willbestoredas": "Will be stored as:",
"roleMappingDescription": "Determine how roles are assigned to users when they sign in with this identity provider.",
"roleMappingDescription": "Determine how roles are assigned to users when they sign in when Auto Provision is enabled.",
"selectRole": "Select a Role",
"roleMappingExpression": "Expression",
"selectRolePlaceholder": "Choose a role",
@@ -2352,7 +2351,7 @@
},
"scale": {
"title": "Scale",
"description": "Enterprise features, 50 users, 100 sites, and priority support."
"description": "Enterprise features, 50 users, 50 sites, and priority support."
}
},
"personalUseOnly": "Personal use only (free license - no checkout)",
@@ -2825,9 +2824,9 @@
"streamingHttpWebhookTitle": "HTTP Webhook",
"streamingHttpWebhookDescription": "Send events to any HTTP endpoint with flexible authentication and templating.",
"streamingS3Title": "Amazon S3",
"streamingS3Description": "Stream events to an S3-compatible object storage bucket. Contact support to enable this destination.",
"streamingS3Description": "Stream events to an S3-compatible object storage bucket. Coming soon.",
"streamingDatadogTitle": "Datadog",
"streamingDatadogDescription": "Forward events directly to your Datadog account. Contact support to enable this destination.",
"streamingDatadogDescription": "Forward events directly to your Datadog account. Coming soon.",
"streamingTypePickerDescription": "Choose a destination type to get started.",
"streamingFailedToLoad": "Failed to load destinations",
"streamingUnexpectedError": "An unexpected error occurred.",
@@ -2850,7 +2849,7 @@
"httpDestNamePlaceholder": "My HTTP destination",
"httpDestUrlLabel": "Destination URL",
"httpDestUrlErrorHttpRequired": "URL must use http or https",
"httpDestUrlErrorHttpsRequired": "HTTPS is required",
"httpDestUrlErrorHttpsRequired": "HTTPS is required on cloud deployments",
"httpDestUrlErrorInvalid": "Enter a valid URL (e.g. https://example.com/webhook)",
"httpDestAuthTitle": "Authentication",
"httpDestAuthDescription": "Choose how requests to your endpoint are authenticated.",
@@ -2900,22 +2899,5 @@
"httpDestUpdatedSuccess": "Destination updated successfully",
"httpDestCreatedSuccess": "Destination created successfully",
"httpDestUpdateFailed": "Failed to update destination",
"httpDestCreateFailed": "Failed to create destination",
"idpAddActionCreateNew": "Create new identity provider",
"idpAddActionImportFromOrg": "Import from another organization",
"idpImportDialogTitle": "Import Identity Provider",
"idpImportDialogDescription": "Choose an identity provider from an organization where you are an admin. It will be linked to this organization.",
"idpImportSearchPlaceholder": "Search by organization or provider name...",
"idpImportEmpty": "No identity providers found.",
"idpImportedDescription": "Identity provider imported successfully.",
"idpDeleteGlobalQuestion": "Are you sure you want to permanently delete this identity provider?",
"idpDeleteGlobalDescription": "This will permanently delete the identity provider from all organizations it is associated with.",
"idpUnassociateTitle": "Unassociate Identity Provider",
"idpUnassociateQuestion": "Are you sure you want to unassociate this identity provider from this organization?",
"idpUnassociateDescription": "All users associated with this identity provider will be removed from this organization, but the identity provider will still continue to exist for other associated organizations.",
"idpUnassociateConfirm": "Confirm Unassociate Identity Provider",
"idpUnassociateWarning": "This cannot be undone for this organization.",
"idpUnassociatedDescription": "Identity provider unassociated from this organization successfully",
"idpUnassociateMenu": "Unassociate",
"idpDeleteAllOrgsMenu": "Delete"
"httpDestCreateFailed": "Failed to create destination"
}

View File

@@ -2351,7 +2351,7 @@
},
"scale": {
"title": "Escala",
"description": "Funcionalidades empresariales, 50 usuarios, 100 sitios y soporte prioritario."
"description": "Características de la empresa, 50 usuarios, 50 sitios y soporte prioritario."
}
},
"personalUseOnly": "Solo uso personal (licencia gratuita - sin salida)",

View File

@@ -2351,7 +2351,7 @@
},
"scale": {
"title": "Échelle",
"description": "Fonctionnalités d'entreprise, 50 utilisateurs, 100 sites et support prioritaire."
"description": "Fonctionnalités d'entreprise, 50 utilisateurs, 50 sites et une prise en charge prioritaire."
}
},
"personalUseOnly": "Usage personnel uniquement (licence gratuite - pas de validation)",

View File

@@ -2351,7 +2351,7 @@
},
"scale": {
"title": "Scala",
"description": "Funzionalità aziendali, 50 utenti, 100 siti e supporto prioritario."
"description": "Funzionalità aziendali, 50 utenti, 50 siti e supporto prioritario."
}
},
"personalUseOnly": "Uso personale esclusivo (licenza gratuita - nessun pagamento)",

View File

@@ -2351,7 +2351,7 @@
},
"scale": {
"title": "스케일",
"description": "기업 기능, 50명의 사용자, 100개의 사이트, 그리고 우선 지원."
"description": "기업 기능, 50명의 사용자, 50개의 사이트, 우선 지원."
}
},
"personalUseOnly": "개인용으로만 사용 (무료 라이선스 - 결제 없음)",

View File

@@ -2351,7 +2351,7 @@
},
"scale": {
"title": "Skala",
"description": "Funksjoner for bedrifter, 50 brukere, 100 nettsteder og prioritert support."
"description": "Enterprise features, 50 brukere, 50 nettsteder og prioritetsstøtte."
}
},
"personalUseOnly": "Kun personlig bruk (gratis lisens - ingen kasse)",

View File

@@ -2351,7 +2351,7 @@
},
"scale": {
"title": "Schaal",
"description": "Enterprise-functies, 50 gebruikers, 100 sites en prioritaire ondersteuning."
"description": "Enterprise functies, 50 gebruikers, 50 sites en prioriteit ondersteuning."
}
},
"personalUseOnly": "Alleen voor persoonlijk gebruik (gratis licentie - geen afrekening)",

View File

@@ -2351,7 +2351,7 @@
},
"scale": {
"title": "Skala",
"description": "Funkcje dla przedsiębiorstw, 50 użytkowników, 100 witryn i priorytetowe wsparcie."
"description": "Cechy przedsiębiorstw, 50 użytkowników, 50 obiektów i wsparcie priorytetowe."
}
},
"personalUseOnly": "Tylko do użytku osobistego (darmowa licencja - bez płatności)",

View File

@@ -2351,7 +2351,7 @@
},
"scale": {
"title": "Escala",
"description": "Recursos empresariais, 50 usuários, 100 sites, e suporte prioritário."
"description": "Recursos de empresa, 50 usuários, 50 sites e apoio prioritário."
}
},
"personalUseOnly": "Uso pessoal apenas (licença gratuita - sem checkout)",

View File

@@ -2351,7 +2351,7 @@
},
"scale": {
"title": "Масштаб",
"description": "Функции корпоративного уровня, 50 пользователей, 100 сайтов и приоритетная поддержка."
"description": "Функции предприятия, 50 пользователей, 50 сайтов, а также приоритетная поддержка."
}
},
"personalUseOnly": "Только для личного использования (бесплатная лицензия - без оформления на кассе)",

View File

@@ -2351,7 +2351,7 @@
},
"scale": {
"title": "Ölçek",
"description": "Kurumsal özellikler, 50 kullanıcı, 100 site ve öncelikli destek."
"description": "Kurumsal özellikler, 50 kullanıcı, 50 site ve öncelikli destek."
}
},
"personalUseOnly": "Kişisel kullanım için (ücretsiz lisans - ödeme yok)",

View File

@@ -2351,7 +2351,7 @@
},
"scale": {
"title": "缩放比例",
"description": "企业功能,50个用户100个站点,以及优先支持。"
"description": "企业特征、50个用户、50个站点优先支持。"
}
},
"personalUseOnly": "仅限个人使用(免费许可 - 无需结账)",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 765 KiB

After

Width:  |  Height:  |  Size: 588 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 742 KiB

After

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 765 KiB

After

Width:  |  Height:  |  Size: 588 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 274 KiB

View File

@@ -1080,7 +1080,6 @@ export type ResourceWhitelist = InferSelectModel<typeof resourceWhitelist>;
export type VersionMigration = InferSelectModel<typeof versionMigrations>;
export type ResourceRule = InferSelectModel<typeof resourceRules>;
export type Domain = InferSelectModel<typeof domains>;
export type DnsRecord = InferSelectModel<typeof dnsRecords>;
export type SupporterKey = InferSelectModel<typeof supporterKey>;
export type Idp = InferSelectModel<typeof idp>;
export type ApiKey = InferSelectModel<typeof apiKeys>;

View File

@@ -9,8 +9,8 @@ export type LicensePriceSet = {
export const licensePriceSet: LicensePriceSet = {
// Free license matches the freeLimitSet
[LicenseId.SMALL_LICENSE]: "price_1TMJzmD3Ee2Ir7Wm05NlGImT",
[LicenseId.BIG_LICENSE]: "price_1TMJzzD3Ee2Ir7WmzJw9TerS"
[LicenseId.SMALL_LICENSE]: "price_1SxKHiD3Ee2Ir7WmvtEh17A8",
[LicenseId.BIG_LICENSE]: "price_1SxKHiD3Ee2Ir7WmMUiP0H6Y"
};
export const licensePriceSetSandbox: LicensePriceSet = {

View File

@@ -591,7 +591,7 @@ export function generateSubnetProxyTargetV2(
pubKey: string | null;
subnet: string | null;
}[]
): SubnetProxyTargetV2[] | undefined {
): SubnetProxyTargetV2 | undefined {
if (clients.length === 0) {
logger.debug(
`No clients have access to site resource ${siteResource.siteResourceId}, skipping target generation.`
@@ -599,7 +599,7 @@ export function generateSubnetProxyTargetV2(
return;
}
let targets: SubnetProxyTargetV2[] = [];
let target: SubnetProxyTargetV2 | null = null;
const portRange = [
...parsePortRangeString(siteResource.tcpPortRangeString, "tcp"),
@@ -614,54 +614,52 @@ export function generateSubnetProxyTargetV2(
if (ipSchema.safeParse(destination).success) {
destination = `${destination}/32`;
targets.push({
target = {
sourcePrefixes: [],
destPrefix: destination,
portRange,
disableIcmp,
resourceId: siteResource.siteResourceId
});
resourceId: siteResource.siteResourceId,
};
}
if (siteResource.alias && siteResource.aliasAddress) {
// also push a match for the alias address
targets.push({
target = {
sourcePrefixes: [],
destPrefix: `${siteResource.aliasAddress}/32`,
rewriteTo: destination,
portRange,
disableIcmp,
resourceId: siteResource.siteResourceId
});
resourceId: siteResource.siteResourceId,
};
}
} else if (siteResource.mode == "cidr") {
targets.push({
target = {
sourcePrefixes: [],
destPrefix: siteResource.destination,
portRange,
disableIcmp,
resourceId: siteResource.siteResourceId
});
resourceId: siteResource.siteResourceId,
};
}
if (targets.length == 0) {
if (!target) {
return;
}
for (const target of targets) {
for (const clientSite of clients) {
if (!clientSite.subnet) {
logger.debug(
`Client ${clientSite.clientId} has no subnet, skipping for site resource ${siteResource.siteResourceId}.`
);
continue;
}
const clientPrefix = `${clientSite.subnet.split("/")[0]}/32`;
// add client prefix to source prefixes
target.sourcePrefixes.push(clientPrefix);
for (const clientSite of clients) {
if (!clientSite.subnet) {
logger.debug(
`Client ${clientSite.clientId} has no subnet, skipping for site resource ${siteResource.siteResourceId}.`
);
continue;
}
const clientPrefix = `${clientSite.subnet.split("/")[0]}/32`;
// add client prefix to source prefixes
target.sourcePrefixes.push(clientPrefix);
}
// print a nice representation of the targets
@@ -669,34 +667,36 @@ export function generateSubnetProxyTargetV2(
// `Generated subnet proxy targets for: ${JSON.stringify(targets, null, 2)}`
// );
return targets;
return target;
}
/**
* Converts a SubnetProxyTargetV2 to an array of SubnetProxyTarget (v1)
* by expanding each source prefix into its own target entry.
* @param targetV2 - The v2 target to convert
* @returns Array of v1 SubnetProxyTarget objects
*/
export function convertSubnetProxyTargetsV2ToV1(
targetsV2: SubnetProxyTargetV2[]
): SubnetProxyTarget[] {
return targetsV2.flatMap((targetV2) =>
targetV2.sourcePrefixes.map((sourcePrefix) => ({
sourcePrefix,
destPrefix: targetV2.destPrefix,
...(targetV2.disableIcmp !== undefined && {
disableIcmp: targetV2.disableIcmp
}),
...(targetV2.rewriteTo !== undefined && {
rewriteTo: targetV2.rewriteTo
}),
...(targetV2.portRange !== undefined && {
portRange: targetV2.portRange
})
}))
);
}
export function convertSubnetProxyTargetsV2ToV1(
targetsV2: SubnetProxyTargetV2[]
): SubnetProxyTarget[] {
return targetsV2.flatMap((targetV2) =>
targetV2.sourcePrefixes.map((sourcePrefix) => ({
sourcePrefix,
destPrefix: targetV2.destPrefix,
...(targetV2.disableIcmp !== undefined && {
disableIcmp: targetV2.disableIcmp
}),
...(targetV2.rewriteTo !== undefined && {
rewriteTo: targetV2.rewriteTo
}),
...(targetV2.portRange !== undefined && {
portRange: targetV2.portRange
})
}))
);
}
// Custom schema for validating port range strings
// Format: "80,443,8000-9000" or "*" for all ports, or empty string

View File

@@ -1,5 +1,3 @@
// Normalizes
/**
* Normalizes a post-authentication path for safe use when building redirect URLs.
* Returns a path that starts with / and does not allow open redirects (no //, no :).

View File

@@ -661,16 +661,16 @@ async function handleSubnetProxyTargetUpdates(
);
if (addedClients.length > 0) {
const targetsToAdd = generateSubnetProxyTargetV2(
const targetToAdd = generateSubnetProxyTargetV2(
siteResource,
addedClients
);
if (targetsToAdd) {
if (targetToAdd) {
proxyJobs.push(
addSubnetProxyTargets(
newt.newtId,
targetsToAdd,
[targetToAdd],
newt.version
)
);
@@ -698,16 +698,16 @@ async function handleSubnetProxyTargetUpdates(
);
if (removedClients.length > 0) {
const targetsToRemove = generateSubnetProxyTargetV2(
const targetToRemove = generateSubnetProxyTargetV2(
siteResource,
removedClients
);
if (targetsToRemove) {
if (targetToRemove) {
proxyJobs.push(
removeSubnetProxyTargets(
newt.newtId,
targetsToRemove,
[targetToRemove],
newt.version
)
);
@@ -1164,7 +1164,7 @@ async function handleMessagesForClientResources(
}
for (const resource of resources) {
const targets = generateSubnetProxyTargetV2(resource, [
const target = generateSubnetProxyTargetV2(resource, [
{
clientId: client.clientId,
pubKey: client.pubKey,
@@ -1172,11 +1172,11 @@ async function handleMessagesForClientResources(
}
]);
if (targets) {
if (target) {
proxyJobs.push(
addSubnetProxyTargets(
newt.newtId,
targets,
[target],
newt.version
)
);
@@ -1241,7 +1241,7 @@ async function handleMessagesForClientResources(
}
for (const resource of resources) {
const targets = generateSubnetProxyTargetV2(resource, [
const target = generateSubnetProxyTargetV2(resource, [
{
clientId: client.clientId,
pubKey: client.pubKey,
@@ -1249,11 +1249,11 @@ async function handleMessagesForClientResources(
}
]);
if (targets) {
if (target) {
proxyJobs.push(
removeSubnetProxyTargets(
newt.newtId,
targets,
[target],
newt.version
)
);

View File

@@ -1,5 +1,3 @@
// Sanitizes
/**
* Sanitize a string field before inserting into a database TEXT column.
*
@@ -39,4 +37,4 @@ export function sanitizeString(
// Strip null bytes, C0 control chars (except HT/LF/CR), and DEL.
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "")
);
}
}

View File

@@ -1,5 +1,3 @@
// tokenCache
/**
* Returns a cached plaintext token from Redis if one exists and decrypts
* cleanly, otherwise calls `createSession` to mint a fresh token, stores the

View File

@@ -19,7 +19,6 @@ export class TraefikConfigManager {
private timeoutId: NodeJS.Timeout | null = null;
private lastCertificateFetch: Date | null = null;
private lastKnownDomains = new Set<string>();
private pendingDeletion = new Map<string, number>(); // domain -> cycles remaining before delete
private lastLocalCertificateState = new Map<
string,
{
@@ -1005,62 +1004,33 @@ export class TraefikConfigManager {
const dirName = dirent.name;
// Only delete if NO current domain is exactly the same or ends with `.${dirName}`
const isUnused = !Array.from(currentActiveDomains).some(
const shouldDelete = !Array.from(currentActiveDomains).some(
(domain) =>
domain === dirName || domain.endsWith(`.${dirName}`)
);
if (!isUnused) {
// Domain is still active — remove from pending deletion if it was queued
if (this.pendingDeletion.has(dirName)) {
logger.info(
`Certificate ${dirName} is active again, cancelling pending deletion`
);
this.pendingDeletion.delete(dirName);
}
continue;
}
// Domain is unused — add to pending deletion or decrement its counter
if (!this.pendingDeletion.has(dirName)) {
const graceCycles = 3;
if (shouldDelete) {
const domainDir = path.join(certsPath, dirName);
logger.info(
`Certificate ${dirName} is no longer in use. Will delete after ${graceCycles} more cycles.`
`Cleaning up unused certificate directory: ${dirName}`
);
this.pendingDeletion.set(dirName, graceCycles);
} else {
const remaining = this.pendingDeletion.get(dirName)! - 1;
if (remaining > 0) {
logger.info(
`Certificate ${dirName} pending deletion: ${remaining} cycle(s) remaining`
fs.rmSync(domainDir, { recursive: true, force: true });
// Remove from local state tracking
this.lastLocalCertificateState.delete(dirName);
// Remove from dynamic config
const certFilePath = path.join(domainDir, "cert.pem");
const keyFilePath = path.join(domainDir, "key.pem");
const before = dynamicConfig.tls.certificates.length;
dynamicConfig.tls.certificates =
dynamicConfig.tls.certificates.filter(
(entry: any) =>
entry.certFile !== certFilePath &&
entry.keyFile !== keyFilePath
);
this.pendingDeletion.set(dirName, remaining);
} else {
// Grace period expired — actually delete now
this.pendingDeletion.delete(dirName);
const domainDir = path.join(certsPath, dirName);
logger.info(
`Cleaning up unused certificate directory: ${dirName}`
);
fs.rmSync(domainDir, { recursive: true, force: true });
// Remove from local state tracking
this.lastLocalCertificateState.delete(dirName);
// Remove from dynamic config
const certFilePath = path.join(domainDir, "cert.pem");
const keyFilePath = path.join(domainDir, "key.pem");
const before = dynamicConfig.tls.certificates.length;
dynamicConfig.tls.certificates =
dynamicConfig.tls.certificates.filter(
(entry: any) =>
entry.certFile !== certFilePath &&
entry.keyFile !== keyFilePath
);
if (dynamicConfig.tls.certificates.length !== before) {
configChanged = true;
}
if (dynamicConfig.tls.certificates.length !== before) {
configChanged = true;
}
}
}

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.

View File

@@ -1,7 +1,7 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
@@ -217,7 +217,7 @@ export async function handleSubscriptionCreated(
subscriptionPriceId === priceSet[LicenseId.BIG_LICENSE]
) {
numUsers = 50;
numSites = 100;
numSites = 50;
} else {
logger.error(
`Unknown price ID ${subscriptionPriceId} for subscription ${subscription.id}`

Some files were not shown because too many files have changed in this diff Show More