From 9114dd5992568ab2db94ccfe5e5332d8fb31dc3c Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 16 Jan 2026 14:57:54 -0800 Subject: [PATCH] Send terminate error messages --- server/lib/calculateUserClientsForOrgs.ts | 3 +++ server/private/routers/re-key/reGenerateClientSecret.ts | 6 +++++- server/routers/client/archiveClient.ts | 3 ++- server/routers/client/blockClient.ts | 3 ++- server/routers/client/deleteClient.ts | 3 ++- server/routers/client/terminate.ts | 8 +++++++- server/routers/olm/archiveUserOlm.ts | 3 ++- server/routers/olm/deleteUserOlm.ts | 3 +++ server/routers/olm/error.ts | 8 +++++++- server/routers/olm/handleOlmPingMessage.ts | 3 +++ server/routers/org/deleteOrg.ts | 6 +++++- 11 files changed, 41 insertions(+), 8 deletions(-) diff --git a/server/lib/calculateUserClientsForOrgs.ts b/server/lib/calculateUserClientsForOrgs.ts index 0b4a131a..123aefdd 100644 --- a/server/lib/calculateUserClientsForOrgs.ts +++ b/server/lib/calculateUserClientsForOrgs.ts @@ -19,6 +19,7 @@ import logger from "@server/logger"; import { sendTerminateClient } from "@server/routers/client/terminate"; import { and, eq, notInArray, type InferInsertModel } from "drizzle-orm"; import { rebuildClientAssociationsFromClient } from "./rebuildClientAssociations"; +import { OlmErrorCodes } from "@server/routers/olm/error"; export async function calculateUserClientsForOrgs( userId: string, @@ -305,6 +306,8 @@ async function cleanupOrphanedClients( if (deletedClient.olmId) { await sendTerminateClient( deletedClient.clientId, + OlmErrorCodes.TERMINATED_DELETED, + "Deleted", deletedClient.olmId ); } diff --git a/server/private/routers/re-key/reGenerateClientSecret.ts b/server/private/routers/re-key/reGenerateClientSecret.ts index 5478c690..a16a0646 100644 --- a/server/private/routers/re-key/reGenerateClientSecret.ts +++ b/server/private/routers/re-key/reGenerateClientSecret.ts @@ -24,6 +24,7 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; import { hashPassword } from "@server/auth/password"; import { disconnectClient, sendToClient } from "#private/routers/ws"; +import { OlmErrorCodes, sendOlmError } from "@server/routers/olm/error"; const reGenerateSecretParamsSchema = z.strictObject({ clientId: z.string().transform(Number).pipe(z.int().positive()) @@ -119,7 +120,10 @@ export async function reGenerateClientSecret( if (disconnect) { const payload = { type: `olm/terminate`, - data: {} + data: { + code: OlmErrorCodes.TERMINATED_REKEYED, + message: "Client secret has been regenerated" + } }; // Don't await this to prevent blocking the response sendToClient(existingOlms[0].olmId, payload).catch((error) => { diff --git a/server/routers/client/archiveClient.ts b/server/routers/client/archiveClient.ts index 330f6ed8..5b1d65df 100644 --- a/server/routers/client/archiveClient.ts +++ b/server/routers/client/archiveClient.ts @@ -11,6 +11,7 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations"; import { sendTerminateClient } from "./terminate"; +import { OlmErrorCodes } from "../olm/error"; const archiveClientSchema = z.strictObject({ clientId: z.string().transform(Number).pipe(z.int().positive()) @@ -82,7 +83,7 @@ export async function archiveClient( // Send terminate signal if there's an associated OLM if (client.olmId) { - await sendTerminateClient(client.clientId, client.olmId); + await sendTerminateClient(client.clientId, OlmErrorCodes.TERMINATED_ARCHIVED, "Archived", client.olmId); } }); diff --git a/server/routers/client/blockClient.ts b/server/routers/client/blockClient.ts index 68ae64f8..3bb878a5 100644 --- a/server/routers/client/blockClient.ts +++ b/server/routers/client/blockClient.ts @@ -10,6 +10,7 @@ import logger from "@server/logger"; import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; import { sendTerminateClient } from "./terminate"; +import { OlmErrorCodes } from "../olm/error"; const blockClientSchema = z.strictObject({ clientId: z.string().transform(Number).pipe(z.int().positive()) @@ -78,7 +79,7 @@ export async function blockClient( // Send terminate signal if there's an associated OLM and it's connected if (client.olmId && client.online) { - await sendTerminateClient(client.clientId, client.olmId); + await sendTerminateClient(client.clientId, OlmErrorCodes.TERMINATED_BLOCKED, "Blocked", client.olmId); } }); diff --git a/server/routers/client/deleteClient.ts b/server/routers/client/deleteClient.ts index a16a2996..db88c365 100644 --- a/server/routers/client/deleteClient.ts +++ b/server/routers/client/deleteClient.ts @@ -11,6 +11,7 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations"; import { sendTerminateClient } from "./terminate"; +import { OlmErrorCodes } from "../olm/error"; const deleteClientSchema = z.strictObject({ clientId: z.string().transform(Number).pipe(z.int().positive()) @@ -91,7 +92,7 @@ export async function deleteClient( await rebuildClientAssociationsFromClient(deletedClient, trx); if (olm) { - await sendTerminateClient(deletedClient.clientId, olm.olmId); // the olmId needs to be provided because it cant look it up after deletion + await sendTerminateClient(deletedClient.clientId, OlmErrorCodes.TERMINATED_DELETED, "Deleted", olm.olmId); // the olmId needs to be provided because it cant look it up after deletion } }); diff --git a/server/routers/client/terminate.ts b/server/routers/client/terminate.ts index 1cfdc709..5c5ca216 100644 --- a/server/routers/client/terminate.ts +++ b/server/routers/client/terminate.ts @@ -1,9 +1,12 @@ import { sendToClient } from "#dynamic/routers/ws"; import { db, olms } from "@server/db"; import { eq } from "drizzle-orm"; +import { OlmErrorCodes } from "../olm/error"; export async function sendTerminateClient( clientId: number, + code: (typeof OlmErrorCodes)[keyof typeof OlmErrorCodes], + message: string, olmId?: string | null ) { if (!olmId) { @@ -20,6 +23,9 @@ export async function sendTerminateClient( await sendToClient(olmId, { type: `olm/terminate`, - data: {} + data: { + code, + message + } }); } diff --git a/server/routers/olm/archiveUserOlm.ts b/server/routers/olm/archiveUserOlm.ts index 46abd1a1..a835dbdc 100644 --- a/server/routers/olm/archiveUserOlm.ts +++ b/server/routers/olm/archiveUserOlm.ts @@ -10,6 +10,7 @@ import { fromError } from "zod-validation-error"; import logger from "@server/logger"; import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations"; import { sendTerminateClient } from "../client/terminate"; +import { OlmErrorCodes } from "./error"; const paramsSchema = z .object({ @@ -52,7 +53,7 @@ export async function archiveUserOlm( .where(eq(clients.clientId, client.clientId)); await rebuildClientAssociationsFromClient(client, trx); - await sendTerminateClient(client.clientId, olmId); + await sendTerminateClient(client.clientId, OlmErrorCodes.TERMINATED_ARCHIVED, "Archived", olmId); } // Archive the OLM (set archived to true) diff --git a/server/routers/olm/deleteUserOlm.ts b/server/routers/olm/deleteUserOlm.ts index 83a3d16f..3d9e5c23 100644 --- a/server/routers/olm/deleteUserOlm.ts +++ b/server/routers/olm/deleteUserOlm.ts @@ -11,6 +11,7 @@ import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations"; import { sendTerminateClient } from "../client/terminate"; +import { OlmErrorCodes } from "./error"; const paramsSchema = z .object({ @@ -76,6 +77,8 @@ export async function deleteUserOlm( if (olm) { await sendTerminateClient( deletedClient.clientId, + OlmErrorCodes.TERMINATED_DELETED, + "Deleted", olm.olmId ); // the olmId needs to be provided because it cant look it up after deletion } diff --git a/server/routers/olm/error.ts b/server/routers/olm/error.ts index c02c571c..11d3d4cf 100644 --- a/server/routers/olm/error.ts +++ b/server/routers/olm/error.ts @@ -10,7 +10,13 @@ export const OlmErrorCodes = { USER_ID_NOT_FOUND: "USER_ID_NOT_FOUND", INVALID_USER_SESSION: "INVALID_USER_SESSION", USER_ID_MISMATCH: "USER_ID_MISMATCH", - ACCESS_POLICY_DENIED: "ACCESS_POLICY_DENIED" + ACCESS_POLICY_DENIED: "ACCESS_POLICY_DENIED", + TERMINATED_REKEYED: "TERMINATED_REKEYED", + TERMINATED_ORG_DELETED: "TERMINATED_ORG_DELETED", + TERMINATED_INACTIVITY: "TERMINATED_INACTIVITY", + TERMINATED_DELETED: "TERMINATED_DELETED", + TERMINATED_ARCHIVED: "TERMINATED_ARCHIVED", + TERMINATED_BLOCKED: "TERMINATED_BLOCKED" } as const; // Helper function to send registration error diff --git a/server/routers/olm/handleOlmPingMessage.ts b/server/routers/olm/handleOlmPingMessage.ts index bfcb7f33..fc1ebb3f 100644 --- a/server/routers/olm/handleOlmPingMessage.ts +++ b/server/routers/olm/handleOlmPingMessage.ts @@ -10,6 +10,7 @@ import { sendTerminateClient } from "../client/terminate"; import { encodeHexLowerCase } from "@oslojs/encoding"; import { sha256 } from "@oslojs/crypto/sha2"; import { sendOlmSyncMessage } from "./sync"; +import { OlmErrorCodes } from "./error"; // Track if the offline checker interval is running let offlineCheckerInterval: NodeJS.Timeout | null = null; @@ -64,6 +65,8 @@ export const startOlmOfflineChecker = (): void => { try { await sendTerminateClient( offlineClient.clientId, + OlmErrorCodes.TERMINATED_INACTIVITY, + "Client terminated due to inactivity", offlineClient.olmId ); // terminate first // wait a moment to ensure the message is sent diff --git a/server/routers/org/deleteOrg.ts b/server/routers/org/deleteOrg.ts index 35dc7503..a4b913e8 100644 --- a/server/routers/org/deleteOrg.ts +++ b/server/routers/org/deleteOrg.ts @@ -21,6 +21,7 @@ import { fromError } from "zod-validation-error"; import { sendToClient } from "#dynamic/routers/ws"; import { deletePeer } from "../gerbil/peers"; import { OpenAPITags, registry } from "@server/openApi"; +import { OlmErrorCodes } from "../olm/error"; const deleteOrgSchema = z.strictObject({ orgId: z.string() @@ -208,7 +209,10 @@ export async function deleteOrg( for (const olmId of olmsToTerminate) { sendToClient(olmId, { type: "olm/terminate", - data: {} + data: { + code: OlmErrorCodes.TERMINATED_REKEYED, + message: "Organization has been deleted" + } }).catch((error) => { logger.error( "Failed to send termination message to olm:",