Merge branch 'dev' into audit-logs

This commit is contained in:
Owen
2025-10-24 10:31:54 -07:00
9 changed files with 52 additions and 70 deletions

View File

@@ -26,8 +26,7 @@ export const orgs = pgTable("orgs", {
orgId: varchar("orgId").primaryKey(), orgId: varchar("orgId").primaryKey(),
name: varchar("name").notNull(), name: varchar("name").notNull(),
subnet: varchar("subnet"), subnet: varchar("subnet"),
createdAt: text("createdAt"), createdAt: text("createdAt")
settings: text("settings") // JSON blob of org-specific settings
}); });
export const orgDomains = pgTable("orgDomains", { export const orgDomains = pgTable("orgDomains", {

View File

@@ -19,8 +19,7 @@ export const orgs = sqliteTable("orgs", {
orgId: text("orgId").primaryKey(), orgId: text("orgId").primaryKey(),
name: text("name").notNull(), name: text("name").notNull(),
subnet: text("subnet"), subnet: text("subnet"),
createdAt: text("createdAt"), createdAt: text("createdAt")
settings: text("settings") // JSON blob of org-specific settings
}); });
export const userDomains = sqliteTable("userDomains", { export const userDomains = sqliteTable("userDomains", {

View File

@@ -1,7 +1,8 @@
export enum AudienceIds { export enum AudienceIds {
General = "", SignUps = "",
Subscribed = "", Subscribed = "",
Churned = "" Churned = "",
Newsletter = ""
} }
let resend; let resend;

View File

@@ -200,10 +200,7 @@ class TelemetryClient {
event: "supporter_status", event: "supporter_status",
properties: { properties: {
valid: stats.supporterStatus.valid, valid: stats.supporterStatus.valid,
tier: stats.supporterStatus.tier, tier: stats.supporterStatus.tier
github_username: stats.supporterStatus.githubUsername
? this.anon(stats.supporterStatus.githubUsername)
: "None"
} }
}); });
} }
@@ -217,21 +214,6 @@ class TelemetryClient {
install_timestamp: hostMeta.createdAt install_timestamp: hostMeta.createdAt
} }
}); });
for (const email of stats.adminUsers) {
// There should only be on admin user, but just in case
if (email) {
this.client.capture({
distinctId: this.anon(email),
event: "admin_user",
properties: {
host_id: hostMeta.hostMetaId,
app_version: stats.appVersion,
hashed_email: this.anon(email)
}
});
}
}
} }
private async collectAndSendAnalytics() { private async collectAndSendAnalytics() {
@@ -262,19 +244,38 @@ class TelemetryClient {
num_clients: stats.numClients, num_clients: stats.numClients,
num_identity_providers: stats.numIdentityProviders, num_identity_providers: stats.numIdentityProviders,
num_sites_online: stats.numSitesOnline, num_sites_online: stats.numSitesOnline,
resources: stats.resources.map((r) => ({ num_resources_sso_enabled: stats.resources.filter(
name: this.anon(r.name), (r) => r.sso
sso_enabled: r.sso, ).length,
protocol: r.protocol, num_resources_non_http: stats.resources.filter(
http_enabled: r.http (r) => !r.http
})), ).length,
sites: stats.sites.map((s) => ({ num_newt_sites: stats.sites.filter((s) => s.type === "newt")
site_name: this.anon(s.siteName), .length,
megabytes_in: s.megabytesIn, num_local_sites: stats.sites.filter(
megabytes_out: s.megabytesOut, (s) => s.type === "local"
type: s.type, ).length,
online: s.online num_wg_sites: stats.sites.filter(
})), (s) => s.type === "wireguard"
).length,
avg_megabytes_in:
stats.sites.length > 0
? Math.round(
stats.sites.reduce(
(sum, s) => sum + (s.megabytesIn ?? 0),
0
) / stats.sites.length
)
: 0,
avg_megabytes_out:
stats.sites.length > 0
? Math.round(
stats.sites.reduce(
(sum, s) => sum + (s.megabytesOut ?? 0),
0
) / stats.sites.length
)
: 0,
num_api_keys: stats.numApiKeys, num_api_keys: stats.numApiKeys,
num_custom_roles: stats.numCustomRoles num_custom_roles: stats.numCustomRoles
} }

View File

@@ -16,9 +16,10 @@ import privateConfig from "#private/lib/config";
import logger from "@server/logger"; import logger from "@server/logger";
export enum AudienceIds { export enum AudienceIds {
General = "5cfbf99b-c592-40a9-9b8a-577a4681c158", SignUps = "5cfbf99b-c592-40a9-9b8a-577a4681c158",
Subscribed = "870b43fd-387f-44de-8fc1-707335f30b20", Subscribed = "870b43fd-387f-44de-8fc1-707335f30b20",
Churned = "f3ae92bd-2fdb-4d77-8746-2118afd62549" Churned = "f3ae92bd-2fdb-4d77-8746-2118afd62549",
Newsletter = "5500c431-191c-42f0-a5d4-8b6d445b4ea0"
} }
const resend = new Resend( const resend = new Resend(

View File

@@ -224,7 +224,7 @@ export async function signup(
res.appendHeader("Set-Cookie", cookie); res.appendHeader("Set-Cookie", cookie);
if (build == "saas") { if (build == "saas") {
moveEmailToAudience(email, AudienceIds.General); moveEmailToAudience(email, AudienceIds.SignUps);
} }
if (config.getRawConfig().flags?.require_email_verification) { if (config.getRawConfig().flags?.require_email_verification) {

View File

@@ -443,14 +443,14 @@ authenticated.post(
resource.setResourceWhitelist, resource.setResourceWhitelist,
); );
authenticated.get( authenticated.post(
`/resource/:resourceId/whitelist/add`, `/resource/:resourceId/whitelist/add`,
verifyApiKeyResourceAccess, verifyApiKeyResourceAccess,
verifyApiKeyHasAction(ActionsEnum.setResourceWhitelist), verifyApiKeyHasAction(ActionsEnum.setResourceWhitelist),
resource.addEmailToResourceWhitelist resource.addEmailToResourceWhitelist
); );
authenticated.get( authenticated.post(
`/resource/:resourceId/whitelist/remove`, `/resource/:resourceId/whitelist/remove`,
verifyApiKeyResourceAccess, verifyApiKeyResourceAccess,
verifyApiKeyHasAction(ActionsEnum.setResourceWhitelist), verifyApiKeyHasAction(ActionsEnum.setResourceWhitelist),

View File

@@ -17,7 +17,7 @@ const getOrgSchema = z
.strict(); .strict();
export type GetOrgResponse = { export type GetOrgResponse = {
org: Org & { settings: { } | null }; org: Org;
}; };
registry.registerPath({ registry.registerPath({
@@ -64,23 +64,9 @@ export async function getOrg(
); );
} }
// Parse settings from JSON string back to object
let parsedSettings = null;
if (org[0].settings) {
try {
parsedSettings = JSON.parse(org[0].settings);
} catch (error) {
// If parsing fails, keep as string for backward compatibility
parsedSettings = org[0].settings;
}
}
return response<GetOrgResponse>(res, { return response<GetOrgResponse>(res, {
data: { data: {
org: { org: org[0]
...org[0],
settings: parsedSettings
}
}, },
success: true, success: true,
error: false, error: false,

View File

@@ -12,15 +12,13 @@ import { OpenAPITags, registry } from "@server/openApi";
const updateOrgParamsSchema = z const updateOrgParamsSchema = z
.object({ .object({
orgId: z.string(), orgId: z.string()
}) })
.strict(); .strict();
const updateOrgBodySchema = z const updateOrgBodySchema = z
.object({ .object({
name: z.string().min(1).max(255).optional(), name: z.string().min(1).max(255).optional()
settings: z.object({
}).optional(),
}) })
.strict() .strict()
.refine((data) => Object.keys(data).length > 0, { .refine((data) => Object.keys(data).length > 0, {
@@ -73,13 +71,10 @@ export async function updateOrg(
const { orgId } = parsedParams.data; const { orgId } = parsedParams.data;
const settings = parsedBody.data.settings ? JSON.stringify(parsedBody.data.settings) : undefined;
const updatedOrg = await db const updatedOrg = await db
.update(orgs) .update(orgs)
.set({ .set({
name: parsedBody.data.name, name: parsedBody.data.name
settings: settings
}) })
.where(eq(orgs.orgId, orgId)) .where(eq(orgs.orgId, orgId))
.returning(); .returning();