make user lookup deterministic in blueprints by sorting

This commit is contained in:
miloschwartz
2026-09-01 15:36:10 -04:00
parent b4f6ae74d7
commit 2f013335f9
4 changed files with 85 additions and 98 deletions
+38
View File
@@ -0,0 +1,38 @@
import { and, asc, eq, or } from "drizzle-orm";
import { Transaction, User, userOrgs, users } from "@server/db";
export async function findOrgUserByIdentifier(
trx: Transaction,
orgId: string,
identifier: string
): Promise<User | null> {
const [match] = await trx
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
or(eq(users.username, identifier), eq(users.email, identifier)),
eq(userOrgs.orgId, orgId)
)
)
.orderBy(asc(users.dateCreated), asc(users.userId))
.limit(1);
return match?.user ?? null;
}
export async function resolveOrgUserIds(
trx: Transaction,
orgId: string,
identifiers: string[]
): Promise<string[]> {
const userIds = new Set<string>();
for (const identifier of identifiers) {
const user = await findOrgUserByIdentifier(trx, orgId, identifier);
if (user) {
userIds.add(user.userId);
}
}
return [...userIds];
}
+32 -45
View File
@@ -11,15 +11,14 @@ import {
siteNetworks, siteNetworks,
siteResources, siteResources,
Transaction, Transaction,
userOrgs,
users,
userSiteResources, userSiteResources,
networks networks
} from "@server/db"; } from "@server/db";
import { sites } from "@server/db"; import { sites } from "@server/db";
import { eq, and, ne, inArray, or, isNotNull } from "drizzle-orm"; import { eq, and, ne, inArray, isNotNull } from "drizzle-orm";
import { Config } from "./types"; import { Config } from "./types";
import { getOrCreateLabelIds, syncSiteResourceLabels } from "./labels"; import { getOrCreateLabelIds, syncSiteResourceLabels } from "./labels";
import { resolveOrgUserIds } from "./findOrgUser";
import logger from "@server/logger"; import logger from "@server/logger";
import { defaultRoleAllowedActions } from "@server/routers/role/createRole"; import { defaultRoleAllowedActions } from "@server/routers/role/createRole";
import { getNextAvailableAliasAddress } from "../ip"; import { getNextAvailableAliasAddress } from "../ip";
@@ -389,28 +388,22 @@ export async function updatePrivateResources(
.where(eq(userSiteResources.siteResourceId, siteResourceId)); .where(eq(userSiteResources.siteResourceId, siteResourceId));
if (resourceData.users.length > 0) { if (resourceData.users.length > 0) {
// get userIds from username const userIds = await resolveOrgUserIds(
const usersToUpdate = await trx trx,
.select() orgId,
.from(users) resourceData.users
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId)) );
.where(
and(
or(
inArray(users.username, resourceData.users),
inArray(users.email, resourceData.users)
),
eq(userOrgs.orgId, orgId)
)
);
const userIds = usersToUpdate.map((user) => user.user.userId); if (userIds.length > 0) {
await trx
await trx .insert(userSiteResources)
.insert(userSiteResources) .values(
.values( userIds.map((userId) => ({
userIds.map((userId) => ({ userId, siteResourceId })) userId,
); siteResourceId
}))
);
}
} }
// Get all admin role IDs for this org to exclude from deletion // Get all admin role IDs for this org to exclude from deletion
@@ -721,28 +714,22 @@ export async function updatePrivateResources(
} }
if (resourceData.users.length > 0) { if (resourceData.users.length > 0) {
// get userIds from username const userIds = await resolveOrgUserIds(
const usersToUpdate = await trx trx,
.select() orgId,
.from(users) resourceData.users
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId)) );
.where(
and(
or(
inArray(users.username, resourceData.users),
inArray(users.email, resourceData.users)
),
eq(userOrgs.orgId, orgId)
)
);
const userIds = usersToUpdate.map((user) => user.user.userId); if (userIds.length > 0) {
await trx
await trx .insert(userSiteResources)
.insert(userSiteResources) .values(
.values( userIds.map((userId) => ({
userIds.map((userId) => ({ userId, siteResourceId })) userId,
); siteResourceId
}))
);
}
} }
if (resourceData.machines.length > 0) { if (resourceData.machines.length > 0) {
+8 -27
View File
@@ -46,11 +46,12 @@ import { encrypt } from "@server/lib/crypto";
import logger from "@server/logger"; import logger from "@server/logger";
import { defaultRoleAllowedActions } from "@server/routers/role/createRole"; import { defaultRoleAllowedActions } from "@server/routers/role/createRole";
import { pickPort } from "@server/routers/target/helpers"; import { pickPort } from "@server/routers/target/helpers";
import { and, asc, eq, isNotNull, ne, or } from "drizzle-orm"; import { and, asc, eq, isNotNull, ne } from "drizzle-orm";
import { tierMatrix } from "../billing/tierMatrix"; import { tierMatrix } from "../billing/tierMatrix";
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators"; import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
import { Config, isTargetsOnlyResource, TargetData } from "./types"; import { Config, isTargetsOnlyResource, TargetData } from "./types";
import { getOrCreateLabelIds, syncResourceLabels } from "./labels"; import { getOrCreateLabelIds, syncResourceLabels } from "./labels";
import { findOrgUserByIdentifier } from "./findOrgUser";
import { LimitId } from "../billing"; import { LimitId } from "../billing";
import { usageService } from "../billing/usageService"; import { usageService } from "../billing/usageService";
import { syncInferenceAiConfig } from "./aiProviders"; import { syncInferenceAiConfig } from "./aiProviders";
@@ -1563,29 +1564,19 @@ async function syncUserResources(
.where(eq(userResources.resourceId, resourceId)); .where(eq(userResources.resourceId, resourceId));
for (const username of ssoUsers) { for (const username of ssoUsers) {
const [user] = await trx const user = await findOrgUserByIdentifier(trx, orgId, username);
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
or(eq(users.username, username), eq(users.email, username)),
eq(userOrgs.orgId, orgId)
)
)
.limit(1);
if (!user) { if (!user) {
throw new Error(`User not found: ${username} in org ${orgId}`); throw new Error(`User not found: ${username} in org ${orgId}`);
} }
const existingUserResource = existingUserResources.find( const existingUserResource = existingUserResources.find(
(rr) => rr.userId === user.user.userId (rr) => rr.userId === user.userId
); );
if (!existingUserResource) { if (!existingUserResource) {
await trx.insert(userResources).values({ await trx.insert(userResources).values({
userId: user.user.userId, userId: user.userId,
resourceId: resourceId resourceId: resourceId
}); });
} }
@@ -1955,29 +1946,19 @@ async function syncUserPolicies(
.where(eq(userPolicies.resourcePolicyId, policyId)); .where(eq(userPolicies.resourcePolicyId, policyId));
for (const username of ssoUsers) { for (const username of ssoUsers) {
const [user] = await trx const user = await findOrgUserByIdentifier(trx, orgId, username);
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
or(eq(users.username, username), eq(users.email, username)),
eq(userOrgs.orgId, orgId)
)
)
.limit(1);
if (!user) { if (!user) {
throw new Error(`User not found: ${username} in org ${orgId}`); throw new Error(`User not found: ${username} in org ${orgId}`);
} }
const existingUserPolicy = existingUserPoliciesList.find( const existingUserPolicy = existingUserPoliciesList.find(
(up) => up.userId === user.user.userId (up) => up.userId === user.userId
); );
if (!existingUserPolicy) { if (!existingUserPolicy) {
await trx.insert(userPolicies).values({ await trx.insert(userPolicies).values({
userId: user.user.userId, userId: user.userId,
resourcePolicyId: policyId resourcePolicyId: policyId
}); });
} }
+7 -26
View File
@@ -13,7 +13,7 @@ import {
userPolicies, userPolicies,
users users
} from "@server/db"; } from "@server/db";
import { eq, and, or } from "drizzle-orm"; import { eq, and } from "drizzle-orm";
import { Config, ResourcePolicyData } from "./types"; import { Config, ResourcePolicyData } from "./types";
import logger from "@server/logger"; import logger from "@server/logger";
import { getUniqueResourcePolicyName } from "@server/db/names"; import { getUniqueResourcePolicyName } from "@server/db/names";
@@ -22,6 +22,7 @@ import { idpExistsForOrg } from "@server/lib/idp/idpExistsForOrg";
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators"; import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { tierMatrix } from "../billing/tierMatrix"; import { tierMatrix } from "../billing/tierMatrix";
import { findOrgUserByIdentifier } from "./findOrgUser";
export type ResourcePoliciesResults = { export type ResourcePoliciesResults = {
resourcePolicyId: number; resourcePolicyId: number;
@@ -466,17 +467,7 @@ async function syncUserPolicies(
.where(eq(userPolicies.resourcePolicyId, policyId)); .where(eq(userPolicies.resourcePolicyId, policyId));
for (const username of ssoUsers) { for (const username of ssoUsers) {
const [user] = await trx const user = await findOrgUserByIdentifier(trx, orgId, username);
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
or(eq(users.username, username), eq(users.email, username)),
eq(userOrgs.orgId, orgId)
)
)
.limit(1);
if (!user) { if (!user) {
logger.warn( logger.warn(
@@ -486,12 +477,12 @@ async function syncUserPolicies(
} }
const alreadyExists = existingUserPolicies.some( const alreadyExists = existingUserPolicies.some(
(up) => up.userId === user.user.userId (up) => up.userId === user.userId
); );
if (!alreadyExists) { if (!alreadyExists) {
await trx.insert(userPolicies).values({ await trx.insert(userPolicies).values({
userId: user.user.userId, userId: user.userId,
resourcePolicyId: policyId resourcePolicyId: policyId
}); });
} }
@@ -536,17 +527,7 @@ async function addUserPolicies(
trx: Transaction trx: Transaction
) { ) {
for (const username of ssoUsers) { for (const username of ssoUsers) {
const [user] = await trx const user = await findOrgUserByIdentifier(trx, orgId, username);
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
or(eq(users.username, username), eq(users.email, username)),
eq(userOrgs.orgId, orgId)
)
)
.limit(1);
if (!user) { if (!user) {
logger.warn( logger.warn(
@@ -556,7 +537,7 @@ async function addUserPolicies(
} }
await trx.insert(userPolicies).values({ await trx.insert(userPolicies).values({
userId: user.user.userId, userId: user.userId,
resourcePolicyId: policyId resourcePolicyId: policyId
}); });
} }