From 1635c145afcdf30953a3147c4363b683a1d2d4ce Mon Sep 17 00:00:00 2001 From: Neeraj Sathish Kumar Date: Fri, 26 Jun 2026 18:31:52 +0530 Subject: [PATCH] fix: reject expired link sessions --- apis/link/src/relay.ts | 6 ++++++ convex/linkSessions.test.ts | 19 +++++++++++++++++++ convex/linkSessions.ts | 7 +++++++ 3 files changed, 32 insertions(+) diff --git a/apis/link/src/relay.ts b/apis/link/src/relay.ts index fb0a031..86ae3cd 100644 --- a/apis/link/src/relay.ts +++ b/apis/link/src/relay.ts @@ -86,6 +86,12 @@ export function handleRelaySocket(socket: WebSocket, app: FastifyInstance) { return; } + if (session.status !== "approved") { + sendJson(socket, { type: "relay.reject", reason: "Session not approved" }); + socket.close(); + return; + } + // Verify deviceId matches session participants if (role === "desktop" && session.desktopDeviceId !== deviceId) { sendJson(socket, { type: "relay.reject", reason: "deviceId mismatch" }); diff --git a/convex/linkSessions.test.ts b/convex/linkSessions.test.ts index f9f57d3..0b46bef 100644 --- a/convex/linkSessions.test.ts +++ b/convex/linkSessions.test.ts @@ -184,4 +184,23 @@ describe("get", () => { expect(result).toBeNull(); }); + + it("does not return expired sessions", async () => { + const ctx = makeCtx(userId); + const id = await ctx.db.insert("linkSessions", { + userId, + desktopDeviceId: "d1", + mobileDeviceId: "m1", + mode: "mirror", + status: "approved", + transport: "websocket", + createdAt: Date.now() - 7200000, + expiresAt: Date.now() - 3600000, + }); + + const result = await (get as any).handler(ctx, { sessionId: id }); + + expect(result).toBeNull(); + }); + }); diff --git a/convex/linkSessions.ts b/convex/linkSessions.ts index ad9b13e..92f25b7 100644 --- a/convex/linkSessions.ts +++ b/convex/linkSessions.ts @@ -2,6 +2,10 @@ import { query, mutation } from "./_generated/server"; import { v } from "convex/values"; import { getAuthUserId } from "@convex-dev/auth/server"; +function isExpired(session: { expiresAt: number }) { + return session.expiresAt <= Date.now(); +} + export const create = mutation({ args: { desktopDeviceId: v.string(), @@ -41,6 +45,7 @@ export const updateStatus = mutation({ if (!userId) throw new Error("Not authenticated"); const session = await ctx.db.get(args.sessionId); if (!session || session.userId !== userId) throw new Error("Session not found"); + if (isExpired(session)) throw new Error("Session expired"); await ctx.db.patch(args.sessionId, { status: args.status }); }, }); @@ -52,6 +57,7 @@ export const updateTransport = mutation({ if (!userId) throw new Error("Not authenticated"); const session = await ctx.db.get(args.sessionId); if (!session || session.userId !== userId) throw new Error("Session not found"); + if (isExpired(session)) throw new Error("Session expired"); await ctx.db.patch(args.sessionId, { transport: args.transport }); }, }); @@ -63,6 +69,7 @@ export const get = query({ if (!userId) throw new Error("Not authenticated"); const session = await ctx.db.get(args.sessionId); if (!session || session.userId !== userId) return null; + if (isExpired(session)) return null; return session; }, });