Compare commits

..

1 Commits
dev ... 1.19.3

Author SHA1 Message Date
Owen Schwartz
7590e8d8a1 Merge pull request #3345 from fosrl/dev
Show utility subnet on org
2026-06-25 13:05:32 -07:00

View File

@@ -29,7 +29,7 @@ type ClientRow = typeof clients.$inferSelect;
function runQueuedClientAssociationRebuilds(
userId: string,
queuedClients: ClientRow[]
) {
): void {
if (queuedClients.length === 0) {
return;
}
@@ -39,29 +39,35 @@ function runQueuedClientAssociationRebuilds(
uniqueClientsById.set(client.clientId, client);
}
void (async () => {
for (const client of uniqueClientsById.values()) {
rebuildClientAssociationsFromClient(client).catch((error) => {
try {
await rebuildClientAssociationsFromClient(client);
} catch (error) {
logger.error(
`Error rebuilding client associations for client ${client.clientId} (user ${userId}): ${String(
error
)}`
`Failed rebuilding associations for client ${client.clientId} (user ${userId}): ${String(error)}`
);
});
}
}
logger.debug(
`Queued association rebuild completed for ${uniqueClientsById.size} client(s) (user ${userId})`
);
})();
}
export async function calculateUserClientsForOrgs(
userId: string
): Promise<void> {
const trx = primaryDb;
const queuedAssociationRebuilds: ClientRow[] = [];
const execute = async (transaction: Transaction | typeof db) => {
const orgCache = new Map<string, typeof orgs.$inferSelect | null>();
const adminRoleCache = new Map<string, typeof roles.$inferSelect | null>();
const adminRoleCache = new Map<
string,
typeof roles.$inferSelect | null
>();
const exitNodesCache = new Map<
string,
Awaited<ReturnType<typeof listExitNodes>>
@@ -74,7 +80,8 @@ export async function calculateUserClientsForOrgs(
const roleClientAccessCache = new Map<string, boolean>();
const userClientAccessCache = new Map<string, boolean>();
const getOrgOlmKey = (orgId: string, olmId: string) => `${orgId}:${olmId}`;
const getOrgOlmKey = (orgId: string, olmId: string) =>
`${orgId}:${olmId}`;
const getRoleClientKey = (roleId: number, clientId: number) =>
`${roleId}:${clientId}`;
const getUserClientKey = (cachedUserId: string, clientId: number) =>
@@ -85,7 +92,7 @@ export async function calculateUserClientsForOrgs(
return orgCache.get(orgId) ?? null;
}
const [org] = await trx
const [org] = await transaction
.select()
.from(orgs)
.where(eq(orgs.orgId, orgId));
@@ -99,7 +106,7 @@ export async function calculateUserClientsForOrgs(
return adminRoleCache.get(orgId) ?? null;
}
const [adminRole] = await trx
const [adminRole] = await transaction
.select()
.from(roles)
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
@@ -140,7 +147,7 @@ export async function calculateUserClientsForOrgs(
return existingClientCache.get(key) ?? null;
}
const [existingClient] = await trx
const [existingClient] = await transaction
.select()
.from(clients)
.where(
@@ -157,13 +164,16 @@ export async function calculateUserClientsForOrgs(
return existingClient ?? null;
};
const hasRoleClientAccess = async (roleId: number, clientId: number) => {
const hasRoleClientAccess = async (
roleId: number,
clientId: number
) => {
const key = getRoleClientKey(roleId, clientId);
if (roleClientAccessCache.has(key)) {
return roleClientAccessCache.get(key)!;
}
const [existingRoleClient] = await trx
const [existingRoleClient] = await transaction
.select()
.from(roleClients)
.where(
@@ -189,7 +199,7 @@ export async function calculateUserClientsForOrgs(
return userClientAccessCache.get(key)!;
}
const [existingUserClient] = await trx
const [existingUserClient] = await transaction
.select()
.from(userClients)
.where(
@@ -207,7 +217,7 @@ export async function calculateUserClientsForOrgs(
};
// Get all OLMs for this user
const userOlms = await trx
const userOlms = await transaction
.select()
.from(olms)
.where(eq(olms.userId, userId));
@@ -216,7 +226,7 @@ export async function calculateUserClientsForOrgs(
// No OLMs for this user, but we should still clean up any orphaned clients
await cleanupOrphanedClients(
userId,
trx,
transaction,
[],
queuedAssociationRebuilds
);
@@ -224,7 +234,7 @@ export async function calculateUserClientsForOrgs(
}
// Get all user orgs with all roles (for org list and role-based logic)
const userOrgRoleRows = await trx
const userOrgRoleRows = await transaction
.select()
.from(userOrgs)
.innerJoin(
@@ -240,7 +250,10 @@ export async function calculateUserClientsForOrgs(
const userOrgIds = [
...new Set(userOrgRoleRows.map((r) => r.userOrgs.orgId))
];
const orgIdToRoleRows = new Map<string, (typeof userOrgRoleRows)[0][]>();
const orgIdToRoleRows = new Map<
string,
(typeof userOrgRoleRows)[0][]
>();
for (const r of userOrgRoleRows) {
const list = orgIdToRoleRows.get(r.userOrgs.orgId) ?? [];
list.push(r);
@@ -287,7 +300,10 @@ export async function calculateUserClientsForOrgs(
}
// Check if a client already exists for this OLM+user+org combination
const existingClient = await getExistingClient(orgId, olm.olmId);
const existingClient = await getExistingClient(
orgId,
olm.olmId
);
if (existingClient) {
// Ensure admin role has access to the client
@@ -297,7 +313,7 @@ export async function calculateUserClientsForOrgs(
);
if (!hasRoleAccess) {
await trx.insert(roleClients).values({
await transaction.insert(roleClients).values({
roleId: adminRole.roleId,
clientId: existingClient.clientId
});
@@ -320,7 +336,7 @@ export async function calculateUserClientsForOrgs(
);
if (!hasUserAccess) {
await trx.insert(userClients).values({
await transaction.insert(userClients).values({
userId,
clientId: existingClient.clientId
});
@@ -350,11 +366,13 @@ export async function calculateUserClientsForOrgs(
}
const randomExitNode =
exitNodesList[Math.floor(Math.random() * exitNodesList.length)];
exitNodesList[
Math.floor(Math.random() * exitNodesList.length)
];
// Get next available subnet
const { value: newSubnet, release: releaseSubnetLock } =
await getNextAvailableClientSubnet(orgId, trx);
await getNextAvailableClientSubnet(orgId, transaction);
const subnet = newSubnet.split("/")[0];
const updatedSubnet = `${subnet}/${org.subnet.split("/")[1]}`;
@@ -380,16 +398,19 @@ export async function calculateUserClientsForOrgs(
};
// Create the client
const [newClient] = await trx
const [newClient] = await transaction
.insert(clients)
.values(newClientData)
.returning();
await releaseSubnetLock();
existingClientCache.set(getOrgOlmKey(orgId, olm.olmId), newClient);
existingClientCache.set(
getOrgOlmKey(orgId, olm.olmId),
newClient
);
// create approval request
if (requireApproval) {
await trx
await transaction
.insert(approvals)
.values({
timestamp: Math.floor(new Date().getTime() / 1000),
@@ -404,7 +425,7 @@ export async function calculateUserClientsForOrgs(
queuedAssociationRebuilds.push(newClient);
// Grant admin role access to the client
await trx.insert(roleClients).values({
await transaction.insert(roleClients).values({
roleId: adminRole.roleId,
clientId: newClient.clientId
});
@@ -414,7 +435,7 @@ export async function calculateUserClientsForOrgs(
);
// Grant user access to the client
await trx.insert(userClients).values({
await transaction.insert(userClients).values({
userId,
clientId: newClient.clientId
});
@@ -432,10 +453,11 @@ export async function calculateUserClientsForOrgs(
// Clean up clients in orgs the user is no longer in
await cleanupOrphanedClients(
userId,
trx,
transaction,
userOrgIds,
queuedAssociationRebuilds
);
};
runQueuedClientAssociationRebuilds(userId, queuedAssociationRebuilds);
}
@@ -474,7 +496,7 @@ async function cleanupOrphanedClients(
)
.returning();
// Queue deleted clients for post-trx association cleanup.
// Queue deleted clients for post-transaction association cleanup.
for (const deletedClient of deletedClients) {
queuedAssociationRebuilds.push(deletedClient);