From 6c8757f23067ba2ccf027d7cca157edcb60f62c7 Mon Sep 17 00:00:00 2001 From: Varun Narravula Date: Tue, 13 Jan 2026 17:14:05 -0800 Subject: [PATCH] feat(olm): reset/send new olm secret if a matching fingerprint is detected --- server/routers/external.ts | 6 + server/routers/olm/index.ts | 1 + .../routers/olm/recoverOlmWithFingerprint.ts | 120 ++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 server/routers/olm/recoverOlmWithFingerprint.ts diff --git a/server/routers/external.ts b/server/routers/external.ts index 89688bf6..2930331c 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -861,6 +861,12 @@ authenticated.get( olm.getUserOlm ); +authenticated.post( + "/user/:userId/olm/recover", + verifyIsLoggedInUser, + olm.recoverOlmWithFingerprint +); + authenticated.put( "/idp/oidc", verifyUserIsServerAdmin, diff --git a/server/routers/olm/index.ts b/server/routers/olm/index.ts index 6957c18b..c9017911 100644 --- a/server/routers/olm/index.ts +++ b/server/routers/olm/index.ts @@ -9,3 +9,4 @@ export * from "./listUserOlms"; export * from "./getUserOlm"; export * from "./handleOlmServerPeerAddMessage"; export * from "./handleOlmUnRelayMessage"; +export * from "./recoverOlmWithFingerprint"; diff --git a/server/routers/olm/recoverOlmWithFingerprint.ts b/server/routers/olm/recoverOlmWithFingerprint.ts new file mode 100644 index 00000000..49f0542f --- /dev/null +++ b/server/routers/olm/recoverOlmWithFingerprint.ts @@ -0,0 +1,120 @@ +import { db, fingerprints, olms } from "@server/db"; +import logger from "@server/logger"; +import HttpCode from "@server/types/HttpCode"; +import { and, eq } from "drizzle-orm"; +import { NextFunction, Request, Response } from "express"; +import response from "@server/lib/response"; +import createHttpError from "http-errors"; +import { z } from "zod"; +import { fromError } from "zod-validation-error"; +import { generateId } from "@server/auth/sessions/app"; +import { hashPassword } from "@server/auth/password"; + +const paramsSchema = z + .object({ + userId: z.string() + }) + .strict(); + +const bodySchema = z + .object({ + platformFingerprint: z.string() + }) + .strict(); + +export async function recoverOlmWithFingerprint( + req: Request, + res: Response, + next: NextFunction +): Promise { + try { + const parsedParams = paramsSchema.safeParse(req.params); + if (!parsedParams.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedParams.error).toString() + ) + ); + } + + const { userId } = parsedParams.data; + + const parsedBody = bodySchema.safeParse(req.body); + if (!parsedBody.success) { + return next( + createHttpError( + HttpCode.BAD_REQUEST, + fromError(parsedBody.error).toString() + ) + ); + } + + const { platformFingerprint } = parsedBody.data; + + const result = await db + .select({ + olm: olms, + fingerprint: fingerprints + }) + .from(olms) + .innerJoin(fingerprints, eq(fingerprints.olmId, olms.olmId)) + .where( + and( + eq(olms.userId, userId), + eq(olms.archived, false), + eq(fingerprints.platformFingerprint, platformFingerprint) + ) + ) + .orderBy(fingerprints.lastSeen); + + if (!result || result.length == 0) { + return next( + createHttpError( + HttpCode.NOT_FOUND, + "corresponding olm with this fingerprint not found" + ) + ); + } + + if (result.length > 1) { + return next( + createHttpError( + HttpCode.CONFLICT, + "multiple matching fingerprints found, not resetting secrets" + ) + ); + } + + const [{ olm: foundOlm }] = result; + + const newSecret = generateId(48); + const newSecretHash = await hashPassword(newSecret); + + await db + .update(olms) + .set({ + secretHash: newSecretHash + }) + .where(eq(olms.olmId, foundOlm.olmId)); + + return response(res, { + data: { + olmId: foundOlm.olmId, + secret: newSecret + }, + success: true, + error: false, + message: "Successfully retrieved olm", + status: HttpCode.OK + }); + } catch (error) { + logger.error(error); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Failed to recover olm using provided fingerprint input" + ) + ); + } +}