mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-05 02:06:41 +00:00
Fix switching orgs having connections from other orgs
This commit is contained in:
@@ -465,7 +465,8 @@ async function handleMessagesForSiteClients(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isAdd) {
|
if (isAdd) {
|
||||||
await holepunchSiteAdd( // this will kick off the add peer process for the client
|
await holepunchSiteAdd(
|
||||||
|
// this will kick off the add peer process for the client
|
||||||
client.clientId,
|
client.clientId,
|
||||||
{
|
{
|
||||||
siteId,
|
siteId,
|
||||||
@@ -728,7 +729,19 @@ export async function rebuildClientAssociationsFromClient(
|
|||||||
const userSiteResourceIds = await trx
|
const userSiteResourceIds = await trx
|
||||||
.select({ siteResourceId: userSiteResources.siteResourceId })
|
.select({ siteResourceId: userSiteResources.siteResourceId })
|
||||||
.from(userSiteResources)
|
.from(userSiteResources)
|
||||||
.where(eq(userSiteResources.userId, client.userId));
|
.innerJoin(
|
||||||
|
siteResources,
|
||||||
|
eq(
|
||||||
|
siteResources.siteResourceId,
|
||||||
|
userSiteResources.siteResourceId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(userSiteResources.userId, client.userId),
|
||||||
|
eq(siteResources.orgId, client.orgId)
|
||||||
|
)
|
||||||
|
); // this needs to be locked onto this org or else cross-org access could happen
|
||||||
|
|
||||||
newSiteResourceIds.push(
|
newSiteResourceIds.push(
|
||||||
...userSiteResourceIds.map((r) => r.siteResourceId)
|
...userSiteResourceIds.map((r) => r.siteResourceId)
|
||||||
@@ -738,7 +751,12 @@ export async function rebuildClientAssociationsFromClient(
|
|||||||
const roleIds = await trx
|
const roleIds = await trx
|
||||||
.select({ roleId: userOrgs.roleId })
|
.select({ roleId: userOrgs.roleId })
|
||||||
.from(userOrgs)
|
.from(userOrgs)
|
||||||
.where(eq(userOrgs.userId, client.userId))
|
.where(
|
||||||
|
and(
|
||||||
|
eq(userOrgs.userId, client.userId),
|
||||||
|
eq(userOrgs.orgId, client.orgId)
|
||||||
|
)
|
||||||
|
) // this needs to be locked onto this org or else cross-org access could happen
|
||||||
.then((rows) => rows.map((row) => row.roleId));
|
.then((rows) => rows.map((row) => row.roleId));
|
||||||
|
|
||||||
if (roleIds.length > 0) {
|
if (roleIds.length > 0) {
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import { generateSessionToken } from "@server/auth/sessions/app";
|
import { generateSessionToken } from "@server/auth/sessions/app";
|
||||||
import { clients, db, ExitNode, exitNodes, sites, clientSitesAssociationsCache } from "@server/db";
|
import {
|
||||||
|
clients,
|
||||||
|
db,
|
||||||
|
ExitNode,
|
||||||
|
exitNodes,
|
||||||
|
sites,
|
||||||
|
clientSitesAssociationsCache
|
||||||
|
} from "@server/db";
|
||||||
import { olms } from "@server/db";
|
import { olms } from "@server/db";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
@@ -99,6 +106,7 @@ export async function getOlmToken(
|
|||||||
await createOlmSession(resToken, existingOlm.olmId);
|
await createOlmSession(resToken, existingOlm.olmId);
|
||||||
|
|
||||||
let orgIdToUse = orgId;
|
let orgIdToUse = orgId;
|
||||||
|
let clientIdToUse;
|
||||||
if (!orgIdToUse) {
|
if (!orgIdToUse) {
|
||||||
if (!existingOlm.clientId) {
|
if (!existingOlm.clientId) {
|
||||||
return next(
|
return next(
|
||||||
@@ -114,7 +122,7 @@ export async function getOlmToken(
|
|||||||
.from(clients)
|
.from(clients)
|
||||||
.where(eq(clients.clientId, existingOlm.clientId))
|
.where(eq(clients.clientId, existingOlm.clientId))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
@@ -125,6 +133,40 @@ export async function getOlmToken(
|
|||||||
}
|
}
|
||||||
|
|
||||||
orgIdToUse = client.orgId;
|
orgIdToUse = client.orgId;
|
||||||
|
clientIdToUse = client.clientId;
|
||||||
|
} else {
|
||||||
|
// we did provide the org
|
||||||
|
const [client] = await db
|
||||||
|
.select()
|
||||||
|
.from(clients)
|
||||||
|
.where(eq(clients.orgId, orgIdToUse))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"No client found for provided orgId"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingOlm.clientId !== client.clientId) {
|
||||||
|
// we only need to do this if the client is changing
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`Switching olm client ${existingOlm.olmId} to org ${orgId} for user ${existingOlm.userId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(olms)
|
||||||
|
.set({
|
||||||
|
clientId: client.clientId
|
||||||
|
})
|
||||||
|
.where(eq(olms.olmId, existingOlm.olmId));
|
||||||
|
}
|
||||||
|
|
||||||
|
clientIdToUse = client.clientId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all exit nodes from sites where the client has peers
|
// Get all exit nodes from sites where the client has peers
|
||||||
@@ -135,7 +177,7 @@ export async function getOlmToken(
|
|||||||
sites,
|
sites,
|
||||||
eq(sites.siteId, clientSitesAssociationsCache.siteId)
|
eq(sites.siteId, clientSitesAssociationsCache.siteId)
|
||||||
)
|
)
|
||||||
.where(eq(clientSitesAssociationsCache.clientId, existingOlm.clientId!));
|
.where(eq(clientSitesAssociationsCache.clientId, clientIdToUse!));
|
||||||
|
|
||||||
// Extract unique exit node IDs
|
// Extract unique exit node IDs
|
||||||
const exitNodeIds = Array.from(
|
const exitNodeIds = Array.from(
|
||||||
|
|||||||
@@ -48,45 +48,36 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { publicKey, relay, olmVersion, orgId, userToken } = message.data;
|
||||||
publicKey,
|
|
||||||
relay,
|
|
||||||
olmVersion,
|
|
||||||
orgId,
|
|
||||||
doNotCreateNewClient,
|
|
||||||
token: userToken
|
|
||||||
} = message.data;
|
|
||||||
|
|
||||||
let client: Client | undefined;
|
if (!olm.clientId) {
|
||||||
let org: Org | undefined;
|
logger.warn("Olm client ID not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [client] = await db
|
||||||
|
.select()
|
||||||
|
.from(clients)
|
||||||
|
.where(eq(clients.clientId, olm.clientId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
logger.warn("Client ID not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [org] = await db
|
||||||
|
.select()
|
||||||
|
.from(orgs)
|
||||||
|
.where(eq(orgs.orgId, client.orgId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!org) {
|
||||||
|
logger.warn("Org not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (orgId) {
|
if (orgId) {
|
||||||
try {
|
|
||||||
const { client: clientRes, org: orgRes } =
|
|
||||||
await getOrCreateOrgClient(
|
|
||||||
orgId,
|
|
||||||
olm.userId,
|
|
||||||
olm.olmId,
|
|
||||||
olm.name || "User Device",
|
|
||||||
// doNotCreateNewClient ? true : false
|
|
||||||
true // for now never create a new client automatically because we create the users clients when they are added to the org
|
|
||||||
// this means that the rebuildClientAssociationsFromClient call below issue is not a problem
|
|
||||||
);
|
|
||||||
|
|
||||||
client = clientRes;
|
|
||||||
org = orgRes;
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(
|
|
||||||
`Error switching olm client ${olm.olmId} to org ${orgId}: ${err}`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!client) {
|
|
||||||
logger.warn("Client not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!olm.userId) {
|
if (!olm.userId) {
|
||||||
logger.warn("Olm has no user ID");
|
logger.warn("Olm has no user ID");
|
||||||
return;
|
return;
|
||||||
@@ -95,11 +86,11 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
|||||||
const { session: userSession, user } =
|
const { session: userSession, user } =
|
||||||
await validateSessionToken(userToken);
|
await validateSessionToken(userToken);
|
||||||
if (!userSession || !user) {
|
if (!userSession || !user) {
|
||||||
logger.warn("Invalid user session for olm ping");
|
logger.warn("Invalid user session for olm register");
|
||||||
return; // by returning here we just ignore the ping and the setInterval will force it to disconnect
|
return; // by returning here we just ignore the ping and the setInterval will force it to disconnect
|
||||||
}
|
}
|
||||||
if (user.userId !== olm.userId) {
|
if (user.userId !== olm.userId) {
|
||||||
logger.warn("User ID mismatch for olm ping");
|
logger.warn("User ID mismatch for olm register");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,48 +106,6 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
`Switching olm client ${olm.olmId} to org ${orgId} for user ${olm.userId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (olm.clientId !== client.clientId) { // we only need to do this if the client is changing
|
|
||||||
await db
|
|
||||||
.update(olms)
|
|
||||||
.set({
|
|
||||||
clientId: client.clientId
|
|
||||||
})
|
|
||||||
.where(eq(olms.olmId, olm.olmId));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!olm.clientId) {
|
|
||||||
logger.warn("Olm has no client ID!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(`Using last connected org for client ${olm.clientId}`);
|
|
||||||
|
|
||||||
[client] = await db
|
|
||||||
.select()
|
|
||||||
.from(clients)
|
|
||||||
.where(eq(clients.clientId, olm.clientId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
[org] = await db
|
|
||||||
.select()
|
|
||||||
.from(orgs)
|
|
||||||
.where(eq(orgs.orgId, client.orgId))
|
|
||||||
.limit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!client) {
|
|
||||||
logger.warn("Client ID not found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!org) {
|
|
||||||
logger.warn("Org not found");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
@@ -357,129 +306,3 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
|||||||
excludeSender: false
|
excludeSender: false
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getOrCreateOrgClient(
|
|
||||||
orgId: string,
|
|
||||||
userId: string | null,
|
|
||||||
olmId: string,
|
|
||||||
name: string,
|
|
||||||
doNotCreateNewClient: boolean,
|
|
||||||
trx: Transaction | typeof db = db
|
|
||||||
): Promise<{
|
|
||||||
client: Client;
|
|
||||||
org: Org;
|
|
||||||
}> {
|
|
||||||
// get the org
|
|
||||||
const [org] = await trx
|
|
||||||
.select()
|
|
||||||
.from(orgs)
|
|
||||||
.where(eq(orgs.orgId, orgId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!org) {
|
|
||||||
throw new Error("Org not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!org.subnet) {
|
|
||||||
throw new Error("Org has no subnet defined");
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if the user has a client in the org and if not then create a client for them
|
|
||||||
const [existingClient] = await trx
|
|
||||||
.select()
|
|
||||||
.from(clients)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(clients.orgId, orgId),
|
|
||||||
userId ? eq(clients.userId, userId) : isNull(clients.userId), // we dont check the user id if it is null because the olm is not tied to a user?
|
|
||||||
eq(clients.olmId, olmId)
|
|
||||||
)
|
|
||||||
) // checking the olmid here because we want to create a new client PER OLM PER ORG
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
let client = existingClient;
|
|
||||||
if (!client && !doNotCreateNewClient) {
|
|
||||||
logger.debug(
|
|
||||||
`Client does not exist in org ${orgId}, creating new client for user ${userId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!userId) {
|
|
||||||
throw new Error("User ID is required to create client in org");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the user belongs to the org
|
|
||||||
const [userOrg] = await trx
|
|
||||||
.select()
|
|
||||||
.from(userOrgs)
|
|
||||||
.where(and(eq(userOrgs.orgId, orgId), eq(userOrgs.userId, userId)))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!userOrg) {
|
|
||||||
throw new Error("User does not belong to org");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: more intelligent way to pick the exit node
|
|
||||||
const exitNodesList = await listExitNodes(orgId);
|
|
||||||
const randomExitNode =
|
|
||||||
exitNodesList[Math.floor(Math.random() * exitNodesList.length)];
|
|
||||||
|
|
||||||
const [adminRole] = await trx
|
|
||||||
.select()
|
|
||||||
.from(roles)
|
|
||||||
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!adminRole) {
|
|
||||||
throw new Error("Admin role not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const newSubnet = await getNextAvailableClientSubnet(orgId);
|
|
||||||
if (!newSubnet) {
|
|
||||||
throw new Error("No available subnet found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const subnet = newSubnet.split("/")[0];
|
|
||||||
const updatedSubnet = `${subnet}/${org.subnet.split("/")[1]}`; // we want the block size of the whole org
|
|
||||||
|
|
||||||
const [newClient] = await trx
|
|
||||||
.insert(clients)
|
|
||||||
.values({
|
|
||||||
exitNodeId: randomExitNode.exitNodeId,
|
|
||||||
orgId,
|
|
||||||
name,
|
|
||||||
subnet: updatedSubnet,
|
|
||||||
type: "olm",
|
|
||||||
userId: userId,
|
|
||||||
olmId: olmId // to lock this client to the olm even as the olm moves between clients in different orgs
|
|
||||||
})
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
await trx.insert(roleClients).values({
|
|
||||||
roleId: adminRole.roleId,
|
|
||||||
clientId: newClient.clientId
|
|
||||||
});
|
|
||||||
|
|
||||||
await trx.insert(userClients).values({
|
|
||||||
// we also want to make sure that the user can see their own client if they are not an admin
|
|
||||||
userId,
|
|
||||||
clientId: newClient.clientId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (userOrg.roleId != adminRole.roleId) {
|
|
||||||
// make sure the user can access the client
|
|
||||||
trx.insert(userClients).values({
|
|
||||||
userId,
|
|
||||||
clientId: newClient.clientId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await rebuildClientAssociationsFromClient(newClient, trx); // TODO: this will try to messages to the olm which has not connected yet - is that a problem?
|
|
||||||
|
|
||||||
client = newClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
client: client,
|
|
||||||
org: org
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user